@zhuan-ai/zhuanspec 2.2.4 → 2.4.8

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 (103) hide show
  1. package/bin/zhuanspec-hook.js +3 -0
  2. package/dist/cli/hooks.d.ts +14 -0
  3. package/dist/cli/hooks.js +465 -0
  4. package/dist/cli/index.js +100 -0
  5. package/dist/commands/artifact-workflow.js +15 -34
  6. package/dist/commands/design.d.ts +42 -0
  7. package/dist/commands/design.js +337 -0
  8. package/dist/commands/progress.d.ts +32 -0
  9. package/dist/commands/progress.js +278 -0
  10. package/dist/commands/review.d.ts +32 -0
  11. package/dist/commands/review.js +472 -0
  12. package/dist/commands/validate.d.ts +14 -0
  13. package/dist/commands/validate.js +161 -10
  14. package/dist/core/archive.d.ts +1 -0
  15. package/dist/core/archive.js +45 -3
  16. package/dist/core/completions/command-registry.js +67 -0
  17. package/dist/core/configurators/slash/amazon-q.js +32 -2
  18. package/dist/core/configurators/slash/antigravity.js +8 -2
  19. package/dist/core/configurators/slash/auggie.js +16 -1
  20. package/dist/core/configurators/slash/base.js +1 -1
  21. package/dist/core/configurators/slash/claude.d.ts +4 -0
  22. package/dist/core/configurators/slash/claude.js +56 -1
  23. package/dist/core/configurators/slash/cline.js +8 -2
  24. package/dist/core/configurators/slash/codebuddy.js +22 -1
  25. package/dist/core/configurators/slash/codex.js +21 -0
  26. package/dist/core/configurators/slash/costrict.js +15 -0
  27. package/dist/core/configurators/slash/crush.js +22 -1
  28. package/dist/core/configurators/slash/cursor.js +22 -1
  29. package/dist/core/configurators/slash/factory.js +16 -1
  30. package/dist/core/configurators/slash/gemini.js +8 -2
  31. package/dist/core/configurators/slash/github-copilot.js +19 -1
  32. package/dist/core/configurators/slash/iflow.js +22 -1
  33. package/dist/core/configurators/slash/kilocode.js +4 -1
  34. package/dist/core/configurators/slash/opencode.js +27 -0
  35. package/dist/core/configurators/slash/qoder.d.ts +4 -0
  36. package/dist/core/configurators/slash/qoder.js +59 -1
  37. package/dist/core/configurators/slash/qwen.js +8 -2
  38. package/dist/core/configurators/slash/roocode.js +8 -2
  39. package/dist/core/configurators/slash/windsurf.js +8 -2
  40. package/dist/core/dashboard/metrics.d.ts +33 -0
  41. package/dist/core/dashboard/metrics.js +114 -0
  42. package/dist/core/hooks/collect-knowledge.d.ts +16 -0
  43. package/dist/core/hooks/collect-knowledge.js +203 -0
  44. package/dist/core/hooks/context-load-hook.d.ts +25 -0
  45. package/dist/core/hooks/context-load-hook.js +159 -0
  46. package/dist/core/hooks/deviation-check.d.ts +27 -0
  47. package/dist/core/hooks/deviation-check.js +403 -0
  48. package/dist/core/hooks/deviation-handler.d.ts +43 -0
  49. package/dist/core/hooks/deviation-handler.js +98 -0
  50. package/dist/core/hooks/init.d.ts +14 -0
  51. package/dist/core/hooks/init.js +244 -0
  52. package/dist/core/hooks/notify-milestone.d.ts +14 -0
  53. package/dist/core/hooks/notify-milestone.js +170 -0
  54. package/dist/core/hooks/post-apply.d.ts +29 -0
  55. package/dist/core/hooks/post-apply.js +173 -0
  56. package/dist/core/hooks/post-archive.d.ts +7 -0
  57. package/dist/core/hooks/post-archive.js +208 -0
  58. package/dist/core/hooks/pre-apply.d.ts +34 -0
  59. package/dist/core/hooks/pre-apply.js +139 -0
  60. package/dist/core/hooks/pre-archive.d.ts +7 -0
  61. package/dist/core/hooks/pre-archive.js +50 -0
  62. package/dist/core/hooks/record-progress.d.ts +49 -0
  63. package/dist/core/hooks/record-progress.js +494 -0
  64. package/dist/core/hooks/review-hooks.d.ts +89 -0
  65. package/dist/core/hooks/review-hooks.js +345 -0
  66. package/dist/core/hooks/review-orchestrator.d.ts +40 -0
  67. package/dist/core/hooks/review-orchestrator.js +146 -0
  68. package/dist/core/hooks/summarize.d.ts +15 -0
  69. package/dist/core/hooks/summarize.js +282 -0
  70. package/dist/core/hooks/user-input-hook.d.ts +25 -0
  71. package/dist/core/hooks/user-input-hook.js +179 -0
  72. package/dist/core/init.d.ts +8 -0
  73. package/dist/core/init.js +251 -23
  74. package/dist/core/parsers/requirement-blocks.js +13 -10
  75. package/dist/core/templates/agents-template.d.ts +1 -1
  76. package/dist/core/templates/agents-template.js +510 -243
  77. package/dist/core/templates/index.d.ts +1 -0
  78. package/dist/core/templates/index.js +1 -0
  79. package/dist/core/templates/skill-templates.js +46 -152
  80. package/dist/core/templates/slash-command-templates.d.ts +1 -1
  81. package/dist/core/templates/slash-command-templates.js +352 -20
  82. package/dist/core/templates/tasks-template.d.ts +7 -0
  83. package/dist/core/templates/tasks-template.js +130 -24
  84. package/dist/core/templates/tdd-tasks-template.d.ts +3 -0
  85. package/dist/core/templates/tdd-tasks-template.js +91 -38
  86. package/dist/core/templates/test-cases-template.d.ts +41 -0
  87. package/dist/core/templates/test-cases-template.js +128 -0
  88. package/dist/core/validation/strict-rules.d.ts +44 -5
  89. package/dist/core/validation/strict-rules.js +302 -8
  90. package/dist/core/validation/validator.js +52 -2
  91. package/dist/core/view.d.ts +1 -0
  92. package/dist/core/view.js +60 -2
  93. package/dist/mcp/index.d.ts +28 -0
  94. package/dist/mcp/index.js +31 -0
  95. package/dist/utils/file-system.d.ts +1 -0
  96. package/dist/utils/file-system.js +11 -0
  97. package/dist/utils/item-discovery.js +24 -2
  98. package/dist/utils/phase-utils.d.ts +36 -0
  99. package/dist/utils/phase-utils.js +117 -0
  100. package/package.json +22 -23
  101. package/schemas/spec-driven/schema.yaml +45 -31
  102. package/schemas/spec-driven/templates/spec.md +142 -5
  103. package/schemas/spec-driven/templates/tasks.md +73 -9
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../dist/cli/hooks.js';
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ZhuanSpec Hooks CLI Entry Point
4
+ *
5
+ * This CLI provides hook commands for Claude Code integration:
6
+ * - init: SessionStart hook - Initialize ZhuanSpec environment
7
+ * - deviation-check: PreToolUse hook - Check for proposal deviations
8
+ * - record-progress: PostToolUse hook - Record task progress
9
+ * - collect-knowledge: PostToolUse hook - Collect knowledge from interactions
10
+ * - summarize: Stop hook - Summarize session progress
11
+ * - review-*: Review phase hooks - Check skill outputs and control fix loops
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1,465 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ZhuanSpec Hooks CLI Entry Point
4
+ *
5
+ * This CLI provides hook commands for Claude Code integration:
6
+ * - init: SessionStart hook - Initialize ZhuanSpec environment
7
+ * - deviation-check: PreToolUse hook - Check for proposal deviations
8
+ * - record-progress: PostToolUse hook - Record task progress
9
+ * - collect-knowledge: PostToolUse hook - Collect knowledge from interactions
10
+ * - summarize: Stop hook - Summarize session progress
11
+ * - review-*: Review phase hooks - Check skill outputs and control fix loops
12
+ */
13
+ import { Command } from 'commander';
14
+ import { initHook } from '../core/hooks/init.js';
15
+ import { deviationCheckHook } from '../core/hooks/deviation-check.js';
16
+ import { recordProgressHook, initializeProgress } from '../core/hooks/record-progress.js';
17
+ import { collectKnowledgeHook } from '../core/hooks/collect-knowledge.js';
18
+ import { summarizeHook } from '../core/hooks/summarize.js';
19
+ import { preArchiveHook } from '../core/hooks/pre-archive.js';
20
+ import { postArchiveHook } from '../core/hooks/post-archive.js';
21
+ import { preApplyHook, checkPreApplyConditions } from '../core/hooks/pre-apply.js';
22
+ import { postApplyHook, executePostApply } from '../core/hooks/post-apply.js';
23
+ import { codeReviewResultCheck, unitTestResultCheck, specConsistencyResultCheck, generateReviewReport, } from '../core/hooks/review-hooks.js';
24
+ import path from 'path';
25
+ import { FileSystemUtils } from '../utils/file-system.js';
26
+ const program = new Command();
27
+ program
28
+ .name('zhuanspec-hook')
29
+ .description('ZhuanSpec hooks for Claude Code integration')
30
+ .version('1.0.0');
31
+ // SessionStart hook
32
+ program
33
+ .command('init')
34
+ .description('SessionStart hook - Initialize ZhuanSpec environment')
35
+ .option('--json', 'Output as JSON for Claude Code consumption')
36
+ .action(async (options) => {
37
+ await initHook(options);
38
+ });
39
+ // PreToolUse hook
40
+ program
41
+ .command('deviation-check')
42
+ .description('PreToolUse hook - Check for proposal deviations before Write/Edit')
43
+ .option('--json', 'Output as JSON for Claude Code consumption')
44
+ .option('--file <path>', 'File being modified')
45
+ .option('--tool <name>', 'Tool being used (Write/Edit)')
46
+ .option('--trigger <type>', 'Trigger type: pre-tool|post-prompt|resume')
47
+ .option('--prompt <text>', 'Prompt content for post-prompt checks')
48
+ .action(async (options) => {
49
+ await deviationCheckHook(options);
50
+ });
51
+ // PostToolUse hook - record progress
52
+ program
53
+ .command('record-progress')
54
+ .description('PostToolUse hook - Record task progress after Write/Edit')
55
+ .option('--json', 'Output as JSON for Claude Code consumption')
56
+ .option('--file <path>', 'File that was modified')
57
+ .option('--tool <name>', 'Tool that was used (Write/Edit)')
58
+ .option('--success <boolean>', 'Whether the operation succeeded')
59
+ .action(async (options) => {
60
+ await recordProgressHook(options);
61
+ });
62
+ // Initialize progress - create progress.json at Propose phase start
63
+ program
64
+ .command('init-progress')
65
+ .description('Initialize progress.json for a change at Propose phase start')
66
+ .option('--change <id>', 'Change ID')
67
+ .action(async (options) => {
68
+ if (!options.change) {
69
+ console.error('Error: --change <id> is required');
70
+ process.exit(1);
71
+ }
72
+ await initializeProgress(options.change);
73
+ console.log(`✓ Progress initialized for change: ${options.change}`);
74
+ });
75
+ // PostToolUse hook - collect knowledge
76
+ program
77
+ .command('collect-knowledge')
78
+ .description('PostToolUse hook - Collect knowledge from interactions')
79
+ .option('--json', 'Output as JSON for Claude Code consumption')
80
+ .option('--file <path>', 'File that was modified')
81
+ .option('--content <string>', 'Content that was written')
82
+ .action(async (options) => {
83
+ await collectKnowledgeHook(options);
84
+ });
85
+ // Stop hook
86
+ program
87
+ .command('summarize')
88
+ .description('Stop hook - Summarize session progress')
89
+ .option('--json', 'Output as JSON for Claude Code consumption')
90
+ .action(async (options) => {
91
+ await summarizeHook(options);
92
+ });
93
+ program
94
+ .command('pre-archive')
95
+ .description('Pre-archive quality gate')
96
+ .option('--json', 'Output as JSON for Claude Code consumption')
97
+ .option('--change <id>', 'Change ID to validate before archive')
98
+ .action(async (options) => {
99
+ await preArchiveHook(options);
100
+ });
101
+ program
102
+ .command('post-archive')
103
+ .description('Post-archive knowledge update')
104
+ .option('--json', 'Output as JSON for Claude Code consumption')
105
+ .option('--archived <id>', 'Archived change ID')
106
+ .action(async (options) => {
107
+ await postArchiveHook(options);
108
+ });
109
+ // Pre-Apply hook - quality gate before entering Apply phase
110
+ program
111
+ .command('pre-apply')
112
+ .description('Pre-Apply quality gate - validate change readiness before Apply phase')
113
+ .option('--json', 'Output as JSON for Claude Code consumption')
114
+ .option('--change <id>', 'Change ID to validate')
115
+ .action(async (options) => {
116
+ await preApplyHook(options);
117
+ });
118
+ // Post-Apply hook - deviation detection and auto-trigger review
119
+ program
120
+ .command('post-apply')
121
+ .description('Post-Apply hook - detect deviations and trigger automatic review')
122
+ .option('--json', 'Output as JSON for Claude Code consumption')
123
+ .option('--change <id>', 'Change ID that was applied')
124
+ .action(async (options) => {
125
+ await postApplyHook(options);
126
+ });
127
+ // Phase Transition Check - Unified phase transition quality gate
128
+ program
129
+ .command('phase-transition-check')
130
+ .description('Check phase transition conditions before moving to next phase')
131
+ .option('--json', 'Output as JSON for Claude Code consumption')
132
+ .option('--from <phase>', 'Current phase: propose|apply|review')
133
+ .option('--to <phase>', 'Target phase: apply|review|archive')
134
+ .option('--change <id>', 'Change ID being transitioned')
135
+ .action(async (options) => {
136
+ const result = await runPhaseTransitionCheck(options);
137
+ if (options.json) {
138
+ console.log(JSON.stringify(result));
139
+ }
140
+ else {
141
+ console.log(result.systemMessage || '');
142
+ if (!result.continue) {
143
+ process.exit(1);
144
+ }
145
+ }
146
+ });
147
+ // ============================================================
148
+ // Review Phase Hooks - Check skill outputs and control fix loops
149
+ // ============================================================
150
+ // Code Review Result Check - Hook after code-review-expert skill
151
+ program
152
+ .command('review-code-check')
153
+ .description('Check code-review-expert skill output and decide fix loop')
154
+ .option('--json', 'Output as JSON for Claude Code consumption')
155
+ .option('--change <id>', 'Change ID being reviewed')
156
+ .option('--output <json>', 'Skill output JSON (passed via stdin or option)')
157
+ .action(async (options) => {
158
+ const result = await codeReviewResultCheck(options.change || '', options.output ? JSON.parse(options.output) : undefined);
159
+ if (options.json) {
160
+ console.log(JSON.stringify(result));
161
+ }
162
+ else {
163
+ console.log(`Code Review: ${result.pass ? '✅ PASS' : '❌ FAIL'}`);
164
+ if (result.fixPrompt) {
165
+ console.log(result.fixPrompt);
166
+ }
167
+ if (!result.pass && result.nextAction === 'stop') {
168
+ process.exit(1);
169
+ }
170
+ }
171
+ });
172
+ // Unit Test Result Check - Hook after generate-mockito-unit-test-skill
173
+ program
174
+ .command('review-unit-test-check')
175
+ .description('Check unit test skill output and decide fix loop')
176
+ .option('--json', 'Output as JSON for Claude Code consumption')
177
+ .option('--change <id>', 'Change ID being reviewed')
178
+ .option('--output <json>', 'Skill output JSON')
179
+ .action(async (options) => {
180
+ const result = await unitTestResultCheck(options.change || '', options.output ? JSON.parse(options.output) : undefined);
181
+ if (options.json) {
182
+ console.log(JSON.stringify(result));
183
+ }
184
+ else {
185
+ console.log(`Unit Test: ${result.pass ? '✅ PASS' : '❌ FAIL'}`);
186
+ console.log(`Coverage: ${result.metrics.coverage || 0}%`);
187
+ if (result.fixPrompt) {
188
+ console.log(result.fixPrompt);
189
+ }
190
+ if (!result.pass && result.nextAction === 'stop') {
191
+ process.exit(1);
192
+ }
193
+ }
194
+ });
195
+ // Spec-Code Consistency Check - Hook after spec-code-consistency check
196
+ program
197
+ .command('review-spec-check')
198
+ .description('Check spec-code consistency output and decide fix loop')
199
+ .option('--json', 'Output as JSON for Claude Code consumption')
200
+ .option('--change <id>', 'Change ID being reviewed')
201
+ .option('--output <json>', 'Skill output JSON')
202
+ .action(async (options) => {
203
+ const result = await specConsistencyResultCheck(options.change || '', options.output ? JSON.parse(options.output) : undefined);
204
+ if (options.json) {
205
+ console.log(JSON.stringify(result));
206
+ }
207
+ else {
208
+ console.log(`Spec-Code Consistency: ${result.pass ? '✅ PASS' : '❌ FAIL'}`);
209
+ console.log(`Consistency Rate: ${result.metrics.consistencyRate || 0}%`);
210
+ if (result.fixPrompt) {
211
+ console.log(result.fixPrompt);
212
+ }
213
+ if (!result.pass && result.nextAction === 'stop') {
214
+ process.exit(1);
215
+ }
216
+ }
217
+ });
218
+ // Generate Review Report - Combine all three check results
219
+ program
220
+ .command('review-report')
221
+ .description('Generate review report from all three check results')
222
+ .option('--json', 'Output as JSON for Claude Code consumption')
223
+ .option('--change <id>', 'Change ID being reviewed')
224
+ .action(async (options) => {
225
+ const changeId = options.change || '';
226
+ // Run all three checks
227
+ const codeReviewResult = await codeReviewResultCheck(changeId);
228
+ const unitTestResult = await unitTestResultCheck(changeId);
229
+ const specConsistencyResult = await specConsistencyResultCheck(changeId);
230
+ // Generate report
231
+ const report = await generateReviewReport(changeId, {
232
+ codeReview: codeReviewResult,
233
+ unitTest: unitTestResult,
234
+ specConsistency: specConsistencyResult,
235
+ });
236
+ if (options.json) {
237
+ console.log(JSON.stringify({
238
+ changeId,
239
+ overallPass: codeReviewResult.pass && unitTestResult.pass && specConsistencyResult.pass,
240
+ codeReview: codeReviewResult,
241
+ unitTest: unitTestResult,
242
+ specConsistency: specConsistencyResult,
243
+ reportPath: `zhuanspec/changes/${changeId}/review-report.md`,
244
+ }));
245
+ }
246
+ else {
247
+ console.log(report);
248
+ const overallPass = codeReviewResult.pass && unitTestResult.pass && specConsistencyResult.pass;
249
+ if (!overallPass) {
250
+ console.log('\n❌ Review NOT passed. Please fix issues before archiving.');
251
+ process.exit(1);
252
+ }
253
+ else {
254
+ console.log('\n✅ Review passed. Ready for Phase 4 (Archive).');
255
+ }
256
+ }
257
+ });
258
+ // Diagnostic command - inspect hook registration and trigger map
259
+ program
260
+ .command('status')
261
+ .description('Show hook registration status and trigger mapping')
262
+ .option('--json', 'Output as JSON')
263
+ .action(async (options) => {
264
+ const summary = await collectHookStatus();
265
+ if (options.json) {
266
+ console.log(JSON.stringify(summary, null, 2));
267
+ return;
268
+ }
269
+ console.log(`settings: ${summary.settingsPath}`);
270
+ console.log(`settings exists: ${summary.settingsExists ? 'yes' : 'no'}`);
271
+ console.log('\n直接注册(Claude 生命周期 hooks):');
272
+ if (summary.registeredLifecycleHooks.length === 0) {
273
+ console.log('- 无');
274
+ }
275
+ else {
276
+ for (const item of summary.registeredLifecycleHooks) {
277
+ console.log(`- ${item.phase}: ${item.command}`);
278
+ }
279
+ }
280
+ console.log('\n重复注册检测:');
281
+ if (summary.duplicates.length === 0) {
282
+ console.log('- 未发现重复');
283
+ }
284
+ else {
285
+ for (const item of summary.duplicates) {
286
+ console.log(`- ${item.phase}: ${item.command} (x${item.count})`);
287
+ }
288
+ }
289
+ console.log('\n间接触发(阶段门禁与 Review 链路):');
290
+ for (const item of summary.phaseCommands) {
291
+ console.log(`- ${item.command}: ${item.trigger}`);
292
+ }
293
+ });
294
+ /**
295
+ * Phase Transition Check - Unified function for phase transition validation
296
+ */
297
+ async function runPhaseTransitionCheck(options) {
298
+ const cwd = process.cwd();
299
+ const changeId = options.change || process.env.ZHUANSPEC_CHANGE_ID || '';
300
+ const fromPhase = options.from || process.env.ZHUANSPEC_PHASE || 'idle';
301
+ const toPhase = options.to || '';
302
+ if (!changeId) {
303
+ return {
304
+ continue: false,
305
+ systemMessage: 'Phase transition check skipped: no change ID provided',
306
+ };
307
+ }
308
+ const changeDir = path.join(cwd, 'zhuanspec', 'changes', changeId);
309
+ // Determine target phase if not specified
310
+ let targetPhase = toPhase;
311
+ if (!targetPhase) {
312
+ switch (fromPhase) {
313
+ case 'propose':
314
+ targetPhase = 'apply';
315
+ break;
316
+ case 'apply':
317
+ targetPhase = 'review';
318
+ break;
319
+ case 'review':
320
+ targetPhase = 'archive';
321
+ break;
322
+ default:
323
+ targetPhase = 'apply';
324
+ }
325
+ }
326
+ // Execute appropriate pre-check based on target phase
327
+ switch (targetPhase) {
328
+ case 'apply':
329
+ // Propose → Apply: Run pre-apply checks
330
+ const preApplyResult = await checkPreApplyConditions(changeDir);
331
+ if (preApplyResult.continue) {
332
+ return {
333
+ continue: true,
334
+ systemMessage: `✓ Phase transition approved: ${fromPhase} → ${targetPhase}\n\nAll pre-apply checks passed. You may now proceed to Apply phase.`,
335
+ checks: preApplyResult.checks,
336
+ };
337
+ }
338
+ return {
339
+ continue: false,
340
+ systemMessage: `✗ Phase transition blocked: ${fromPhase} → ${targetPhase}\n\n${preApplyResult.systemMessage}`,
341
+ checks: preApplyResult.checks,
342
+ };
343
+ case 'review':
344
+ // Apply → Review: Run post-apply deviation check
345
+ const postApplyResult = await executePostApply(changeDir, cwd);
346
+ if (postApplyResult.hookSpecificOutput?.deviationDetected) {
347
+ return {
348
+ continue: false,
349
+ systemMessage: `✗ Phase transition blocked: ${fromPhase} → ${targetPhase}\n\nDeviation detected: ${postApplyResult.hookSpecificOutput.deviationFiles?.length || 0} files outside proposal scope.\nPlease update proposal (Reverse Sync) before proceeding to Review.`,
350
+ };
351
+ }
352
+ return {
353
+ continue: true,
354
+ systemMessage: `✓ Phase transition approved: ${fromPhase} → ${targetPhase}\n\nNo deviations detected. Review phase can proceed.`,
355
+ };
356
+ case 'archive':
357
+ // Review → Archive: Check for review completion
358
+ const metricsDir = path.join(changeDir, 'metrics');
359
+ const reviewPath = path.join(metricsDir, 'review.json');
360
+ const testsPath = path.join(metricsDir, 'tests.json');
361
+ const reportPath = path.join(changeDir, 'review-report.md');
362
+ const checks = {
363
+ reviewCompleted: false,
364
+ testsCompleted: false,
365
+ reportExists: false,
366
+ };
367
+ checks.reviewCompleted = await FileSystemUtils.fileExists(reviewPath);
368
+ checks.testsCompleted = await FileSystemUtils.fileExists(testsPath);
369
+ checks.reportExists = await FileSystemUtils.fileExists(reportPath);
370
+ if (!checks.reviewCompleted || !checks.testsCompleted || !checks.reportExists) {
371
+ const missing = [];
372
+ if (!checks.reviewCompleted)
373
+ missing.push('review.json');
374
+ if (!checks.testsCompleted)
375
+ missing.push('tests.json');
376
+ if (!checks.reportExists)
377
+ missing.push('review-report.md');
378
+ return {
379
+ continue: false,
380
+ systemMessage: `✗ Phase transition blocked: ${fromPhase} → ${targetPhase}\n\nMissing review artifacts:\n${missing.map(m => `- ${m}`).join('\n')}\n\nPlease complete Review phase before archiving.`,
381
+ checks,
382
+ };
383
+ }
384
+ return {
385
+ continue: true,
386
+ systemMessage: `✓ Phase transition approved: ${fromPhase} → ${targetPhase}\n\nAll review artifacts present. Archive phase can proceed.`,
387
+ checks,
388
+ };
389
+ default:
390
+ return {
391
+ continue: false,
392
+ systemMessage: `Unknown target phase: ${targetPhase}`,
393
+ };
394
+ }
395
+ }
396
+ async function collectHookStatus() {
397
+ const cwd = process.cwd();
398
+ const settingsPath = path.join(cwd, '.claude', 'settings.json');
399
+ const settingsExists = await FileSystemUtils.fileExists(settingsPath);
400
+ const registeredLifecycleHooks = [];
401
+ const duplicates = [];
402
+ if (settingsExists) {
403
+ try {
404
+ const content = await FileSystemUtils.readFile(settingsPath);
405
+ const parsed = JSON.parse(content);
406
+ const hooks = parsed.hooks ?? {};
407
+ for (const [phase, phaseConfigs] of Object.entries(hooks)) {
408
+ if (!Array.isArray(phaseConfigs))
409
+ continue;
410
+ const commandCounts = new Map();
411
+ for (const phaseConfig of phaseConfigs) {
412
+ const hooksArray = phaseConfig.hooks;
413
+ if (!Array.isArray(hooksArray))
414
+ continue;
415
+ for (const hookItem of hooksArray) {
416
+ const command = hookItem.command;
417
+ if (typeof command !== 'string')
418
+ continue;
419
+ registeredLifecycleHooks.push({ phase, command });
420
+ commandCounts.set(command, (commandCounts.get(command) ?? 0) + 1);
421
+ }
422
+ }
423
+ for (const [command, count] of commandCounts.entries()) {
424
+ if (count > 1) {
425
+ duplicates.push({ phase, command, count });
426
+ }
427
+ }
428
+ }
429
+ }
430
+ catch {
431
+ // Keep defaults; command should still provide guidance.
432
+ }
433
+ }
434
+ const phaseCommands = [
435
+ {
436
+ command: 'zhuanspec-hook pre-apply',
437
+ trigger: '由 deviation-check 内部调用 checkPreApplyConditions(Propose → Apply 前门禁)',
438
+ },
439
+ {
440
+ command: 'zhuanspec-hook post-apply',
441
+ trigger: '由 summarize/executePostApply 间接触发(Apply 完成后偏差检测与 review 触发)',
442
+ },
443
+ {
444
+ command: 'zhuanspec-hook review-code-check',
445
+ trigger: '由 zhuanspec review 结果链路调用(代码审查结果判定)',
446
+ },
447
+ {
448
+ command: 'zhuanspec-hook review-unit-test-check',
449
+ trigger: '由 zhuanspec review 结果链路调用(单测结果判定)',
450
+ },
451
+ {
452
+ command: 'zhuanspec-hook review-spec-check',
453
+ trigger: '由 zhuanspec review 结果链路调用(Spec/Code 一致性判定)',
454
+ },
455
+ ];
456
+ return {
457
+ settingsPath,
458
+ settingsExists,
459
+ registeredLifecycleHooks,
460
+ duplicates,
461
+ phaseCommands,
462
+ };
463
+ }
464
+ program.parse();
465
+ //# sourceMappingURL=hooks.js.map
package/dist/cli/index.js CHANGED
@@ -13,9 +13,12 @@ import { ChangeCommand } from '../commands/change.js';
13
13
  import { ValidateCommand } from '../commands/validate.js';
14
14
  import { ShowCommand } from '../commands/show.js';
15
15
  import { CompletionCommand } from '../commands/completion.js';
16
+ import { ReviewCommand } from '../commands/review.js';
17
+ import { ProgressCommand } from '../commands/progress.js';
16
18
  import { discoverSkills, formatSkillsList } from '../core/skill-discovery.js';
17
19
  import { registerConfigCommand } from '../commands/config.js';
18
20
  import { registerArtifactWorkflowCommands } from '../commands/artifact-workflow.js';
21
+ import { DesignCommand } from '../commands/design.js';
19
22
  const program = new Command();
20
23
  const require = createRequire(import.meta.url);
21
24
  const { version } = require('../../package.json');
@@ -89,6 +92,36 @@ program
89
92
  process.exit(1);
90
93
  }
91
94
  });
95
+ const registerTechDesignCommand = (commandName, deprecatedAlias = false) => program
96
+ .command(`${commandName} [change-name]`)
97
+ .description(deprecatedAlias
98
+ ? 'DEPRECATED alias of techDesign. Prepare technical design generation request.'
99
+ : 'Prepare technical design generation request (techDesign phase, independent before proposal by default)')
100
+ .option('--change <change-id>', 'Optionally bind generated design source to a change')
101
+ .option('--desc <text>', 'Requirement description used for technical design generation')
102
+ .option('--dashen-page-id <id>', 'Dashen pageId used as requirement source')
103
+ .option('--dashen-url <url>', 'Dashen document URL used as requirement source')
104
+ .option('--output <path>', 'Output path of generated request markdown (default: doc/*技术方案生成请求.md)')
105
+ .option('--init-template', 'Generate empty design.md template inside a change (legacy mode)')
106
+ .option('--force', 'Overwrite existing output file or bound design.md')
107
+ .option('--validate', 'Validate existing design.md for required sections')
108
+ .option('--auto-fix', 'Auto-fix missing sections when validation fails (use with --validate)')
109
+ .action(async (changeName, options) => {
110
+ try {
111
+ if (deprecatedAlias) {
112
+ console.error('Warning: "zhuanspec design" is deprecated. Please use "zhuanspec techDesign".');
113
+ }
114
+ const designCommand = new DesignCommand();
115
+ await designCommand.execute(changeName, options);
116
+ }
117
+ catch (error) {
118
+ console.log();
119
+ ora().fail(`Error: ${error.message}`);
120
+ process.exit(1);
121
+ }
122
+ });
123
+ registerTechDesignCommand('techDesign');
124
+ registerTechDesignCommand('design', true);
92
125
  program
93
126
  .command('list')
94
127
  .description('List items (changes by default). Use --specs to list specs.')
@@ -183,6 +216,72 @@ changeCmd
183
216
  process.exitCode = 1;
184
217
  }
185
218
  });
219
+ program
220
+ .command('review [change-name]')
221
+ .description('Run ZhuanSpec review and unit test verification')
222
+ .option('--json', 'Output review report as JSON')
223
+ .option('--test-command <command>', 'Test command to execute (default: npm test)')
224
+ .option('--coverage-threshold <n>', 'Coverage threshold percentage (default: 80)')
225
+ .action(async (changeName, options) => {
226
+ try {
227
+ const reviewCommand = new ReviewCommand();
228
+ await reviewCommand.execute(changeName, options);
229
+ if (typeof process.exitCode === 'number' && process.exitCode !== 0) {
230
+ process.exit(process.exitCode);
231
+ }
232
+ }
233
+ catch (error) {
234
+ console.log();
235
+ ora().fail(`Error: ${error.message}`);
236
+ process.exit(1);
237
+ }
238
+ });
239
+ const progressCmd = program
240
+ .command('progress')
241
+ .description('Display current execution status and progress')
242
+ .option('--json', 'Output as JSON for programmatic use')
243
+ .action(async (options) => {
244
+ try {
245
+ const progressCommand = new ProgressCommand();
246
+ await progressCommand.execute(options);
247
+ }
248
+ catch (error) {
249
+ console.log();
250
+ ora().fail(`Error: ${error.message}`);
251
+ process.exit(1);
252
+ }
253
+ });
254
+ // Progress show subcommand
255
+ progressCmd
256
+ .command('show <change-id>')
257
+ .description('Show progress for a specific change')
258
+ .option('--json', 'Output as JSON for programmatic use')
259
+ .action(async (changeId, options) => {
260
+ try {
261
+ const progressCommand = new ProgressCommand();
262
+ await progressCommand.show(changeId, options);
263
+ }
264
+ catch (error) {
265
+ console.log();
266
+ ora().fail(`Error: ${error.message}`);
267
+ process.exit(1);
268
+ }
269
+ });
270
+ // Progress set-phase subcommand
271
+ progressCmd
272
+ .command('set-phase <change-id> <phase>')
273
+ .description('Set phase for a change (e.g., propose, apply, review, archive)')
274
+ .action(async (changeId, phase) => {
275
+ try {
276
+ const progressCommand = new ProgressCommand();
277
+ await progressCommand.setPhase(changeId, phase);
278
+ }
279
+ catch (error) {
280
+ console.log();
281
+ ora().fail(`Error: ${error.message}`);
282
+ process.exit(1);
283
+ }
284
+ });
186
285
  program
187
286
  .command('archive [change-name]')
188
287
  .description('Archive a completed change and update main specs')
@@ -252,6 +351,7 @@ program
252
351
  .option('--json', 'Output validation results as JSON')
253
352
  .option('--concurrency <n>', 'Max concurrent validations (defaults to env OPENSPEC_CONCURRENCY or 6)')
254
353
  .option('--no-interactive', 'Disable interactive prompts')
354
+ .option('--auto-fix', 'Auto-fix validation errors (max 3 attempts)')
255
355
  .action(async (itemName, options) => {
256
356
  try {
257
357
  const validateCommand = new ValidateCommand();