@zhuan-ai/zhuanspec 2.2.5 → 2.5.0
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/bin/zhuanspec-hook.js +3 -0
- package/dist/cli/hooks.d.ts +14 -0
- package/dist/cli/hooks.js +465 -0
- package/dist/cli/index.js +100 -0
- package/dist/commands/artifact-workflow.js +15 -34
- package/dist/commands/design.d.ts +42 -0
- package/dist/commands/design.js +337 -0
- package/dist/commands/progress.d.ts +38 -0
- package/dist/commands/progress.js +349 -0
- package/dist/commands/review.d.ts +32 -0
- package/dist/commands/review.js +472 -0
- package/dist/commands/validate.d.ts +14 -0
- package/dist/commands/validate.js +161 -10
- package/dist/core/archive.d.ts +1 -0
- package/dist/core/archive.js +45 -3
- package/dist/core/completions/command-registry.js +67 -0
- package/dist/core/configurators/slash/amazon-q.js +32 -2
- package/dist/core/configurators/slash/antigravity.js +8 -2
- package/dist/core/configurators/slash/auggie.js +16 -1
- package/dist/core/configurators/slash/base.js +1 -1
- package/dist/core/configurators/slash/claude.d.ts +4 -0
- package/dist/core/configurators/slash/claude.js +56 -1
- package/dist/core/configurators/slash/cline.js +8 -2
- package/dist/core/configurators/slash/codebuddy.js +22 -1
- package/dist/core/configurators/slash/codex.js +21 -0
- package/dist/core/configurators/slash/costrict.js +15 -0
- package/dist/core/configurators/slash/crush.js +22 -1
- package/dist/core/configurators/slash/cursor.js +22 -1
- package/dist/core/configurators/slash/factory.js +16 -1
- package/dist/core/configurators/slash/gemini.js +8 -2
- package/dist/core/configurators/slash/github-copilot.js +19 -1
- package/dist/core/configurators/slash/iflow.js +22 -1
- package/dist/core/configurators/slash/kilocode.js +4 -1
- package/dist/core/configurators/slash/opencode.js +27 -0
- package/dist/core/configurators/slash/qoder.d.ts +4 -0
- package/dist/core/configurators/slash/qoder.js +59 -1
- package/dist/core/configurators/slash/qwen.js +8 -2
- package/dist/core/configurators/slash/roocode.js +8 -2
- package/dist/core/configurators/slash/windsurf.js +8 -2
- package/dist/core/dashboard/metrics.d.ts +33 -0
- package/dist/core/dashboard/metrics.js +114 -0
- package/dist/core/hooks/collect-knowledge.d.ts +16 -0
- package/dist/core/hooks/collect-knowledge.js +203 -0
- package/dist/core/hooks/context-load-hook.d.ts +25 -0
- package/dist/core/hooks/context-load-hook.js +159 -0
- package/dist/core/hooks/deviation-check.d.ts +27 -0
- package/dist/core/hooks/deviation-check.js +403 -0
- package/dist/core/hooks/deviation-handler.d.ts +43 -0
- package/dist/core/hooks/deviation-handler.js +98 -0
- package/dist/core/hooks/init.d.ts +14 -0
- package/dist/core/hooks/init.js +244 -0
- package/dist/core/hooks/notify-milestone.d.ts +14 -0
- package/dist/core/hooks/notify-milestone.js +170 -0
- package/dist/core/hooks/post-apply.d.ts +29 -0
- package/dist/core/hooks/post-apply.js +173 -0
- package/dist/core/hooks/post-archive.d.ts +7 -0
- package/dist/core/hooks/post-archive.js +208 -0
- package/dist/core/hooks/pre-apply.d.ts +34 -0
- package/dist/core/hooks/pre-apply.js +139 -0
- package/dist/core/hooks/pre-archive.d.ts +7 -0
- package/dist/core/hooks/pre-archive.js +50 -0
- package/dist/core/hooks/record-progress.d.ts +49 -0
- package/dist/core/hooks/record-progress.js +609 -0
- package/dist/core/hooks/review-hooks.d.ts +89 -0
- package/dist/core/hooks/review-hooks.js +345 -0
- package/dist/core/hooks/review-orchestrator.d.ts +40 -0
- package/dist/core/hooks/review-orchestrator.js +146 -0
- package/dist/core/hooks/summarize.d.ts +15 -0
- package/dist/core/hooks/summarize.js +282 -0
- package/dist/core/hooks/user-input-hook.d.ts +25 -0
- package/dist/core/hooks/user-input-hook.js +179 -0
- package/dist/core/init.d.ts +8 -0
- package/dist/core/init.js +251 -23
- package/dist/core/parsers/requirement-blocks.js +13 -10
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +513 -244
- package/dist/core/templates/index.d.ts +1 -0
- package/dist/core/templates/index.js +1 -0
- package/dist/core/templates/skill-templates.js +46 -156
- package/dist/core/templates/slash-command-templates.d.ts +1 -1
- package/dist/core/templates/slash-command-templates.js +352 -21
- package/dist/core/templates/tasks-template.d.ts +7 -0
- package/dist/core/templates/tasks-template.js +121 -25
- package/dist/core/templates/tdd-tasks-template.d.ts +3 -0
- package/dist/core/templates/tdd-tasks-template.js +91 -38
- package/dist/core/templates/test-cases-template.d.ts +41 -0
- package/dist/core/templates/test-cases-template.js +128 -0
- package/dist/core/validation/strict-rules.d.ts +44 -5
- package/dist/core/validation/strict-rules.js +302 -8
- package/dist/core/validation/validator.js +52 -2
- package/dist/core/view.d.ts +1 -0
- package/dist/core/view.js +60 -2
- package/dist/mcp/index.d.ts +28 -0
- package/dist/mcp/index.js +31 -0
- package/dist/utils/file-system.d.ts +1 -0
- package/dist/utils/file-system.js +11 -0
- package/dist/utils/item-discovery.js +24 -2
- package/dist/utils/phase-utils.d.ts +36 -0
- package/dist/utils/phase-utils.js +117 -0
- package/package.json +3 -2
- package/schemas/spec-driven/schema.yaml +45 -31
- package/schemas/spec-driven/templates/spec.md +142 -5
- package/schemas/spec-driven/templates/tasks.md +73 -9
|
@@ -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();
|