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