- 全表新增 deleted_at 列,统一软删除替代硬删除+archived_at - models.model_id 重命名为 external_id,消除语义混淆 - conversations.model_id 改为可空(模型为建议而非绑定) - messages 新增 updated_at,移除 CASCADE 改为 DAO 层级联 - 移除 DB 层 UNIQUE 约束,改为应用层检查(配合软删除) - 新增 helpers.ts(baseColumns + 构造层防御)、ESLint 规则、契约测试 - 迁移 0004 补全 CHECK 约束(providers.type/materials.status/messages.role) - DAO 层全面重写:级联软删除、应用层唯一、provider 删除保护 - 路由/前端/测试全量适配 externalId 重命名及类型变更
381 lines
19 KiB
Markdown
381 lines
19 KiB
Markdown
# 开发规范
|
||
|
||
AI 工具必须严格遵守以下全部约束。
|
||
|
||
## 专题文档
|
||
|
||
| 文档 | 内容 |
|
||
| ---------------------------------- | ---------------------------------- |
|
||
| [architecture.md](architecture.md) | 项目结构、启动流程、前后端边界 |
|
||
| [backend.md](backend.md) | 模块 API、数据访问函数、AI 层说明 |
|
||
| [frontend.md](frontend.md) | 组件索引、页面组成、hooks/工具清单 |
|
||
| [release.md](release.md) | 开发服务、构建、脚本、环境变量 |
|
||
|
||
---
|
||
|
||
## 一、全局规则
|
||
|
||
### 语言与环境
|
||
|
||
- 使用中文编写注释、文档和交流内容。
|
||
- 仅使用 bun 作为包管理器和 bunx 作为工具运行器;禁止 npm、pnpm、yarn、npx、pnpx。
|
||
- 无需考虑向前兼容。
|
||
|
||
### 依赖引入
|
||
|
||
**后端**优先级(上层已有方案则不得引入新依赖):
|
||
|
||
1. Bun 内置 API(Bun.serve、bun:sqlite 等)
|
||
2. es-toolkit
|
||
3. 标准 Web API(fetch、Headers 等)
|
||
4. 已批准三方库:pino、@sinclair/typebox、ajv、drizzle-orm、ai、@ai-sdk/\*
|
||
5. 自行实现(仅以上都不满足时)
|
||
|
||
**前端**:优先复用已有组件/hooks/依赖库;确需新增依赖时先说明原因。
|
||
|
||
**Zod**:AI 工具层(`src/server/ai/tools/`)使用 Zod 定义 `tool()` 的 `inputSchema`,以满足 AI SDK 对 `ZodSchema` 的类型推断要求,属于框架级约束而非项目选型冲突。配置校验层使用 TypeBox + Ajv,两层级各司其职,不混用。
|
||
|
||
### 目录边界
|
||
|
||
| 目录 | 约束 |
|
||
| ------------------------ | --------------------------------------------------- |
|
||
| `src/server/` | 后端,禁止 import src/web/ |
|
||
| `src/server/db/` | 数据库层:schema、connection、migration、DAO |
|
||
| `src/server/ai/` | AI Provider Registry + Agent + 工具 |
|
||
| `src/server/config/` | 配置子系统:types、variables、issues、schema |
|
||
| `src/server/helpers/` | 跨路由工具:响应格式化、URL 拼接 |
|
||
| `src/server/middleware/` | 参数校验 + 错误处理中间件 |
|
||
| `src/web/` | 前端,禁止 import src/server/ 运行时实现 |
|
||
| `src/web/layouts/` | 布局组件(AdminLayout / WorkbenchLayout) |
|
||
| `src/web/features/` | 功能模块(dashboard / projects / models / chat 等) |
|
||
| `src/web/shared/` | 前端共享代码(components / hooks / utils / types) |
|
||
| `src/shared/` | 前后端共享类型(api.ts)和常量(app.ts) |
|
||
| `scripts/` | 独立脚本,可 import 项目源码 |
|
||
| `drizzle/` | SQL migration 文件(开发期产出) |
|
||
| `tests/` | 测试目录,镜像 src/ 结构 |
|
||
|
||
### 前端目录使用规范
|
||
|
||
#### 目录职责定义
|
||
|
||
```
|
||
src/web/
|
||
├── main.tsx ← 入口(Provider 装配、全局初始化)
|
||
├── app.tsx ← App 组件(路由挂载)
|
||
├── routes.tsx ← 全局路由表(集中声明)
|
||
├── styles.css ← 全局样式
|
||
├── menu.tsx ← 菜单配置类型定义
|
||
├── layouts/ ← 布局组件
|
||
│ ├── admin-layout/ ← Admin 布局 + 侧边栏 + 菜单配置
|
||
│ └── workbench-layout/ ← Workbench 布局 + 项目上下文 + 入口守卫
|
||
├── features/ ← 功能模块(核心目录)
|
||
│ ├── dashboard/ ← 仪表盘页面 + 私有组件/hooks
|
||
│ ├── projects/ ← 项目管理页面 + 私有组件/hooks
|
||
│ ├── models/ ← 模型管理页面 + 私有组件/hooks
|
||
│ ├── chat/ ← 聊天页面 + 私有组件/hooks
|
||
│ └── <新增功能>/ ← 新功能直接新增目录
|
||
├── shared/ ← 跨 feature 共享代码
|
||
│ ├── components/ ← 通用 UI 组件(ErrorBoundary, Sidebar, ConsoleShell 等)
|
||
│ ├── hooks/ ← 通用 hooks(use-logger, use-theme-preference, use-sidebar-collapsed 等)
|
||
│ ├── utils/ ← 通用工具函数(api, logger, time)
|
||
│ └── types.ts ← 前端内部共享类型
|
||
└── css.d.ts ← CSS 模块类型声明
|
||
```
|
||
|
||
#### 依赖规则
|
||
|
||
```text
|
||
layouts/ → shared/ ✅ 布局可使用共享代码
|
||
features/* → shared/ ✅ 功能模块可使用共享代码
|
||
features/* → layouts/ ❌ 功能模块不依赖布局(路由表负责组合)
|
||
features/A → features/B ❌ 功能模块间禁止直接依赖
|
||
shared/ → features/* ❌ 共享代码不依赖功能模块
|
||
```
|
||
|
||
#### feature 内部组织
|
||
|
||
每个 feature 目录自治,不强制统一内部结构,按需组织以下子目录:
|
||
|
||
```text
|
||
features/<name>/
|
||
├── index.tsx ← 页面组件(必须,路由入口)
|
||
├── components/ ← 私有 UI 组件
|
||
├── hooks/ ← 私有 hooks(如 use-conversations 仅 chat 使用)
|
||
├── utils/ ← 私有工具函数
|
||
└── types.ts ← 私有类型定义
|
||
```
|
||
|
||
- 只有 `index.tsx`(页面组件)是必需的,其他按实际复杂度按需创建。
|
||
- feature 内部文件只在本 feature 内导入。被外部使用的必须提升到 `shared/`。
|
||
|
||
#### 组件归属判定规则
|
||
|
||
| 判定维度 | 放在 feature/ | 放在 shared/ |
|
||
| -------- | -------------------------------------------------- | -------------------------------------------- |
|
||
| 使用范围 | 仅一个功能模块使用 | 两个及以上功能模块使用 |
|
||
| 业务耦合 | 包含特定业务逻辑(如项目 CRUD 表单、聊天消息渲染) | 纯展示或通用交互(如 ErrorBoundary、侧边栏) |
|
||
| 数据依赖 | 依赖特定 API 或业务数据 | 无业务数据依赖或通过 props 注入 |
|
||
| 可替换性 | 替换需理解业务上下文 | 可直接复用于任何页面 |
|
||
|
||
#### 组件升降级流程
|
||
|
||
**升级(feature → shared):** 当一个 feature 内的组件/hook/tool 同时满足以下条件时,应提升到 `shared/`:
|
||
|
||
1. 至少被 2 个不同的 feature 或 layout 使用
|
||
2. 已消除对原 feature 业务逻辑的直接依赖(数据通过 props/callback 注入)
|
||
3. 有清晰的 props 接口定义
|
||
|
||
升级步骤:
|
||
|
||
1. 将文件从 `features/<name>/` 移动到 `shared/` 对应子目录
|
||
2. 更新所有 import 路径
|
||
3. 如原 feature 有对应的测试文件,一并迁移(`tests/web/shared/`)
|
||
4. 运行 `bun run check` 确认无遗漏
|
||
|
||
**降级(shared → feature):** 当一个 shared 组件/hook/tool 仅被一个 feature 使用时,应降级到该 feature 内部:
|
||
|
||
1. 确认仅一个消费方(全局搜索 import)
|
||
2. 移动到消费方 feature 的对应子目录
|
||
3. 更新 import 路径
|
||
4. 迁移对应测试文件
|
||
5. 运行 `bun run check` 确认无遗漏
|
||
|
||
#### 新增功能开发检查清单
|
||
|
||
新增功能模块时按以下顺序操作:
|
||
|
||
1. 在 `features/` 下创建以功能名命名的目录(kebab-case)
|
||
2. 创建 `index.tsx` 作为页面组件入口
|
||
3. 在 `routes.tsx` 中注册路由,选择对应 layout 包裹
|
||
4. 如需布局内菜单项,更新对应 layout 的菜单配置
|
||
5. 组件/hooks/utils 先写在 feature 内部
|
||
6. 当确认需要跨 feature 复用时,按升级流程提升到 `shared/`
|
||
7. 测试文件创建在 `tests/web/features/<name>/`
|
||
|
||
#### 禁止事项
|
||
|
||
- 禁止在 feature 目录外直接创建页面组件(`pages/` 目录不再使用)
|
||
- 禁止 feature 间通过 `../features/other-feature/` 直接导入
|
||
- 禁止在 shared/ 中放置仅单个 feature 使用的代码
|
||
- 禁止跳过升降级流程直接在 shared/ 中新建"预判通用"的代码(先写 feature,确认复用后再提升)
|
||
|
||
### 类型与配置
|
||
|
||
- 共享类型唯一源头:`src/shared/api.ts`;应用常量唯一源头:`src/shared/app.ts`;版本号唯一源头:`package.json`。
|
||
- 配置加载流程:unknown → AuthoringConfig → NormalizedConfig → ValidatedConfig → ResolvedConfig。
|
||
- 配置系统入口:`src/server/config.ts`(统一配置加载、运行时校验、默认值解析)。
|
||
- Ajv 严格拒绝模式:不类型转换、不注入默认值、不删除未知字段。
|
||
- 新增/修改配置字段必须同步更新 TypeBox schema、`config.schema.json`、测试和用户文档。
|
||
|
||
### 后端日志
|
||
|
||
- 运行时代码通过 Logger 接口输出,禁止 `console.*`(仅 `logger.ts` 实现类内部可用)。
|
||
- 敏感字段(authorization、cookie、password 等)自动 redact。
|
||
|
||
### API 路由
|
||
|
||
- 每个端点一个文件:`src/server/routes/{资源}/{操作}.ts`。
|
||
- 路由在 `server.ts` 的 `Bun.serve({ routes })` 中声明式注册。
|
||
- 新增路由:创建 handler → 在 `server.ts` 注册 → 在 `tests/server/` 添加测试。
|
||
|
||
### 前端数据层
|
||
|
||
- 统一使用 `fetch`,不引入 axios。
|
||
- 错误抛异常,TanStack Query error 状态承接。
|
||
- 返回类型必须匹配后端 JSON 形状;包装对象(如 `{ project }`)须在 hook 内提取业务对象。
|
||
- 服务端状态用 TanStack Query,组件内状态用 `useState`,不引入额外状态管理库。
|
||
|
||
### 禁止事项
|
||
|
||
- 禁止前端 import `src/server/`
|
||
- 禁止后端使用 `console.*`
|
||
- 禁止组件内联 `style` 属性
|
||
- 禁止 CSS 覆盖 `.ant-*` 内部类名
|
||
- 禁止 `!important`
|
||
- 禁止硬编码色值(使用 `var(--ant-*)` CSS 变量)
|
||
- 禁止路由 handler 直接操作 `bun:sqlite` / Drizzle ORM(须通过 DAO 层)
|
||
- 禁止跳过或忽略测试
|
||
- 禁止在 `Modal onOk` 中直接执行异步提交(用 `Form onFinish`)
|
||
|
||
---
|
||
|
||
## 二、后端红线
|
||
|
||
### 路由 handler
|
||
|
||
- 签名:`(req: Request, db: Database, mode: RuntimeMode, logger?: Logger): Promise<Response>`。
|
||
- 业务错误:`jsonResponse(createApiError(msg, status), { mode, status })`;未知异常直接 throw,`withErrorHandler` 兜底。
|
||
- body 解析后立即校验必填字段和类型,失败返回 400。
|
||
- ID 参数走 `validateIdParam`,分页参数走 `validatePagination`。
|
||
- 路由注册:`withErrorHandler` 包裹 + 动态 `await import()` 加载 handler。
|
||
|
||
### 数据访问层
|
||
|
||
- handler 通过 `src/server/db/*.ts` 函数操作数据库,禁止直接使用 `bun:sqlite` 或 Drizzle。
|
||
- DAO 函数第一个参数接收原始 `Database`,内部 `wrap(db)` 转 Drizzle。
|
||
- 返回联合类型:成功 `{ resource: T }`,失败 `{ error: string; status: number }`。
|
||
- 输入输出类型来自 `src/shared/api.ts`。
|
||
- 列表查询使用 `paginateQuery()`,不重复实现分页。
|
||
- 列名 snake_case,TS 类型 camelCase,Drizzle schema 映射。
|
||
- 软删除:所有业务表使用 `deleted_at` 列,通过 `notDeleted(table)` 或 `paginateQuery({ softDelete })` 过滤。`deleted_at` 不暴露到 API 层。
|
||
- 唯一性:无数据库级 UNIQUE 约束,DAO 层应用校验(同字段 + `deleted_at IS NULL`)。
|
||
- 表定义:通过 `helpers.ts` 的 `baseColumns` 展开 id/created_at/updated_at/deleted_at,禁止直接 `sqliteTable()`(ESLint 强制)。
|
||
|
||
### AI 调用层
|
||
|
||
- Provider 实例:`buildProviderRegistry(db)`,每次从 DB 重建,不缓存。
|
||
- Agent 实例:`createAlfredAgent(model)` 工厂。
|
||
- SSE 响应:`createAgentUIStreamResponse`,`onFinish` 持久化完整 parts。
|
||
- 工具定义:`src/server/ai/tools/`。
|
||
|
||
### 错误处理
|
||
|
||
- 业务异常用 `AppError(statusCode)` 或 `jsonResponse(createApiError(...))`。
|
||
- handler 外层 `withErrorHandler` 兜底,不手动 try-catch 整个 handler。
|
||
- 生产模式错误响应不暴露内部细节。
|
||
|
||
---
|
||
|
||
## 三、前端红线
|
||
|
||
### 组件规范
|
||
|
||
- 优先 antd 默认能力 + props,不额外改写视觉。
|
||
- `ConfigProvider(zhCN)` 配置中文 locale 和主题,不在 CSS 硬编码亮/暗分支。
|
||
- 应用级能力(message、modal、notification)通过 `AntApp` + `App.useApp()` 获取。
|
||
- 页面保持编排职责,组合 hooks + 展示组件;复杂页面按功能边界拆分。
|
||
|
||
### 能力优先级(上层满足则不用下层)
|
||
|
||
1. antd 组件/props
|
||
2. antd 布局组件(Layout、Space、Flex)
|
||
3. antd theme token + CSS 变量
|
||
4. TanStack Query + useState
|
||
5. 已有 hooks(`shared/hooks/use-*.ts`)和工具函数(`shared/utils/`)
|
||
6. CSS Modules(就近放置在 feature 内部)
|
||
7. 引入新依赖(需说明原因)
|
||
|
||
### 前端代码组织
|
||
|
||
- 新增页面在 `features/` 下创建功能目录,不使用 `pages/`。
|
||
- 新增组件/hook/tool 默认放在所属 feature 内部;跨 feature 复用时提升到 `shared/`。
|
||
- 布局组件放 `layouts/`,布局与页面通过 `routes.tsx` 组合,不互相导入。
|
||
- 详细规则见上方「前端目录使用规范」章节。
|
||
|
||
### 样式红线
|
||
|
||
- 严禁内联 `style`、覆盖 `.ant-*`、`!important`、硬编码色值。
|
||
- 颜色使用 `var(--ant-*)` CSS 变量。
|
||
- 不引入 Tailwind/Sass/Less/CSS-in-JS 等额外样式方案。
|
||
- 全局 CSS 类名用 `app-*` 前缀,禁止泛名。样式增长后用 CSS Modules 就近维护。
|
||
|
||
### 表单与交互
|
||
|
||
- Modal + Form:`Form onFinish` 处理提交,`Modal onOk` 只调 `form.submit()`。
|
||
- 必填文本字段同时配 `required` + `whitespace`。
|
||
- 操作确认用 `Popconfirm`,反馈用 antd message。
|
||
|
||
### 错误边界
|
||
|
||
- 生产入口必须启用 `ErrorBoundary`。`ReactQueryDevtools` 仅 `DEV` 模式渲染。
|
||
|
||
---
|
||
|
||
## 四、测试规范
|
||
|
||
### 后端
|
||
|
||
- 路由测试通过真实 `startServer` 覆盖路由注册、HTTP method、fallback、header 和核心错误路径。
|
||
- SQLite 测试复用 `tests/helpers.ts`,不分散实现临时目录清理。
|
||
- DAO/路由测试优先用真实 migration 初始化。
|
||
- logger/bootstrap fallback 输出须捕获断言,正常测试不污染 stdout/stderr。
|
||
|
||
### 前端
|
||
|
||
- 目录 `tests/web/`,结构对应 `src/web/`。
|
||
- 用 jsdom + `@testing-library/react` 测试用户行为,断言基于可见文本/role/按钮。
|
||
- 系统边界复用 `tests/web/test-utils.tsx`。
|
||
- 数据页面覆盖:请求参数、成功可见结果、关键错误路径。
|
||
- ErrorBoundary/hooks/fetch helper 用单元测试覆盖异常,页面测试只保留用户路径。
|
||
|
||
---
|
||
|
||
## 常用命令
|
||
|
||
| 命令 | 说明 |
|
||
| -------------------------------- | -------------------------------------- |
|
||
| `bun run dev config.yaml` | 启动双进程开发环境 |
|
||
| `bun run dev:server config.yaml` | 仅后端 API server |
|
||
| `bun run dev:web` | 仅 Vite dev server |
|
||
| `bun run check` | schema:check + typecheck + lint + test |
|
||
| `bun run verify` | check + build 完整验证 |
|
||
| `bun run build` | 构建生产可执行文件 |
|
||
| `bun run schema` | 生成 config.schema.json |
|
||
| `bun run schema:check` | 检查 schema 同步 |
|
||
| `bun run typecheck` | TypeScript 类型检查 |
|
||
| `bun run lint` | ESLint + Prettier 检查 |
|
||
| `bun run format` | Prettier 格式化 |
|
||
| `bun test` | 运行全部测试 |
|
||
| `bun run clean` | 清理构建缓存 |
|
||
| `bun run version:patch` | 升迁 patch 版本 |
|
||
| `bun run version:minor` | 升迁 minor 版本 |
|
||
| `bun run version:major` | 升迁 major 版本 |
|
||
| `bun run version:set` | 显式设置版本号 |
|
||
|
||
## 质量门禁
|
||
|
||
| 变更类型 | 必跑命令 |
|
||
| ----------------------- | --------------------------------------------------------- |
|
||
| 常规代码变更 | `bun run check` |
|
||
| 构建、部署、集成变更 | `bun run verify` |
|
||
| 配置 schema 变化 | `bun run schema`、`bun run schema:check`、`bun run check` |
|
||
| SQLite 测试基础设施变化 | 相关测试 + SQLite 聚焦 `--rerun-each` + `bun run check` |
|
||
| 仅文档变更 | 检查链接、索引和文档归属一致性 |
|
||
|
||
正式提交优先运行 `bun run verify`。无法执行时须在收尾说明中记录。
|
||
|
||
## 已知设计决策
|
||
|
||
| 决策 | 原因 |
|
||
| -------------------------- | ----------------------------------------------------------------------------------------- |
|
||
| Provider.apiKey 返回给前端 | 个人项目,apiKey 非严格密码,前端需要展示和编辑。如需保护,应改为返回脱敏值或仅后端存储。 |
|
||
|
||
## 文档影响分析
|
||
|
||
| 变更影响 | 更新 |
|
||
| -------------------------------------- | ------------------------------------------ |
|
||
| 用户可见行为、配置、部署、运行行为 | `docs/user/` 对应文档 |
|
||
| 开发流程、架构、测试、构建发布流程 | `docs/development/` 对应文档 |
|
||
| 项目定位、快速开始、核心能力、文档导航 | `README.md` |
|
||
| 文档同步规则或归属矩阵 | `docs/README.md` 和 `openspec/config.yaml` |
|
||
| 开发规范变化 | 本文档对应章节 |
|
||
|
||
无需更新文档时,须在收尾说明中说明原因。
|
||
|
||
## 更新触发条件
|
||
|
||
修改常用命令、质量门禁、开发规范(任何章节)、目录边界或开发文档索引时,必须更新本文档。
|
||
|
||
### 文档编撰规范
|
||
|
||
本节开发文档面向 AI 工具阅读,编撰时遵循以下原则:
|
||
|
||
**精简原则**
|
||
|
||
- 删除引导语、适用场景、过渡句等装饰性文字。AI 无需"应首先阅读"或"本文档说明…"类引导。
|
||
- 不重复项目结构树,AI 可通过 glob 获取目录结构。
|
||
- 不重复已在 README.md 声明的规范细节,专题文档只记录实现层面的函数签名、API 端点、模块职责等索引信息。
|
||
- 表格标题自明时不再加说明段落。
|
||
|
||
**信息完整性**
|
||
|
||
- 所有函数签名、API 端点(方法+路径+说明)、数据访问函数清单必须完整列举,不可用"等"省略。
|
||
- 页面行为描述须包含关键交互逻辑(如 Tab 切换、默认值、条件跳转、测试不阻止保存等),不可只写组件名。
|
||
- 配置文件列表必须完整,不可遗漏已有文件。
|
||
|
||
**结构规范**
|
||
|
||
- 每个专题文档末尾保留 `## 更新触发条件` 章节,明确列出哪些变更必须更新该文档。
|
||
- 用表格和编号列表替代散文段落,减少 token 消耗。
|
||
- 同一信息只在一处维护,避免多处重复导致不一致。
|