Commit 176a79262fd3a719732550f2134d6a1d73d2a95d

Authored by zichun
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
README.md
... ... @@ -86,7 +86,6 @@ erp-workflow-plugin/
86 86 ├── workflows/
87 87 │ └── coding.mjs # 阶段 B:整个编码阶段编排为单个静默 Workflow
88 88 ├── lib/ # 跨平台 Node 助手(ESM,node:test 单测)
89   -│ ├── merge-gitignore.mjs # .gitignore 逐行判重合并(替代 merge-gitignore.sh)
90 89 │ ├── validate-ddl.mjs # docs/03 ↔ DDL 5 维校验(替代 validate.sh)
91 90 │ ├── yaml-config.mjs # config-vars.yaml 极简 YAML 读取(2 层 map + 标量)
92 91 │ ├── apply-ddl.mjs # 解析 config-vars.yaml database: 段 + mysql2 apply
... ... @@ -121,7 +120,7 @@ erp-workflow-plugin/
121 120 |---|---|---|---|
122 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 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 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 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 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 2 name: skeleton-gen
3 3 description: A2 骨架生成——基于 docs/04 § 零 技术栈 + docs/01-需求清单/index.md 模块索引,生成项目专属的架构文档(docs/04 § 一+、docs/07、docs/09)和工具脚本(.mjs,跨平台)。固定工具文件用 Read/Write 落盘,架构文档由 LLM 按大纲生成。
4 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 76  
77 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 84 完成后,用 `Edit` 在 `docs/08-模块任务管理.md` 中勾选:
88 85 - ` - [ ] .gitignore 已配置`
... ... @@ -140,4 +137,3 @@ QA 横幅涵盖:产出文件清单(docs/04 / 07 / 09 + scripts/*.mjs + .giti
140 137 - `config-vars.yaml`(A1 产出,含 DB 凭据;setup-test-db.mjs 运行时读取)
141 138 - `${CLAUDE_SKILL_DIR}/templates/gitignore-append-template`
142 139 - `${CLAUDE_SKILL_DIR}/templates/styles-tokens-template.css`
143   -- `${CLAUDE_PLUGIN_ROOT}/lib/merge-gitignore.mjs`
... ...