claude-sdlc 1.0.4 → 1.0.6

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
@@ -85,6 +85,11 @@ function mergeSettings(existing, template) {
85
85
  const hookTypes = ['PreToolUse', 'PostToolUse', 'Stop', 'PreCompact'];
86
86
  const result = JSON.parse(JSON.stringify(existing));
87
87
 
88
+ // 合并 $schema
89
+ if (template.$schema && !result.$schema) {
90
+ result.$schema = template.$schema;
91
+ }
92
+
88
93
  for (const type of hookTypes) {
89
94
  const existingHooks = result.hooks?.[type] || [];
90
95
  const newHooks = template.hooks?.[type] || [];
@@ -107,6 +112,46 @@ function mergeSettings(existing, template) {
107
112
  return result;
108
113
  }
109
114
 
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
+
110
155
  function copyFilesQuiet(srcDir, destDir, ext) {
111
156
  if (!fs.existsSync(srcDir)) return [];
112
157
  const files = fs.readdirSync(srcDir).filter(f => f.endsWith(ext));
@@ -146,13 +191,31 @@ function install(targetDir) {
146
191
  console.log(` ${SYM.arrow} 目标 ${BOLD}${targetDir}${RESET}`);
147
192
  blank();
148
193
 
149
- // Step 1: CLAUDE.md
194
+ // Step 1: CLAUDE.md(智能合并:更新模板指令,保留项目状态)
150
195
  step(1, STEPS, '安装核心控制文件');
151
- fs.copyFileSync(
152
- path.join(templateDir, 'CLAUDE.md'),
153
- path.join(targetDir, 'CLAUDE.md')
154
- );
155
- fileLog(SYM.check, 'CLAUDE.md');
196
+ const targetClaudeMd = path.join(targetDir, 'CLAUDE.md');
197
+ const templateClaudeMd = path.join(templateDir, 'CLAUDE.md');
198
+ 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
+ }
156
219
 
157
220
  // Step 2: 目录结构
158
221
  step(2, STEPS, '创建目录结构');
@@ -296,6 +359,9 @@ function uninstall(targetDir) {
296
359
  if (settings.hooks) {
297
360
  delete settings.hooks;
298
361
  }
362
+ if (settings.$schema === 'https://json.schemastore.org/claude-code-settings.json') {
363
+ delete settings.$schema;
364
+ }
299
365
  if (Object.keys(settings).length === 0) {
300
366
  fs.unlinkSync(settingsPath);
301
367
  removed.push('settings.json');
@@ -332,4 +398,4 @@ function uninstall(targetDir) {
332
398
  blank();
333
399
  }
334
400
 
335
- module.exports = { install, uninstall, mergeSettings };
401
+ module.exports = { install, uninstall, mergeSettings, extractYamlState, hasActiveState, mergeClaudeMd };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-sdlc",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "让 Claude Code 严格按 SDLC 规范开发 — 一条命令安装",
5
5
  "bin": {
6
6
  "claude-sdlc": "./bin/cli.js"
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
2
3
  "hooks": {
3
4
  "PreToolUse": [
4
5
  {
@@ -6,7 +7,7 @@
6
7
  "hooks": [
7
8
  {
8
9
  "type": "command",
9
- "command": "bash .claude/hooks/check-phase-write.sh"
10
+ "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-phase-write.sh"
10
11
  }
11
12
  ]
12
13
  },
@@ -15,7 +16,7 @@
15
16
  "hooks": [
16
17
  {
17
18
  "type": "command",
18
- "command": "bash .claude/hooks/check-phase-test.sh"
19
+ "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-phase-test.sh"
19
20
  }
20
21
  ]
21
22
  }
@@ -25,8 +26,8 @@
25
26
  "matcher": "Write|Edit",
26
27
  "hooks": [
27
28
  {
28
- "type": "prompt",
29
- "prompt": "文件已修改。用 Edit 更新 CLAUDE.md:将文件路径追加到 modified_files(如未记录),更新 last_updated。必须执行,不可跳过。"
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
31
  }
31
32
  ]
32
33
  }
@@ -36,7 +37,7 @@
36
37
  "hooks": [
37
38
  {
38
39
  "type": "prompt",
39
- "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nCheck 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.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
41
  "timeout": 30
41
42
  }
42
43
  ]