--- name: db-init description: A4 DB 初始化——LLM 解析 docs/03-数据库设计文档.md → 生成 sql/migrations/V1__initial_schema.sql(DDL only,Flyway 初始 migration)→ 用 lib/validate-ddl.mjs 全量校验 DDL ↔ docs/03 一致性 → 验证 MySQL 连接 → 调 scripts/setup-test-db.mjs DROP+CREATE 空库 → 用 lib/apply-ddl.mjs apply V1。 user-invocable: false allowed-tools: Read Write Edit Glob Skill Bash(node *) --- **所有输出必须使用中文。** # db-init 每步骤完成后用 `Edit` 在 `docs/08-模块任务管理.md` § 一 勾选对应子项。 ## 执行步骤 ### A. DDL 生成(不依赖数据库连接) #### A.1 读 docs/03 并翻译为 DDL 读取 `docs/03-数据库设计文档.md`,对每张表生成一段 `CREATE TABLE`(字段顺序/可空/默认/列注释严格对齐 docs/03 行序),随后按顺序追加 `CREATE INDEX` 与统一追加的 `ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY`。**严禁臆造或省略** docs/03 中的任何表/字段/索引/外键/约束。字符集 `utf8mb4` + `utf8mb4_unicode_ci`、引擎 `InnoDB`,除非 docs/03 业务注记另有说明。 #### A.2 落盘 V1 文件 用 `Write` 写 `sql/migrations/V1__initial_schema.sql`(`Write` 自动创建父目录)。文件开头是以下 6 行注释,其后接 A.1 的 DDL 主体(`CREATE TABLE` → `CREATE INDEX` → `ALTER TABLE ... ADD FOREIGN KEY`): ```sql -- Flyway migration V1 — initial schema for -- 从 CLAUDE.md § 🎯 项目概述 读 -- Generated: -- UTC ISO 8601 -- Source: 由 A4 db-init 从 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. ``` #### A.3 校验 V1 ↔ docs/03 5 维一致性 + 自主修正 调 `${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs` 做跨平台、纯 Node 的 5 维校验(表集合 / 列名 / 列类型 / 索引 / 外键)。**注意参数顺序:docs/03 在前,V1.sql 在后。** ```bash node "${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs" \ docs/03-数据库设计文档.md \ sql/migrations/V1__initial_schema.sql ``` 退出码与处理: - `0` → 通过,进入步骤 B - `1` → 存在差异(5 维 diff 明细打印到 stderr)。进入**自主修正循环**(最多 3 轮,docs/03 是 SSoT 不动): 1. 解析 stderr 差异清单,修正 V1.sql 2. 重跑 `validate-ddl.mjs` 3. 退出 0 → 进入 B;退出 1 且本轮 < 3 → 回步骤 1;本轮 ≥ 3 仍失败 → 停下,打印最终残留差异 + 已尝试的 3 轮修正摘要,让用户介入 - `2` → 用法错(docs/03 / V1 路径找不到),打印路径并停下 完成后(V1 写入并通过 `validate-ddl.mjs` 校验),勾选: - ` - [ ] sql/migrations/V1__initial_schema.sql 已生成` - ` - [ ] DDL 与 docs/03 全量一致` ### B. 数据库环境检查 用 `Glob` 确认 `config-vars.yaml` 存在(不存在 → 提示重跑 A1 `scope-lock` 并停下)。用 `Read` 读其 `database:` 段,校验 `host` / `port` / `user` / `password` / `schema` 5 项均非空且非 `【人工填写` 占位——任一缺失 → 打印缺失字段并停下。 用解析出的值跑连通性自检。必须用 Node `spawnSync('mysql', args, {shell:false})`,不要把密码拼进 shell 命令;空密码也要传 `--password=`,避免 `mysql -p` 进入交互式等待。下面的 `node -e` 内联了与 `lib/yaml-config.mjs` 同规则的极简 YAML 读取(2 层 map + 标量),仅读 `database:` 段: ```bash node -e ' const { spawnSync } = require("node:child_process"); const { readFileSync } = require("node:fs"); const SQ = String.fromCharCode(39), DQ = String.fromCharCode(34); function parseScalar(raw) { let s = String(raw).trim(); if (s === "" || s[0] === "#") return ""; const q = s[0]; if (q === SQ || q === DQ) { const e = s.indexOf(q, 1); if (e !== -1) return s.slice(1, e); } const h = s.indexOf(" #"); if (h !== -1) s = s.slice(0, h).trim(); return s; } const cfg = {}; let sec = null; for (const raw of readFileSync("config-vars.yaml", "utf8").split(/\r?\n/)) { const t = raw.trim(); if (!t || t[0] === "#") continue; const c = raw.indexOf(":"); if (c < 0) continue; const k = raw.slice(0, c).trim(); if (!k) continue; const ind = raw.length - raw.replace(/^\s+/, "").length; const v = parseScalar(raw.slice(c + 1)); if (ind === 0) { if (v === "") { sec = {}; cfg[k] = sec; } else { cfg[k] = v; sec = null; } } else if (sec) { sec[k] = v; } } const db = cfg.database || {}; const args = [ `--host=${db.host}`, `--port=${db.port || "3306"}`, `--user=${db.user}`, `--password=${db.password || ""}`, "-e", "SELECT 1;" ]; const r = spawnSync("mysql", args, { stdio: "inherit" }); process.exit(r.status === null ? 1 : r.status); ' ``` 成功 → 进入步骤 C;失败 → 打印具体错误(认证 / 主机不可达 / 端口拒接)并停下。 勾选:` - [ ] config-vars.yaml DB 凭据已验证(mysql -e "SELECT 1" OK)` ### C. 自动导入 MySQL #### C.1 DROP+CREATE 空库 ```bash node scripts/setup-test-db.mjs ``` #### C.2 把 V1 灌入已清空的 schema 调 `${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs`:它用纯 JS 解析 `config-vars.yaml` 的 `database:` 段(**不** shell-source,消除注入),再经 mysql2 把 DDL 灌入 schema。 ```bash node "${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs" config-vars.yaml sql/migrations/V1__initial_schema.sql ``` 退出码与处理: - `0` → 成功,进入步骤 D - `1` → 失败:打印 stderr 并停下 - `2` → 用法错(路径找不到),打印路径并停下 勾选:` - [ ] setup-test-db.mjs DROP+CREATE + apply V1 已执行` ### D. 勾选 docs/08 进度 + 进入 A5 1. 勾选(A4 子项 + A4 顶层;DDL ↔ docs/03 5 维一致已由 A.3 校验过,V1 文件自此未变): - ` - [ ] DDL ↔ docs/03 apply 后 5 维一致(validate-ddl.mjs)` - `- [ ] A4 DB 初始化 — db-init` 2. 立即调用 `Skill(downstream-gen)` 进入 A5,不等用户手动输入。 ## 参考 - `${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs`(A.3 docs/03 ↔ V1.sql 5 维一致性校验,跨平台纯 Node) - `${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs`(C.2 安全解析 config-vars.yaml 的 database: 段 + mysql2 灌入 DDL,不 shell-source) - `${CLAUDE_PLUGIN_ROOT}/lib/yaml-config.mjs`(apply-ddl 依赖的极简 YAML 读取) - `docs/03-数据库设计文档.md`(DDL 翻译输入,SSoT) - `config-vars.yaml`(DB 凭据,A1 产出) - 产物:`sql/migrations/V1__initial_schema.sql`(由 Flyway 在 Spring Boot 启动时验证 / apply)