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