--- name: build-db description: 计划第 3 段——解析 docs/03 → V1 migration + validate.sh 校验 + apply 到本地 MySQL;生成 docs/05 API 契约 + 回填 REQ 依赖接口;计算模块/REQ 依赖顺序写入 docs/08 § 二(排序权威);最终占位符扫描后停下(计划完成)。 user-invocable: false allowed-tools: Read Write Edit Glob Grep Skill AskUserQuestion Bash(mkdir *) Bash(mysql *) Bash(set *) Bash(. .env.local) Bash(grep *) Bash(bash *) Bash(./scripts/setup-test-db.sh) Bash(cat *) Bash(cp *) --- **所有输出必须使用中文。** # build-db ## 执行步骤 ### 步骤 0:打印当前位置流程图 用 `Bash` 执行以下命令(文件不存在时静默忽略,不报错): ```bash cat "${CLAUDE_PLUGIN_ROOT}/skills/crosscut/plan-start/banners/flow.txt" 2>/dev/null || true ``` 打印横幅:`▶ 计划阶段 ③ — build-db`。 ### A. DDL 生成(不依赖数据库连接) #### A.1 读 docs/03 并翻译为 DDL 读取 `docs/03-数据库设计文档.md`,按字段 / 索引 / 外键 / 业务注记**严格翻译**为: - 每张表一段 `CREATE TABLE` - 字段顺序与 docs/03 表格行序一致;`Nullable` 列直接映射 `NOT NULL` / `NULL`;`默认` 列映射 `DEFAULT `;列尾用 `COMMENT '<业务含义>'` 写注释 - 索引 - 外键:在所有表创建完成后**统一追加** 要求: - **严禁臆造** docs/03 中没有的表 / 字段 / 索引 / 外键 - **严禁省略** docs/03 中已有的列、注释或约束 - 字符集统一 `utf8mb4` + `utf8mb4_unicode_ci`,引擎统一 `InnoDB`,除非 docs/03 业务注记明确要求其他设置 #### A.2 落盘 V1 文件 `Bash`: `mkdir -p sql/migrations`。 用 `Write` 写 `sql/migrations/V1__initial_schema.sql`,内容 = 头部注释 + DDL 主体: 1. **头部注释**(6 行 SQL 注释): - `-- Flyway migration V1 — initial schema for `(从 `CLAUDE.md § 🎯 项目概述` 读) - `-- Generated: `(UTC ISO 8601 时间戳) - `-- Source: 由计划 ③ build-db 从 docs/03-数据库设计文档.md 翻译生成(schema SSoT 是 docs/03)` - `-- This is the FIRST migration; subsequent schema changes must be written as new files sql/migrations/V2__.sql, V3__... etc.` - `-- Apply: Flyway runs this automatically at Spring Boot startup.` - `-- Do not hand-edit this file after it is committed; write a new migration instead.` 2. **DDL 主体**:A.1 推导出的所有 `CREATE TABLE` → `CREATE INDEX` → `ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY`,按此顺序拼接。 #### A.3 校验 V1 ↔ docs/03 集合一致性 + 自主修正 调 `${CLAUDE_SKILL_DIR}/scripts/validate.sh` 做脚本化的校验: ```bash bash "${CLAUDE_SKILL_DIR}/scripts/validate.sh" \ sql/migrations/V1__initial_schema.sql \ docs/03-数据库设计文档.md ``` 退出码与处理: - `0` → 通过,进入步骤 B - `1` → **自主修正循环**(最多 3 轮,docs/03 是 SSoT 不动): 1. 解析 stderr 差异清单,修正 V1.sql 2. 重跑 validate.sh 3. 退出 0 → 进入 B;退出 1 且本轮 < 3 → 回步骤 1;本轮 ≥ 3 仍失败 → 停下,打印最终残留差异 + 已尝试的 3 轮修正摘要,让用户介入 - `2` → 用法错(V1 / docs 路径找不到),打印路径并停下 完成后(V1 写入并通过 validate.sh 校验),用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] V1 migration 已生成并校验 + apply 到本地 MySQL`(步骤 C 完成后一并勾选) ### B. 数据库环境检查 #### B.1 检查 .env.local 凭据 用 `Glob` 检查 `.env.local` 是否存在;不存在 → 提示用户重新运行计划 ② `design` 重建并停下。 用 `Bash` 加载并校验 5 个必填字段非空: ```bash set -a; . .env.local; set +a for v in DB_HOST DB_PORT DB_USER DB_PASSWORD DB_SCHEMA; do eval val=\${$v:-} [ -z "$val" ] && echo "MISSING: $v" done ``` 任一缺失 → 打印缺失字段名并停下,提示用户编辑 `.env.local` 后重跑。 #### B.2 验证 MySQL 连接 ```bash set -a; . .env.local; set +a mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" -e "SELECT 1;" ``` - **成功** → 进入步骤 C - **失败** → 打印具体错误(认证 / 主机不可达 / 端口拒接等),提示检查 `.env.local`,**停下**。 ### C. 自动导入 MySQL #### C.1 DROP+CREATE 空库 ```bash ./scripts/setup-test-db.sh ``` #### C.2 把 V1 灌入已清空的 schema ```bash set -a; . .env.local; set +a mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" "$DB_SCHEMA" \ < sql/migrations/V1__initial_schema.sql ``` 非零退出 → 报错停下,打印 mysql stderr。 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] V1 migration 已生成并校验 + apply 到本地 MySQL` #### C.3 自检 SHOW TABLES ```bash set -a; . .env.local; set +a ACTUAL=$(mysql -N -B -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" \ -e "SHOW TABLES;" "$DB_SCHEMA" | wc -l | tr -d ' ') EXPECTED=$(grep -c '^## `' docs/03-数据库设计文档.md) [ "$ACTUAL" = "$EXPECTED" ] || { echo "MISMATCH: actual=$ACTUAL expected=$EXPECTED"; exit 1; } ``` 行数不一致 → 报错停下;一致 → 进入步骤 D。 ### D. 生成 docs/05 API 契约 + 回填 REQ 依赖接口 #### D.1 渲染 docs/05 1. 读取 `${CLAUDE_SKILL_DIR}/templates/docs-05-header-template.md`,填充 `base_path`(从 `docs/04 § 一` 推导 API 前缀,如 `/api/v1`)、`auth_note`(认证方式说明)、`pagination_note`(分页参数约定),写入 `docs/05-API接口契约.md` 头部。 2. 对所有模块的每个 REQ:读取并推断 `${CLAUDE_SKILL_DIR}/templates/docs-05-endpoint-template.md`,追加到 docs/05。 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] docs/05 API 契约已生成 + 回填 REQ 依赖接口`(步骤 D.2 完成后一并勾选) #### D.2 回填模块头 + REQ 卡片的 TBD 字段 1. 在 `docs/01-需求清单/*/_module.md`(模块头)和 `docs/01-需求清单/*/REQ-*.md`(REQ 卡片)中搜索 `TBD(build-db 自动补)` 并回填。**不动** `TBD(design 自动补)`(应已由 design 回填完毕)。 - `_module.md` 的 `依赖模块` 字段:填入该模块所依赖的其他模块 ID(逗号分隔,无则填 `—`) - `REQ-*.md` 的 `依赖接口` 字段:填入该 REQ 依赖的接口路径(逗号分隔,无则填 `—`) 2. 打印回填统计:`build-db 回填 处模块"依赖模块" + 处 REQ"依赖接口"`。 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] docs/05 API 契约已生成 + 回填 REQ 依赖接口` ### E. 模块/REQ 依赖顺序 → docs/08 § 二 本步骤计算模块/REQ 依赖拓扑顺序,将结果写入 docs/08 § 二(该 § 二 行序是 coding-start 的排序权威,无单独开发计划文件)。 #### E.1 构建模块依赖 DAG + 拓扑排序 1. 读取所有 `docs/01-需求清单/*/_module.md`,提取每个模块的 `依赖模块` 字段(经步骤 D.2 已回填)。 2. 构建**模块依赖 DAG**(有向无环图),节点 = 模块 ID,边 = 「A 依赖 B → B 先于 A」。 3. 拓扑排序得到 `module_topo_order[]`。 4. **环依赖打破**:若模块 DAG 存在环(module_A ↔ module_B),按启发式(字母序 / 被依赖次数多者先)破环排出 `module_topo_order`,并在**参与环的模块里第一个 REQ** 的 `note` 字段填入原因(如 "A↔B 互依赖:先做 A 的骨架")。 #### E.2 模块内 REQ 排序 对每个模块内部: 1. 读取该模块所有 `REQ-*.md`,提取每个 REQ 的 `依赖接口` + `依赖表` 字段,推导 REQ 间依赖关系。 2. 拓扑排序得到模块内 `req_order[]`。 3. **REQ 级环依赖打破**:若模块内 REQ 互依赖,按字母序 / 被依赖次数多者先破环,`note` 填原因;非环 REQ `note` 留 `—`。 #### E.3 渲染 docs/08 § 二 按 `module_topo_order[]` 依次为每个模块渲染 bullet,追加到 `docs/08-模块任务管理.md` § 二(追加在 § 二 的注释块之后,保留原有注释格式示例): 读取 `${CLAUDE_SKILL_DIR}/templates/docs-08-module-row-template.md`,填充: - `{{module_id}}`:模块 ID - `{{module_name}}`:模块中文名 - `{{depends_on}}`:依赖模块 ID 列表(逗号分隔,无则填 `—`) - `{{path_scopes}}`:`backend/module//` - `{{req_checklist}}`:按模块内 `req_order[]` 生成,每行 ` - [ ] REQ-XXX-NNN 功能名` 每个模块 bullet 间空一行,一次性追加全部模块。 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] docs/08 § 二 模块清单(含 REQ 顺序)已生成` ### F. 一致性检查 + 占位符扫描 + 计划完成 #### F.1 一致性检查(最多 3 轮自修复) **检查项**: - 每个 docs/01 REQ 都出现在 docs/05(作为接口,如适用) - docs/08 § 二 的 `module_id` 集合 = docs/01-需求清单/ 的模块目录集合 **不一致 → 按差异类型自主修复**: - **REQ 缺 docs/05 endpoint** → 按步骤 D.1 规则为该 REQ 推测,并追加到 docs/05 - **module_id 缺 docs/08 § 二** → 按步骤 E 规则渲染该模块 bullet,按拓扑序插入正确位置 修复后重跑检查;通过 → 进入 F.2;3 轮仍失败 → 停下,打印最终残留差异 + 已尝试的 3 轮修复摘要让用户介入。 #### F.2 最终占位符扫描 a. **`TBD` → 自动补齐**:Grep 搜索 `TBD(design 自动补)` 和 `TBD(build-db 自动补)`。有命中则就地补填(design 残留 → 查 docs/03 填 `依赖表`;build-db 残留 → 查 docs/05 按 REQ-ID 填 `依赖接口`),再 Grep 确认 0 命中;仍残留报错停下。 b. **`【人工填写:...】` → QA 循环等用户补**: 循环执行直到搜索不到 `【人工填写:` 且用户选「继续」: - 0 命中 → 直接放行进入步骤 F.3 - 有命中 → 打印残留清单(`<文件:行号> — <内容摘要>`),用 `AskUserQuestion` 弹「继续」/「有疑问想先沟通」二选一;用户回答后重扫验证再决定放行 / 继续循环 **每次弹 QA 前都重扫一次**——保证用户看到的 N 是最新的,避免「用户以为填完但实际还有残留」直接放行。 #### F.3 勾选 § 一 ③ 顶层 + 打印计划完成横幅 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选 ③ 顶层: - `- [ ] 计划 ③ DB 初始化 + 下游文档 — build-db` 打印计划阶段终止横幅并**停下**(不自动进入 coding 阶段): ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ [build-db] ✅ 计划阶段(①②③)全部完成 所有规划文档已就绪,docs/08 § 一 全部勾选。 ⚠️ 进入 Coding 阶段前必须完成: 1. 审核全部 Plan 产出(docs/01 / 03 / 04 / 05 / 08 + CLAUDE.md + sql/migrations/V1 + scripts/*) 2. 把全部 Plan 产物 commit 到本地默认分支(main / master): git add -A && git commit -m "chore: plan phase done" 3. 运行 /erp-workflow:coding-start 进入编码阶段 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` **停止**,不调用任何下游 skill。 ## 参考 - `docs/03-数据库设计文档.md`(DDL 翻译输入,SSoT) - `docs/01-需求清单/index.md`(模块索引) - `docs/01-需求清单//_module.md`(模块依赖:回填 `依赖模块`;拓扑排序输入) - `docs/01-需求清单//REQ-*.md`(REQ 依赖:回填 `依赖接口`;模块内 REQ 排序输入) - `docs/08-模块任务管理.md` § 二(模块拓扑顺序追加目标;是 coding-start 的排序权威) - `${CLAUDE_SKILL_DIR}/scripts/validate.sh`(A.3 表名 + 每表列名集合校验脚本) - `${CLAUDE_SKILL_DIR}/templates/docs-05-header-template.md` - `${CLAUDE_SKILL_DIR}/templates/docs-05-endpoint-template.md` - `${CLAUDE_SKILL_DIR}/templates/docs-08-module-row-template.md`(模块 bullet 行模板) - `.env.local`(DB 凭据) - 产物:`sql/migrations/V1__initial_schema.sql`(由 Flyway 在 Spring Boot 启动时 apply)