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 +73 -7
- package/package.json +1 -1
- package/template/.claude/settings.json +6 -5
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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,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
|
|
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
|
|
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": "
|
|
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
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\
|
|
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
|
]
|