Commit 176a79262fd3a719732550f2134d6a1d73d2a95d
1 parent
fa22585a
skeleton-gen: drop merge-gitignore.mjs, D merges .gitignore via Read/Write
- delete lib/merge-gitignore.mjs + merge-gitignore.test.mjs: over-engineered for a once-run greenfield step (base .gitignore usually absent; duplicate ignore patterns are harmless) - D now uses C.1-style Read+Write: Write template if no .gitignore, else append missing lines (consistent with render.mjs removal + scope-lock direct-write) - removes the 'pre-Write empty .gitignore' fragility (Write self-creates); skeleton-gen drops Bash(node *) from allowed-tools
Showing
4 changed files
with
5 additions
and
81 deletions
README.md
| @@ -86,7 +86,6 @@ erp-workflow-plugin/ | @@ -86,7 +86,6 @@ erp-workflow-plugin/ | ||
| 86 | ├── workflows/ | 86 | ├── workflows/ |
| 87 | │ └── coding.mjs # 阶段 B:整个编码阶段编排为单个静默 Workflow | 87 | │ └── coding.mjs # 阶段 B:整个编码阶段编排为单个静默 Workflow |
| 88 | ├── lib/ # 跨平台 Node 助手(ESM,node:test 单测) | 88 | ├── lib/ # 跨平台 Node 助手(ESM,node:test 单测) |
| 89 | -│ ├── merge-gitignore.mjs # .gitignore 逐行判重合并(替代 merge-gitignore.sh) | ||
| 90 | │ ├── validate-ddl.mjs # docs/03 ↔ DDL 5 维校验(替代 validate.sh) | 89 | │ ├── validate-ddl.mjs # docs/03 ↔ DDL 5 维校验(替代 validate.sh) |
| 91 | │ ├── yaml-config.mjs # config-vars.yaml 极简 YAML 读取(2 层 map + 标量) | 90 | │ ├── yaml-config.mjs # config-vars.yaml 极简 YAML 读取(2 层 map + 标量) |
| 92 | │ ├── apply-ddl.mjs # 解析 config-vars.yaml database: 段 + mysql2 apply | 91 | │ ├── apply-ddl.mjs # 解析 config-vars.yaml database: 段 + mysql2 apply |
| @@ -121,7 +120,7 @@ erp-workflow-plugin/ | @@ -121,7 +120,7 @@ erp-workflow-plugin/ | ||
| 121 | |---|---|---|---| | 120 | |---|---|---|---| |
| 122 | | A0 | `project-init` | • **依赖检查**:检测 git / mysql / node 是否在 PATH,缺失则按 OS 自动安装,装不上再停下提示用户<br>• 空目录初始化:用 Read/Write/Glob 工具拷模板创建 CLAUDE.md / docs/01/index.md / docs/08<br>• `git init` | `plan-start` | | 121 | | A0 | `project-init` | • **依赖检查**:检测 git / mysql / node 是否在 PATH,缺失则按 OS 自动安装,装不上再停下提示用户<br>• 空目录初始化:用 Read/Write/Glob 工具拷模板创建 CLAUDE.md / docs/01/index.md / docs/08<br>• `git init` | `plan-start` | |
| 123 | | A1 | `scope-lock` | • 引导填项目概述 / 技术栈 / 需求索引<br>• 按 `docs/01-需求清单/<module>/{_module.md, REQ-*.md}` 子目录结构生成 REQ 卡片(CC 据 index.md 填 `{{req_id/title/goal/rules/constraints/acceptance}}` 6 个占位,模板其余内容含输入/输出示例字段表原样复制)<br>• **A1 终结校验**:REQ 6 个占位均填真实数据、无 `{{` 残留、`config-vars.yaml` **全部配置**(包名 / 端口 / 初始账号 + DB 凭据 / 密钥占位)已锁、各 stack 的 build/lint/unit/e2e 命令写入 docs/04 § 零;缺失则在此(Plan 期)用 `AskUserQuestion` 问清(敏感凭据由用户自填,不进会话)<br>• 据模板直接 `Write` 生成 `_module.md` / `REQ-*.md`<br>• 终结校验通过后**自动**调用 `Skill(skeleton-gen)` 进入 A2(不停下) | A0 | | 122 | | A1 | `scope-lock` | • 引导填项目概述 / 技术栈 / 需求索引<br>• 按 `docs/01-需求清单/<module>/{_module.md, REQ-*.md}` 子目录结构生成 REQ 卡片(CC 据 index.md 填 `{{req_id/title/goal/rules/constraints/acceptance}}` 6 个占位,模板其余内容含输入/输出示例字段表原样复制)<br>• **A1 终结校验**:REQ 6 个占位均填真实数据、无 `{{` 残留、`config-vars.yaml` **全部配置**(包名 / 端口 / 初始账号 + DB 凭据 / 密钥占位)已锁、各 stack 的 build/lint/unit/e2e 命令写入 docs/04 § 零;缺失则在此(Plan 期)用 `AskUserQuestion` 问清(敏感凭据由用户自填,不进会话)<br>• 据模板直接 `Write` 生成 `_module.md` / `REQ-*.md`<br>• 终结校验通过后**自动**调用 `Skill(skeleton-gen)` 进入 A2(不停下) | A0 | |
| 124 | -| A2 | `skeleton-gen` | • 生成架构文档:docs/04 § 一+ / docs/07 / docs/09<br>• 生成跨平台工具脚本:`scripts/*.mjs`(**无 chmod**;凭据 / 配置统一在 A1 产出的 config-vars.yaml)<br>• 用 `node ${CLAUDE_PLUGIN_ROOT}/lib/merge-gitignore.mjs` 合并 .gitignore(逐行判重) | `plan-start` | | 123 | +| A2 | `skeleton-gen` | • 生成架构文档:docs/04 § 一+ / docs/07 / docs/09<br>• 生成跨平台工具脚本:`scripts/*.mjs`(**无 chmod**;凭据 / 配置统一在 A1 产出的 config-vars.yaml)<br>• 据 `gitignore-append-template` 用 Read/Write 并入项目 .gitignore | `plan-start` | |
| 125 | | A3 | `db-design-gen` | • A3 起始用 `AskUserQuestion` 确认 ERP 约定(主键策略 / 租户列 / 列前缀规则,默认值可覆盖),结果写 docs/04 + CLAUDE.md<br>• 从 docs/01 REQ 卡片正向设计 `docs/03-数据库设计文档.md`(schema SSoT)<br>• 回填 REQ 卡片依赖表(`TBD(A3 自动补)` → 实际表名)<br>• **停下**等人工审阅 docs/03,审阅完毕用 `/plan-start` 续进 A4 | A2 | | 124 | | A3 | `db-design-gen` | • A3 起始用 `AskUserQuestion` 确认 ERP 约定(主键策略 / 租户列 / 列前缀规则,默认值可覆盖),结果写 docs/04 + CLAUDE.md<br>• 从 docs/01 REQ 卡片正向设计 `docs/03-数据库设计文档.md`(schema SSoT)<br>• 回填 REQ 卡片依赖表(`TBD(A3 自动补)` → 实际表名)<br>• **停下**等人工审阅 docs/03,审阅完毕用 `/plan-start` 续进 A4 | A2 | |
| 126 | | A4 | `db-init` | • LLM 解析 docs/03 → `sql/migrations/V1__initial_schema.sql`(DDL only)<br>• `node ${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs` 校验 DDL ↔ docs/03(5 维:表/列名/列类型/索引/FK),fail-closed<br>• `node ${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs config-vars.yaml V1.sql`(解析 config-vars.yaml database: 段 + mysql2 apply,**无 shell-source**) | A3 | | 125 | | A4 | `db-init` | • LLM 解析 docs/03 → `sql/migrations/V1__initial_schema.sql`(DDL only)<br>• `node ${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs` 校验 DDL ↔ docs/03(5 维:表/列名/列类型/索引/FK),fail-closed<br>• `node ${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs config-vars.yaml V1.sql`(解析 config-vars.yaml database: 段 + mysql2 apply,**无 shell-source**) | A3 | |
| 127 | | A5 | `downstream-gen` | • 一次性生成 docs/02 / docs/05 / docs/10<br>• 回填 REQ 卡片依赖接口(`TBD(A5 自动补)` → 实际 endpoint)<br>• 追加模块清单到 docs/08 § 二<br>• **docs/05 + docs/02 评审闸**:用 `AskUserQuestion` 让用户确认 API 端点/字段无误 + 构建顺序可接受,未确认不勾 A5<br>• **prototype/ 门禁 + 推导 FE 清单写 docs/08 § 三**(原 A6 已并入;无 prototype 则问「无前端」→ § 三 留空)<br>• 最终占位符 + 结构残留扫描 | A4 | | 126 | | A5 | `downstream-gen` | • 一次性生成 docs/02 / docs/05 / docs/10<br>• 回填 REQ 卡片依赖接口(`TBD(A5 自动补)` → 实际 endpoint)<br>• 追加模块清单到 docs/08 § 二<br>• **docs/05 + docs/02 评审闸**:用 `AskUserQuestion` 让用户确认 API 端点/字段无误 + 构建顺序可接受,未确认不勾 A5<br>• **prototype/ 门禁 + 推导 FE 清单写 docs/08 § 三**(原 A6 已并入;无 prototype 则问「无前端」→ § 三 留空)<br>• 最终占位符 + 结构残留扫描 | A4 | |
lib/merge-gitignore.mjs deleted
| 1 | -// lib/merge-gitignore.mjs | ||
| 2 | -// 合并两份 .gitignore,对**规则行**逐行判重并集合并;注释行透传(相邻去重),空行丢弃(节由注释头承担)。 | ||
| 3 | -export function mergeGitignore(baseText, addText) { | ||
| 4 | - const seenRules = new Set() | ||
| 5 | - const out = [] | ||
| 6 | - const push = (line) => { | ||
| 7 | - const trimmed = line.trim() | ||
| 8 | - if (!trimmed) return // drop blank lines (输出靠 join('\n')+尾换行;分节由注释行承担) | ||
| 9 | - if (trimmed.startsWith('#')) { | ||
| 10 | - // 注释:仅折叠**相邻**完全相同的注释,避免分节标题被吞 | ||
| 11 | - if (out.length && out[out.length - 1].trim() === trimmed) return | ||
| 12 | - out.push(line) | ||
| 13 | - return | ||
| 14 | - } | ||
| 15 | - // 规则行:全局去重(含 negation `!pattern`,按原文比对,顺序保留首次出现位置) | ||
| 16 | - if (seenRules.has(trimmed)) return | ||
| 17 | - seenRules.add(trimmed) | ||
| 18 | - out.push(line) | ||
| 19 | - } | ||
| 20 | - for (const l of baseText.split('\n')) push(l) | ||
| 21 | - for (const l of addText.split('\n')) push(l) | ||
| 22 | - let text = out.join('\n').replace(/\n+$/, '') + '\n' | ||
| 23 | - return text | ||
| 24 | -} | ||
| 25 | - | ||
| 26 | -const { pathToFileURL } = await import('node:url') // CLI entry guard:pathToFileURL 规范化 argv[1] 以匹配 import.meta.url(路径含空格 / 非 ASCII / Windows 反斜杠时字面比较会失配) | ||
| 27 | -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { | ||
| 28 | - const [basePath, addPath] = process.argv.slice(2) | ||
| 29 | - const { readFileSync, writeFileSync } = await import('node:fs') | ||
| 30 | - const base = readFileSync(basePath, 'utf8') | ||
| 31 | - const add = readFileSync(addPath, 'utf8') | ||
| 32 | - writeFileSync(basePath, mergeGitignore(base, add)) | ||
| 33 | -} |
lib/merge-gitignore.test.mjs deleted
| 1 | -// lib/merge-gitignore.test.mjs | ||
| 2 | -import { test } from 'node:test' | ||
| 3 | -import assert from 'node:assert/strict' | ||
| 4 | -import { mergeGitignore } from './merge-gitignore.mjs' | ||
| 5 | - | ||
| 6 | -test('union dedupes and preserves base order, appends new', () => { | ||
| 7 | - const base = 'node_modules\n.env\n' | ||
| 8 | - const add = '.env\ndist\n.DS_Store\n' | ||
| 9 | - assert.equal(mergeGitignore(base, add), 'node_modules\n.env\ndist\n.DS_Store\n') | ||
| 10 | -}) | ||
| 11 | - | ||
| 12 | -test('blank lines and comments in add are ignored for dedupe but kept once', () => { | ||
| 13 | - assert.equal(mergeGitignore('a\n', '\n# c\nb\n'), 'a\n# c\nb\n') | ||
| 14 | -}) | ||
| 15 | - | ||
| 16 | -// 回归:两段不同区块共用同一注释标题(如 `# generated`)时,第二段的标题不应被去重吞掉。 | ||
| 17 | -test('cross-section duplicate comment headers are preserved (no global dedupe on comments)', () => { | ||
| 18 | - const base = '# generated\na\n' | ||
| 19 | - const add = '# generated\nb\n' | ||
| 20 | - assert.equal(mergeGitignore(base, add), '# generated\na\n# generated\nb\n') | ||
| 21 | -}) | ||
| 22 | - | ||
| 23 | -// 相邻完全重复的注释会折叠成一行(避免无意义连续重复)。 | ||
| 24 | -test('adjacent duplicate comments are folded', () => { | ||
| 25 | - assert.equal(mergeGitignore('# x\n# x\n', 'a\n'), '# x\na\n') | ||
| 26 | -}) | ||
| 27 | - | ||
| 28 | -// negation 规则 (!pattern) 按原文比对、顺序保留。 | ||
| 29 | -test('negation patterns are deduped by literal and order is preserved', () => { | ||
| 30 | - const merged = mergeGitignore('node_modules\n!node_modules/keep\n', '!node_modules/keep\ndist\n') | ||
| 31 | - assert.equal(merged, 'node_modules\n!node_modules/keep\ndist\n') | ||
| 32 | -}) | ||
| 33 | - | ||
| 34 | -// 输出始终以单个 \n 结尾,即便 base 末尾无换行。 | ||
| 35 | -test('output always ends with exactly one newline', () => { | ||
| 36 | - assert.equal(mergeGitignore('a', 'b'), 'a\nb\n') | ||
| 37 | - assert.equal(mergeGitignore('a\n\n\n', 'b\n\n'), 'a\nb\n') | ||
| 38 | -}) |
skills/plan/skeleton-gen/SKILL.md
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | name: skeleton-gen | 2 | name: skeleton-gen |
| 3 | description: A2 骨架生成——基于 docs/04 § 零 技术栈 + docs/01-需求清单/index.md 模块索引,生成项目专属的架构文档(docs/04 § 一+、docs/07、docs/09)和工具脚本(.mjs,跨平台)。固定工具文件用 Read/Write 落盘,架构文档由 LLM 按大纲生成。 | 3 | description: A2 骨架生成——基于 docs/04 § 零 技术栈 + docs/01-需求清单/index.md 模块索引,生成项目专属的架构文档(docs/04 § 一+、docs/07、docs/09)和工具脚本(.mjs,跨平台)。固定工具文件用 Read/Write 落盘,架构文档由 LLM 按大纲生成。 |
| 4 | user-invocable: false | 4 | user-invocable: false |
| 5 | -allowed-tools: Read Write Edit Skill Grep Glob AskUserQuestion Bash(node *) | 5 | +allowed-tools: Read Write Edit Skill Grep Glob AskUserQuestion |
| 6 | --- | 6 | --- |
| 7 | 7 | ||
| 8 | **所有输出必须使用中文。** | 8 | **所有输出必须使用中文。** |
| @@ -76,13 +76,10 @@ docs/04 已由 scope-lock 写入 § 零。本步骤追加 § 一 ~ 三。 | @@ -76,13 +76,10 @@ docs/04 已由 scope-lock 写入 § 零。本步骤追加 § 一 ~ 三。 | ||
| 76 | 76 | ||
| 77 | ### D. 追加 .gitignore 忽略项 | 77 | ### D. 追加 .gitignore 忽略项 |
| 78 | 78 | ||
| 79 | -调 `${CLAUDE_PLUGIN_ROOT}/lib/merge-gitignore.mjs`(跨平台纯 Node,逐行判重并集合并,把 append 模板内容并入项目 `.gitignore`,原地写回第一个参数): | 79 | +用 `Read` 读 `${CLAUDE_SKILL_DIR}/templates/gitignore-append-template`,并入项目根 `.gitignore`(与 C.1 同款 Read+Write,跨平台无 shell): |
| 80 | 80 | ||
| 81 | -```bash | ||
| 82 | -node "${CLAUDE_PLUGIN_ROOT}/lib/merge-gitignore.mjs" .gitignore "${CLAUDE_SKILL_DIR}/templates/gitignore-append-template" | ||
| 83 | -``` | ||
| 84 | - | ||
| 85 | -> `.gitignore` 不存在则先 Write 空文件。 | 81 | +- `.gitignore` **不存在** → 用 `Write` 原样写入模板内容。 |
| 82 | +- **已存在** → `Read` 现有 `.gitignore`,把模板里**尚未出现**的规则行用 `Edit` 追加到末尾(已有的跳过;重复 ignore pattern 本身无害,判重只为整洁)。 | ||
| 86 | 83 | ||
| 87 | 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: | 84 | 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选: |
| 88 | - ` - [ ] .gitignore 已配置` | 85 | - ` - [ ] .gitignore 已配置` |
| @@ -140,4 +137,3 @@ QA 横幅涵盖:产出文件清单(docs/04 / 07 / 09 + scripts/*.mjs + .giti | @@ -140,4 +137,3 @@ QA 横幅涵盖:产出文件清单(docs/04 / 07 / 09 + scripts/*.mjs + .giti | ||
| 140 | - `config-vars.yaml`(A1 产出,含 DB 凭据;setup-test-db.mjs 运行时读取) | 137 | - `config-vars.yaml`(A1 产出,含 DB 凭据;setup-test-db.mjs 运行时读取) |
| 141 | - `${CLAUDE_SKILL_DIR}/templates/gitignore-append-template` | 138 | - `${CLAUDE_SKILL_DIR}/templates/gitignore-append-template` |
| 142 | - `${CLAUDE_SKILL_DIR}/templates/styles-tokens-template.css` | 139 | - `${CLAUDE_SKILL_DIR}/templates/styles-tokens-template.css` |
| 143 | -- `${CLAUDE_PLUGIN_ROOT}/lib/merge-gitignore.mjs` |