claude-sdlc 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/installer.js CHANGED
@@ -74,11 +74,11 @@ function resultBanner(text, ok = true) {
74
74
 
75
75
  // ── 工具函数 ──────────────────────────────────────────
76
76
 
77
- function hookGroupKey(group) {
78
- if (group.hooks && Array.isArray(group.hooks)) {
79
- return group.hooks.map(h => h.command || h.prompt).join('|');
80
- }
81
- return group.command || group.prompt || JSON.stringify(group);
77
+ /**
78
+ * 获取 hook 组的 matcher 标识(用于按 matcher 匹配升级,而非按内容去重)
79
+ */
80
+ function hookMatcherKey(group) {
81
+ return group.matcher || '';
82
82
  }
83
83
 
84
84
  function mergeSettings(existing, template) {
@@ -92,16 +92,36 @@ function mergeSettings(existing, template) {
92
92
 
93
93
  for (const type of hookTypes) {
94
94
  const existingHooks = result.hooks?.[type] || [];
95
- const newHooks = template.hooks?.[type] || [];
95
+ const templateHooks = template.hooks?.[type] || [];
96
+
97
+ // 按 matcher 建立模板 hook 索引
98
+ const templateByMatcher = new Map();
99
+ for (const hook of templateHooks) {
100
+ templateByMatcher.set(hookMatcherKey(hook), hook);
101
+ }
96
102
 
97
- const merged = [...existingHooks];
98
- const existingKeys = new Set(existingHooks.map(hookGroupKey));
103
+ const merged = [];
104
+ const processedMatchers = new Set();
99
105
 
100
- for (const hook of newHooks) {
101
- const key = hookGroupKey(hook);
102
- if (!existingKeys.has(key)) {
106
+ // 遍历已有 hooks:模板有同 matcher 替换为模板版本;模板没有 → 保留(用户自定义)
107
+ for (const hook of existingHooks) {
108
+ const mk = hookMatcherKey(hook);
109
+ if (processedMatchers.has(mk)) continue;
110
+ processedMatchers.add(mk);
111
+
112
+ if (templateByMatcher.has(mk)) {
113
+ merged.push(JSON.parse(JSON.stringify(templateByMatcher.get(mk))));
114
+ } else {
103
115
  merged.push(hook);
104
- existingKeys.add(key);
116
+ }
117
+ }
118
+
119
+ // 追加模板中新增的 hooks(已有中没有的 matcher)
120
+ for (const hook of templateHooks) {
121
+ const mk = hookMatcherKey(hook);
122
+ if (!processedMatchers.has(mk)) {
123
+ processedMatchers.add(mk);
124
+ merged.push(JSON.parse(JSON.stringify(hook)));
105
125
  }
106
126
  }
107
127
 
@@ -112,46 +132,6 @@ function mergeSettings(existing, template) {
112
132
  return result;
113
133
  }
114
134
 
115
- /**
116
- * 从 CLAUDE.md 中提取 YAML 状态块(```yaml ... ``` 包裹的 SDLC 项目状态)
117
- */
118
- function extractYamlState(content) {
119
- const match = content.match(/```yaml\n# === SDLC 项目状态 ===\n[\s\S]*?```/);
120
- return match ? match[0] : null;
121
- }
122
-
123
- /**
124
- * 判断 YAML 状态块是否有实际项目数据(非空白模板)
125
- */
126
- function hasActiveState(yamlBlock) {
127
- if (!yamlBlock) return false;
128
- // 检查 current_phase 是否不是 P0
129
- if (/current_phase:\s*P[1-6]/.test(yamlBlock)) return true;
130
- // 检查 task_description 是否非空
131
- if (/task_description:\s*"[^"]+"/.test(yamlBlock)) return true;
132
- // 检查 modified_files 是否有内容
133
- if (/modified_files:\s*\n\s*-/.test(yamlBlock)) return true;
134
- // 检查 prd 是否有实际条目(非注释)
135
- if (/prd:\s*\n\s*-\s*id:/.test(yamlBlock)) return true;
136
- return false;
137
- }
138
-
139
- /**
140
- * 合并 CLAUDE.md — 更新模板指令,保留用户的 YAML 状态块
141
- */
142
- function mergeClaudeMd(existingContent, templateContent) {
143
- const existingState = extractYamlState(existingContent);
144
- const templateState = extractYamlState(templateContent);
145
-
146
- if (!existingState || !templateState) {
147
- // 格式不匹配,无法合并,返回模板
148
- return templateContent;
149
- }
150
-
151
- // 用旧的状态块替换新模板中的空白状态块
152
- return templateContent.replace(templateState, existingState);
153
- }
154
-
155
135
  function copyFilesQuiet(srcDir, destDir, ext) {
156
136
  if (!fs.existsSync(srcDir)) return [];
157
137
  const files = fs.readdirSync(srcDir).filter(f => f.endsWith(ext));
@@ -165,7 +145,7 @@ function copyFilesQuiet(srcDir, destDir, ext) {
165
145
 
166
146
  function install(targetDir) {
167
147
  const templateDir = path.join(__dirname, '..', 'template');
168
- const STEPS = 6;
148
+ const STEPS = 7;
169
149
 
170
150
  // 验证
171
151
  if (!fs.existsSync(targetDir)) {
@@ -191,31 +171,16 @@ function install(targetDir) {
191
171
  console.log(` ${SYM.arrow} 目标 ${BOLD}${targetDir}${RESET}`);
192
172
  blank();
193
173
 
194
- // Step 1: CLAUDE.md(智能合并:更新模板指令,保留项目状态)
174
+ // Step 1: CLAUDE.md(纯控制指令,升级时直接替换为最新版本)
195
175
  step(1, STEPS, '安装核心控制文件');
196
176
  const targetClaudeMd = path.join(targetDir, 'CLAUDE.md');
197
177
  const templateClaudeMd = path.join(templateDir, 'CLAUDE.md');
198
178
  const templateContent = fs.readFileSync(templateClaudeMd, 'utf-8');
199
-
200
- if (fs.existsSync(targetClaudeMd)) {
201
- const existingContent = fs.readFileSync(targetClaudeMd, 'utf-8');
202
- const existingState = extractYamlState(existingContent);
203
-
204
- if (hasActiveState(existingState)) {
205
- // 有活跃项目状态 → 更新指令模板,保留状态块
206
- const merged = mergeClaudeMd(existingContent, templateContent);
207
- fs.writeFileSync(targetClaudeMd, merged, 'utf-8');
208
- fileLog(SYM.check, `CLAUDE.md ${DIM}(升级模板,保留项目状态)${RESET}`);
209
- } else {
210
- // 状态为空(P0)→ 直接覆盖
211
- fs.writeFileSync(targetClaudeMd, templateContent, 'utf-8');
212
- fileLog(SYM.check, 'CLAUDE.md');
213
- }
214
- } else {
215
- // 首次安装
216
- fs.writeFileSync(targetClaudeMd, templateContent, 'utf-8');
217
- fileLog(SYM.check, 'CLAUDE.md');
218
- }
179
+ const isUpgrade = fs.existsSync(targetClaudeMd);
180
+ fs.writeFileSync(targetClaudeMd, templateContent, 'utf-8');
181
+ fileLog(SYM.check, isUpgrade
182
+ ? `CLAUDE.md ${DIM}(已更新至最新版本)${RESET}`
183
+ : 'CLAUDE.md');
219
184
 
220
185
  // Step 2: 目录结构
221
186
  step(2, STEPS, '创建目录结构');
@@ -225,8 +190,19 @@ function install(targetDir) {
225
190
  fileLog(SYM.check, `.claude/${dir}/`);
226
191
  }
227
192
 
228
- // Step 3: 规则文件
229
- step(3, STEPS, '安装规则文件');
193
+ // Step 3: project-state.md(项目状态文件 — 已有则跳过,保护用户数据)
194
+ step(3, STEPS, '初始化项目状态文件');
195
+ const targetState = path.join(targetDir, '.claude', 'project-state.md');
196
+ const templateState = path.join(templateDir, '.claude', 'project-state.md');
197
+ if (fs.existsSync(targetState)) {
198
+ fileLog(SYM.check, `.claude/project-state.md ${DIM}(已有状态,跳过保护)${RESET}`);
199
+ } else {
200
+ fs.copyFileSync(templateState, targetState);
201
+ fileLog(SYM.check, '.claude/project-state.md');
202
+ }
203
+
204
+ // Step 4: 规则文件
205
+ step(4, STEPS, '安装规则文件');
230
206
  const rules = copyFilesQuiet(
231
207
  path.join(templateDir, '.claude', 'rules'),
232
208
  path.join(targetDir, '.claude', 'rules'),
@@ -234,8 +210,8 @@ function install(targetDir) {
234
210
  );
235
211
  fileLog(SYM.check, `${rules.length} 个规则文件`);
236
212
 
237
- // Step 4: Hook 脚本
238
- step(4, STEPS, '安装 Hook 脚本');
213
+ // Step 5: Hook 脚本
214
+ step(5, STEPS, '安装 Hook 脚本');
239
215
  const hooksSrcDir = path.join(templateDir, '.claude', 'hooks');
240
216
  const hooksDestDir = path.join(targetDir, '.claude', 'hooks');
241
217
  let hookCount = 0;
@@ -250,8 +226,8 @@ function install(targetDir) {
250
226
  }
251
227
  fileLog(SYM.check, `${hookCount} 个 Hook 脚本`);
252
228
 
253
- // Step 5: 斜杠命令
254
- step(5, STEPS, '安装斜杠命令');
229
+ // Step 6: 斜杠命令
230
+ step(6, STEPS, '安装斜杠命令');
255
231
  const cmds = copyFilesQuiet(
256
232
  path.join(templateDir, '.claude', 'commands'),
257
233
  path.join(targetDir, '.claude', 'commands'),
@@ -259,8 +235,8 @@ function install(targetDir) {
259
235
  );
260
236
  fileLog(SYM.check, `${cmds.length} 个命令 — ${DIM}/phase /status /checkpoint /review${RESET}`);
261
237
 
262
- // Step 6: settings.json
263
- step(6, STEPS, '配置 Hooks');
238
+ // Step 7: settings.json
239
+ step(7, STEPS, '配置 Hooks');
264
240
  const targetSettings = path.join(targetDir, '.claude', 'settings.json');
265
241
  const sourceSettings = path.join(templateDir, '.claude', 'settings.json');
266
242
 
@@ -284,11 +260,12 @@ function install(targetDir) {
284
260
  resultBanner('安装完成');
285
261
 
286
262
  // 文件树
287
- console.log(` ${DIM}已安装 ${rules.length + hookCount + cmds.length + 2} 个文件:${RESET}`);
263
+ console.log(` ${DIM}已安装 ${rules.length + hookCount + cmds.length + 3} 个文件:${RESET}`);
288
264
  blank();
289
265
  console.log(` ${BOLD}${path.basename(targetDir)}/${RESET}`);
290
- console.log(` ${SYM.tee} CLAUDE.md ${DIM}核心控制文件${RESET}`);
266
+ console.log(` ${SYM.tee} CLAUDE.md ${DIM}核心控制文件(升级时自动更新)${RESET}`);
291
267
  console.log(` ${SYM.corner} ${BOLD}.claude/${RESET}`);
268
+ console.log(` ${SYM.tee} project-state.md ${DIM}项目状态(升级时保留)${RESET}`);
292
269
  console.log(` ${SYM.tee} settings.json ${DIM}Hooks 配置${RESET}`);
293
270
  console.log(` ${SYM.tee} ${BOLD}rules/${RESET} ${DIM}${rules.length} 个规则 (自动加载)${RESET}`);
294
271
  console.log(` ${SYM.tee} ${BOLD}hooks/${RESET} ${DIM}${hookCount} 个拦截脚本${RESET}`);
@@ -329,29 +306,37 @@ function uninstall(targetDir) {
329
306
  console.log(` ${SYM.arrow} 目标 ${BOLD}${targetDir}${RESET}`);
330
307
  blank();
331
308
 
332
- const removed = [];
309
+ let removed = 0;
333
310
 
334
311
  // CLAUDE.md
335
312
  const claudeMd = path.join(targetDir, 'CLAUDE.md');
336
313
  if (fs.existsSync(claudeMd)) {
337
314
  fs.unlinkSync(claudeMd);
338
- removed.push('CLAUDE.md');
315
+ removed++;
339
316
  fileLog(SYM.check, `${DIM}删除${RESET} CLAUDE.md`);
340
317
  }
341
318
 
342
- // .claude 子目录
319
+ // project-state.md
320
+ const stateMd = path.join(targetDir, '.claude', 'project-state.md');
321
+ if (fs.existsSync(stateMd)) {
322
+ fs.unlinkSync(stateMd);
323
+ removed++;
324
+ fileLog(SYM.check, `${DIM}删除${RESET} .claude/project-state.md`);
325
+ }
326
+
327
+ // .claude 子目录(全部删除)
343
328
  const removeDirs = ['rules', 'hooks', 'commands', 'reviews'];
344
329
  for (const dir of removeDirs) {
345
330
  const dirPath = path.join(targetDir, '.claude', dir);
346
331
  if (fs.existsSync(dirPath)) {
347
332
  const count = fs.readdirSync(dirPath).length;
348
333
  fs.rmSync(dirPath, { recursive: true });
349
- removed.push(`.claude/${dir}/`);
334
+ removed++;
350
335
  fileLog(SYM.check, `${DIM}删除${RESET} .claude/${dir}/ ${DIM}(${count} 个文件)${RESET}`);
351
336
  }
352
337
  }
353
338
 
354
- // settings.json
339
+ // settings.json — 仅清理 SDLC hooks,保留用户其他配置
355
340
  const settingsPath = path.join(targetDir, '.claude', 'settings.json');
356
341
  if (fs.existsSync(settingsPath)) {
357
342
  try {
@@ -364,16 +349,16 @@ function uninstall(targetDir) {
364
349
  }
365
350
  if (Object.keys(settings).length === 0) {
366
351
  fs.unlinkSync(settingsPath);
367
- removed.push('settings.json');
352
+ removed++;
368
353
  fileLog(SYM.check, `${DIM}删除${RESET} .claude/settings.json`);
369
354
  } else {
370
355
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
371
- removed.push('settings.json (hooks)');
356
+ removed++;
372
357
  fileLog(SYM.check, `${DIM}清理${RESET} settings.json ${DIM}(仅移除 hooks,保留其他配置)${RESET}`);
373
358
  }
374
359
  } catch (_) {
375
360
  fs.unlinkSync(settingsPath);
376
- removed.push('settings.json');
361
+ removed++;
377
362
  fileLog(SYM.check, `${DIM}删除${RESET} .claude/settings.json`);
378
363
  }
379
364
  }
@@ -388,8 +373,8 @@ function uninstall(targetDir) {
388
373
  }
389
374
  }
390
375
 
391
- if (removed.length > 0) {
392
- resultBanner(`卸载完成 — 已清理 ${removed.length} 项`);
376
+ if (removed > 0) {
377
+ resultBanner(`卸载完成 — 已清理 ${removed} 项`);
393
378
  console.log(` ${DIM}重新安装:npx claude-sdlc${RESET}`);
394
379
  } else {
395
380
  blank();
@@ -398,4 +383,4 @@ function uninstall(targetDir) {
398
383
  blank();
399
384
  }
400
385
 
401
- module.exports = { install, uninstall, mergeSettings, extractYamlState, hasActiveState, mergeClaudeMd };
386
+ module.exports = { install, uninstall, mergeSettings };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-sdlc",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "让 Claude Code 严格按 SDLC 规范开发 — 一条命令安装",
5
5
  "bin": {
6
6
  "claude-sdlc": "./bin/cli.js"
@@ -9,8 +9,8 @@
9
9
  ## 执行逻辑
10
10
 
11
11
  1. **收集当前状态信息**:
12
- - 读取 CLAUDE.md 中的 `current_phase`
13
- - 读取 CLAUDE.md 中的 `task_description`
12
+ - 读取 .claude/project-state.md 中的 `current_phase`
13
+ - 读取 .claude/project-state.md 中的 `task_description`
14
14
  - 执行 `git status`(如果是 git 仓库)获取文件变更状态
15
15
  - 执行 `git diff --stat`(如果是 git 仓库)获取变更统计
16
16
 
@@ -36,7 +36,7 @@
36
36
  ========================
37
37
  ```
38
38
 
39
- 3. **更新 CLAUDE.md**:
39
+ 3. **更新 .claude/project-state.md**:
40
40
  - 确保 `modified_files` 列表是最新的
41
41
  - 确保 `todo_items` 列表是最新的
42
42
  - 更新 `last_updated` 时间戳
@@ -46,7 +46,7 @@
46
46
  ```
47
47
  ✅ 检查点已保存。
48
48
 
49
- 如果后续发生上下文压缩(compaction),可以从 CLAUDE.md 恢复以下状态:
49
+ 如果后续发生上下文压缩(compaction),可以从 .claude/project-state.md 恢复以下状态:
50
50
  - 阶段:{阶段}
51
51
  - 任务:{任务描述}
52
52
  - 已修改 {n} 个文件
@@ -11,7 +11,7 @@
11
11
  根据参数执行以下操作:
12
12
 
13
13
  ### 无参数 或 `status`
14
- 1. 读取 CLAUDE.md 中的 `current_phase` 值
14
+ 1. 读取 .claude/project-state.md 中的 `current_phase` 值
15
15
  2. 输出当前阶段信息:
16
16
  - 阶段编号和名称
17
17
  - 该阶段允许的操作
@@ -52,9 +52,9 @@ Step 3: 审查通过?
52
52
  - 自动执行当前阶段对应的 `/review`
53
53
  - 输出审查报告
54
54
  5. **审查通过**:
55
- - 将 `current_phase` 更新为下一阶段
56
- - 更新 `phase_history` 记录(含审查结果摘要)
57
- - 更新 `last_updated` 时间戳
55
+ - 将 .claude/project-state.md 的 `current_phase` 更新为下一阶段
56
+ - 更新 .claude/project-state.md 的 `phase_history` 记录(含审查结果摘要)
57
+ - 更新 .claude/project-state.md 的 `last_updated` 时间戳
58
58
  - **P1→P2、P2→P3**:输出新阶段的入口信息(P2→P3 时提示"进入自动驱动模式")
59
59
  - **P3→P4、P4→P5、P5→P6**:不输出冗余信息,立即开始下一阶段工作
60
60
  - **P6 完成**:输出交付摘要报告
@@ -67,9 +67,9 @@ Step 3: 审查通过?
67
67
  1. 读取当前阶段
68
68
  2. 如果已是 P0/P1,提示无法继续回退
69
69
  3. 要求提供回退原因
70
- 4. 更新 `current_phase` 为上一阶段
71
- 5. 更新 `phase_history` 记录(含回退原因)
72
- 6. 更新 `last_updated` 时间戳
70
+ 4. 更新 .claude/project-state.md 的 `current_phase` 为上一阶段
71
+ 5. 更新 .claude/project-state.md 的 `phase_history` 记录(含回退原因)
72
+ 6. 更新 .claude/project-state.md 的 `last_updated` 时间戳
73
73
  7. 输出回退后的阶段信息
74
74
  8. 注意:回退后上一阶段的审查状态重置,再次推进时需重新审查
75
75
 
@@ -35,7 +35,7 @@
35
35
 
36
36
  ### 1. 自动识别当前阶段
37
37
 
38
- 读取 CLAUDE.md 中的 `current_phase`,根据阶段选择对应的审查清单。
38
+ 读取 .claude/project-state.md 中的 `current_phase`,根据阶段选择对应的审查清单。
39
39
 
40
40
  ### 2. 确定审查范围
41
41
 
@@ -57,7 +57,7 @@
57
57
  - [ ] **影响分析**:受影响的文件和模块是否已识别完整
58
58
  - [ ] **风险识别**:技术风险和业务风险是否已列出
59
59
  - [ ] **用户已确认**:PRD 是否已经用户明确确认
60
- - [ ] **已写入 CLAUDE.md**:PRD 是否已写入 `prd` 字段
60
+ - [ ] **已写入 .claude/project-state.md**:PRD 是否已写入 `prd` 字段
61
61
 
62
62
  ### 输出格式
63
63
  ```
@@ -141,7 +141,7 @@
141
141
  ## P3 编码实现 — 代码审查
142
142
 
143
143
  ### 审查范围
144
- - 审查 CLAUDE.md 中 `modified_files` 列表里的所有文件
144
+ - 审查 .claude/project-state.md 中 `modified_files` 列表里的所有文件
145
145
  - 如指定了文件路径,仅审查该文件
146
146
 
147
147
  ### 工具执行(P3 审查前必须运行)
@@ -10,7 +10,7 @@
10
10
 
11
11
  执行深度自检并生成全面的项目状态报告:
12
12
 
13
- ### 1. 读取 CLAUDE.md 状态
13
+ ### 1. 读取 .claude/project-state.md 状态
14
14
  - `current_phase` — 当前阶段
15
15
  - `task_description` — 任务描述
16
16
  - `started_at` / `last_updated` — 时间信息
@@ -74,7 +74,7 @@
74
74
  ```
75
75
 
76
76
  ### 4. 同步更新
77
- - 如果发现 CLAUDE.md 中的信息不是最新,同步更新
77
+ - 如果发现 .claude/project-state.md 中的信息不是最新,同步更新
78
78
  - 更新 `last_updated` 时间戳
79
79
 
80
80
  ---
@@ -22,30 +22,18 @@ if [ -z "$COMMAND" ]; then
22
22
  fi
23
23
 
24
24
  # ==============================
25
- # 查找 CLAUDE.md 并读取阶段
25
+ # 读取项目状态
26
26
  # ==============================
27
27
 
28
- find_claude_md() {
29
- local dir="$PWD"
30
- while [ "$dir" != "/" ]; do
31
- if [ -f "$dir/CLAUDE.md" ]; then
32
- echo "$dir/CLAUDE.md"
33
- return 0
34
- fi
35
- dir=$(dirname "$dir")
36
- done
37
- return 1
38
- }
39
-
40
- CLAUDE_MD=$(find_claude_md 2>/dev/null) || true
41
-
42
- # 如果找不到 CLAUDE.md,默认放行(容错)
43
- if [ -z "$CLAUDE_MD" ]; then
28
+ STATE_FILE="${CLAUDE_PROJECT_DIR:-.}/.claude/project-state.md"
29
+
30
+ # 如果找不到状态文件,默认放行(容错)
31
+ if [ ! -f "$STATE_FILE" ]; then
44
32
  exit 0
45
33
  fi
46
34
 
47
35
  # 读取当前阶段 — 兼容 macOS(不使用 grep -oP)
48
- CURRENT_PHASE=$(sed -n 's/^current_phase:[[:space:]]*\([^[:space:]#]*\).*/\1/p' "$CLAUDE_MD" 2>/dev/null | head -1)
36
+ CURRENT_PHASE=$(sed -n 's/^current_phase:[[:space:]]*\([^[:space:]#]*\).*/\1/p' "$STATE_FILE" 2>/dev/null | head -1)
49
37
 
50
38
  if [ -z "$CURRENT_PHASE" ]; then
51
39
  exit 0
@@ -23,43 +23,29 @@ if [ -z "$FILE_PATH" ]; then
23
23
  fi
24
24
 
25
25
  # 文档/配置类文件扩展名 — 任何阶段都允许写入
26
- # 注意:.html 和 .css 是代码文件(Web 项目),不在白名单中
27
26
  DOC_EXTENSIONS='\.(md|txt|json|yaml|yml|toml|ini|cfg|conf|gitignore|editorconfig|prettierrc|eslintrc|csv|xml|svg|lock|log|env|env\..*)$'
28
27
 
29
28
  if echo "$FILE_PATH" | grep -qiE "$DOC_EXTENSIONS"; then
30
29
  exit 0
31
30
  fi
32
31
 
33
- # 查找项目根目录的 CLAUDE.md
34
- find_claude_md() {
35
- local dir="$PWD"
36
- while [ "$dir" != "/" ]; do
37
- if [ -f "$dir/CLAUDE.md" ]; then
38
- echo "$dir/CLAUDE.md"
39
- return 0
40
- fi
41
- dir=$(dirname "$dir")
42
- done
43
- return 1
44
- }
45
-
46
- CLAUDE_MD=$(find_claude_md 2>/dev/null) || true
47
-
48
- # 如果找不到 CLAUDE.md,默认放行(容错)
49
- if [ -z "$CLAUDE_MD" ]; then
32
+ # 读取项目状态文件
33
+ STATE_FILE="${CLAUDE_PROJECT_DIR:-.}/.claude/project-state.md"
34
+
35
+ # 如果找不到状态文件,默认放行(容错)
36
+ if [ ! -f "$STATE_FILE" ]; then
50
37
  exit 0
51
38
  fi
52
39
 
53
- # 读取当前阶段 — 兼容 macOS (BSD grep) 和 Linux (GNU grep)
54
- # 不使用 grep -oP(macOS 不支持 Perl 正则)
55
- CURRENT_PHASE=$(sed -n 's/^current_phase:[[:space:]]*\([^[:space:]#]*\).*/\1/p' "$CLAUDE_MD" 2>/dev/null | head -1)
40
+ # 读取当前阶段 — 兼容 macOS (BSD sed)
41
+ CURRENT_PHASE=$(sed -n 's/^current_phase:[[:space:]]*\([^[:space:]#]*\).*/\1/p' "$STATE_FILE" 2>/dev/null | head -1)
56
42
 
57
43
  # 如果无法确定阶段,默认放行(容错)
58
44
  if [ -z "$CURRENT_PHASE" ]; then
59
45
  exit 0
60
46
  fi
61
47
 
62
- # 提取阶段编号(P0→0, P1→1, ...)— 兼容 macOS
48
+ # 提取阶段编号(P0→0, P1→1, ...)
63
49
  PHASE_NUM=$(echo "$CURRENT_PHASE" | sed 's/[^0-9]//g' 2>/dev/null)
64
50
 
65
51
  if [ -z "$PHASE_NUM" ]; then
@@ -0,0 +1,42 @@
1
+ # SDLC 项目状态(活文档 — 持续更新)
2
+
3
+ > **COMPACTION 保护区域:压缩时必须保留。每次变更后立即更新。**
4
+ > 本文件是项目状态唯一存储位置,通过 CLAUDE.md 的 @import 自动加载。
5
+ > 升级 claude-sdlc 时本文件不会被覆盖。
6
+
7
+ ```yaml
8
+ # === SDLC 项目状态 ===
9
+ current_phase: P0 # P0=未开始, P1=需求, P2=设计, P3=编码, P4=测试, P5=审查, P6=部署
10
+ task_description: ""
11
+ started_at: ""
12
+ last_updated: ""
13
+
14
+ # PRD — 用户确认的需求清单(P1 产出,P2-P6 唯一依据)
15
+ prd:
16
+ # - id: R1
17
+ # description: "需求描述"
18
+ # acceptance_criteria: "验收标准"
19
+
20
+ architecture_decisions: []
21
+ modified_files: []
22
+ todo_items: []
23
+ review_retry_count: 0 # 自动驱动审查重试计数,阶段推进后重置
24
+ phase_history: []
25
+ key_context: "" # compaction 后恢复用
26
+ ```
27
+
28
+ ### 状态更新时机
29
+
30
+ | 事件 | 更新字段 |
31
+ |------|---------|
32
+ | 新任务 | `current_phase`→P1, `task_description`, `started_at` |
33
+ | PRD 确认 | `prd`(编号化需求列表,此后为唯一依据) |
34
+ | 阶段推进 | `current_phase`, `phase_history`, `review_retry_count`→0, `last_updated` |
35
+ | 文件修改 | `modified_files`(追加路径), `last_updated` |
36
+ | 架构决策 | `architecture_decisions`, `last_updated` |
37
+ | 审查重试 | `review_retry_count`+1, `last_updated` |
38
+ | 即将压缩 | 所有字段确认最新, `key_context`(写入当前工作摘要) |
39
+
40
+ # Compact Instructions
41
+
42
+ 压缩时必须保留:(1) 本文件路径和存在性 (2) 上方 YAML 块完整内容 (3) 当前阶段和任务 (4) 已修改文件列表 (5) 架构决策 (6) 用户最近指令。恢复后:P1/P2 等待用户确认继续,P3-P6 自动恢复自动驱动继续完成。
@@ -33,7 +33,7 @@
33
33
 
34
34
  ### PRD 确认流程
35
35
  1. Claude 整理 PRD 初稿后,**必须向用户展示完整的需求清单**
36
- 2. 用户确认后,将 PRD 写入 CLAUDE.md 的 `prd` 字段
36
+ 2. 用户确认后,将 PRD 写入 .claude/project-state.md 的 `prd` 字段
37
37
  3. **PRD 一旦写入即锁定**,后续阶段以此为唯一依据
38
38
  4. 如需修改 PRD,必须回到 P1 重新获得用户确认
39
39
 
@@ -46,11 +46,11 @@
46
46
  - [ ] 技术可行性 — 在当前技术栈下可实现
47
47
  - [ ] 影响分析 — 受影响的文件和模块已识别
48
48
  - [ ] **用户已确认** — PRD 经用户明确确认
49
- - [ ] **PRD 已写入 CLAUDE.md** — `prd` 字段已填写
49
+ - [ ] **PRD 已写入 .claude/project-state.md** — `prd` 字段已填写
50
50
 
51
51
  ### 退出条件
52
52
  - PRD 已整理完成并经用户确认
53
- - PRD 已写入 CLAUDE.md 的 `prd` 字段
53
+ - PRD 已写入 .claude/project-state.md 的 `prd` 字段
54
54
  - 影响范围已明确
55
55
  - **需求审查已通过**(`/review`)
56
56
 
@@ -69,7 +69,7 @@
69
69
 
70
70
  ### 入口条件
71
71
  - P1 需求分析已完成(含需求审查通过)
72
- - PRD 已获用户确认并写入 CLAUDE.md
72
+ - PRD 已获用户确认并写入 .claude/project-state.md
73
73
 
74
74
  ### 阶段活动
75
75
  - **【最新技术与设计调研 — 必须首先执行】**:
@@ -145,7 +145,7 @@
145
145
  3. 确保使用当前推荐的 API,不使用已废弃(deprecated)的方法或过时写法
146
146
  - **严格按 P2 设计方案编写代码,不添加 PRD 之外的功能**
147
147
  - 遵循编码规范(见 02-coding-standards.md)
148
- - 每完成一个逻辑单元,更新 CLAUDE.md 中的 modified_files
148
+ - 每完成一个逻辑单元,更新 .claude/project-state.md 中的 modified_files
149
149
  - 记录实现中的关键决策
150
150
  - **如发现 PRD 有遗漏或设计需调整,停止编码,回退到 P1/P2 与用户确认**
151
151
  - **多 Agent 并行编码**(如有独立模块):按设计方案拆分独立模块,用 Task 工具并行派发子 Agent 编码(见 07-parallel-agents.md)
@@ -159,7 +159,7 @@
159
159
  ### 必需产出物
160
160
  - [ ] 功能代码实现(**仅限 PRD 范围**)
161
161
  - [ ] 代码符合编码规范
162
- - [ ] CLAUDE.md 中 modified_files 已更新
162
+ - [ ] .claude/project-state.md 中 modified_files 已更新
163
163
  - [ ] 关键实现决策已记录
164
164
 
165
165
  ### 阶段审查(代码审查)
@@ -10,9 +10,9 @@ Claude Code 存在以下可能导致规范失效的场景:
10
10
 
11
11
  | 场景 | 风险 | 防御机制 |
12
12
  |------|------|---------|
13
- | 新会话启动 | Claude 不知道有规范 | CLAUDE.md + rules/ 自动加载 |
13
+ | 新会话启动 | Claude 不知道有规范 | CLAUDE.md + rules/ + project-state.md 自动加载 |
14
14
  | 长对话漂移 | Claude 渐渐忘记规范 | Stop hook 每次回复后强制自检 |
15
- | Context Compaction | 早期对话被压缩丢弃 | PreCompact hook 保存状态 → CLAUDE.md 重新加载恢复 |
15
+ | Context Compaction | 早期对话被压缩丢弃 | PreCompact hook 保存状态 → .claude/project-state.md 重新加载恢复 |
16
16
  | 用户催促跳过 | Claude 放弃规范直接执行 | Hooks 硬拦截违规操作 |
17
17
  | 新任务覆盖 | 上一任务状态残留 | 新任务检测 → 自动重置 |
18
18
  | Claude 自行判断不需要 | Claude 认为简单任务不需要走流程 | CLAUDE.md 明确"所有开发任务必须" |
@@ -23,6 +23,7 @@ Claude Code 存在以下可能导致规范失效的场景:
23
23
 
24
24
  ### 第一层:CLAUDE.md 启动指令(主动)
25
25
  - CLAUDE.md 在每次会话开始和 compaction 后自动加载
26
+ - CLAUDE.md 通过 @import 引用 `.claude/project-state.md`,项目状态存储在该文件中
26
27
  - **「启动指令」要求 Claude 读到该文件时立即执行状态检查**
27
28
  - 这确保 Claude 不是被动接收规范,而是主动执行初始化
28
29
  - **这是最关键的防线**
@@ -42,20 +43,20 @@ Claude Code 存在以下可能导致规范失效的场景:
42
43
  - 这是**不依赖 Claude 自觉性的被动安全网**
43
44
 
44
45
  ### 第四层:PostToolUse Hook — 状态同步(主动)
45
- - 每次 Write/Edit 操作后,prompt hook 提醒 Claude 更新 CLAUDE.md
46
- - 确保 modified_files 列表实时最新
46
+ - 每次 Write/Edit 操作后,prompt hook 提醒 Claude 更新 .claude/project-state.md
47
+ - 确保 modified_files 列表实时最新(状态存储在 .claude/project-state.md 中)
47
48
  - 为 compaction 后的恢复提供准确的文件清单
48
49
 
49
50
  ### 第五层:Stop Hook — 回复后审查(主动)
50
51
  - 每次回复完成后触发
51
- - **强制 Claude 用 Read 工具重新读取 CLAUDE.md**,而非依赖记忆
52
+ - **强制 Claude 用 Read 工具重新读取 .claude/project-state.md**,而非依赖记忆
52
53
  - 检查阶段合规性、状态最新性
53
54
  - 发现偏离时立即纠正
54
55
 
55
56
  ### 第六层:PreCompact Hook — 压缩前保存(主动)
56
57
  - **prompt 类型**,直接指令 Claude 在压缩前保存所有状态
57
- - 要求 Claude 用 Read 读取 CLAUDE.md 确认状态,用 Edit 更新缺失信息
58
- - 特别要求写入 key_context 摘要,确保恢复时有足够上下文
58
+ - 要求 Claude 用 Read 读取 .claude/project-state.md 确认状态,用 Edit 更新缺失信息
59
+ - 特别要求写入 key_context 摘要到 .claude/project-state.md,确保恢复时有足够上下文
59
60
 
60
61
  ### 第七层:用户命令(按需)
61
62
  - `/status` — 随时查看完整状态,触发深度自检
@@ -73,7 +74,7 @@ Claude Code 存在以下可能导致规范失效的场景:
73
74
  1. Claude Code 启动 → 自动加载 CLAUDE.md + rules/
74
75
  2. CLAUDE.md「启动指令」执行 → 检查 current_phase = P0
75
76
  3. Claude 识别到"实现"是开发请求 → 自动进入 P1
76
- 4. 更新 CLAUDE.md: current_phase=P1, task_description="实现登录功能"
77
+ 4. 更新 .claude/project-state.md: current_phase=P1, task_description="实现登录功能"
77
78
  5. 开始需求分析 → 整理 PRD → 向用户展示 PRD 等待确认
78
79
  6. 用户确认 PRD → 自动审查 → 进入 P2 → 设计方案 → 向用户展示等待确认
79
80
  7. 用户确认设计 → 自动审查 → 进入自动驱动模式
@@ -85,7 +86,7 @@ Claude Code 存在以下可能导致规范失效的场景:
85
86
 
86
87
  ```
87
88
  1. 当前在 P3 自动驱动中
88
- 2. PreCompact hook 触发 → Claude 被指令保存状态到 CLAUDE.md
89
+ 2. PreCompact hook 触发 → Claude 被指令保存状态到 .claude/project-state.md
89
90
  3. Compaction 执行 → 对话历史被压缩
90
91
  4. CLAUDE.md 重新加载 → 「启动指令」重新执行
91
92
  5. Claude 读取 current_phase=P3 → 知道正在自动驱动的编码阶段
@@ -98,7 +99,7 @@ Claude Code 存在以下可能导致规范失效的场景:
98
99
  ```
99
100
  1. Claude 调用 Write 工具写 .py 文件
100
101
  2. PreToolUse hook (check-phase-write.sh) 执行
101
- 3. 读取 CLAUDE.md: current_phase=P1
102
+ 3. 读取 .claude/project-state.md: current_phase=P1
102
103
  4. P1 < P3 → 输出拦截信息,exit 2
103
104
  5. Claude 收到拦截信息 → 向用户说明不能在 P1 写代码
104
105
  6. 继续需求分析工作
@@ -153,13 +154,13 @@ Claude Code 存在以下可能导致规范失效的场景:
153
154
  **每次回复前执行(内部 4 步):**
154
155
 
155
156
  ```
156
- 1. 【阶段】我当前在哪个阶段?→ 检查 CLAUDE.md 的 current_phase
157
+ 1. 【阶段】我当前在哪个阶段?→ 检查 .claude/project-state.md 的 current_phase
157
158
  2. 【PRD】我接下来要做的事,对应 PRD 中的哪条需求?→ 如果对应不上,不做
158
159
  3. 【合规】我要执行的操作在当前阶段允许吗?→ 对照允许列表
159
- 4. 【状态】CLAUDE.md 的状态是最新的吗?→ 如有变更需更新
160
+ 4. 【状态】.claude/project-state.md 的状态是最新的吗?→ 如有变更需更新
160
161
  ```
161
162
 
162
- **疑问时的行为:不猜测,用 Read 工具重新读取 CLAUDE.md。**
163
+ **疑问时的行为:不猜测,用 Read 工具重新读取 .claude/project-state.md。**
163
164
 
164
165
  ---
165
166
 
@@ -173,7 +174,7 @@ Claude Code 存在以下可能导致规范失效的场景:
173
174
  - Stop hook 提醒时
174
175
 
175
176
  ```
176
- 1. 用 Read 工具读取 CLAUDE.md「当前项目状态」YAML
177
+ 1. 用 Read 工具读取 .claude/project-state.mdYAML 状态
177
178
  2. 确认 current_phase 值
178
179
  3. 确认 task_description 值
179
180
  4. 回顾 modified_files 列表 — 是否有遗漏
@@ -188,7 +189,7 @@ Claude Code 存在以下可能导致规范失效的场景:
188
189
  ## 6. 异常恢复流程
189
190
 
190
191
  ### 自动恢复(优先)
191
- 1. 用 Read 工具读取 CLAUDE.md 获取最新状态
192
+ 1. 用 Read 工具读取 .claude/project-state.md 获取最新状态
192
193
  2. 根据 modified_files 列表,用 Read 工具读取已修改的文件以恢复上下文
193
194
  3. 向用户报告恢复结果
194
195
 
@@ -27,7 +27,7 @@
27
27
  "hooks": [
28
28
  {
29
29
  "type": "command",
30
- "command": "echo '{\"decision\":\"block\",\"reason\":\"SDLC: File modified. Use Edit to update CLAUDE.md: append the modified file path to modified_files (if not already listed) and update last_updated. This is mandatory, do not skip.\"}'"
30
+ "command": "echo '{\"decision\":\"block\",\"reason\":\"SDLC: File modified. Use Edit to update .claude/project-state.md: append the modified file path to modified_files (if not already listed) and update last_updated. This is mandatory, do not skip.\"}'"
31
31
  }
32
32
  ]
33
33
  }
@@ -37,7 +37,7 @@
37
37
  "hooks": [
38
38
  {
39
39
  "type": "prompt",
40
- "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nIMPORTANT: First check if stop_hook_active is true in the context above. If it is true, respond immediately with {\"ok\": true} to prevent infinite loops.\n\nOtherwise, check the conversation transcript and determine:\n1. Is there an active SDLC phase (P1-P6) with remaining work?\n2. For P3/P4/P5 auto-drive phases: has the /review been executed and passed?\n3. Are there uncompleted PRD requirements?\n4. Has CLAUDE.md YAML state been updated after the latest changes?\n\nRespond with JSON: {\"ok\": true} if all tasks are complete and Claude can stop, or {\"ok\": false, \"reason\": \"SDLC self-check: [describe what still needs to be done, e.g. run /review, update CLAUDE.md YAML, continue to next phase, etc.]\"} if Claude should continue working.",
40
+ "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nIMPORTANT: First check if stop_hook_active is true in the context above. If it is true, respond immediately with {\"ok\": true} to prevent infinite loops.\n\nOtherwise, check the conversation transcript and determine:\n1. Is there an active SDLC phase (P1-P6) with remaining work?\n2. For P3/P4/P5 auto-drive phases: has the /review been executed and passed?\n3. Are there uncompleted PRD requirements?\n4. Has .claude/project-state.md YAML state been updated after the latest changes?\n\nRespond with JSON: {\"ok\": true} if all tasks are complete and Claude can stop, or {\"ok\": false, \"reason\": \"SDLC self-check: [describe what still needs to be done, e.g. run /review, update .claude/project-state.md YAML, continue to next phase, etc.]\"} if Claude should continue working.",
41
41
  "timeout": 30
42
42
  }
43
43
  ]
@@ -48,7 +48,7 @@
48
48
  "hooks": [
49
49
  {
50
50
  "type": "prompt",
51
- "prompt": "You are evaluating a conversation before context compaction. Context: $ARGUMENTS\n\nCheck if CLAUDE.md YAML state block has been updated with latest values for: current_phase, task_description, prd, modified_files, architecture_decisions, todo_items, key_context.\n\nRespond with JSON: {\"ok\": true} if state is saved, or {\"ok\": false, \"reason\": \"Before compaction: update CLAUDE.md YAML - save current_phase, task_description, modified_files, and write key_context summary of current work and next steps.\"}",
51
+ "prompt": "You are evaluating a conversation before context compaction. Context: $ARGUMENTS\n\nCheck if .claude/project-state.md YAML state block has been updated with latest values for: current_phase, task_description, prd, modified_files, architecture_decisions, todo_items, key_context.\n\nRespond with JSON: {\"ok\": true} if state is saved, or {\"ok\": false, \"reason\": \"Before compaction: update .claude/project-state.md YAML - save current_phase, task_description, modified_files, and write key_context summary of current work and next steps.\"}",
52
52
  "timeout": 30
53
53
  }
54
54
  ]
@@ -2,9 +2,11 @@
2
2
 
3
3
  > 详细规则见 `.claude/rules/`(自动加载)。可用命令:`/phase`、`/status`、`/checkpoint`、`/review`。
4
4
 
5
+ @.claude/project-state.md
6
+
5
7
  ## 启动指令(加载时立即执行)
6
8
 
7
- 1. 读取下方 `current_phase` 值
9
+ 1. Read 工具读取 `.claude/project-state.md` 获取 `current_phase` 值
8
10
  2. **P1-P6**:向用户简要报告状态(阶段、任务、已修改文件数),继续工作
9
11
  3. **P0 或空**:等待用户提出任务
10
12
  4. 所有后续操作遵守 SDLC 规范
@@ -66,52 +68,9 @@ P3/P4/P5 阶段有独立模块时,用 Task 工具并行派发子 Agent 提高
66
68
 
67
69
  ### 每次回复前 4 步自检
68
70
 
69
- 1. **阶段** — `current_phase` 是什么?
71
+ 1. **阶段** — `current_phase` 是什么?(读 `.claude/project-state.md`)
70
72
  2. **PRD** — 要做的事对应 PRD 哪条?对应不上则不做
71
73
  3. **合规** — 操作在当前阶段允许吗?
72
- 4. **状态** — 变更后 YAML 更新了吗?
73
-
74
- 有疑问 → 用 Read 重读本文件,不依赖记忆。详见 `.claude/rules/05-anti-amnesia.md`。
75
-
76
- ---
77
-
78
- ## 当前项目状态(活文档 — 持续更新)
79
-
80
- > **COMPACTION 保护区域:压缩时必须保留。每次变更后立即更新。**
81
-
82
- ```yaml
83
- # === SDLC 项目状态 ===
84
- current_phase: P0 # P0=未开始, P1=需求, P2=设计, P3=编码, P4=测试, P5=审查, P6=部署
85
- task_description: ""
86
- started_at: ""
87
- last_updated: ""
88
-
89
- # PRD — 用户确认的需求清单(P1 产出,P2-P6 唯一依据)
90
- prd:
91
- # - id: R1
92
- # description: "需求描述"
93
- # acceptance_criteria: "验收标准"
94
-
95
- architecture_decisions: []
96
- modified_files: []
97
- todo_items: []
98
- review_retry_count: 0 # 自动驱动审查重试计数,阶段推进后重置
99
- phase_history: []
100
- key_context: "" # compaction 后恢复用
101
- ```
102
-
103
- ### 状态更新时机
104
-
105
- | 事件 | 更新字段 |
106
- |------|---------|
107
- | 新任务 | `current_phase`→P1, `task_description`, `started_at` |
108
- | PRD 确认 | `prd`(编号化需求列表,此后为唯一依据) |
109
- | 阶段推进 | `current_phase`, `phase_history`, `review_retry_count`→0, `last_updated` |
110
- | 文件修改 | `modified_files`(追加路径), `last_updated` |
111
- | 架构决策 | `architecture_decisions`, `last_updated` |
112
- | 审查重试 | `review_retry_count`+1, `last_updated` |
113
- | 即将压缩 | 所有字段确认最新, `key_context`(写入当前工作摘要) |
114
-
115
- # Compact Instructions
74
+ 4. **状态** — 变更后 `.claude/project-state.md` 更新了吗?
116
75
 
117
- 压缩时必须保留:(1) 本文件路径和存在性 (2) 上方 YAML 块完整内容 (3) 当前阶段和任务 (4) 已修改文件列表 (5) 架构决策 (6) 用户最近指令。恢复后:P1/P2 等待用户确认继续,P3-P6 自动恢复自动驱动继续完成。
76
+ 有疑问 Read 重读 `.claude/project-state.md`,不依赖记忆。详见 `.claude/rules/05-anti-amnesia.md`。