From 4c3fb21b07409f4bc832bca7f08d224e7c6522c3 Mon Sep 17 00:00:00 2001 From: zichun Date: Tue, 26 May 2026 10:32:28 +0800 Subject: [PATCH] feat(plan): front-load gates (per-REQ form, secrets/commands, docs05+02 review, frontend scope-lock) + de-bash + ERP-confirmable --- lib/merge-gitignore.mjs | 6 +++++- skills/db-design-gen/SKILL.md | 39 +++++++++++++++++++++++++++++++++------ skills/db-design-gen/templates/docs-03-header-template.md | 14 +++++--------- skills/db-design-gen/templates/docs-03-table-template.md | 6 ++---- skills/db-init/SKILL.md | 81 ++++++++++++++++++++++++++++++++++++--------------------------------------------- skills/db-init/scripts/validate.sh | 96 ------------------------------------------------------------------------------------------------ skills/downstream-gen/SKILL.md | 35 +++++++++++++++++++++++++++-------- skills/frontend-scope-lock/SKILL.md | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ skills/frontend-scope-lock/templates/fe-scope-template.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ skills/plan-start/SKILL.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- skills/project-init/SKILL.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++----------------------- skills/project-init/templates/docs-08-initial-template.md | 13 +++++++++---- skills/scope-lock/SKILL.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- skills/scope-lock/scripts/render.sh | 42 ------------------------------------------ skills/scope-lock/templates/req-card-template.md | 65 +++++++++++++++++++++++------------------------------------------ skills/skeleton-gen/SKILL.md | 62 +++++++++++++++++++++++++++++--------------------------------- skills/skeleton-gen/scripts/merge-gitignore.sh | 39 --------------------------------------- 17 files changed, 573 insertions(+), 384 deletions(-) delete mode 100644 skills/db-init/scripts/validate.sh create mode 100644 skills/frontend-scope-lock/SKILL.md create mode 100644 skills/frontend-scope-lock/templates/fe-scope-template.md delete mode 100644 skills/scope-lock/scripts/render.sh delete mode 100755 skills/skeleton-gen/scripts/merge-gitignore.sh diff --git a/lib/merge-gitignore.mjs b/lib/merge-gitignore.mjs index c4191e8..2024344 100644 --- a/lib/merge-gitignore.mjs +++ b/lib/merge-gitignore.mjs @@ -15,7 +15,11 @@ export function mergeGitignore(baseText, addText) { return text } -if (import.meta.url === `file://${process.argv[1]}`) { +// CLI entry: node lib/merge-gitignore.mjs +// Use pathToFileURL so the guard matches even when the path contains spaces or +// non-ASCII chars (import.meta.url is percent-encoded; process.argv[1] is raw). +const { pathToFileURL } = await import('node:url') +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { const [basePath, addPath] = process.argv.slice(2) const { readFileSync, writeFileSync } = await import('node:fs') const base = readFileSync(basePath, 'utf8') diff --git a/skills/db-design-gen/SKILL.md b/skills/db-design-gen/SKILL.md index 31e6750..e37b67d 100644 --- a/skills/db-design-gen/SKILL.md +++ b/skills/db-design-gen/SKILL.md @@ -2,7 +2,7 @@ name: db-design-gen description: A3 DB 设计 + REQ 回填——基于 docs/01-需求清单//REQ-*.md 正向设计 docs/03-数据库设计文档.md(业务实体 → 表 + 字段 + 索引 + 外键 + 业务注记),并把回填值写入 REQ 卡片的「依赖表: TBD(A3 自动补)」与模块头的「涉及表: TBD(A3 自动补)」占位。生成完毕停下等人工审阅。 user-invocable: false -allowed-tools: Read Write Edit Grep Glob Bash(cat *) +allowed-tools: Read Write Edit Grep Glob AskUserQuestion Bash(cat *) --- **所有输出必须使用中文。** @@ -24,6 +24,33 @@ allowed-tools: Read Write Edit Grep Glob Bash(cat *) cat "${CLAUDE_PLUGIN_ROOT}/skills/db-design-gen/banners/flow.txt" ``` +### 步骤 A0:确认 ERP schema 约定(一次性) + +在正向设计 schema 之前,**先确认本项目的 3 项数据库约定**。默认值即现 ERP 约定,用户可逐项覆盖。结果写入 `docs/04` + `CLAUDE.md`,本 skill 后续步骤(B/C)严格引用确认后的值,不再字面写死。 + +> 先用 `Grep` 检查 `docs/04-技术规范.md` 是否已含 `## 四、数据库 schema 约定` 节。**已存在则视为本约定已确认**,跳过本步骤直接读取该节作为 `PK_CONVENTION` / `TENANT_COLS` / `COL_PREFIX_RULE` 的值,进入步骤 A。 + +用 `AskUserQuestion` 一次性提出以下 3 个问题(每题给「沿用默认」与「自定义」两类选项;选自定义时追问具体值): + +1. **主键约定(PK_CONVENTION)** + - 默认(沿用 ERP):`iIncrement` int 整数主键(自增方式由实现决定:DB `AUTO_INCREMENT` 或应用/触发器分配),另设 `sId` varchar(100) 业务 ID 对外暴露。 + - 自定义:用户给出主键列名 / 类型 / 生成策略(如复合主键 / UUID / 业务主键)。 + +2. **租户隔离列(TENANT_COLS)** + - 默认(沿用 ERP):`sBrandsId` varchar(100)(品牌 ID,多租户隔离)+ `sSubsidiaryId` varchar(100)(子公司 ID,组织层级隔离),默认值均为 `1111111111`。 + - 自定义:用户给出租户列名 / 类型 / 默认值,或声明本项目不做多租户隔离(此时 TENANT_COLS = 「无」)。 + +3. **列命名前缀规则(COL_PREFIX_RULE)** + - 默认(沿用 ERP):匈牙利前缀——`i` = int、`s` = varchar、`t` = datetime。 + - 自定义:用户给出自己的前缀/命名规则,或声明不使用前缀(此时 COL_PREFIX_RULE = 「无前缀,列名直接用业务语义」)。 + +得到三项确认值后: + +- **写入 `docs/04-技术规范.md`**:用 `Grep` 确认无 `## 四、数据库 schema 约定` 节后,用 `Edit` 在文末追加该节,列出 `PK_CONVENTION` / `TENANT_COLS` / `COL_PREFIX_RULE` 三项的确认值(每项含列名/类型/默认值/生成策略的完整描述)。 +- **写入 `CLAUDE.md`**:用 `Edit` 在「Schema 演化规约」节末尾追加一条「数据库 schema 约定(A3 确认)」小节,简述三项约定,并注明「详见 `docs/04 § 四`」。 + +后续步骤 B/C 用到 `{{PK_CONVENTION}}` / `{{TENANT_COLS}}` / `{{COL_PREFIX_RULE}}` 处,一律引用此处确认后的值。 + ### A. 读取设计输入 读: @@ -36,18 +63,18 @@ cat "${CLAUDE_PLUGIN_ROOT}/skills/db-design-gen/banners/flow.txt" 基于步骤 A 读到的 REQ + 命名规范,**正向推导**业务实体 → 表 + 字段 + 索引 + 外键。要求: -1. 严格套用 `docs/04` 的命名规范 -2. **主键**:模板内置 `iIncrement` 为主键。REQ 明确要求复合主键 / UUID / 业务主键时按 REQ;其他主键变更需同步改 docs-03-header / docs-03-table 两份模板 +1. 严格套用 `docs/04` 的命名规范 + 步骤 A0 确认的 `COL_PREFIX_RULE` 列命名前缀规则 +2. **主键**:套用步骤 A0 确认的 `PK_CONVENTION`。REQ 明确要求与确认约定不同的主键(复合主键 / UUID / 业务主键)时按 REQ,并在该表业务注记里注明偏离原因 3. **外键**:依据 REQ 中的引用关系(如「订单引用客户」),明确列出 `ON DELETE` / `ON UPDATE` 策略;不能确定时默认 `RESTRICT` -4. **索引**:根据 REQ 的查询模式推导业务索引;外键列默认建索引;标准列里 `sBrandsId` / `sSubsidiaryId` 这类多租户隔离列,按业务查询模式建组合索引 +4. **索引**:根据 REQ 的查询模式推导业务索引;外键列默认建索引;步骤 A0 确认的 `TENANT_COLS` 租户隔离列(若非「无」),按业务查询模式建组合索引 5. **业务注记**:对每张表用一两句话说明业务用途、关键约束、与其他表的关系 如果某 REQ 表述模糊以致无法推断关键 schema 细节(如:枚举值范围 / 字段长度上限 / 必填性),先按合理默认推导并在该字段「业务含义」列加 `【人工填写:需用户审阅】` 标注,待步骤 E 用户审阅时调整;**不打断本次推导**。 ### C. 渲染 docs/03 -1. 读取 `${CLAUDE_SKILL_DIR}/templates/docs-03-header-template.md`,填充 `schema_name`(从 `.env.local` 读 `DB_SCHEMA`,无则填 `【人工填写:DB_SCHEMA】`)、`er_overview`(纯文本 ER 概览)。 -2. 渲染「表清单」:对每张表:读取并填充 `${CLAUDE_SKILL_DIR}/templates/docs-03-table-template.md`。 +1. 读取 `${CLAUDE_SKILL_DIR}/templates/docs-03-header-template.md`,填充 `schema_name`(从 `.env.local` 读 `DB_SCHEMA`,无则填 `【人工填写:DB_SCHEMA】`)、`er_overview`(纯文本 ER 概览),以及步骤 A0 确认的 `{{PK_CONVENTION}}` / `{{TENANT_COLS}}` / `{{COL_PREFIX_RULE}}`。 +2. 渲染「表清单」:对每张表:读取并填充 `${CLAUDE_SKILL_DIR}/templates/docs-03-table-template.md`,其中标准列区块用步骤 A0 确认的 `{{PK_CONVENTION}}` / `{{TENANT_COLS}}` 展开(`TENANT_COLS` = 「无」时不输出租户列行)。 3. 写入 `docs/03-数据库设计文档.md`。 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: diff --git a/skills/db-design-gen/templates/docs-03-header-template.md b/skills/db-design-gen/templates/docs-03-header-template.md index 6ef65d9..fa8507c 100644 --- a/skills/db-design-gen/templates/docs-03-header-template.md +++ b/skills/db-design-gen/templates/docs-03-header-template.md @@ -6,15 +6,11 @@ ## 项目标准列约定 -下文每张业务表的字段清单都自动包含以下 5 个标准列(匈牙利前缀 `i` int / `s` varchar / `t` datetime)。渲染时由 `docs-03-table-template.md` 模板内置原样输出。 - -| 列名 | 类型 | 可空 | 主键 | 说明 | -|---|---|---|---|---| -| `iIncrement` | int | 否 | 是 | 整数主键 ID(自增方式由实现决定:DB `AUTO_INCREMENT` 或应用 / 触发器分配) | -| `sId` | varchar(100) | 是 | — | 业务 ID(对外暴露的字符串标识,如 UUID / 人类可读编号) | -| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离) | -| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离) | -| `tCreateDate` | datetime | 否 | — | 记录创建时间 | +本项目的数据库约定(主键 / 租户隔离列 / 列命名前缀)由 A3 `db-design-gen` 在设计起始时一次性确认,确认值见 `docs/04-技术规范.md § 四、数据库 schema 约定`。下文每张业务表的字段清单都自动包含以下标准列,渲染时由 `docs-03-table-template.md` 模板按确认值展开。 + +- **列命名前缀规则**:{{COL_PREFIX_RULE}} +- **主键约定**:{{PK_CONVENTION}} +- **租户隔离列**:{{TENANT_COLS}} 字典 / 辅助表如有豁免,在该表业务注记里注明豁免原因。 diff --git a/skills/db-design-gen/templates/docs-03-table-template.md b/skills/db-design-gen/templates/docs-03-table-template.md index d05c5be..f43e1ea 100644 --- a/skills/db-design-gen/templates/docs-03-table-template.md +++ b/skills/db-design-gen/templates/docs-03-table-template.md @@ -4,10 +4,8 @@ | 字段 | 类型 | Nullable | 默认 | 业务含义 | |---|---|---|---|---| -| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | -| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | -| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | -| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | +{{PK_CONVENTION}} +{{TENANT_COLS}} | `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | {{#each columns}} | {{name}} | {{type}} | {{nullable}} | {{default}} | {{business_meaning}} | diff --git a/skills/db-init/SKILL.md b/skills/db-init/SKILL.md index fcc2be9..d574abc 100644 --- a/skills/db-init/SKILL.md +++ b/skills/db-init/SKILL.md @@ -1,8 +1,8 @@ --- name: db-init -description: A4 DB 初始化——LLM 解析 docs/03-数据库设计文档.md → 生成 sql/migrations/V1__initial_schema.sql(DDL only,Flyway 初始 migration)→ 全量校验 DDL ↔ docs/03 一致性 → 验证 MySQL 连接 → 调 scripts/setup-test-db.sh 复用三层防护并 DROP+CREATE 空库 → apply V1。 +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(mkdir *) Bash(mysql *) Bash(set *) Bash(. .env.local) Bash(grep *) Bash(bash *) Bash(./scripts/setup-test-db.sh) Bash(cat *) +allowed-tools: Read Write Edit Glob Skill Bash(node *) Bash(mysql *) --- **所有输出必须使用中文。** @@ -13,11 +13,7 @@ allowed-tools: Read Write Edit Glob Skill Bash(mkdir *) Bash(mysql *) Bash(set * ### 步骤 0:打印当前位置流程图 -用 `Bash` 执行 `cat` 命令向用户展示当前位置流程图(stdout 即 ASCII 框图): - -```bash -cat "${CLAUDE_PLUGIN_ROOT}/skills/db-init/banners/flow.txt" -``` +用 `Read` 读取 `${CLAUDE_PLUGIN_ROOT}/skills/db-init/banners/flow.txt`,再把其内容**原样直接输出**给用户(ASCII 框图,不要用 `cat`)。 ### A. DDL 生成(不依赖数据库连接) @@ -37,9 +33,7 @@ cat "${CLAUDE_PLUGIN_ROOT}/skills/db-init/banners/flow.txt" #### A.2 落盘 V1 文件 -`Bash`: `mkdir -p sql/migrations`。 - -用 `Write` 写 `sql/migrations/V1__initial_schema.sql`,内容 = 头部注释 + DDL 主体: +用 `Write` 写 `sql/migrations/V1__initial_schema.sql`(`Write` 会自动创建缺失的父目录 `sql/migrations/`),内容 = 头部注释 + DDL 主体: 1. **头部注释**(6 行 SQL 注释): - `-- Flyway migration V1 — initial schema for `(从 `CLAUDE.md § 🎯 项目概述` 读) @@ -51,25 +45,25 @@ cat "${CLAUDE_PLUGIN_ROOT}/skills/db-init/banners/flow.txt" 2. **DDL 主体**:A.1 推导出的所有 `CREATE TABLE` → `CREATE INDEX` → `ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY`,按此顺序拼接。 -#### A.3 校验 V1 ↔ docs/03 集合一致性 + 自主修正 +#### A.3 校验 V1 ↔ docs/03 5 维一致性 + 自主修正 -调 `${CLAUDE_SKILL_DIR}/scripts/validate.sh` 做脚本化的校验: +调 `${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs` 做跨平台、纯 Node 的 5 维校验(表集合 / 列名 / 列类型 / 索引 / 外键)。**注意参数顺序:docs/03 在前,V1.sql 在后。** ```bash -bash "${CLAUDE_SKILL_DIR}/scripts/validate.sh" \ - sql/migrations/V1__initial_schema.sql \ - docs/03-数据库设计文档.md +node "${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs" \ + docs/03-数据库设计文档.md \ + sql/migrations/V1__initial_schema.sql ``` 退出码与处理: - `0` → 通过,进入步骤 B -- `1` → **自主修正循环**(最多 3 轮,docs/03 是 SSoT 不动): +- `1` → 存在差异(5 维 diff 明细打印到 stderr)。进入**自主修正循环**(最多 3 轮,docs/03 是 SSoT 不动): 1. 解析 stderr 差异清单,修正 V1.sql - 2. 重跑 validate.sh + 2. 重跑 `validate-ddl.mjs` 3. 退出 0 → 进入 B;退出 1 且本轮 < 3 → 回步骤 1;本轮 ≥ 3 仍失败 → 停下,打印最终残留差异 + 已尝试的 3 轮修正摘要,让用户介入 -- `2` → 用法错(V1 / docs 路径找不到),打印路径并停下 +- `2` → 用法错(docs/03 / V1 路径找不到),打印路径并停下 -完成后(V1 写入并通过 validate.sh 校验),用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: +完成后(V1 写入并通过 `validate-ddl.mjs` 校验),用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] sql/migrations/V1__initial_schema.sql 已生成` - ` - [ ] DDL 与 docs/03 全量一致` @@ -79,23 +73,16 @@ bash "${CLAUDE_SKILL_DIR}/scripts/validate.sh" \ 用 `Glob` 检查 `.env.local` 是否存在;不存在 → 提示用户重新运行 A2 `skeleton-gen` 重建并停下。 -用 `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 -``` +用 `Read` 读取 `.env.local`,逐行解析 `KEY=VALUE`(跳过空行 / `#` 注释,**不做** shell-source / 变量展开),校验 5 个必填字段 `DB_HOST`、`DB_PORT`、`DB_USER`、`DB_PASSWORD`、`DB_SCHEMA` 均存在且非空。 任一缺失 → 打印缺失字段名并停下,提示用户编辑 `.env.local` 后重跑。 #### B.2 验证 MySQL 连接 +用 B.1 解析出的字段值(**不要** shell-source `.env.local`)拼出 `mysql` 客户端调用,做一次连通性自检。把 `` 等占位替换为读到的实际值: + ```bash -set -a; . .env.local; set +a -mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" -e "SELECT 1;" +mysql -h -P -u -p -e "SELECT 1;" ``` - **成功** → 进入步骤 C @@ -109,46 +96,50 @@ mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" -e "SELECT 1;" #### C.1 DROP+CREATE 空库 ```bash -./scripts/setup-test-db.sh +node scripts/setup-test-db.mjs ``` #### C.2 把 V1 灌入已清空的 schema +调 `${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs`:它用纯 JS 解析 `.env.local`(**不** shell-source,消除注入),再经 mysql2 把 DDL 灌入 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 +node "${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs" .env.local sql/migrations/V1__initial_schema.sql ``` -非零退出 → 报错停下,打印 mysql stderr。 +退出码与处理: +- `0` → 成功,进入 C.3 +- `1` → 失败:若错误提示 `mysql2 not found`,先在目标项目执行 `npm i mysql2` 再重跑;其余错误打印 stderr 并停下 +- `2` → 用法错(路径找不到),打印路径并停下 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: -- ` - [ ] setup-test-db.sh 防护通过 + DROP+CREATE + apply V1 已执行` +- ` - [ ] setup-test-db.mjs 防护通过 + DROP+CREATE + apply V1 已执行` + +#### C.3 自检:docs/03 ↔ V1 表集合一致 -#### C.3 自检 SHOW TABLES +再跑一次 `validate-ddl.mjs`,确认刚 apply 的 `V1__initial_schema.sql` 与 docs/03 的表/列/类型/索引/外键 5 维仍然一致(参数顺序:docs/03 在前,V1.sql 在后): ```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; } +node "${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs" \ + docs/03-数据库设计文档.md \ + sql/migrations/V1__initial_schema.sql ``` -行数不一致 → 报错停下;一致 → 进入步骤 D。 +退出码 `0` → 进入步骤 D;`1` → 打印 stderr 5 维 diff 并停下;`2` → 路径错,打印路径并停下。 ### D. 勾选 docs/08 进度 + 进入 A5 1. 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选(A4 子项 + A4 顶层): - - ` - [ ] SHOW TABLES 行数 == docs/03 表数量` + - ` - [ ] DDL ↔ docs/03 apply 后 5 维一致(validate-ddl.mjs)` - `- [ ] A4 DB 初始化 — db-init` 2. 立即调用 `Skill(downstream-gen)` 进入 A5,不等用户手动输入。 ## 参考 -- `${CLAUDE_SKILL_DIR}/scripts/validate.sh`(A.3 表名 + 每表列名集合校验脚本) +- `${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs`(A.3 / C.3 docs/03 ↔ V1.sql 5 维一致性校验,跨平台纯 Node) +- `${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs`(C.2 安全解析 .env.local + mysql2 灌入 DDL,不 shell-source) - `docs/03-数据库设计文档.md`(DDL 翻译输入,SSoT) - `.env.local`(DB 凭据) - 产物:`sql/migrations/V1__initial_schema.sql`(由 Flyway 在 Spring Boot 启动时验证 / apply) diff --git a/skills/db-init/scripts/validate.sh b/skills/db-init/scripts/validate.sh deleted file mode 100644 index bed28ad..0000000 --- a/skills/db-init/scripts/validate.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash -# validate.sh — 校验 V1.sql 与 docs/03 的两个集合一致性: -# 维度 1: 表名集合 -# 维度 2: 每张共有表的列名集合 -# -# 用法: bash validate.sh -# 退出码: -# 0 = 一致 -# 1 = 不一致(差异明细打印到 stderr) -# 2 = 用法错误(路径找不到等) - -set -uo pipefail -export LC_ALL=C # sort / comm 行为确定 - -V1=${1:?missing V1 sql path} -DOC=${2:?missing docs/03 path} - -[ -f "$V1" ] || { echo "validate.sh: V1 not found: $V1" >&2; exit 2; } -[ -f "$DOC" ] || { echo "validate.sh: docs not found: $DOC" >&2; exit 2; } - -ERR=0 - -# ─── 维度 1: 表名集合 ─────────────────────────────────────────── -TABLES_DOC=$(grep -E '^## `[^`]+`' "$DOC" \ - | sed -E 's/^## `([^`]+)`.*/\1/' | sort -u) -TABLES_SQL=$(grep -E '^CREATE TABLE `[^`]+`' "$V1" \ - | sed -E 's/^CREATE TABLE `([^`]+)`.*/\1/' | sort -u) - -ONLY_DOC=$(comm -23 <(echo "$TABLES_DOC") <(echo "$TABLES_SQL")) -ONLY_SQL=$(comm -13 <(echo "$TABLES_DOC") <(echo "$TABLES_SQL")) - -if [ -n "$ONLY_DOC" ] || [ -n "$ONLY_SQL" ]; then - { - echo "=== 维度 1: 表名集合不一致 ===" - [ -n "$ONLY_DOC" ] && { echo " docs/03 有但 V1 无:"; echo "$ONLY_DOC" | sed 's/^/ - /'; } - [ -n "$ONLY_SQL" ] && { echo " V1 有但 docs/03 无:"; echo "$ONLY_SQL" | sed 's/^/ - /'; } - } >&2 - ERR=1 - # 表数差异 → 不再做列校验 - exit 1 -fi - -# ─── 维度 2: 每张共有表的列名集合 ────────────────────────────── -COMMON=$(comm -12 <(echo "$TABLES_DOC") <(echo "$TABLES_SQL")) - -extract_doc_cols() { - local table=$1 - awk -v t="$table" ' - $0 ~ "^## `" t "`" { in_table=1; in_fields=0; next } - in_table && /^## `/ { exit } - in_table && /^### 字段/ { in_fields=1; next } - in_table && in_fields && /^###/ { in_fields=0 } - in_table && in_fields && /^\|/ { - n = split($0, a, "|") - gsub(/^[ ]+|[ ]+$/, "", a[2]) - gsub(/`/, "", a[2]) - if (a[2] != "" && a[2] != "字段" && a[2] !~ /^-+$/) print a[2] - } - ' "$DOC" | sort -u -} - -extract_sql_cols() { - local table=$1 - awk -v t="$table" ' - $0 ~ "^CREATE TABLE `" t "`" { in_table=1; next } - in_table && /^\)/ { in_table=0; next } - in_table && /^[[:space:]]*`[^`]+`/ \ - && $0 !~ /^[[:space:]]*(PRIMARY|UNIQUE|KEY|FOREIGN|CONSTRAINT|INDEX)/ { - match($0, /`[^`]+`/) - print substr($0, RSTART+1, RLENGTH-2) - } - ' "$V1" | sort -u -} - -while IFS= read -r t; do - [ -z "$t" ] && continue - D_COLS=$(extract_doc_cols "$t") - S_COLS=$(extract_sql_cols "$t") - ONLY_D=$(comm -23 <(echo "$D_COLS") <(echo "$S_COLS")) - ONLY_S=$(comm -13 <(echo "$D_COLS") <(echo "$S_COLS")) - if [ -n "$ONLY_D" ] || [ -n "$ONLY_S" ]; then - { - echo "=== 维度 2: 表 \`$t\` 列名不一致 ===" - [ -n "$ONLY_D" ] && { echo " docs/03 有但 V1 无:"; echo "$ONLY_D" | sed 's/^/ - /'; } - [ -n "$ONLY_S" ] && { echo " V1 有但 docs/03 无:"; echo "$ONLY_S" | sed 's/^/ - /'; } - } >&2 - ERR=1 - fi -done <<< "$COMMON" - -if [ $ERR -ne 0 ]; then - exit 1 -fi - -echo "validate.sh: ✓ 表名集合 + 每表列名集合 与 docs/03 一致" -exit 0 diff --git a/skills/downstream-gen/SKILL.md b/skills/downstream-gen/SKILL.md index 9d3cfc0..6c78c88 100644 --- a/skills/downstream-gen/SKILL.md +++ b/skills/downstream-gen/SKILL.md @@ -96,22 +96,41 @@ cp "${CLAUDE_SKILL_DIR}/templates/docs-10-header-template.md" docs/10-验收检 修复后重跑检查;通过 → 进入 2;3 轮仍失败 → 停下,打印最终残留差异 + 已尝试的 3 轮修复摘要让用户介入。 -2. **最终占位符扫描**(覆盖 Plan 阶段全部产出): +2. **最终占位符扫描 + 结构性残留检查**(覆盖 Plan 阶段全部产出): a. **`TBD` → 自动补齐**:Grep 搜索 `TBD(A3 自动补)` 和 `TBD(A5 自动补)`。有命中则就地补填(A3 残留 → 查 docs/03 填 `依赖表`;A5 残留 → 查 docs/05 按 REQ-ID 填 `依赖接口`),再 Grep 确认 0 命中;仍残留报错停下。 - b. **`【人工填写:...】` → QA 循环等用户补**: + b. **结构性残留检查(不只看字面占位符,校验 SSoT 完整性)**: - 循环执行直到搜索不到 `【人工填写:` 且用户选「继续」: - - 0 命中 → 直接放行进入步骤 3 - - 有命中 → 打印残留清单(`<文件:行号> — <内容摘要>`),用 `AskUserQuestion` 弹「继续」/「有疑问想先沟通」二选一;用户回答后重扫验证再决定放行 / 继续循环 + - **docs/05 每个端点都有请求/响应 schema**:逐个解析 docs/05 的每个 endpoint 段(以 `### REQ-` 开头的小节)。每段必须同时含非空的 `- **请求**:` 与 `- **响应**:` 行——值不得为空、`TBD`、`—` 或 `【人工填写:…】`。缺失/留空 → 按步骤 B 规则就地为该端点推测补全请求/响应 schema,再复核;3 轮仍补不全则计入残留清单(见下方 c),交由 QA 循环让用户介入。 + - **docs/02 DAG 覆盖每个 REQ**:收集 docs/01 全部 REQ-ID 集合,与 `docs/02 § 二、开发顺序清单` 表中出现的 `req_id` 集合比对。任一 docs/01 REQ 未出现在 docs/02 顺序清单 → 按步骤 A 子流程把该 REQ 插入其所属模块段的正确拓扑位置(保持同模块 REQ 连续),再复核;3 轮仍无法补齐则计入残留清单交由 QA 循环。 - **每次弹 QA 前都重扫一次**——保证用户看到的 N 是最新的,避免「用户以为填完但实际还有残留」直接放行。 + c. **`【人工填写:...】` + 结构性残留 → QA 循环等用户补**: -3. 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 勾选 A5 父项: + 循环执行直到(搜索不到 `【人工填写:`)**且**(步骤 b 两项结构检查均通过)且用户选「继续」: + - 0 残留 → 直接放行进入步骤 3 + - 有残留 → 打印残留清单(含字面占位符 `<文件:行号> — <内容摘要>`,以及结构性缺口 `docs/05 端点 缺请求/响应 schema` / `docs/02 顺序清单缺 REQ `),用 `AskUserQuestion` 弹「继续」/「有疑问想先沟通」二选一;用户回答后重扫 + 重跑结构检查再决定放行 / 继续循环 + + **每次弹 QA 前都重扫一次**(含两项结构检查)——保证用户看到的 N 是最新的,避免「用户以为填完但实际还有残留」直接放行。 + +3. **docs/05 + docs/02 人工评审闸(强制,未确认不得勾选 A5)**: + + 向用户摘要展示两份 SSoT 的关键内容,请其确认无误: + - **docs/05 API 契约**:列出全部端点(`METHOD PATH — REQ-ID`),并标注任何「由 A5 自动推断」的端点/字段供重点核对。 + - **docs/02 构建顺序**:展示 `req_order[]` 顺序,**特别标出所有 `note ≠ —` 的环依赖打破项**(启发式选定的顺序 + 原因),供用户判断该顺序是否可接受。 + + 用 `AskUserQuestion` 同时问两项确认(多问题表单),每项二选一: + - 「docs/05 端点/字段确认无误」:`确认` / `需要修改` + - 「docs/02 构建顺序可接受(含 cycle-breaking 选定的顺序)」:`确认` / `需要修改` + + 裁决: + - 两项均 `确认` → 进入步骤 4 勾选 A5。 + - 任一为 `需要修改` → 用 `AskUserQuestion` 收集具体修改点,就地修订对应文档(docs/05 端点/字段或 docs/02 顺序/备注),改完重新展示摘要并重跑本评审闸,直到两项均 `确认`。**未两项确认前禁止勾选 A5、禁止打印终止横幅。** + +4. 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 勾选 A5 父项: - `- [ ] A5 下游文档生成 — downstream-gen` -4. 打印 Plan 阶段终止横幅并**停下**(不自动进入 B 阶段): +5. 打印 Plan 阶段终止横幅并**停下**(不自动进入 B 阶段): ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/skills/frontend-scope-lock/SKILL.md b/skills/frontend-scope-lock/SKILL.md new file mode 100644 index 0000000..92682e6 --- /dev/null +++ b/skills/frontend-scope-lock/SKILL.md @@ -0,0 +1,126 @@ +--- +name: frontend-scope-lock +description: A6 前端范围锁定(Plan 期)——检查项目根 prototype/*.html,从 prototype + docs/01 REQ 卡片 + docs/05/06 提炼项目级 UI 约定、Design Tokens、组件库选型,逐项 AskUserQuestion 确认后写入 docs/06(UI 交互规范)+ docs/04(前端栈),并为每个 FE-NN 锁定设计决策表。把全部前端歧义前移到 Plan 期问清,使 Coding 阶段前端循环无需再弹窗。 +user-invocable: false +allowed-tools: Read Write Edit Glob Grep Skill AskUserQuestion +--- + +**所有输出必须使用中文。** + +# frontend-scope-lock + +A6 是 **Plan 阶段最后一个前端守门 skill**,由 `plan-start` 在 A5(downstream-gen)完成后派发。 + +**定位**:把原 Coding 阶段晚到的前端闸(`frontend-start` 的 prototype 检查 + `fe-feature-brainstorm` / `fe-feature-plan` 的交互式 Q&A)整体**前移到 Plan 期**。Plan 期允许 `AskUserQuestion` 问人;问清后写入 SSoT(docs/06 + docs/04),Coding 阶段的前端循环只读不问。 + +## 阶段范围 + +锁定**项目级**前端契约:UI 约定 / Design Tokens / 组件库选型 + 每个 FE-NN 的设计决策。**不涉及** SQL / migration / controller / service / DTO(后端范畴),也**不在此实现**任何页面代码(Coding 阶段做)。 + +## 执行步骤 + +### 步骤 0:打印当前位置流程图 + +向用户**直接输出**(模型自己打印,不调 bash / cat)当前位置: + +``` +┌────────────────────────────────────────────────────────┐ +│ 📋 阶段 A:规划(一次性) │ +│ A0 初始化 → A1 锁范围 → A2 骨架 → A3 DB 设计 │ +│ → A4 DB 初始化 → A5 下游文档 │ +│ ▶ A6 前端范围锁定(prototype + tokens + 约定) │ +│ 规划阶段到此结束 │ +└────────────────────────────────────────────────────────┘ +``` + +### 步骤 1:检查 prototype(缺失则在此问) + +用 `Glob` 检查项目根 `prototype/**/*.html`: + +- **至少 1 个 `.html`** → 通过,记下文件清单,进入步骤 2。 +- **0 个** → 这是 Plan 期,**可以问**。用 `AskUserQuestion` 告知用户: + - **question**:`未在 prototype/ 找到任何 .html 原型。前端范围锁定依赖原型作为页面骨架权威。` + - 选项:「我已补齐原型,请重新检查」(→ 重新 `Glob`,仍为 0 则重复本问)/「本项目无前端,跳过 A6」。 + - 选「无前端」→ 在 docs/08 § 一 勾选 A6 父项并注明「无前端,A6 跳过」,打印步骤 6 的终止横幅(产出标注「跳过」),**停止**,不写 docs/06 / docs/04。 + - 选「已补齐」且 `Glob` 命中 → 进入步骤 2。 + +### 步骤 2:收集证据(只读,不问) + +为提炼项目级约定,`Read` 以下来源: + +- **prototype/**:所有 `*.html`,作为页面布局 / 组件 / 交互的**实测权威**(DOM 结构、表单展现、列表范式、状态色实例)。 +- **docs/01-需求清单/**:各 `_module.md` + `REQ-*.md`,提取 UI 描述、业务校验、acceptance 中与界面相关的部分。 +- **docs/05-API接口契约.md**:端点列表,确认前端要消费的接口集合(影响页面状态机 / 加载态)。 +- **docs/06-UI交互规范.md**:已有的整体布局(§ 一)、标准页面类型(§ 二)、通用交互规则(§ 三)、Design Tokens(§ 四)、页面清单(§ 三 由 A5 填)。本 skill 在此基础上**收敛 / 补全**,不推翻已确认内容。 +- **docs/04-技术规范.md § 零**:技术栈表里的前端行(如 `前端 UI 组件` = Ant Design),作为组件库选型默认值来源。 + +把证据归纳为三组**草案**:(a) 项目级 UI 约定、(b) Design Tokens(全局调色板 + 组件级状态色)、(c) 组件库选型。草案优先复用 docs/06 / docs/04 既有值(已锁定的不重问),仅对 prototype 与现文档**不一致**或**缺失**的点进入步骤 3 确认。 + +### 步骤 3:逐项 AskUserQuestion 确认 + +**一次一问**,仅对真歧义点。每问给出「从证据提炼的默认值」让用户**确认 / 覆盖**,不造无意义确认题。建议覆盖(无歧义的项直接采用默认值,不弹问): + +1. **UI 约定**:整体布局骨架、最小视口 / 响应式策略、列表页范式、表单展现阈值(Modal / Drawer / 独立页)、操作反馈范式、数据展示约定、前端权限控制方式。 +2. **Design Tokens**:全局调色板语义→变量→默认值;组件级状态色表(编辑 / 只读 / 悬浮 的 bg/fg)。prototype 中出现但未登记的色值 → 问是否新增 token。 +3. **组件库选型**:UI 组件库 + 版本(默认取 docs/04 § 零)、主题接入方式、图标库、表格 / 表单是否二次封装。 + +每个确认结果即时记入对应草案。**绝不**留 `【人工填写:】` / `{{占位}}` / `TBD` 作为最终值。 + +### 步骤 4:写入 docs/06 + docs/04 + +用 `${CLAUDE_SKILL_DIR}/templates/fe-scope-template.md` 作为填充骨架,把步骤 3 确认后的真实值填入(剥掉模板内 HTML 注释),分别落盘: + +- **docs/06-UI交互规范.md**(用 `Edit` 合并,不另起文件): + - 模板 § 一 → 收敛 / 补全 docs/06 § 一(整体布局)+ § 三(通用交互规则)的项目级约定。 + - 模板 § 二 → 写入 / 校正 docs/06 § 四(Design Tokens 全局调色板 + 组件级状态色 + Token 默认值),与 `src/styles/tokens.css` 命名规范(docs/04 § 2.5)一致。 + - 模板 § 五 → 追加到 docs/06 § 三(页面清单)之后,作为 **FE 级设计决策表**:FE 清单来自 docs/08 § 三(若 § 三 尚无 FE bullet,则在此按 prototype + docs/01 + docs/05 推导 FE 清单并**同时写入 docs/08 § 三**「功能:」项,行格式见 docs/08 模板)。一 FE 一行。 +- **docs/04-技术规范.md § 二(前端编码规范)**(用 `Edit`): + - 把组件库选型 + 模板 § 四前端栈摘要写入 / 校正 § 2.3(组件 / 页面编写规范)与 § 2.5(样式与主题)的引用说明;色值约定指向 docs/06 § 四。 + - 不重复抄 docs/06 全文,只写「前端组件库 = X、tokens 锁定于 docs/06 § 四」这类引用,保持 SSoT。 + +写入时遵循模板的字面安全约定:值含 `$` / `{` / `}` 等字符**原样写入**,不做二次解释。 + +### 步骤 5:自审(inline 修,不等用户) + +- 模板五节均已填充,**无** `{{...}}` / `【人工填写:】` / `TBD` 残留(`Grep` 校验 docs/06 + docs/04 本次写入区域)。 +- 组件库选型与 docs/04 § 零前端行一致;如确认值覆盖了 § 零默认值,回写 § 零保持一致。 +- docs/06 § 五 / docs/08 § 三 的 FE-NN 集合一致,每个 FE 行字段非空。 +- 命中残留 → 就地修正后重新校验;无法自决的歧义 → 回步骤 3 补问。 + +### 步骤 6:勾选 docs/08 + 终止横幅 + +1. 用 `Edit` 在 `docs/08-模块任务管理.md § 一` 勾选 A6 子项 + 父项: + - ` - [ ] docs/06 项目级 UI 约定 + Design Tokens + 组件库已锁定` + - ` - [ ] docs/04 § 二 前端栈已锁定(引用 docs/06)` + - ` - [ ] 各 FE-NN 设计决策表已生成(docs/06 § 三之后 / docs/08 § 三)` + - `- [ ] A6 前端范围锁定 — frontend-scope-lock` +2. 向用户**直接输出**终止横幅并**停止**(不自动进入 B 阶段,回交给 plan-start 的终结闸判定): + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + [frontend-scope-lock] ✅ A6 前端范围锁定完成 + + 产出: + ✓ docs/06 § 一/§ 三 项目级 UI 约定 + ✓ docs/06 § 四 Design Tokens(全局调色板 + 组件级状态色) + ✓ docs/06 § 三之后 各 FE-NN 设计决策表 + ✓ docs/04 § 二 前端栈 + 组件库选型(引用 docs/06) + + 前端契约已全部锁定,Coding 阶段前端循环将只读不问。 + + Plan 阶段(A0~A6)到此结束。请运行 /erp-workflow:plan-start + 让终结闸校验全部前移闸门是否通过,再进入 B 阶段。 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +## 参考 + +- `${CLAUDE_SKILL_DIR}/templates/fe-scope-template.md`(产出骨架) +- `prototype/**/*.html`(页面骨架实测权威,步骤 1 前置门由本 skill 自承) +- `docs/01-需求清单/**/*.md`(UI 描述 / 业务校验来源) +- `docs/05-API接口契约.md`(前端消费端点) +- `docs/06-UI交互规范.md`(写入目标:§ 一/§ 三 约定、§ 四 Tokens、§ 三之后 FE 决策表) +- `docs/04-技术规范.md § 二 / § 零`(前端栈 + 组件库选型写入目标) +- `docs/08-模块任务管理.md § 一`(A6 进度勾选)/ `§ 三`(FE 清单) +- 上游:`plan-start`(A5 完成后派发到此) +- 下游:`plan-start` 终结闸(A6 完成后回交校验);Coding 阶段 `workflows/coding.mjs` 前端循环消费本 skill 的产出 diff --git a/skills/frontend-scope-lock/templates/fe-scope-template.md b/skills/frontend-scope-lock/templates/fe-scope-template.md new file mode 100644 index 0000000..bb8bd6a --- /dev/null +++ b/skills/frontend-scope-lock/templates/fe-scope-template.md @@ -0,0 +1,65 @@ + +## 一、项目级 UI 约定 + +> 来源:prototype/*.html 实测 + docs/01 REQ 卡片 UI 描述 + docs/06 已有布局。经用户确认。 + +| 约定项 | 锁定值 | 依据 | +| --- | --- | --- | +| 整体布局骨架 | {{LAYOUT_SKELETON}} | {{LAYOUT_SOURCE}} | +| 最小支持视口 / 响应式策略 | {{VIEWPORT_POLICY}} | {{VIEWPORT_SOURCE}} | +| 列表页交互范式 | {{LIST_PATTERN}} | {{LIST_SOURCE}} | +| 表单展现规则(Modal / Drawer / 独立页阈值) | {{FORM_PATTERN}} | {{FORM_SOURCE}} | +| 操作反馈范式(成功 / 失败 / 二次确认) | {{FEEDBACK_PATTERN}} | {{FEEDBACK_SOURCE}} | +| 数据展示约定(状态 / 日期 / 金额 / 空值 / 操作列) | {{DISPLAY_PATTERN}} | {{DISPLAY_SOURCE}} | +| 前端权限控制方式 | {{AUTH_PATTERN}} | {{AUTH_SOURCE}} | + +## 二、Design Tokens + +> 单元格写 token 名(`--color-xxx`),不写裸 hex;色值默认值落地于 `src/styles/tokens.css`。命名规范见 docs/04 § 2.5。 + +### 2.1 全局调色板 + +| 语义 | 变量名 | 默认值 | 用途 | +| --- | --- | --- | --- | +| {{TOKEN_SEMANTIC}} | {{TOKEN_VAR}} | {{TOKEN_VALUE}} | {{TOKEN_USAGE}} | + +### 2.2 组件级状态色 + +| 序号 | 组件 | 编辑 bg | 只读 bg | 悬浮 bg | 编辑 fg | 只读 fg | 悬浮 fg | 备注 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| {{COMP_NO}} | {{COMP_NAME}} | {{COMP_BG_EDIT}} | {{COMP_BG_READONLY}} | {{COMP_BG_HOVER}} | {{COMP_FG_EDIT}} | {{COMP_FG_READONLY}} | {{COMP_FG_HOVER}} | {{COMP_NOTE}} | + +## 三、组件库选型 + +| 项 | 锁定值 | 依据 / 备注 | +| --- | --- | --- | +| 前端 UI 组件库 | {{UI_LIB}} | {{UI_LIB_SOURCE}} | +| 版本 | {{UI_LIB_VERSION}} | 与 docs/04 § 零 一致 | +| 主题接入方式 | {{UI_LIB_THEME}} | 如 ConfigProvider.theme.token 引用 § 二全局调色板 | +| 图标库 | {{ICON_LIB}} | {{ICON_LIB_SOURCE}} | +| 表格 / 表单封装策略 | {{WRAP_STRATEGY}} | 是否二次封装、统一 props 约定 | + +## 四、前端栈摘要(同步 docs/04 § 二) + +| 分层 | 技术 | 版本 | 说明 | +| --- | --- | --- | --- | +| {{FE_LAYER}} | {{FE_TECH}} | {{FE_VERSION}} | {{FE_NOTE}} | + +## 五、各 FE 设计决策表 + +> 一 FE 一行,FE 清单来自 docs/08 § 三。每行锁定该 FE 的页面骨架来源、页面类型、组件库选型与状态机要点,供 Coding 阶段前端循环直接消费(不再二次问人)。 + +| FE-NN | 功能名 | 关联 REQ | 关联原型 | 页面类型 | 核心组件树要点 | 关键状态机(loading/empty/error/正常/提交中) | 引用 Design Tokens | +| --- | --- | --- | --- | --- | --- | --- | --- | +| {{FE_ID}} | {{FE_NAME}} | {{FE_REQS}} | {{FE_PROTOTYPES}} | {{FE_PAGE_TYPE}} | {{FE_COMPONENT_TREE}} | {{FE_STATE_MACHINE}} | {{FE_TOKENS}} | diff --git a/skills/plan-start/SKILL.md b/skills/plan-start/SKILL.md index 541fdd7..cb4dc95 100644 --- a/skills/plan-start/SKILL.md +++ b/skills/plan-start/SKILL.md @@ -1,17 +1,17 @@ --- name: plan-start -description: A 阶段(Plan)入口与分发器。根据 docs/08 § 一 的 checkbox 状态派发到 A0~A5 对应 skill。Plan 全部完成(A5 已勾)时打印提示让用户运行 /erp-workflow:coding-start 进入 B 阶段。 +description: A 阶段(Plan)入口与分发器。根据 docs/08 § 一 的 checkbox 状态派发到 A0~A6 对应 skill。Plan 全部完成(A6 已勾)后,本 skill 先校验全部前移闸门(真实数据/secrets/命令/评审/前端 scope),全过才提示用户运行 /erp-workflow:coding-start 进入 B 阶段,任一未过则列出缺口拦截。 user-invocable: true allowed-tools: Skill Read Glob Grep Bash(cat *) --- **所有输出必须使用中文。** -你是 ERP 项目**规划阶段的编排器**。你**只派发 A 阶段(A0~A5)的 skill**;docs/08 § 一 全部勾选后即停下,提示用户显式运行 `/erp-workflow:coding-start` 进入 B 阶段。你不直接生成任何文件。 +你是 ERP 项目**规划阶段的编排器**。你**只派发 A 阶段(A0~A6)的 skill**;docs/08 § 一 全部勾选后,你**不立即放行**——先执行 §2.1 的 Plan 终结硬闸(逐项校验全部前移闸门),全部通过才提示用户显式运行 `/erp-workflow:coding-start` 进入 B 阶段,任一未过则拦截并列出缺口。你不直接生成任何文件。 ## 第一步:读取 docs/08 + 决定分发目标 -docs/08 § 一 是**Plan 阶段进度追踪**(A0~A5 的 checkbox)。§ 二的模块元数据由 coding-start 读写,本 skill 不读。 +docs/08 § 一 是**Plan 阶段进度追踪**(A0~A6 的 checkbox)。§ 二的模块元数据由 coding-start 读写,本 skill 不读。 ### 分发判定 @@ -30,32 +30,70 @@ docs/08 § 一 是**Plan 阶段进度追踪**(A0~A5 的 checkbox)。§ 二 | 含 `A3` / `A3 子项` | `db-design-gen` | `A3` | | 含 `A4` / `A4 子项` | `db-init` | `A4` | | 含 `A5` / `A5 子项` | `downstream-gen` | `A5` | +| 含 `A6` / `A6 子项` | `frontend-scope-lock` | `A6` | | `A` 全勾,Plan 阶段结束 | **无分发** | - | ## 第二步:分发通知 + 调用目标 skill -### 2.1 Plan 已完成 +### 2.1 Plan 已完成 — 终结硬闸(HARD GATE) -A 阶段所有 checkbox 均 `[x]`。无后续 skill,本步骤**先打印整体流程图**,再输出完成横幅,然后**停下**: +A 阶段所有 checkbox 均 `[x]` 时**不代表可以进 B 阶段**。Coding 阶段为全自动静默 Workflow(子代理无法弹窗问人),因此**所有需求/配置必须在 Plan 期锁死**。本步骤是**硬闸**:先逐项校验下列全部前移闸门,**全部通过**才提示用户运行 `coding-start`;**任一未过**则**不提示进 B 阶段**,转而列出缺口并指明回填位置。 + +#### 第 1 步:先打印整体流程图 ```bash cat "${CLAUDE_PLUGIN_ROOT}/skills/plan-start/banners/flow-overview.txt" ``` -再向用户输出完成横幅: +#### 第 2 步:逐项校验前移闸门(用 Read / Glob / Grep,禁止跳过任何一项) + +逐项检查,记录每项 `通过` / `缺口`。任一缺口即整闸 `不通过`。 + +1. **REQ 卡片真实数据**(来自 A1 scope-lock) + - `Glob` 找出全部 REQ 卡片(如 `docs/01-需求清单/**/*.md`)。 + - 对每张卡片 `Grep` 残留占位:命中任一即缺口 — + `【人工填写`、`TBD`、`待补`、`<示例`、`示例值`(结构化字段的 `示例值` 列若仍是模板默认占位)。 + - 缺口表述示例:`REQ-USER-001 仍含 TBD / 示例值未替换为真实约束`。 + +2. **docs/07 secrets 全锁**(来自 A1 收集的 secret/account/package-name/namespace 清单) + - `Read` `docs/07-环境配置.md`。 + - 校验:scope-lock 写入的每个 secret/account/package-name/namespace 字段均有真实值,无 `【人工填写`/`TBD`/空值。任一未填即缺口。 + +3. **docs/04 §零 命令齐**(来自 A1 收集的每栈构建/lint/单测/e2e 命令) + - `Read` `docs/04-技术规范.md`,定位 `§ 零` 命令区。 + - 校验:每个技术栈的 build / lint / unit / e2e 命令均显式存在且非占位。缺命令或留 `TBD` 即缺口(Coding 期 test-gate / featureLoop 依赖这些命令,缺即无法静默跑)。 + +4. **docs/05 + docs/02 已评审**(来自 A5 downstream-gen 的评审闸) + - `Read` `docs/05-API接口契约.md` 与 `docs/02-开发计划.md`。 + - 校验:(a) docs/05 每个端点都有请求/响应 schema、无 `【人工填写`/`TBD`;(b) docs/02 每个 REQ 都在构建顺序 DAG 中、cycle-breaking 顺序有 `note` 说明;(c) downstream-gen 记录的「已人工评审」标记存在。缺任一即缺口。 + +5. **A6 前端 scope 已锁**(来自 A6 frontend-scope-lock) + - `Read` `docs/06-UI交互规范.md`。 + - 校验:项目级 UI 约定 / design tokens / 组件库选型已确认;每个 FE-NN 的设计决策表非占位;prototype 闸门已过(docs/08 § 一 A6 勾选即代表此项已由 A6 skill 锁定,但仍核对 docs/06 无 `【人工填写`/`TBD` 残留)。缺任一即缺口。 + +#### 第 3 步(A):全部通过 → 放行 + +仅当第 2 步 **5 项全部 `通过`** 时,向用户输出完成横幅: ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - [plan-start] ✅ Plan 阶段全部完成 + [plan-start] ✅ Plan 阶段全部完成,前移闸门全部通过 + + 已校验通过: + ✓ REQ 卡片均为真实数据(无占位/示例残留) + ✓ docs/07 secrets/account/package/namespace 全锁 + ✓ docs/04 §零 各栈 build/lint/unit/e2e 命令齐全 + ✓ docs/05 API 契约 + docs/02 构建顺序已评审 + ✓ A6 前端 scope(UI 约定 / tokens / 组件库)已锁 ⚠️ 进入 B 阶段前必须完成: 1. 人工通读 docs/* + CLAUDE.md + sql/migrations/V1 + 各 scripts/* 2. 把全部 Plan 产物 commit 到本地默认分支(main / master): - git add -A && git commit -m "chore: plan phase A0~A5 done" + git add -A && git commit -m "chore: plan phase A0~A6 done" 3. B 阶段全程纯本地(无需远程仓库 / push / MR): - 每个模块由 milestone-tag 本地 merge 进默认分支并打 milestone/ tag。 + 每个模块由 coding.mjs 的 milestone stage 本地 merge 进默认分支并打 milestone/ tag。 确认当前已在本地默认分支(main / master)上即可。 4. 运行 /erp-workflow:coding-start 进入 B 阶段 @@ -64,6 +102,29 @@ cat "${CLAUDE_PLUGIN_ROOT}/skills/plan-start/banners/flow-overview.txt" 不调任何下游 skill。 +#### 第 3 步(B):存在缺口 → 拦截,**不提示进 B 阶段** + +只要第 2 步出现**任一缺口**,**禁止**输出上面的放行横幅、**禁止**提示运行 `coding-start`。改为输出拦截横幅,逐条列出缺口与回填位置,让用户先补齐: + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + [plan-start] ⛔ Plan 终结闸未通过 — 暂不能进入 B 阶段 + + Coding 阶段全自动静默,缺失项无法在编码期补问,必须现在锁死。 + 以下缺口需补齐后重新运行 /erp-workflow:plan-start: + + <逐条列出每个缺口,格式:[闸门] 缺口描述 → 回填位置> + 例: + [REQ 真实数据] REQ-USER-001 输入字段「示例值」列仍为模板占位 → docs/01-需求清单/... + [docs/07 secrets] DB_PASSWORD 未填 → docs/07-环境配置.md + [docs/04 §零] node 栈缺 e2e 命令 → docs/04-技术规范.md §零 + + 补齐后再次运行 /erp-workflow:plan-start 重新校验。 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +输出拦截横幅后**停下**,不调任何下游 skill,不提示 coding-start。 + ### 2.2 正常派发(`后续` 非空) 先打印整体流程图,再打印分发通知: diff --git a/skills/project-init/SKILL.md b/skills/project-init/SKILL.md index d1aecf6..b5bdf86 100644 --- a/skills/project-init/SKILL.md +++ b/skills/project-init/SKILL.md @@ -1,8 +1,8 @@ --- name: project-init -description: A0 项目初始化——从插件模板幂等地复制 CLAUDE.md / docs/01-需求清单/index.md / docs/04-技术规范.md / docs/08-模块任务管理.md(已存在则跳过),并初始化 Git(如未初始化)。session-start 在 docs/08 缺失时派发本 skill。 +description: A0 项目初始化——从插件模板幂等地用 Read/Write 落盘 CLAUDE.md / docs/01-需求清单/index.md / docs/04-技术规范.md / docs/08-模块任务管理.md(已存在则跳过),做跨平台依赖检查(git/mysql/node 缺失则按 OS 给安装指引并停下),并初始化 Git(如未初始化)。session-start 在 docs/08 缺失时派发本 skill。 user-invocable: false -allowed-tools: Glob Edit Skill Bash(mkdir *) Bash(cp -n *) Bash(git init) Bash(command -v *) Bash(uname *) Bash(brew *) Bash(apt *) Bash(apt-get *) Bash(yum *) Bash(apk *) Bash(export PATH=*) Bash(echo *) Bash(cat *) +allowed-tools: Read Write Glob Edit Skill Bash(node *) Bash(git init) Bash(git rev-parse *) --- **所有输出必须使用中文。** @@ -13,38 +13,63 @@ allowed-tools: Glob Edit Skill Bash(mkdir *) Bash(cp -n *) Bash(git init) Bash(c ### 步骤 0:打印当前位置流程图 -用 `Bash` 执行 `cat` 命令向用户展示当前位置流程图(stdout 即 ASCII 框图): +用 `Read` 读取 `${CLAUDE_PLUGIN_ROOT}/skills/project-init/banners/flow.txt`,再把其内容**原样直接输出**给用户(ASCII 框图,不要用 `cat`)。 -```bash -cat "${CLAUDE_PLUGIN_ROOT}/skills/project-init/banners/flow.txt" -``` +### A. 幂等复制模板文件(用 Glob + Read + Write,跨平台无 shell) -### A. 幂等复制模板文件 +对下表每个文件:先用 `Glob` 判断目标路径是否已存在;**已存在则跳过**(不覆盖,幂等);不存在则用 `Read` 读模板、用 `Write` **原样**写到目标路径(`Write` 会自动创建缺失的父目录 `docs/01-需求清单/`,无需 `mkdir`)。 -用 `Bash` 一次性完成。`cp -n` 表示"不覆盖已存在的文件": - -```bash -mkdir -p docs/01-需求清单 -cp -n "${CLAUDE_SKILL_DIR}/templates/CLAUDE-template.md" CLAUDE.md -cp -n "${CLAUDE_SKILL_DIR}/templates/docs-01-index-template.md" docs/01-需求清单/index.md -cp -n "${CLAUDE_SKILL_DIR}/templates/docs-04-stack-template.md" docs/04-技术规范.md -cp -n "${CLAUDE_SKILL_DIR}/templates/docs-08-initial-template.md" docs/08-模块任务管理.md -``` +| 模板 | 目标路径 | +|---|---| +| `${CLAUDE_SKILL_DIR}/templates/CLAUDE-template.md` | `CLAUDE.md` | +| `${CLAUDE_SKILL_DIR}/templates/docs-01-index-template.md` | `docs/01-需求清单/index.md` | +| `${CLAUDE_SKILL_DIR}/templates/docs-04-stack-template.md` | `docs/04-技术规范.md` | +| `${CLAUDE_SKILL_DIR}/templates/docs-08-initial-template.md` | `docs/08-模块任务管理.md` | 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] 项目文件骨架已创建(CLAUDE.md + docs/01-需求清单/index.md + docs/04-技术规范.md)` -### B. 依赖检查 + 自动安装(命令行工具) +### B. 依赖检查(跨平台,只检测不自动安装) -对 **git、mysql** 两个工具依次执行以下流程。 - 1. 如果缺失,尝试自动安装。 - 2. 如果检测到不在 PATH,尝试添加进 PATH,并加载。 +对 **git、mysql、node** 三个命令做**跨平台 PATH 检测**:缺失则按当前操作系统(`process.platform`)打印对应安装指引并**停下**(不自动安装、不改 PATH——自动包管理在 Windows / 受限环境下不可靠,且静默改 PATH 有副作用)。 -全部通过后打印单行汇总再进入步骤 C: +用 `Bash` 跑下面这段 `node` 脚本(用 `process.platform` 区分 `darwin` / `win32` / `linux`,跨平台检测命令是否在 PATH 上): +```bash +node -e ' +const { spawnSync } = require("node:child_process"); +const plat = process.platform; +const has = (cmd) => { + const r = plat === "win32" + ? spawnSync("where", [cmd], { stdio: "ignore" }) + : spawnSync("sh", ["-c", "command -v " + cmd], { stdio: "ignore" }); + return r.status === 0; +}; +const guide = { + darwin: { git: "xcode-select --install 或 brew install git", + mysql: "brew install mysql", + node: "brew install node 或见 https://nodejs.org" }, + linux: { git: "sudo apt-get install git / sudo yum install git / sudo apk add git", + mysql: "sudo apt-get install mysql-client / sudo yum install mysql", + node: "sudo apt-get install nodejs 或见 https://nodejs.org" }, + win32: { git: "winget install Git.Git 或见 https://git-scm.com/download/win", + mysql: "winget install Oracle.MySQL 或见 https://dev.mysql.com/downloads/", + node: "winget install OpenJS.NodeJS 或见 https://nodejs.org" }, +}[plat] || {}; +const tools = ["git", "mysql", "node"]; +const missing = tools.filter((t) => !has(t)); +const mark = (t) => (missing.includes(t) ? "✗" : "✓"); +console.log("[project-init] 依赖检查 (" + plat + "): " + tools.map((t) => t + " " + mark(t)).join(" ")); +if (missing.length) { + console.error("[project-init] 缺失依赖,请先安装后重跑:"); + for (const t of missing) console.error(" - " + t + " : " + (guide[t] || "见对应官网")); + process.exit(1); +} +' ``` -[project-init] 依赖检查: git ✓ mysql ✓ -``` + +- 退出码 `0`(全部 ✓)→ 进入步骤 C +- 退出码 `1`(有缺失)→ 已打印缺失工具 + OS 安装指引,**停下**,提示用户安装后重跑本 skill 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] 依赖检查通过` diff --git a/skills/project-init/templates/docs-08-initial-template.md b/skills/project-init/templates/docs-08-initial-template.md index 092902c..2b9349b 100644 --- a/skills/project-init/templates/docs-08-initial-template.md +++ b/skills/project-init/templates/docs-08-initial-template.md @@ -1,7 +1,7 @@ # 08-工作流进度 > 全流程进度跟踪。CC 每完成一项产出就勾选一项。 -> - **§ 一 Plan(A0~A5)**:`plan-start` 找第一个未勾 A 子项分发到对应 skill +> - **§ 一 Plan(A0~A6)**:`plan-start` 找第一个未勾 A 子项分发到对应 skill > - **§ 二 Coding(模块)**:分发以 `docs/02-开发计划.md § 二 开发顺序清单` 为准;`coding-start` 按 docs/02 顺序扫描,对每个 REQ 所属模块查询本 § 二的 `里程碑:` 字段 + 本地 `git tag -l 'milestone/'`,找第一个未打里程碑模块分发。本 § 二 行序无语义,仅作模块元数据表 ## 一、Plan 阶段(一次性) @@ -19,7 +19,7 @@ - [ ] A2 骨架生成 — skeleton-gen - [ ] 架构文档已生成(docs/04 § 一+、docs/06、docs/07、docs/09) - - [ ] 工具脚本已生成(scripts/*.sh、.env.local) + - [ ] 工具脚本已生成(scripts/*.mjs、.env.local) - [ ] .gitignore 已配置 - [ ] A3 DB 设计 + REQ 回填 — db-design-gen @@ -30,8 +30,8 @@ - [ ] sql/migrations/V1__initial_schema.sql 已生成 - [ ] DDL 与 docs/03 全量一致 - [ ] .env.local 凭据已验证(mysql -e "SELECT 1" OK) - - [ ] setup-test-db.sh 防护通过 + DROP+CREATE + apply V1 已执行 - - [ ] SHOW TABLES 行数 == docs/03 表数量 + - [ ] setup-test-db.mjs 防护通过 + DROP+CREATE + apply V1 已执行 + - [ ] DDL ↔ docs/03 apply 后 5 维一致(validate-ddl.mjs) - [ ] A5 下游文档生成 — downstream-gen - [ ] docs/02 开发计划已生成 @@ -41,6 +41,11 @@ - [ ] 下方模块列表已填入 - [ ] REQ 卡片依赖接口已回填 +- [ ] A6 前端范围锁定 — frontend-scope-lock + - [ ] docs/06 项目级 UI 约定 + Design Tokens + 组件库已锁定 + - [ ] docs/04 § 二 前端栈已锁定(引用 docs/06) + - [ ] 各 FE-NN 设计决策表已生成(docs/06 § 三之后 / docs/08 § 三) + ## 二、Coding 阶段(后端模块循环) (A5 填入后,每行一个后端模块。每个模块的 `里程碑:` 字段在 `—` 和 `milestone/` 之间变化,完成由本地 `git tag -l` 判定。`coding-start` 每次按 docs/02 REQ 序扫每模块的里程碑 tag 决定派发。后端模块全部打里程碑后自动进入 § 三 前端阶段。) diff --git a/skills/scope-lock/SKILL.md b/skills/scope-lock/SKILL.md index c5607c3..7441abc 100644 --- a/skills/scope-lock/SKILL.md +++ b/skills/scope-lock/SKILL.md @@ -1,8 +1,8 @@ --- name: scope-lock -description: A1 计划范围锁定——引导用户填写项目概述 + 技术栈 + 需求索引,并按模块子目录生成 REQ 卡片骨架(CC 推断 req_id/title/goal/rules/constraints/acceptance;输入/输出 各含一句简述 + N 张示例字段表全部原样复制由人工编辑;依赖表/依赖接口模板写死 `TBD(A3/A5 自动补)` 由后续 skill 回填)。 +description: A1 计划范围锁定——引导用户填写项目概述 + 技术栈 + 需求索引,并按模块子目录生成 REQ 卡片骨架(CC 推断 req_id/title/goal/rules/constraints/acceptance;输入/输出 字段表为结构化 6 列表单由人工逐行填真实数据);末尾执行 A1 终结校验:每张 REQ 卡片字段含真实数据、secrets/account/包名/命名空间字段名锁进 docs/07、build/lint/unit/e2e 命令锁进 docs/04 §零,缺则当场 AskUserQuestion 问清。 user-invocable: false -allowed-tools: Read Edit Grep Skill AskUserQuestion Bash(mkdir *) Bash(cp *) Bash(sed *) Bash(bash *) Bash(cat *) +allowed-tools: Read Write Edit Grep Glob Skill AskUserQuestion Bash(mkdir *) Bash(node *) Bash(rm *) Bash(cat *) --- **所有输出必须使用中文。** @@ -108,43 +108,114 @@ cat "${CLAUDE_PLUGIN_ROOT}/skills/scope-lock/banners/flow.txt" 1. 用 `Grep` 校验 `docs/01-需求清单/index.md` 无 `【人工填写:` 残留;有则回步骤 C。 2. 用 `Read` 读 `index.md` 解析模块索引。 -3. **单次 Bash 写入所有文件**(替代 N×9 次 Edit): +3. **逐文件渲染落盘**(用 Node 渲染助手,跨平台、字面量安全): - - **每模块推断**:三元组 `{module_code, module_name, module_brief}` + N 个 REQ 的六元组 `{req_id, title, goal, rules, constraints, acceptance}`。`req_id`/`title` 从核心功能点拆分;`goal` 展开 `title`;`rules`/`constraints`/`acceptance` 起草业务语义。**不推断**输入 / 输出(模板原样保留示例)。 - - **单次 Bash**:调 `${CLAUDE_SKILL_DIR}/scripts/render.sh` 落盘所有文件(脚本内部完成模板读取 + 占位符替换 + HTML 注释剥离)。下面是**调用形态**示例,`` / `<模块名>` / `` 等尖括号位置 CC 按 `index.md` 实际值替换: + - **每模块推断**:三元组 `{module_code, module_name, module_brief}` + N 个 REQ 的六元组 `{req_id, title, goal, rules, constraints, acceptance}`。`req_id`/`title` 从核心功能点拆分;`goal` 展开 `title`;`rules`/`constraints`/`acceptance` 起草业务语义。**不推断**输入 / 输出字段(模板的结构化字段表保留示例行,留待人工逐行填真实数据)。 + - **渲染助手** `lib/render.mjs` 的 CLI 形态为 `node <模板路径> <输出路径>`:读模板 + 读 JSON 变量 + 占位符字面替换 + 自动剥离 HTML 注释,写到输出路径。值含 `$`、`{`、`}`、`}}` 均按字面插入,无需兜底。 + - **每个文件三步**:(a) 用 `Write` 写一个临时 vars JSON(仅含该文件用到的占位符键值);(b) `mkdir -p` 模块目录;(c) `node` 调 render.mjs 渲染。`` / `<模块名>` / `` 等尖括号位置 CC 按 `index.md` 实际值替换。调用形态: ```bash - R="${CLAUDE_SKILL_DIR}/scripts/render.sh" - # 每个模块:mkdir + 1 个 render module + N 个 render req(每 REQ 一行) + # 模块头:先 Write docs/01-需求清单/-<模块名>/_module.vars.json,内容形如 + # {"module_code":"","module_name":"<模块名>","module_brief":""} mkdir -p "docs/01-需求清单/-<模块名>" - bash "$R" module "docs/01-需求清单/-<模块名>/_module.md" "" "<模块名>" "" - bash "$R" req "docs/01-需求清单/-<模块名>/.md" "" "" "<goal>" "<rules>" "<constraints>" "<acceptance>" + node "${CLAUDE_PLUGIN_ROOT}/lib/render.mjs" \ + "${CLAUDE_SKILL_DIR}/templates/_module-template.md" \ + "docs/01-需求清单/<MOD>-<模块名>/_module.vars.json" \ + "docs/01-需求清单/<MOD>-<模块名>/_module.md" + + # 每个 REQ:先 Write 该 REQ 的 vars JSON,内容形如 + # {"req_id":"<REQ-MOD-NNN>","title":"<title>","goal":"<goal>","rules":"<rules>","constraints":"<constraints>","acceptance":"<acceptance>"} + node "${CLAUDE_PLUGIN_ROOT}/lib/render.mjs" \ + "${CLAUDE_SKILL_DIR}/templates/req-card-template.md" \ + "docs/01-需求清单/<MOD>-<模块名>/<REQ-MOD-NNN>.vars.json" \ + "docs/01-需求清单/<MOD>-<模块名>/<REQ-MOD-NNN>.md" ``` - - **兜底**:值含字面 `$xxx` 或 `}}` 的 REQ 单独走 cp + Edit。 -4. 用 `Edit` 在 `docs/08-模块任务管理.md` 勾选(A1 子项 + A1 顶层): + - 渲染完成后删除临时 `*.vars.json`(`rm` 或不留亦可,不进入交付物)。 +4. 用 `Edit` 在 `docs/08-模块任务管理.md` 勾选(A1 子项): - ` - [ ] REQ 卡片骨架已生成(docs/01-需求清单/<module>/REQ-*.md,业务内容留待人工填写)` - - `- [ ] A1 范围锁定 — scope-lock` -5. 打印停下横幅并**停止**: + + **注意**:此处先**不**勾选 `- [ ] A1 范围锁定 — scope-lock` 顶层项;A1 顶层项必须在步骤 E(A1 终结校验)全部通过后才勾选。 +5. 打印「请人工填写 REQ 卡片」横幅并提示用户填完后回来继续(不停止,下一步是 A1 终结校验): ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - [scope-lock] ✅ A1 范围锁定完成 + [scope-lock] REQ 卡片骨架已生成 产出: - ✓ CLAUDE.md § 🎯 项目概述 - ✓ docs/04 § 零 技术栈 - ✓ docs/01-需求清单/index.md 模块索引 ✓ docs/01-需求清单/<module>/_module.md 模块头 ✓ docs/01-需求清单/<module>/REQ-*.md REQ 卡片骨架 - ⏸ 现在请你逐张打开 REQ 卡片: - - **必改**:输入 / 输出 两段 - · `表1` / `表2` 是模板示例,按本 REQ 业务**改字段 / 增删行 / 增删整张表** + ⏸ 现在请你逐张打开 REQ 卡片,把结构化字段表填成真实数据: + - **必改**:`输入字段` / `输出字段` 两张表 + · 删掉「【示例行,替换为真实字段】」示例行,按本 REQ 业务逐行填真实字段 + · 6 列全部填:字段名 / 类型 / 必填 / 校验规则 / 业务规则 / 示例值 + · **「示例值」列必须是真实约束下的合法取值**——留 `<示例值>` 或示例行会被 A1 终结校验阻断 - **审阅**:目标 / 跨字段规则 / 边界 / 验收(已起草,对照业务校正) - **保留**:`TBD` 不要改,由之后流程自动回填 - 审阅完成后,运行以下命令继续进入 A2: + 填完后回来选择「继续」,进入 A1 终结校验。 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +用 `AskUserQuestion` 询问: +- **question**: `所有 REQ 卡片的结构化字段都填成真实数据了吗?` + - 用户选择「继续」→ 进入步骤 E(A1 终结校验)。 + - 用户选择「有疑问想先沟通」→ 回答用户问题后,再次弹出同样的 QA。 + +### E. A1 终结校验(硬闸:全部通过才勾选 A1 顶层) + +> 本步骤把原本会在编码期(feature-brainstorm / feature-plan)弹出的需求澄清 / config Q&A **全部前移到此处**。任一检查不满足,用 `AskUserQuestion` 在**此处(Plan 期)**问清并修订,**禁止**留待编码期。三项检查全过后才在 `docs/08` 勾选 `- [ ] A1 范围锁定 — scope-lock`。 + +#### E.1 真实数据校验(每张 REQ 卡片) + +1. 用 `Glob` 列出所有 `docs/01-需求清单/<module>/REQ-*.md`。 +2. 对每张卡片用 `Read` + `Grep` 校验: + - **无模板默认占位**:卡片内**不得**出现 `【示例行,替换为真实字段】` 或 `<示例值>`(用 `Grep` 搜这两个字面量,命中即视为未完成)。 + - **结构化字段非空**:`输入字段` / `输出字段` 两张表每行 6 列均非空,且不是模板示例行内容;至少各有 1 行真实字段(除非该 REQ 确实无输入/输出,需在卡片显式标注)。 + - **`示例值` 列已填真实取值**:每行示例值列为真实约束下的合法取值,非占位。 + - **`{{...}}` 残留**:不得残留任何 `{{` 占位(说明渲染未替换)。 +3. 任一卡片不通过:打印不通过的卡片路径 + 具体缺口行,用 `AskUserQuestion` 引导用户当场补齐(可针对具体字段含义/校验规则发问),修订后重跑 E.1,直到全部通过。 + +#### E.2 secrets / account / 包名 / 命名空间字段名锁进 docs/07 + +1. 用 `Read` 读 `docs/04-技术规范.md` § 零技术栈,结合 REQ 卡片,盘点本项目需要的**敏感 / 专属配置字段名**,至少覆盖以下类别(按实际技术栈裁剪): + - **secrets**:数据库密码、JWT/签名密钥、第三方 API key/secret、OSS/对象存储凭证、短信/邮件服务凭证等。 + - **account**:数据库账号、第三方服务账号、管理员初始账号等。 + - **包名 / 命名空间**:后端根包名(Java package / C# namespace / Python 顶层包 / Go module path 等)、前端 npm 包名 / scope。 +2. 用 `Read` 读 `docs/07-环境配置.md` § 零「人工占位速查表」。把上面盘点出的**每一个字段名**作为一行登记进该表(位置 / 含义 / 示例值三列),对应正文配置里以 `【人工填写:...】` 标记。**只锁字段名清单**(哪些值需要人工提供),不要求此刻填入真实 secret 值——真实值由人工在 docs/07 Step 3 填,但字段清单必须在此锁全。 +3. 用 `AskUserQuestion` 向用户确认:「以下 secret/account/包名/命名空间字段已登记进 docs/07 速查表,是否齐全?还有遗漏的吗?」展示登记的字段名清单。用户补充则继续登记,直到确认齐全。 + +#### E.3 build / lint / unit / e2e 命令锁进 docs/04 § 零 + +1. 用 `Read` 读 `docs/04-技术规范.md` § 零技术栈表,确定本项目涉及的每个 stack(如后端、前端,可能多个)。 +2. 对**每个 stack**,用 `AskUserQuestion`(如默认值可推断则展示默认值让用户确认/覆盖)收集四类命令: + - **build**(构建命令,如 `mvn package` / `npm run build`) + - **lint**(静态检查命令,如 `npm run lint`) + - **unit**(单元测试命令,如 `mvn test` / `npm run test:unit`) + - **e2e**(端到端测试命令,如 `npm run test:e2e`;无则显式记 `无`) +3. 把确认后的命令写入 `docs/04-技术规范.md` § 零(若 § 零无「命令」小节则用 `Edit` 新增一个「命令清单」小节,按 stack 分组列出 build/lint/unit/e2e 四行)。这些命令是 Coding 阶段 `coding.mjs` 的 tdd / test-gate 读取来源,必须在此锁全。 + +#### E.4 全部通过后勾选 A1 顶层并停止 + +三项检查(E.1 / E.2 / E.3)全部通过后: + +1. 用 `Edit` 在 `docs/08-模块任务管理.md` 勾选 `- [ ] A1 范围锁定 — scope-lock` 顶层项。 +2. 打印 A1 完成横幅并**停止**: + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + [scope-lock] ✅ A1 范围锁定完成(终结校验通过) + + 产出 & 锁定: + ✓ CLAUDE.md § 🎯 项目概述 + ✓ docs/04 § 零 技术栈 + build/lint/unit/e2e 命令 + ✓ docs/01-需求清单/index.md 模块索引 + ✓ docs/01-需求清单/<module>/_module.md 模块头 + ✓ docs/01-需求清单/<module>/REQ-*.md 字段表已填真实数据 + ✓ docs/07 § 零 secrets/account/包名/命名空间字段清单已锁 + + 运行以下命令继续进入 A2: /erp-workflow:plan-start ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -157,6 +228,7 @@ cat "${CLAUDE_PLUGIN_ROOT}/skills/scope-lock/banners/flow.txt" - `docs/01-需求清单/index.md`(模块索引输入) - `docs/01-需求清单/<module>/_module.md`(模块头输出) - `docs/01-需求清单/<module>/REQ-*.md`(REQ 卡片骨架输出,A3 db-design-gen / A5 downstream-gen 会回填 TBD 字段) -- `${CLAUDE_SKILL_DIR}/templates/req-card-template.md` +- `docs/07-环境配置.md` § 零(A1 终结校验 E.2:secrets/account/包名/命名空间字段名锁定) +- `${CLAUDE_SKILL_DIR}/templates/req-card-template.md`(结构化 6 列字段表) - `${CLAUDE_SKILL_DIR}/templates/_module-template.md` -- `${CLAUDE_SKILL_DIR}/scripts/render.sh`(步骤 D 渲染助手,dispatch `module` / `req` 两种模式) +- `${CLAUDE_PLUGIN_ROOT}/lib/render.mjs`(步骤 D 渲染助手;CLI 形态 `node render.mjs <模板> <vars.json> <输出>`,字面量安全 + 自动剥 HTML 注释) diff --git a/skills/scope-lock/scripts/render.sh b/skills/scope-lock/scripts/render.sh deleted file mode 100644 index 8a83488..0000000 --- a/skills/scope-lock/scripts/render.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -# render.sh — scope-lock 步骤 D 渲染单个 _module.md 或 REQ-*.md -# -# 用法: -# bash render.sh module <out_path> <module_code> <module_name> <module_brief> -# bash render.sh req <out_path> <req_id> <title> <goal> <rules> <constraints> <acceptance> -# -# 模板路径自定位:脚本同级父目录下 templates/{_module-template.md, req-card-template.md} - -set -euo pipefail - -TYPE=${1:?missing type (module|req)} -shift - -SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -TPL_DIR="$SCRIPT_DIR/../templates" - -case "$TYPE" in - module) - out=${1:?missing out_path}; code=${2:?}; name=${3:?}; brief=${4:?} - c=$(cat "$TPL_DIR/_module-template.md") - c="${c//\{\{module_code\}\}/$code}" - c="${c//\{\{module_name\}\}/$name}" - c="${c//\{\{module_brief\}\}/$brief}" - ;; - req) - out=${1:?missing out_path}; req_id=${2:?}; title=${3:?}; goal=${4:?}; rules=${5:?}; constraints=${6:?}; acceptance=${7:?} - c=$(sed '/^<!--$/,/^-->$/d' "$TPL_DIR/req-card-template.md") - c="${c//\{\{req_id\}\}/$req_id}" - c="${c//\{\{title\}\}/$title}" - c="${c//\{\{goal\}\}/$goal}" - c="${c//\{\{rules\}\}/$rules}" - c="${c//\{\{constraints\}\}/$constraints}" - c="${c//\{\{acceptance\}\}/$acceptance}" - ;; - *) - echo "render.sh: unknown type '$TYPE' (expect module|req)" >&2 - exit 1 - ;; -esac - -printf '%s\n' "$c" > "$out" diff --git a/skills/scope-lock/templates/req-card-template.md b/skills/scope-lock/templates/req-card-template.md index bb5a169..1b88515 100644 --- a/skills/scope-lock/templates/req-card-template.md +++ b/skills/scope-lock/templates/req-card-template.md @@ -1,60 +1,41 @@ <!-- req-card-template:单张 REQ 卡片骨架。每张卡片是 docs/01-需求清单/<module>/REQ-XXX-NNN.md 一个独立文件。 + 渲染约定: -1) scope-lock 渲染时**只替换 6 个占位符**:{{req_id}} / {{title}} / {{goal}} / {{rules}} / {{constraints}} / {{acceptance}} +1) scope-lock 渲染时替换 6 个占位符:{{req_id}} / {{title}} / {{goal}} / {{rules}} / {{constraints}} / {{acceptance}} 这 6 项由 CC 根据 docs/01-需求清单/index.md 的「核心功能点」推断起草: - req_id / title:从核心功能点拆分命名得来 - goal:用一句话展开 title - rules / constraints / acceptance:业务语义层面的合理起草,待人工审阅修订 -2) `**输入**` / `**输出**` 二级结构:每段先一句话总结(如「用户提交...」/「返回用户 id」),再跟「表1 / 表2 / ...」N 张平铺的字段表。CC 渲染时对**两段简述 + 所有表(含示例数据)全部原样复制**,不要根据 REQ 业务篡改示例字段、也不要换列结构: - - `输入` 的表:8 列(字段 / 类型 / 必填 / 输入方式 / 显示来源 / 预加载 / 默认值 / 业务规则),其中 `预加载` 取值为 `页面加载时` / `用户操作时` / `—`;模板内含示例数据(表1 6 行、表2 3 行) - - `输出` 的表:3 列(字段 / 类型 / 显示来源),模板内含示例数据(表1 / 表2 各 3 行) - - 表的数量是可变的:人工拿到卡片后按 REQ 实际情况增删表(删多余的 `表N` 整段 / 复制为 `表3 / 表4 / ...`)和编辑字段 -3) `**依赖表**: TBD(A3 自动补)` 和 `**依赖接口**: TBD(A5 自动补)` 是后续 skill 自动回填的占位,渲染时**保持原样**,不要替换为 `-` 也不要替换为 `{{...}}` -4) 渲染后这段 HTML 注释要**剥掉**,不进入最终卡片 +2) `**输入字段**` / `**输出字段**` 为**结构化字段表**:每个字段一行,6 列固定结构 + (字段名 | 类型 | 必填 | 校验规则 | 业务规则 | 示例值)。 + 模板内每张表给出一行示例行,列首注明 `【示例行,替换为真实字段】`。 + 人工拿到卡片后按本 REQ 实际业务**逐行替换为真实字段 / 增删行**: + - 字段名:本 REQ 真实的输入 / 输出字段 + - 类型:真实数据类型(文本 / 整数 / 金额 / 日期 / 布尔 / 枚举 / …) + - 必填:是 / 否 + - 校验规则:长度 / 格式 / 范围 / 唯一性等可验证约束(如「3-20 位字母数字下划线;系统内唯一」) + - 业务规则:该字段的业务含义 / 来源 / 默认值 / 联动逻辑 + - 示例值:一个真实约束下的合法取值 +3) **「示例值」列必须替换为真实约束下的取值。留模板默认占位(`【示例行,替换为真实字段】` 或 `<示例值>`)即视为该 REQ 卡片未完成,A1 终结校验会阻断进入下一阶段。** +4) `**依赖表**: TBD(A3 自动补)` 和 `**依赖接口**: TBD(A5 自动补)` 是后续 skill 自动回填的占位,渲染时**保持原样**,不要替换为 `-` 也不要替换为 `{{...}}` +5) 渲染后这段 HTML 注释要**剥掉**,不进入最终卡片(lib/render.mjs 自动剥离) --> ### {{req_id}} {{title}} **目标**: {{goal}} -- **输入**: 用户提交用户名(3-20 位字母数字下划线,系统内唯一)、姓名(2-50字符)、手机号(11 位数字,系统内唯一)、邮箱(标准邮箱格式,可选)、初始密码(8-20位含大小写字母和数字) - - - **表1**: - - | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | - | ---- | ---- | --- | ---- | ----- | --- | --- | ------------------- | - | 用户名 | 文本 | 是 | 手工输入 | `职员表` | 否 | — | 3-20 位字母数字下划线;系统内唯一 | - | 姓名 | 文本 | 是 | 下拉单选 | `职员表` | 用户操作时 | — | 2-50 个字符 | - | 手机号 | 文本 | 是 | 手工输入 | `职员表` | 否 | — | 11 位数字;系统内唯一 | - | 邮箱 | 文本 | 否 | 手工输入 | `职员表` | 否 | — | 标准邮箱格式 | - | 角色 | 文本 | 是 | 下拉单选 | 普通用户/超级管理员 | 页面加载时 | 普通用户 | 至少选择 1 个 | - | 初始密码 | 文本 | 是 | 手工输入 | — | — | — | 8-20 位;含大小写字母和数字;显示星号 | - - - **表2**: - - | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | - | --- | --- | --- | --- | --- | --- | --- | --- | - | 用户名 | 文本 | 是 | 手工输入 | `职员表` | 否 | — | 3-20 位字母数字下划线;系统内唯一 | - | 姓名 | 文本 | 是 | 下拉单选 | `职员表` | 用户操作时 | — | 2-50 个字符 | - | 手机号 | 文本 | 是 | 手工输入 | `职员表` | 否 | — | 11 位数字;系统内唯一 | - -- **输出**: 返回用户 id - - - **表1**: +**输入字段**: - | 字段 | 类型 | 显示来源 | - | --- | --- | --- | - | 用户名 | 文本 | `职员表` | - | 姓名 | 文本 | `职员表` | - | 角色 | 文本 | `职员表` | +| 字段名 | 类型 | 必填 | 校验规则 | 业务规则 | 示例值 | +| --- | --- | --- | --- | --- | --- | +| 【示例行,替换为真实字段】 | 文本 | 是 | 3-20 位字母数字下划线;系统内唯一 | 用户登录账号,系统内唯一标识 | <示例值> | - - **表2**: +**输出字段**: - | 字段 | 类型 | 显示来源 | - | --- | --- | --- | - | 用户名 | 文本 | `职员表` | - | 姓名 | 文本 | `职员表` | - | 角色 | 文本 | `职员表` | +| 字段名 | 类型 | 必填 | 校验规则 | 业务规则 | 示例值 | +| --- | --- | --- | --- | --- | --- | +| 【示例行,替换为真实字段】 | 整数 | 是 | 大于 0 | 新建记录的主键 id | <示例值> | - **跨字段规则**: {{rules}} - **边界**: {{constraints}} diff --git a/skills/skeleton-gen/SKILL.md b/skills/skeleton-gen/SKILL.md index 8a177ca..4d097c8 100644 --- a/skills/skeleton-gen/SKILL.md +++ b/skills/skeleton-gen/SKILL.md @@ -1,8 +1,8 @@ --- name: skeleton-gen -description: A2 骨架生成——基于 docs/04 § 零 技术栈 + docs/01-需求清单/index.md 模块索引,生成项目专属的架构文档(docs/04 § 一+、docs/06、docs/07、docs/09)和工具脚本。固定工具文件走 cp,架构文档由 LLM 按大纲生成。 +description: A2 骨架生成——基于 docs/04 § 零 技术栈 + docs/01-需求清单/index.md 模块索引,生成项目专属的架构文档(docs/04 § 一+、docs/06、docs/07、docs/09)和工具脚本(.mjs,跨平台)。固定工具文件用 Read/Write 落盘,架构文档由 LLM 按大纲生成。 user-invocable: false -allowed-tools: Read Write Edit Skill Grep Glob AskUserQuestion Bash(mkdir *) Bash(cp *) Bash(touch *) Bash(chmod *) Bash(cat *) Bash(bash *) +allowed-tools: Read Write Edit Skill Grep Glob AskUserQuestion Bash(node *) --- **所有输出必须使用中文。** @@ -13,11 +13,7 @@ allowed-tools: Read Write Edit Skill Grep Glob AskUserQuestion Bash(mkdir *) Bas ### 步骤 0:打印当前位置流程图 -用 `Bash` 执行 `cat` 命令向用户展示当前位置流程图(stdout 即 ASCII 框图): - -```bash -cat "${CLAUDE_PLUGIN_ROOT}/skills/skeleton-gen/banners/flow.txt" -``` +用 `Read` 读取 `${CLAUDE_PLUGIN_ROOT}/skills/skeleton-gen/banners/flow.txt`,再把其内容**原样直接输出**给用户(ASCII 框图,不要用 `cat`)。 ### A. 读取锁定的输入 @@ -58,50 +54,50 @@ docs/04 已由 scope-lock 写入 § 零。本步骤追加 § 一 ~ 三。 ### C. 生成工具脚本 -#### C.1 复制 +#### C.1 复制固定模板文件(用 Read + Write,跨平台无 shell) -```bash -mkdir -p scripts sql/migrations src/styles -touch sql/migrations/.gitkeep -cp "${CLAUDE_SKILL_DIR}/templates/env-local-template" .env.local -cp "${CLAUDE_SKILL_DIR}/templates/scripts-setup-test-db-template.sh" scripts/setup-test-db.sh -cp "${CLAUDE_SKILL_DIR}/templates/styles-tokens-template.css" src/styles/tokens.css -``` +对下表每个文件:用 `Read` 读模板内容,用 `Write` **原样**写到目标路径(`Write` 会自动创建缺失的父目录,无需 `mkdir`)。`.gitkeep` 用 `Write` 写空文件。 -#### C.2 渲染 scripts/test.sh +| 模板 | 目标路径 | +|---|---| +| `${CLAUDE_SKILL_DIR}/templates/env-local-template` | `.env.local` | +| `${CLAUDE_SKILL_DIR}/templates/scripts-setup-test-db-template.mjs` | `scripts/setup-test-db.mjs` | +| `${CLAUDE_SKILL_DIR}/templates/styles-tokens-template.css` | `src/styles/tokens.css` | +| (空文件) | `sql/migrations/.gitkeep` | -读取 `${CLAUDE_SKILL_DIR}/templates/scripts-test-template.sh`,基于步骤 A 的技术栈(docs/04 § 零)为 7 个占位推断命令后写到 `scripts/test.sh`: +#### C.2 渲染 scripts/test.mjs + +用 `Read` 读取 `${CLAUDE_SKILL_DIR}/templates/scripts-test-template.mjs`,基于步骤 A 的技术栈(docs/04 § 零)为 7 个占位推断命令后用 `Write` 写到 `scripts/test.mjs`: - `{{backend_build}}` / `{{backend_lint}}` / `{{backend_test}}` 后端各 stage 命令 - `{{frontend_build}}` / `{{frontend_lint}}` / `{{frontend_test}}` 前端各 stage 命令 -- `{{e2e_cmd}}` E2E(无 E2E 工具则填 `echo "[test.sh] e2e 略"`) +- `{{e2e_cmd}}` E2E(无 E2E 工具则填 `echo "[test.mjs] e2e 略"`) +> 占位是普通 shell 命令字符串,模板在运行期用 `spawnSync(cmd, { shell:true })` 跨平台执行(Windows 走 cmd.exe,*nix 走 /bin/sh),缺席 stack 由模板内的 `existsSync('backend')` / `existsSync('frontend')` 守卫跳过。 > 推断规则:根据 `docs/04 § 零` 。 > - 「后端*」存在 → 据后端技术栈推 backend 三槽(build / lint / test 命令) > - 「前端*」存在 → 据前端技术栈推 frontend 三槽 -> - 缺席 stack → 三槽全填 `:`(运行期 `[ -d X ]` 守卫对接;语法合法即可) -> - `{{e2e_cmd}}` 通常仅前端,按上述前端规则或填 `echo "[test.sh] e2e 略"` +> - 缺席 stack → 三槽全填 `echo skip`(该 stack 目录不存在时模板不会执行,但占位须为合法命令) +> - `{{e2e_cmd}}` 通常仅前端,按上述前端规则或填 `echo "[test.mjs] e2e 略"` > > 表结构异常(列名变更 / 无中文前缀)时停下,用 `AskUserQuestion` 让用户显式确认每 stack 命令。 -#### C.3 赋权 - -```bash -chmod +x scripts/*.sh -``` +> 注:生成的 `scripts/*.mjs` 由 `node` 直接执行,**无需 `chmod +x`**(跨平台无文件权限位概念)。 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: -- ` - [ ] 工具脚本已生成(scripts/*.sh、.env.local)` +- ` - [ ] 工具脚本已生成(scripts/*.mjs、.env.local)` - ` - [ ] 样式 token 骨架已生成(src/styles/tokens.css)` ### D. 追加 .gitignore 忽略项 -调用脚本完成合并: +调 `${CLAUDE_PLUGIN_ROOT}/lib/merge-gitignore.mjs`(跨平台纯 Node,逐行判重并集合并,把 append 模板内容并入项目 `.gitignore`,原地写回第一个参数): ```bash -bash "${CLAUDE_SKILL_DIR}/scripts/merge-gitignore.sh" "${CLAUDE_SKILL_DIR}/templates/gitignore-append-template" +node "${CLAUDE_PLUGIN_ROOT}/lib/merge-gitignore.mjs" .gitignore "${CLAUDE_SKILL_DIR}/templates/gitignore-append-template" ``` +> `.gitignore` 不存在时先用 `Write` 建一个空文件再调用(`merge-gitignore.mjs` 的第一个参数须可读)。 + 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - ` - [ ] .gitignore 已配置` @@ -111,7 +107,7 @@ bash "${CLAUDE_SKILL_DIR}/scripts/merge-gitignore.sh" "${CLAUDE_SKILL_DIR}/templ 用 `Grep` 在以下 8 个路径扫 `【人工填写:`,记录命中(文件 / 行号 / 说明): - `docs/04-技术规范.md` / `docs/06-UI交互规范.md` / `docs/07-环境配置.md` / `docs/09-项目目录结构.md` -- `scripts/*.sh` / `.gitignore` +- `scripts/*.mjs` / `.gitignore` - `.env.local` 分两组: @@ -137,7 +133,7 @@ bash "${CLAUDE_SKILL_DIR}/scripts/merge-gitignore.sh" "${CLAUDE_SKILL_DIR}/templ 每次弹 QA 前重扫;有残留则打印残留位置清单(文件:行号 — 说明)+ 再弹 QA。 -QA 横幅涵盖:产出文件清单(docs/04 / 06 / 07 / 09 + scripts/*.sh + .env.local + .gitignore)、占位状态(N=0 或待填清单)、「继续」/「有疑问先沟通」两选项。 +QA 横幅涵盖:产出文件清单(docs/04 / 06 / 07 / 09 + scripts/*.mjs + .env.local + .gitignore)、占位状态(N=0 或待填清单)、「继续」/「有疑问先沟通」两选项。 通过后(N=0 且用户选「继续」),用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: - `- [ ] A2 骨架生成 — skeleton-gen` @@ -154,9 +150,9 @@ QA 横幅涵盖:产出文件清单(docs/04 / 06 / 07 / 09 + scripts/*.sh + . - `${CLAUDE_SKILL_DIR}/templates/docs-06-static-template.md`(大纲) - `${CLAUDE_SKILL_DIR}/templates/docs-07-env-template.md`(大纲) - `${CLAUDE_SKILL_DIR}/templates/docs-09-structure-template.md`(大纲) -- `${CLAUDE_SKILL_DIR}/templates/scripts-test-template.sh`(推断命令填充 7 槽:backend/frontend × build/lint/test + e2e;缺席 stack 填 `:`) -- `${CLAUDE_SKILL_DIR}/templates/scripts-setup-test-db-template.sh`(0 槽位) +- `${CLAUDE_SKILL_DIR}/templates/scripts-test-template.mjs`(推断命令填充 7 槽:backend/frontend × build/lint/test + e2e;缺席 stack 填 `echo skip`) +- `${CLAUDE_SKILL_DIR}/templates/scripts-setup-test-db-template.mjs`(0 槽位,跨平台 .env.local 解析 + DROP/CREATE) - `${CLAUDE_SKILL_DIR}/templates/env-local-template`(0 槽位) - `${CLAUDE_SKILL_DIR}/templates/gitignore-append-template`(0 槽位) - `${CLAUDE_SKILL_DIR}/templates/styles-tokens-template.css`(0 槽位,样式 token 骨架) -- `${CLAUDE_SKILL_DIR}/scripts/merge-gitignore.sh`(.gitignore 逐行判重合并脚本) +- `${CLAUDE_PLUGIN_ROOT}/lib/merge-gitignore.mjs`(.gitignore 逐行判重并集合并,跨平台纯 Node) diff --git a/skills/skeleton-gen/scripts/merge-gitignore.sh b/skills/skeleton-gen/scripts/merge-gitignore.sh deleted file mode 100755 index cfff18c..0000000 --- a/skills/skeleton-gen/scripts/merge-gitignore.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -# merge-gitignore.sh -# 把模板里的忽略规则合并到项目根的 .gitignore: -# - 若 .gitignore 不存在 → 直接 cp 模板(含注释头和结构) -# - 若已存在 → 逐行判重,只追加模板里缺失的规则行(跳过注释/空行) -# -# 用法:merge-gitignore.sh <template_path> [<target_gitignore_path>] -# template_path 模板文件绝对路径(由 skeleton-gen skill 传入) -# target_gitignore_path 目标 .gitignore 路径,默认为当前工作目录下的 .gitignore -# -# 判重:grep -xF 整行精确匹配 + 字面字符串(非正则),避免 .env 误匹配 .env.local, -# 也避免 *.class / *.iml 等通配符被当作 regex。 - -set -euo pipefail - -template="${1:?usage: merge-gitignore.sh <template_path> [<target_gitignore_path>]}" -target="${2:-.gitignore}" - -if [ ! -f "$template" ]; then - echo "[merge-gitignore] ERROR: template not found: $template" >&2 - exit 1 -fi - -if [ ! -f "$target" ]; then - cp "$template" "$target" - echo "[merge-gitignore] created $target from template" - exit 0 -fi - -added=0 -while IFS= read -r line; do - case "$line" in ""|"#"*) continue ;; esac - if ! grep -qxF "$line" "$target"; then - echo "$line" >> "$target" - added=$((added + 1)) - fi -done < "$template" - -echo "[merge-gitignore] $target updated (+$added rules)" -- libgit2 0.22.2