AI 驱动开发
Castor 的目标是:你用自然语言描述业务需求,AI 编程工具自行推断技术细节,端到端交付符合项目规范的功能模块(数据表、接口、页面、权限、迁移),并通过验证门禁。
本页介绍这套流程依赖的四样东西:项目上下文 AGENTS.md、各 AI 工具的配置、代码骨架生成器 pnpm scaffold 和验证门禁 pnpm verify。
AGENTS.md:唯一的项目上下文
仓库根目录的 AGENTS.md 是写给 AI 的完整项目说明,所有 AI 工具都以它为准。内容包括:
- 技术栈、目录结构和命名规则
- 后端分层规则、路由规范、权限检查写法、横切约定(时间、数值、错误、CSRF)
- 前端动态路由、页面结构、公共组件、设计 tokens、多语言规则
- 导入导出规范、RBAC 约定、菜单 ID 分配规则和当前菜单树
- 字段类型推断表(业务描述 → 字段类型)
- 反模式清单和标准交付流程
各工具的专属配置文件只做补充,并都指回 AGENTS.md。修改项目约定时,应当先改 AGENTS.md。
更深入的架构说明在 docs/architecture.md,前端 UI 方案在 docs/frontend-design-system.md。
支持的 AI 工具
| 工具 | 读取的文件 |
|---|---|
| Claude Code | CLAUDE.md;技能在 .claude/skills/(new-feature-autopilot、shadcn-ui-skills) |
| Codex CLI | AGENTS.md(自动读取);技能在 .agents/skills/ |
| Cursor、Windsurf、GitHub Copilot 等 | AGENTS.md(这些工具都会自动读取) |
| 文档站上的 AI | 官网 /llms.txt(入口索引,源文件 website/public/llms.txt) |
| MCP 客户端 | apps/mcp,见下文 MCP Server |
.claude/skills/ 与 .agents/skills/ 的内容保持一致,后端测试 skills-sync.test.ts 会检查两者是否同步。
新功能交付流程
new-feature-autopilot 技能(Claude Code 中输入 /new-feature-autopilot,或直接说“做一个 XX 功能”)按以下五步执行。其他工具通过各自的规则文件遵循同样的流程。
1. 读取上下文
AI 读取 AGENTS.md、docs/templates/ 下的代码骨架模板、现有的参考模块(后端 apps/api/src/modules/admin/users/,前端 apps/web/src/modules/admin/pages/users/index.tsx),以及 apps/api/scripts/seed-rbac.ts 中的菜单树。如果需求可以通过扩展已有模块实现,会优先扩展。
2. 推断技术规格
AI 在内部推断以下内容,不向你询问:
- 资源名和所属域(
admin或component_center) - 接口路径,例如
/api/admin/customer-orders - 字段名与字段类型(依据下方的字段类型推断表)
- 权限编码:
admin域为system_<name>,component_center域为cc_<name>,按钮权限加_add/_edit/_delete/_export/_import - 前端文件路径、父菜单、迁移名称,以及页面模板(默认是普通列表,需求需要时换成别的页面模板)
推断结果写成一个 spec 文件,并用 pnpm scaffold -- --spec <file> --validate-only 校验;校验结果还会列出接口路径、权限、表名,以及 scaffold 将分配的菜单 ID。
3. 展示业务预览
AI 只展示业务层面的信息,等你确认或调整:
客户管理
位置:业务管理 → 客户管理
展示为:列表
功能:列表查看、新增、编辑、删除、导入、导出
字段:
· 客户名称(必填)
· 联系电话
· 状态
确认这样做吗?或者需要调整什么?只有在数据模型存在不可逆的歧义、需要外部系统配置、或权限边界有安全影响时,AI 才会额外提问。
4. 实现
- 用
pnpm scaffold -- --spec <file>生成模块(可先加--dry-run列出将写入的文件)。 - 按
db/schema → schema → repository → service → routes顺序补充业务逻辑。中文标签、必填 / 唯一 / 默认值规则和选项已经由 spec 生成;超出这些的部分(表间关系、跨字段校验、计算字段)在这一步手写。 - 打磨前端页面(页面专属文字的译文、额外的校验),或按选定的页面模板重做页面。
- spec 里有
menu时,scaffold 已经把菜单和按钮权限写进seed-rbac.ts;否则手动添加。然后运行pnpm seed:rbac -- --incremental。 - 审查新生成的迁移 SQL,运行
pnpm db:migrate,并用psql -d <库名> -c '\d <表名>'确认表真实存在。 - 接口文档:
pnpm scaffold已把模块的接口写进docs/apifox-full.openapi.json。改了生成的路由、字段或校验,或新增了路由时,按AGENTS.md的「OpenAPI writing rules」照代码同步修改。这一步必须做,否则pnpm verify的openapi_sync检查不通过。
5. 验证门禁
运行 pnpm verify -- --module <name>,失败项由 AI 修复后重新验证。全部通过后输出交付报告,报告中注明迁移版本(如“migrated to 0001_customer”)。
迁移必须真实落库
只生成迁移文件、只通过静态检查都不算完成。必须执行 pnpm db:migrate,用 psql \d 确认,并且 pnpm verify 的 migration_applied 检查通过。
pnpm scaffold
pnpm scaffold 一次生成后端模块、前端页面、接口测试和迁移。常用的输入是 spec 文件(中文标签、规则、选项、菜单):
pnpm scaffold -- --spec device.spec.json --validate-only # 校验,并预览接口、权限、表和菜单
pnpm scaffold -- --spec device.spec.json --dry-run # 列出将写入的文件,不写入
pnpm scaffold -- --spec device.spec.json # 生成没有 spec 时也可以只给字段列表(标签是英文占位,没有规则和菜单):
# 预览将生成的文件,不写入
pnpm scaffold -- --name customer --domain admin --fields "name:str,phone:str20,status:str20" --dry-run
# 正式生成
pnpm scaffold -- --name customer --domain admin --fields "name:str,phone:str20,status:str20"参数
| 参数 | 说明 | 默认值 |
|---|---|---|
--name | 资源名,snake_case,如 customer_order | 必填 |
--domain | 所属域:admin 或 component_center | admin |
--fields | 字段列表,格式 字段:类型,字段:类型 | name:str |
--spec | 用 JSON 文件描述模块(代替 --name / --fields),能写中文标签、必填、唯一、默认值、选项和菜单,见下方 spec 文件 | — |
--dry-run | 只打印将要生成的内容,不写文件、不注册、不生成迁移 | 关闭 |
--validate-only | 配合 --spec:只校验规格并说明会生成的接口、权限、表和菜单,不写任何文件;有问题时逐条列出并以 1 退出 | 关闭 |
--write-schema | 按脚手架当前的字段类型等规则重新生成 docs/spec.schema.json | 关闭 |
--skip-migration | 不调用 drizzle-kit 生成迁移 | 关闭 |
--data-scope | 配合 --fields 时,接入数据权限:表上加 dept_id / created_by,列表、详情、修改、删除、导出按当前用户的数据范围过滤,新建时写入创建人与部门,并生成对应的接口测试。用 --spec 时改在 spec 里写 "dataScope": true(此参数会被忽略) | 关闭 |
-h / --help | 打印用法 | — |
生成内容
已存在的文件会被跳过,不会覆盖。
| 生成文件 | 说明 |
|---|---|
apps/api/src/db/schema/<domain-dir>/<name-kebab>.ts | 表定义 + toDict |
apps/api/src/modules/<domain-dir>/<name-kebab>/{schema,repository,service,routes}.ts | 后端四层 |
apps/api/test/<admin|cc>-<name-kebab>.test.ts | 接口基础测试(增删改查、搜索、404、导出、导入模板、导入) |
apps/web/src/modules/<module>/api/<name>.ts | 前端 API 调用,类型来自模块的 OpenAPI 条目(行类型 ApiItem<'/api/admin/<name-kebab>s'>) |
前端列表页 index.tsx | admin 域在 pages/<name>/,component_center 域在 pages/patterns/<name>_page/;用公共组件的类型写成(FormValues、DataTableColumn<Row>[]) |
页面 locales/{en-US,ja-JP}.json | 公共译文里还没有的译文:模块的 Webhook 事件名(新增 / 修改 / 删除)以及标题、标签等页面文字 |
<domain-dir> 为 admin 或 component-center,<name-kebab> 是把下划线换成连字符后的资源名。
同时自动完成:
- 在
apps/api/src/db/schema/index.ts和apps/api/src/modules/<domain-dir>/router.ts注册 - 把模块的接口写进
docs/apifox-full.openapi.json,并据此重新生成前端 API 类型(apps/web/src/shared/api/openapi.d.ts) - spec 里有
menu时:把菜单和按钮权限写进apps/api/scripts/seed-rbac.ts,菜单的英文、日文名写进apps/web/src/locales/menus/ - 执行
drizzle-kit generate --name <name>生成迁移
scaffold 会在输出中打印权限编码前缀(Perm prefix)、菜单 component 值和接口路径,手动添加菜单时(用 --fields,或 spec 里没有 menu)直接使用。
字段类型
| 类型 | Drizzle 列 | 表单组件 | 说明 |
|---|---|---|---|
str | varchar(100) | FormInput | |
str20 | varchar(20) | FormInput | |
str50 | varchar(50) | FormInput | |
str500 | varchar(500) | FormInput | |
text | text | FormTextarea | |
int | integer | FormNumber | |
float | numeric(10, 2) | FormNumber | 接口输出为字符串,如 "12.50" |
bool | boolean | FormSwitch | |
date | date(字符串模式) | FormDate | YYYY-MM-DD |
datetime | timestamp(字符串模式) | FormDateTime | |
file | varchar(36),存文件中心的文件 ID | FormFileUpload | 列表显示「查看」链接;保存时自动登记引用 |
image | varchar(36),存文件中心的文件 ID | FormImageUpload | 列表显示缩略图;保存时自动登记引用 |
enum | varchar(50),存选项值 | FormSelect | 固定选项(只能用 --spec 写 options);列表可按它筛选,并以徽标显示选项名称(颜色取选项的 tone),导出显示名称,导入时名称和值都接受 |
dict | varchar(100),存字典项的值 | FormSelect | 选项来自「数据字典」(--spec 写 dict 字典编码);列表显示字典标签 |
用 --fields 时未知类型按 str 处理;spec 里写未知类型会报错。id、created_at、updated_at 会自动添加。
字段类型推断
AI 根据业务描述推断类型,你不需要指定:
| 业务描述关键词 | 类型 |
|---|---|
| 名称、标题、姓名、邮箱 | str |
| 编码、代码、编号 | str50 |
| 手机、电话、颜色 | str20 |
| 选项固定的状态、类型、等级 | enum(spec 里写 options;只用 --fields 时为 str20) |
| 选项由管理员维护的分类、来源、行业 | dict(spec 里写数据字典编码) |
| URL、链接、地址(外部地址) | str500 |
| 图片、头像、封面、照片 | image |
| 附件、文件、合同、扫描件 | file |
| 描述、备注、简介、内容、正文、标签(JSON 字符串) | text |
| 金额、价格、费用、成本 | float |
| 数量、次数、进度、百分比、排序、权重 | int |
| 日期(无时间) | date |
| 时间 | datetime |
| 是否、启用、禁用、开关 | bool |
spec 文件
--spec 读取一个 JSON 文件。AI 推断出规格后写成这样的文件再生成,中文标签、必填、唯一、默认值、选项和菜单一次到位:
{
"name": "device",
"title": "设备台账",
"fields": [
{ "name": "code", "type": "str50", "label": "设备编号", "required": true, "unique": true },
{ "name": "name", "type": "str", "label": "设备名称", "required": true },
{ "name": "status", "type": "enum", "label": "状态", "required": true, "default": "idle",
"options": [{ "value": "idle", "label": "闲置" }, { "value": "in_use", "label": "使用中", "tone": "success" }] },
{ "name": "category", "type": "dict", "label": "分类", "dict": "device_category" },
{ "name": "price", "type": "float", "label": "采购价格" }
],
"menu": {},
"i18n": { "en-US": { "设备台账": "Devices", "设备编号": "Device no." }, "ja-JP": { "设备台账": "設備台帳" } }
}pnpm scaffold -- --spec device.spec.json --validate-only # 先校验,看清会生成什么
pnpm scaffold -- --spec device.spec.jsontitle和每个字段的label必填;拼错的属性名(如requried)直接报错,不会被悄悄忽略- 完整格式见
docs/spec.schema.json(在 JSON 里写"$schema": "<相对路径>/docs/spec.schema.json",编辑器就能补全和提示);4 个「一句需求 → spec」示例及逐字段的推断理由见docs/examples/specs/ required:列加NOT NULL,新增 / 编辑时为空返回 400「<标签>不能为空」,表单标出必填并校验;image/file不能必填unique:列加UNIQUE,重复时返回 400;只用于文本和数字类型default:列默认值,新增时留空就用它,表单也预先填好label/title:页面、表头、导入导出和报错里的中文;i18n是它们的英文、日文,没写的用字段名代替menu:同时把菜单和按钮权限(新增 / 编辑 / 删除 / 导出 / 导入)写进apps/api/scripts/seed-rbac.ts,默认挂在顶级目录「业务管理」下(ID 1000,第一次生成时创建;模块 ID 从 1001 起);component_center域的模块则挂在组件示例中心的「页面模板」目录下(ID 4301–4399,接口在/api/admin/component-center/下);parentId可以指定其他目录;菜单的英文、日文名写进apps/web/src/locales/menus/dataScope:设为true时模块接入数据权限,相当于--fields方式下的--data-scopeoptions[].tone:该选项在列表中的徽标颜色(默认neutral;状态类字段用success/warning/danger等)- 生成的接口测试多一条「字段规则」用例,覆盖必填、选项、唯一和默认值
已知限制
- 只用
--fields时表达不了必填、唯一、默认值,标题和标签是英文占位——需要这些时用--spec。 - 表名固定为资源名加
s,接口路径同理。选资源名时要考虑复数形式。 - 加了业务规则后,要同步维护生成的接口测试。
- 后端报错里的字段名是中文标签,英文、日文界面下也显示中文标签。
脚手架不可用时,可以照 docs/templates/ 手写,替换规则见 docs/templates/backend/README.md。
pnpm verify
pnpm verify 是交付门禁,全部通过才算完成。
pnpm verify -- --module customer # 全部检查
pnpm verify -- --module customer --skip-build # 跳过前端构建(调试时提速)
pnpm verify -- --module customer --json # 输出结构化 JSON(stdout 只有 JSON)检查项
共 16 项检查,分三组。不适用的检查(例如没有数据权限的模块上的 data_scope_filter)会显示为跳过。
全局检查(每次都执行):
| 检查 | 内容 |
|---|---|
typescript_compile | tsc --noEmit,覆盖 apps/api(含 scripts、test)、apps/mcp 和 apps/web(含测试) |
no_local_has_permission | routes 文件中不得自定义 hasPermission |
migration_chain | drizzle 迁移 journal 线性、快照链完整、每条记录都有 SQL、没有多余 SQL |
migration_applied | 对比 journal 与数据库中的 drizzle.__drizzle_migrations,并确认模块表存在 |
openapi_sync | 每个已注册的 /api 路由都按 AGENTS.md「OpenAPI writing rules」写进了 docs/apifox-full.openapi.json,请求体也要与代码一致(执行 pnpm openapi:generate -- --dry-run --strict) |
docs_paths | AI 上下文文档中引用的路径是否存在(默认只警告,--strict-docs 时阻断) |
模块检查(传入 --module 时执行):
| 检查 | 内容 |
|---|---|
backend_file | 后端 routes / repository / service 文件存在 |
data_scope_filter | schema.ts 声明了 DATA_SCOPE 的模块,repository 必须用 dataScopeWhere 过滤;未声明时跳过 |
frontend_page | 前端页面文件存在 |
frontend_api | 前端 API 文件存在 |
router_registration | 路由已在 src/router.ts 或域 router.ts 注册 |
schema_registration | 表定义已在 db/schema/index.ts 注册 |
rbac_seed | seed-rbac.ts 中包含该模块的菜单或权限编码 |
构建与测试(可跳过):
| 检查 | 内容 | 跳过参数 |
|---|---|---|
frontend_build | 前端 Vite 构建 | --skip-build |
frontend_tests | 前端 Vitest | --skip-frontend-tests |
api_tests | 后端 Vitest(需要测试库) | --skip-api-tests |
其他参数
| 参数 | 说明 |
|---|---|
--skip-db | 跳过 migration_applied(不连接数据库) |
--run-rbac-sync | 额外执行一次 seed:rbac --incremental(检查项 rbac_sync) |
--database-url <url> | 指定 migration_applied 使用的数据库连接 |
--strict-docs | docs_paths 失败时阻断 |
调试过程中可以用跳过参数提速,交付前必须完整跑一次。
MCP Server
apps/mcp 把工具链暴露为 MCP 工具,MCP 客户端(如 Claude Desktop)不需要命令行也能走完整的开发流程。
| 工具 | 作用 |
|---|---|
get_project_context | 返回 AGENTS.md 全文和当前模块结构,实现新功能前调用 |
get_menu_tree | 返回数据库中的菜单树,用于确定 parent_id 和可用 ID |
get_spec_guide | 返回 spec 的 JSON Schema 和「需求 → spec」示例,写 spec 前调用 |
validate_spec | 校验 spec(参数 spec),说明会生成什么;不写文件 |
scaffold_feature | 调用 pnpm scaffold:传 spec(推荐),或 name、domain、fields;dry_run 只预览 |
check_openapi | 按 AGENTS.md「OpenAPI writing rules」检查接口文档,列出不合规的接口 |
run_verify | 调用 pnpm verify --json 并返回结果(参数 module、skip_build) |
init_rbac | 调用 pnpm seed:rbac -- --incremental |
run_migration | 执行 db:generate + db:migrate(参数 message 作为迁移描述) |
list_templates | 列出 docs/templates/ 下的模板 |
Claude Desktop 配置示例(claude_desktop_config.json):
{
"mcpServers": {
"castor": {
"command": "pnpm",
"args": ["--dir", "/path/to/castorjs", "-s", "mcp"]
}
}
}也可以先构建再用 node 直接运行:
pnpm --filter @castorjs/mcp build
node /path/to/castorjs/apps/mcp/dist/index.jsMCP Server 默认以自身所在位置推算仓库根目录,可用环境变量 CASTOR_KIT_ROOT 覆盖。
