@zhuan-ai/zhuanspec 2.9.5 → 2.11.2

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.
Files changed (36) hide show
  1. package/dist/cli/hooks.js +8 -8
  2. package/dist/cli/index.js +1 -0
  3. package/dist/commands/design.d.ts +12 -2
  4. package/dist/commands/design.js +83 -10
  5. package/dist/commands/progress.js +11 -0
  6. package/dist/commands/review.d.ts +1 -25
  7. package/dist/commands/review.js +51 -408
  8. package/dist/core/completions/command-registry.js +4 -0
  9. package/dist/core/hooks/collect-knowledge.d.ts +13 -5
  10. package/dist/core/hooks/collect-knowledge.js +56 -164
  11. package/dist/core/hooks/deviation-check.js +159 -55
  12. package/dist/core/hooks/init.js +46 -0
  13. package/dist/core/hooks/post-apply.js +2 -0
  14. package/dist/core/hooks/pre-archive.js +13 -14
  15. package/dist/core/hooks/pre-review.d.ts +27 -0
  16. package/dist/core/hooks/pre-review.js +169 -0
  17. package/dist/core/hooks/record-progress.d.ts +25 -0
  18. package/dist/core/hooks/record-progress.js +88 -1
  19. package/dist/core/hooks/review-hooks.js +176 -7
  20. package/dist/core/hooks/review-orchestrator.js +58 -11
  21. package/dist/core/hooks/tdd-phase-hook.d.ts +59 -0
  22. package/dist/core/hooks/tdd-phase-hook.js +313 -0
  23. package/dist/core/init.d.ts +1 -0
  24. package/dist/core/init.js +45 -2
  25. package/dist/core/templates/agents-template.d.ts +1 -1
  26. package/dist/core/templates/agents-template.js +139 -23
  27. package/dist/core/templates/skill-templates.js +21 -1
  28. package/dist/core/templates/slash-command-templates.js +163 -63
  29. package/dist/core/templates/tasks-template.js +89 -0
  30. package/dist/core/update.d.ts +3 -0
  31. package/dist/core/update.js +85 -2
  32. package/dist/core/validation/strict-rules.d.ts +47 -0
  33. package/dist/core/validation/strict-rules.js +589 -42
  34. package/dist/utils/git-repo-detector.js +1 -1
  35. package/dist/utils/phase-utils.js +14 -1
  36. package/package.json +22 -20
@@ -25,7 +25,7 @@ function generateCodeReviewPrompt(changeId) {
25
25
  1. 执行 code-review-expert skill 进行代码审查
26
26
  2. 如果发现 Critical 问题,立即修复
27
27
  3. 修复后重新检查(最多3轮)
28
- 4. 输出 code-review-result.json 报告到 zhuanspec/.skill-outputs/${changeId}/
28
+ 4. 输出 code-review-result.json 报告到 zhuanspec/changes/${changeId}/review/
29
29
 
30
30
  ## 输出格式
31
31
  {
@@ -50,7 +50,7 @@ function generateUnitTestPrompt(changeId) {
50
50
  2. 运行单测,检查通过率和覆盖率
51
51
  3. 如果失败或不达标,立即修复
52
52
  4. 修复后重新测试(最多3轮)
53
- 5. 输出 unit-test-result.json 报告到 zhuanspec/.skill-outputs/${changeId}/
53
+ 5. 输出 unit-test-result.json 报告到 zhuanspec/changes/${changeId}/review/
54
54
 
55
55
  ## 输出格式
56
56
  {
@@ -69,22 +69,69 @@ function generateSpecConsistencyPrompt(changeId) {
69
69
  执行 Spec-Code 一致性检查,发现未覆盖 Scenario 时启动修复循环:
70
70
 
71
71
  ## 任务目标
72
- 验证 changeId=${changeId} 的代码实现与 Spec Requirement 一致
72
+ 验证 changeId=${changeId} 的代码实现与 Spec Scenario 语义一致。
73
+ ⚠️ 禁止用 Requirement 标题做字符串匹配,必须逐条读取 Scenario 内容做语义比对。
73
74
 
74
75
  ## 自闭环指令
75
- 1. 解析 specs/*.md 中的所有 Requirement 和 Scenario
76
- 2. 检查代码实现是否覆盖每个 Scenario
77
- 3. 如果发现未覆盖,立即补充代码或更新 Spec
78
- 4. 修复后重新检查(最多3轮)
79
- 5. 输出 spec-consistency-result.json 报告到 zhuanspec/.skill-outputs/${changeId}/
76
+
77
+ ### Step 1:解析 Spec Scenarios
78
+ 读取 zhuanspec/changes/${changeId}/specs/**/*.md,提取所有内容:
79
+ - 每个 \`### Requirement:\` 下的每个 \`#### Scenario:\` 标题
80
+ - Scenario 的 Given/When/Then 描述(业务语义)
81
+ - 标记每个 Scenario 的所属 Requirement
82
+
83
+ 输出 Scenario 清单,格式:
84
+ [Requirement名] > [Scenario名]: [Given/When/Then核心语义]
85
+
86
+ ### Step 2:逐 Scenario 语义比对
87
+ 对每个 Scenario,在代码中找对应实现:
88
+
89
+ **查找顺序**:
90
+ 1. 读取 proposal.md 的「后端改动」和「前端改动」表格,定位涉及文件
91
+ 2. 在对应文件中找与 Scenario 语义匹配的方法/逻辑:
92
+ - Scenario 描述「新建客户」→ 找 createCustomer 相关方法
93
+ - Scenario 描述「手机号去重」→ 找手机号唯一性校验逻辑
94
+ - Scenario 描述「租户隔离」→ 找 tenantId 过滤条件
95
+ - Scenario 描述「删除企微客户拒绝」→ 找 source_type 判断逻辑
96
+ 3. 验证找到的代码是否满足 Scenario 的 Then 条件
97
+
98
+ **判断标准**(语义覆盖,不是字符串匹配):
99
+ - ✅ 覆盖:代码中存在与 Scenario Given→When→Then 对应的业务逻辑
100
+ - ❌ 未覆盖:相关文件不存在、方法不存在、或 Then 条件未实现
101
+ - ⚠️ 部分覆盖:正向路径实现了,异常路径未实现
102
+
103
+ ### Step 3:输出比对结果表格
104
+ \`\`\`
105
+ | Requirement | Scenario | 对应文件/方法 | 覆盖状态 | 说明 |
106
+ |---|---|---|---|---|
107
+ | 客户CRUD | 新建客户-成功 | CustomerDomainService#createCustomer | ✅ | - |
108
+ | 客户CRUD | 手机号重复-拒绝 | CustomerDomainService#checkPhoneDuplicate | ✅ | - |
109
+ | 多租户隔离 | 租户隔离查询 | CustomerEsQueryService | ❌ | 未找到tenantId过滤 |
110
+ \`\`\`
111
+
112
+ ### Step 4:自闭环修复
113
+ - 发现 ❌ 未覆盖的 Scenario → 补充对应代码实现
114
+ - 修复后重新执行 Step 2 验证(最多3轮)
115
+ - ⚠️ 仅修复真实遗漏,禁止把"文件存在但逻辑未实现"误报为已覆盖
116
+
117
+ ### Step 5:输出 spec-consistency-result.json
118
+ 路径:zhuanspec/changes/${changeId}/review/spec-consistency-result.json
80
119
 
81
120
  ## 输出格式
82
121
  {
83
- "consistencyRate": number,
122
+ "consistencyRate": number, // 覆盖 Scenario 数 / 总 Scenario 数
84
123
  "totalRequirements": number,
85
124
  "totalScenarios": number,
86
125
  "coveredScenarios": number,
87
- "uncoveredScenarios": [...],
126
+ "uncoveredScenarios": [ // 真实未覆盖,非字符串匹配失败
127
+ {
128
+ "requirement": "Requirement名",
129
+ "scenario": "Scenario名",
130
+ "reason": "具体原因:方法不存在/逻辑未实现/文件缺失",
131
+ "suggestedFix": "建议修复方式"
132
+ }
133
+ ],
134
+ "partialCoverage": [...], // 部分覆盖的 Scenario
88
135
  "loopCount": number,
89
136
  "finalStatus": "PASS" | "FAIL"
90
137
  }
@@ -100,7 +147,7 @@ export async function orchestrateReview(options) {
100
147
  const { changeId } = options;
101
148
  // Ensure output directory exists
102
149
  const cwd = process.cwd();
103
- const outputDir = path.join(cwd, 'zhuanspec', '.skill-outputs', changeId);
150
+ const outputDir = path.join(cwd, 'zhuanspec', 'changes', changeId, 'review');
104
151
  await FileSystemUtils.createDirectory(outputDir);
105
152
  return {
106
153
  codeReviewPrompt: generateCodeReviewPrompt(changeId),
@@ -0,0 +1,59 @@
1
+ /**
2
+ * PostToolUse Hook - TDD Phase Tracking
3
+ *
4
+ * This hook tracks TDD workflow compliance for tasks annotated with @test-case:
5
+ * 1. Tracks file creation order (test files should precede implementation files)
6
+ * 2. Detects test execution results (RED → GREEN → REFACTOR phases)
7
+ * 3. Updates metrics/tdd-phases.json with current TDD status
8
+ */
9
+ export type TddPhase = 'PENDING' | 'RED' | 'GREEN' | 'REFACTOR' | 'DONE';
10
+ export interface TddTaskStatus {
11
+ taskId: string;
12
+ testCases: string[];
13
+ tddPhase: TddPhase;
14
+ testFileCreated: boolean;
15
+ testInitiallyFailed: boolean;
16
+ testFinallyPassed: boolean;
17
+ implFileCreated: boolean;
18
+ updatedAt: string;
19
+ }
20
+ export interface TddHookResult {
21
+ action: 'continue' | 'warn' | 'block';
22
+ message?: string;
23
+ updatedStatus?: TddTaskStatus;
24
+ }
25
+ /**
26
+ * Determine whether a file path looks like a test file.
27
+ */
28
+ export declare function isTestFile(filePath: string): boolean;
29
+ /**
30
+ * Load the persisted TDD status for a given task.
31
+ * Returns `null` when no status has been recorded yet.
32
+ */
33
+ export declare function loadTddStatus(changePath: string, taskId: string): Promise<TddTaskStatus | null>;
34
+ /**
35
+ * Persist the TDD status for a task into `metrics/tdd-phases.json`.
36
+ * Creates the file (and parent directories) when they do not exist.
37
+ */
38
+ export declare function saveTddStatus(changePath: string, taskId: string, status: TddTaskStatus): Promise<void>;
39
+ /**
40
+ * Determine TDD phase transition based on the current status and latest event.
41
+ *
42
+ * Transition rules:
43
+ * PENDING → RED when a test file is created
44
+ * RED → GREEN when tests pass after initial failure is recorded
45
+ * GREEN → REFACTOR when an implementation file is modified / created after tests pass
46
+ * REFACTOR → DONE when tests still pass after refactoring
47
+ */
48
+ export declare function detectTddPhaseTransition(currentStatus: TddTaskStatus, toolName: string, filePath: string, toolOutput?: string): TddTaskStatus;
49
+ /**
50
+ * TDD Phase Hook – called after tool use to enforce TDD discipline.
51
+ *
52
+ * @param changePath Absolute path to the current change directory
53
+ * @param toolName Name of the tool just used (e.g. 'Write', 'Edit', 'Bash')
54
+ * @param filePath File affected by the tool invocation
55
+ * @param taskId Current task identifier (e.g. "2.1")
56
+ * @param toolOutput Optional output captured from Bash executions
57
+ */
58
+ export declare function tddPhaseHook(changePath: string, toolName: string, filePath: string, taskId: string, toolOutput?: string): Promise<TddHookResult>;
59
+ //# sourceMappingURL=tdd-phase-hook.d.ts.map
@@ -0,0 +1,313 @@
1
+ /**
2
+ * PostToolUse Hook - TDD Phase Tracking
3
+ *
4
+ * This hook tracks TDD workflow compliance for tasks annotated with @test-case:
5
+ * 1. Tracks file creation order (test files should precede implementation files)
6
+ * 2. Detects test execution results (RED → GREEN → REFACTOR phases)
7
+ * 3. Updates metrics/tdd-phases.json with current TDD status
8
+ */
9
+ import path from 'path';
10
+ import { FileSystemUtils } from '../../utils/file-system.js';
11
+ import { atomicWriteJson } from './record-progress.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Test-file detection patterns
14
+ // ---------------------------------------------------------------------------
15
+ /** Common test-file naming patterns across languages */
16
+ const TEST_FILE_PATTERNS = [
17
+ /Test\.java$/, // Java – JUnit / TestNG
18
+ /Tests?\.cs$/, // C# – xUnit / NUnit
19
+ /\.test\.[jt]sx?$/, // JS/TS – Jest / Vitest
20
+ /\.spec\.[jt]sx?$/, // JS/TS – Jasmine / Mocha / Vitest
21
+ /_test\.go$/, // Go
22
+ /_test\.py$/, // Python – pytest
23
+ /test_.*\.py$/, // Python – unittest
24
+ /Spec\.(?:scala|kt)$/, // Scala / Kotlin – ScalaTest / Kotest
25
+ ];
26
+ // ---------------------------------------------------------------------------
27
+ // Test-result detection patterns (for Bash output)
28
+ // ---------------------------------------------------------------------------
29
+ const TEST_FAILURE_PATTERNS = [
30
+ /FAIL(?:ED|URE)?/i,
31
+ /Tests?\s+failed/i,
32
+ /BUILD FAILURE/i,
33
+ /AssertionError/i,
34
+ /AssertError/i,
35
+ /FAILURES!/,
36
+ /failures:\s*[1-9]/i,
37
+ /errors:\s*[1-9]/i,
38
+ /TEST.*FAIL/i,
39
+ ];
40
+ const TEST_SUCCESS_PATTERNS = [
41
+ /PASS(?:ED)?/i,
42
+ /Tests?\s+passed/i,
43
+ /BUILD SUCCESS/i,
44
+ /All tests passed/i,
45
+ /Tests:\s+\d+ passed,\s+\d+ total/i,
46
+ /OK \(\d+ tests?\)/i,
47
+ /\d+ passing/i,
48
+ /failures:\s*0.*errors:\s*0/i,
49
+ ];
50
+ // ---------------------------------------------------------------------------
51
+ // Helpers
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Return the path to the TDD phases JSON inside a change directory.
55
+ */
56
+ function tddPhasesPath(changePath) {
57
+ return path.join(changePath, 'metrics', 'tdd-phases.json');
58
+ }
59
+ /**
60
+ * Determine whether a file path looks like a test file.
61
+ */
62
+ export function isTestFile(filePath) {
63
+ const basename = path.basename(filePath);
64
+ return TEST_FILE_PATTERNS.some((pattern) => pattern.test(basename));
65
+ }
66
+ /**
67
+ * Load the persisted TDD status for a given task.
68
+ * Returns `null` when no status has been recorded yet.
69
+ */
70
+ export async function loadTddStatus(changePath, taskId) {
71
+ const jsonPath = tddPhasesPath(changePath);
72
+ if (!(await FileSystemUtils.fileExists(jsonPath))) {
73
+ return null;
74
+ }
75
+ try {
76
+ const content = await FileSystemUtils.readFile(jsonPath);
77
+ const data = JSON.parse(content);
78
+ return data.tasks?.[taskId] ?? null;
79
+ }
80
+ catch {
81
+ // Corrupted or unreadable – treat as absent
82
+ return null;
83
+ }
84
+ }
85
+ /**
86
+ * Persist the TDD status for a task into `metrics/tdd-phases.json`.
87
+ * Creates the file (and parent directories) when they do not exist.
88
+ */
89
+ export async function saveTddStatus(changePath, taskId, status) {
90
+ const jsonPath = tddPhasesPath(changePath);
91
+ let data = { tasks: {}, lastUpdated: '' };
92
+ if (await FileSystemUtils.fileExists(jsonPath)) {
93
+ try {
94
+ const content = await FileSystemUtils.readFile(jsonPath);
95
+ data = JSON.parse(content);
96
+ }
97
+ catch {
98
+ // Start fresh when the file is corrupted
99
+ }
100
+ }
101
+ data.tasks[taskId] = status;
102
+ data.lastUpdated = new Date().toISOString();
103
+ // Ensure the metrics directory exists
104
+ const metricsDir = path.dirname(jsonPath);
105
+ await FileSystemUtils.createDirectory(metricsDir);
106
+ await atomicWriteJson(jsonPath, data);
107
+ }
108
+ /**
109
+ * Detect whether a Bash tool invocation contains test-execution output
110
+ * and whether the tests passed or failed.
111
+ *
112
+ * Returns `'pass'`, `'fail'`, or `null` (when unable to determine).
113
+ */
114
+ function detectTestResult(toolOutput) {
115
+ if (!toolOutput)
116
+ return null;
117
+ const hasFail = TEST_FAILURE_PATTERNS.some((p) => p.test(toolOutput));
118
+ const hasPass = TEST_SUCCESS_PATTERNS.some((p) => p.test(toolOutput));
119
+ // Failure takes precedence when both signals are present (e.g. "1 passed, 1 failed")
120
+ if (hasFail)
121
+ return 'fail';
122
+ if (hasPass)
123
+ return 'pass';
124
+ return null;
125
+ }
126
+ /**
127
+ * Determine TDD phase transition based on the current status and latest event.
128
+ *
129
+ * Transition rules:
130
+ * PENDING → RED when a test file is created
131
+ * RED → GREEN when tests pass after initial failure is recorded
132
+ * GREEN → REFACTOR when an implementation file is modified / created after tests pass
133
+ * REFACTOR → DONE when tests still pass after refactoring
134
+ */
135
+ export function detectTddPhaseTransition(currentStatus, toolName, filePath, toolOutput) {
136
+ const status = { ...currentStatus, updatedAt: new Date().toISOString() };
137
+ const fileIsTest = isTestFile(filePath);
138
+ // Track file creation
139
+ if (fileIsTest) {
140
+ status.testFileCreated = true;
141
+ }
142
+ else if (toolName === 'Write' || toolName === 'Edit' || toolName === 'MultiEdit') {
143
+ status.implFileCreated = true;
144
+ }
145
+ // Detect test execution results (only relevant when toolName is Bash)
146
+ if (toolName === 'Bash') {
147
+ const result = detectTestResult(toolOutput);
148
+ switch (status.tddPhase) {
149
+ case 'PENDING':
150
+ // If tests are already running and they fail → move to RED
151
+ if (result === 'fail') {
152
+ status.testInitiallyFailed = true;
153
+ status.tddPhase = 'RED';
154
+ }
155
+ // If a test file was just created, also transition to RED
156
+ if (status.testFileCreated && status.tddPhase === 'PENDING') {
157
+ status.tddPhase = 'RED';
158
+ }
159
+ break;
160
+ case 'RED':
161
+ if (result === 'fail') {
162
+ status.testInitiallyFailed = true;
163
+ }
164
+ if (result === 'pass' && status.testInitiallyFailed) {
165
+ status.testFinallyPassed = true;
166
+ status.tddPhase = 'GREEN';
167
+ }
168
+ break;
169
+ case 'GREEN':
170
+ // After GREEN, any passing test run during refactor moves to REFACTOR
171
+ if (result === 'pass') {
172
+ status.tddPhase = 'REFACTOR';
173
+ }
174
+ break;
175
+ case 'REFACTOR':
176
+ // Stays in REFACTOR; transition to DONE is handled externally
177
+ // when the task is marked complete.
178
+ if (result === 'pass') {
179
+ status.tddPhase = 'DONE';
180
+ }
181
+ break;
182
+ default:
183
+ break;
184
+ }
185
+ }
186
+ // Also transition PENDING → RED when a test file is created (non-Bash tool)
187
+ if (status.tddPhase === 'PENDING' && status.testFileCreated) {
188
+ status.tddPhase = 'RED';
189
+ }
190
+ return status;
191
+ }
192
+ /**
193
+ * Check whether the given task line from tasks.md contains a @test-case annotation.
194
+ * Returns the list of referenced TC identifiers, or an empty array.
195
+ */
196
+ function parseTestCaseAnnotations(taskLine) {
197
+ const match = taskLine.match(/@test-case[:\s]+([^\n]+)/i);
198
+ if (!match)
199
+ return [];
200
+ return match[1]
201
+ .split(/[,\s]+/)
202
+ .map((tc) => tc.trim())
203
+ .filter(Boolean);
204
+ }
205
+ /**
206
+ * Read tasks.md and locate the line that corresponds to the given taskId.
207
+ * Returns `null` when the task is not found.
208
+ */
209
+ async function findTaskLine(changePath, taskId) {
210
+ const tasksPath = path.join(changePath, 'tasks.md');
211
+ if (!(await FileSystemUtils.fileExists(tasksPath))) {
212
+ return null;
213
+ }
214
+ const content = await FileSystemUtils.readFile(tasksPath);
215
+ const lines = content.split('\n');
216
+ // Normalise taskId for comparison (e.g. "2.1" should match "2.1")
217
+ const normalised = taskId.trim();
218
+ for (let idx = 0; idx < lines.length; idx++) {
219
+ const line = lines[idx];
220
+ // Match checkbox task lines: "- [ ] 2.1 ..." or "- [x] 2.1 ..."
221
+ const taskMatch = line.match(/^-\s+\[[ x]\]\s+(\S+)/);
222
+ if (taskMatch && taskMatch[1] === normalised) {
223
+ let fullBlock = line;
224
+ for (let i = idx + 1; i < lines.length; i++) {
225
+ if (/^\s{2,}/.test(lines[i]) && !/^-\s+\[/.test(lines[i].trim())) {
226
+ fullBlock += '\n' + lines[i];
227
+ }
228
+ else {
229
+ break;
230
+ }
231
+ }
232
+ return fullBlock;
233
+ }
234
+ }
235
+ return null;
236
+ }
237
+ // ---------------------------------------------------------------------------
238
+ // Main hook entry point
239
+ // ---------------------------------------------------------------------------
240
+ /**
241
+ * TDD Phase Hook – called after tool use to enforce TDD discipline.
242
+ *
243
+ * @param changePath Absolute path to the current change directory
244
+ * @param toolName Name of the tool just used (e.g. 'Write', 'Edit', 'Bash')
245
+ * @param filePath File affected by the tool invocation
246
+ * @param taskId Current task identifier (e.g. "2.1")
247
+ * @param toolOutput Optional output captured from Bash executions
248
+ */
249
+ export async function tddPhaseHook(changePath, toolName, filePath, taskId, toolOutput) {
250
+ // 1. Determine whether this task is a TDD task (@test-case annotated)
251
+ const taskLine = await findTaskLine(changePath, taskId);
252
+ if (!taskLine) {
253
+ return { action: 'continue' };
254
+ }
255
+ const testCases = parseTestCaseAnnotations(taskLine);
256
+ if (testCases.length === 0) {
257
+ // Not a TDD task – nothing to enforce
258
+ return { action: 'continue' };
259
+ }
260
+ // 2. Load or initialise TDD status
261
+ let status = await loadTddStatus(changePath, taskId);
262
+ if (!status) {
263
+ status = {
264
+ taskId,
265
+ testCases,
266
+ tddPhase: 'PENDING',
267
+ testFileCreated: false,
268
+ testInitiallyFailed: false,
269
+ testFinallyPassed: false,
270
+ implFileCreated: false,
271
+ updatedAt: new Date().toISOString(),
272
+ };
273
+ }
274
+ // 3. Check file creation order: test file should be created before impl file
275
+ if (!isTestFile(filePath) && !status.testFileCreated) {
276
+ if (toolName === 'Write' || toolName === 'Edit' || toolName === 'MultiEdit') {
277
+ // Implementation file created/modified before any test file exists
278
+ status.implFileCreated = true;
279
+ status.updatedAt = new Date().toISOString();
280
+ await saveTddStatus(changePath, taskId, status);
281
+ return {
282
+ action: 'warn',
283
+ message: `TDD violation: Implementation file "${path.basename(filePath)}" ` +
284
+ `created before test file. In TDD, write tests first (RED phase).`,
285
+ updatedStatus: status,
286
+ };
287
+ }
288
+ }
289
+ // 4. Detect phase transitions & test results
290
+ const previousPhase = status.tddPhase;
291
+ status = detectTddPhaseTransition(status, toolName, filePath, toolOutput);
292
+ // 5. Enforce GREEN-phase constraint: tests must pass
293
+ if (previousPhase === 'GREEN' && toolName === 'Bash') {
294
+ const result = detectTestResult(toolOutput);
295
+ if (result === 'fail') {
296
+ await saveTddStatus(changePath, taskId, status);
297
+ return {
298
+ action: 'block',
299
+ message: 'TDD violation: Tests must pass in GREEN phase. ' +
300
+ 'Fix the implementation until all tests pass before proceeding.',
301
+ updatedStatus: status,
302
+ };
303
+ }
304
+ }
305
+ // 6. Persist updated status
306
+ await saveTddStatus(changePath, taskId, status);
307
+ // 7. Return result
308
+ return {
309
+ action: 'continue',
310
+ updatedStatus: status,
311
+ };
312
+ }
313
+ //# sourceMappingURL=tdd-phase-hook.js.map
@@ -83,6 +83,7 @@ export declare class InitCommand {
83
83
  private displayWorkflowDiagram;
84
84
  private formatToolNames;
85
85
  private renderBanner;
86
+ private ensureClaudeHud;
86
87
  private startSpinner;
87
88
  }
88
89
  export {};
package/dist/core/init.js CHANGED
@@ -353,6 +353,8 @@ export class InitCommand {
353
353
  symbol: PALETTE.white('▌'),
354
354
  text: PALETTE.white('AI 工具已配置'),
355
355
  });
356
+ // Step 4: Ensure claude-hud is installed (for version display in statusLine)
357
+ await this.ensureClaudeHud();
356
358
  // Success message
357
359
  this.displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, toolConfigResult.rootStubStatus, toolConfigResult.claudeHookSummary);
358
360
  }
@@ -1258,6 +1260,17 @@ export class InitCommand {
1258
1260
  ],
1259
1261
  },
1260
1262
  ],
1263
+ UserPromptSubmit: [
1264
+ {
1265
+ hooks: [
1266
+ {
1267
+ type: 'command',
1268
+ command: 'zhuanspec-hook deviation-check --trigger post-prompt',
1269
+ statusMessage: 'Checking prompt vs proposal consistency...',
1270
+ },
1271
+ ],
1272
+ },
1273
+ ],
1261
1274
  PreToolUse: [
1262
1275
  {
1263
1276
  matcher: 'Write|Edit',
@@ -1280,10 +1293,15 @@ export class InitCommand {
1280
1293
  statusMessage: '📝 Recording progress...',
1281
1294
  async: false,
1282
1295
  },
1296
+ ],
1297
+ },
1298
+ {
1299
+ matcher: 'Write|Edit',
1300
+ hooks: [
1283
1301
  {
1284
1302
  type: 'command',
1285
- command: 'zhuanspec-hook collect-knowledge',
1286
- statusMessage: '📚 Collecting knowledge...',
1303
+ command: 'zhuanspec-hook collect-knowledge --json',
1304
+ statusMessage: '💡 Checking for knowledge to capture...',
1287
1305
  async: false,
1288
1306
  },
1289
1307
  ],
@@ -1526,6 +1544,31 @@ export class InitCommand {
1526
1544
  console.log(PALETTE.white('Welcome to ZhuanSpec!'));
1527
1545
  console.log();
1528
1546
  }
1547
+ async ensureClaudeHud() {
1548
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(process.env.HOME || '~', '.claude');
1549
+ const hudConfigPath = path.join(configDir, 'plugins', 'claude-hud', 'config.json');
1550
+ if (existsSync(hudConfigPath)) {
1551
+ return;
1552
+ }
1553
+ const spinner = this.startSpinner('正在安装 claude-hud(用于版本信息展示)...');
1554
+ try {
1555
+ execSync('claude plugin install claude-hud', {
1556
+ encoding: 'utf-8',
1557
+ stdio: ['pipe', 'pipe', 'pipe'],
1558
+ timeout: 30000,
1559
+ });
1560
+ spinner.stopAndPersist({
1561
+ symbol: PALETTE.white('▌'),
1562
+ text: PALETTE.white('claude-hud 安装完成'),
1563
+ });
1564
+ }
1565
+ catch {
1566
+ spinner.stopAndPersist({
1567
+ symbol: PALETTE.midGray('▌'),
1568
+ text: PALETTE.midGray('claude-hud 安装失败,可手动执行: claude mcp install claude-hud'),
1569
+ });
1570
+ }
1571
+ }
1529
1572
  startSpinner(text) {
1530
1573
  return ora({
1531
1574
  text,