@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.
- 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 +32 -0
- package/dist/commands/progress.js +278 -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 +494 -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 +510 -243
- 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 -152
- package/dist/core/templates/slash-command-templates.d.ts +1 -1
- package/dist/core/templates/slash-command-templates.js +352 -20
- package/dist/core/templates/tasks-template.d.ts +7 -0
- package/dist/core/templates/tasks-template.js +130 -24
- 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 +22 -23
- 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,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreToolUse Hook - Deviation Check
|
|
3
|
+
*
|
|
4
|
+
* This hook runs before Write/Edit operations to check if the modification
|
|
5
|
+
* is within the approved proposal scope. It implements the "Reverse Sync" iron law.
|
|
6
|
+
*
|
|
7
|
+
* Logic:
|
|
8
|
+
* 1. If ZHUANSPEC_PHASE is 'idle' or no change bound → allow (normal workflow)
|
|
9
|
+
* 2. If ZHUANSPEC_PHASE is 'propose' → block (no code changes in propose phase)
|
|
10
|
+
* 3. If ZHUANSPEC_PHASE is 'apply' → check if file is in proposal scope:
|
|
11
|
+
* - In scope → allow
|
|
12
|
+
* - Out of scope → block with deviation handling options
|
|
13
|
+
*/
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
17
|
+
import { checkPreApplyConditions } from './pre-apply.js';
|
|
18
|
+
import { recordHookTrigger } from './record-progress.js';
|
|
19
|
+
// Pitfall keywords for knowledge collection
|
|
20
|
+
const PITFALL_KEYWORDS = [
|
|
21
|
+
'不对', '错了', '踩坑', '坑', '失败', '问题', 'bug',
|
|
22
|
+
'错误', '异常', '不工作', '不生效', '没想到',
|
|
23
|
+
'注意', '小心', '避免', '陷阱', '教训',
|
|
24
|
+
'wrong', 'error', 'fail', 'bug', 'issue', 'pitfall',
|
|
25
|
+
];
|
|
26
|
+
// Best practice keywords for knowledge collection
|
|
27
|
+
const BEST_PRACTICE_KEYWORDS = [
|
|
28
|
+
'最佳实践', '推荐做法', '最佳做法', '建议', '经验',
|
|
29
|
+
'成功经验', '最佳方案', '最佳选择', '正确做法',
|
|
30
|
+
'best practice', 'recommended', 'recommendation',
|
|
31
|
+
];
|
|
32
|
+
// Implicit convention keywords for knowledge collection
|
|
33
|
+
const IMPLICIT_CONVENTION_KEYWORDS = [
|
|
34
|
+
'隐式约定', '默认规则', '约定', '习惯', '惯例',
|
|
35
|
+
'默认行为', '默认配置', '默认值', '隐式规则',
|
|
36
|
+
'implicit convention', 'default behavior', 'convention',
|
|
37
|
+
];
|
|
38
|
+
// User correction keywords for deviation detection trigger
|
|
39
|
+
const USER_CORRECTION_KEYWORDS = [
|
|
40
|
+
'不对', '错了', '这里应该是', '应该是', '不是这样',
|
|
41
|
+
'这里不对', '改一下', '需要修改', '修改提案',
|
|
42
|
+
'wrong', 'should be', 'not right', 'need to fix',
|
|
43
|
+
];
|
|
44
|
+
export async function deviationCheckHook(options) {
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
const toolName = options.tool || 'Write';
|
|
47
|
+
// Read stdin for Claude Code hook input
|
|
48
|
+
let stdinData = {};
|
|
49
|
+
try {
|
|
50
|
+
const stdinContent = await readStdin();
|
|
51
|
+
if (stdinContent) {
|
|
52
|
+
stdinData = JSON.parse(stdinContent);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Ignore parse errors
|
|
57
|
+
}
|
|
58
|
+
// Get file path from stdin or options
|
|
59
|
+
const filePath = stdinData.tool_input?.file_path
|
|
60
|
+
|| options.file
|
|
61
|
+
|| '';
|
|
62
|
+
const promptText = stdinData?.prompt
|
|
63
|
+
|| options.prompt
|
|
64
|
+
|| '';
|
|
65
|
+
const trigger = options.trigger || 'pre-tool';
|
|
66
|
+
const output = await runDeviationCheck(filePath, trigger, promptText, options);
|
|
67
|
+
const durationMs = Date.now() - startTime;
|
|
68
|
+
// Record hook trigger
|
|
69
|
+
await recordHookTrigger('deviation-check', 'PreToolUse', output.continue, {
|
|
70
|
+
toolName,
|
|
71
|
+
filePath,
|
|
72
|
+
message: output.systemMessage,
|
|
73
|
+
durationMs,
|
|
74
|
+
});
|
|
75
|
+
if (options.json) {
|
|
76
|
+
console.log(JSON.stringify(output));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (output.systemMessage) {
|
|
80
|
+
console.log(output.systemMessage);
|
|
81
|
+
}
|
|
82
|
+
if (!output.continue) {
|
|
83
|
+
// Exit with non-zero to block the operation
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function readStdin() {
|
|
89
|
+
return new Promise((resolve) => {
|
|
90
|
+
let data = '';
|
|
91
|
+
process.stdin.setEncoding('utf8');
|
|
92
|
+
process.stdin.on('data', chunk => data += chunk);
|
|
93
|
+
process.stdin.on('end', () => resolve(data));
|
|
94
|
+
process.stdin.on('error', () => resolve(null));
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function runDeviationCheck(filePath, trigger, promptText, _options) {
|
|
98
|
+
const phase = process.env.ZHUANSPEC_PHASE || 'idle';
|
|
99
|
+
const changeId = process.env.ZHUANSPEC_CHANGE_ID || '';
|
|
100
|
+
const currentTask = process.env.ZHUANSPEC_CURRENT_TASK || '';
|
|
101
|
+
// No change bound or idle phase → allow normal workflow
|
|
102
|
+
if (!changeId || phase === 'idle') {
|
|
103
|
+
return {
|
|
104
|
+
continue: true,
|
|
105
|
+
systemMessage: '✓ No active ZhuanSpec change, normal workflow allowed',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const cwd = process.cwd();
|
|
109
|
+
const zhuanspecDir = path.join(cwd, 'zhuanspec');
|
|
110
|
+
const changeDir = path.join(zhuanspecDir, 'changes', changeId);
|
|
111
|
+
const proposalPath = path.join(changeDir, 'proposal.md');
|
|
112
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
113
|
+
// Resume check: when session is resumed in apply phase, do a one-time deviation scan.
|
|
114
|
+
if (phase === 'apply' && trigger === 'resume') {
|
|
115
|
+
const estimatedFiles = await parseEstimatedChanges(proposalPath);
|
|
116
|
+
const gitDiffFiles = await parseGitChangedFiles(cwd);
|
|
117
|
+
const outOfScope = gitDiffFiles.filter((file) => {
|
|
118
|
+
if (file.includes('zhuanspec/'))
|
|
119
|
+
return false;
|
|
120
|
+
const normalized = normalizePath(file);
|
|
121
|
+
return !estimatedFiles.map(normalizePath).some((scope) => normalized.includes(scope) || scope.includes(normalized));
|
|
122
|
+
});
|
|
123
|
+
if (outOfScope.length > 0) {
|
|
124
|
+
return {
|
|
125
|
+
continue: false,
|
|
126
|
+
stopReason: 'Resume deviation detected',
|
|
127
|
+
systemMessage: `⚠️ RESUME DEVIATION: 检测到 ${outOfScope.length} 个提案范围外改动,请先执行 Reverse Sync(更新 proposal/tasks)再继续 Apply。`,
|
|
128
|
+
hookSpecificOutput: {
|
|
129
|
+
additionalContext: `Out-of-scope files: ${outOfScope.join(', ')}`,
|
|
130
|
+
options: ['更新提案后继续实施', '取消本次恢复并重新评估'],
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
continue: true,
|
|
136
|
+
systemMessage: '✓ Resume deviation check passed',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Post-prompt check: catch "additional request after apply/interruption" and force reverse-sync first.
|
|
140
|
+
if (phase === 'apply' && trigger === 'post-prompt') {
|
|
141
|
+
// Check for user correction keywords
|
|
142
|
+
const correctionKeywordFound = USER_CORRECTION_KEYWORDS.some(keyword => promptText.toLowerCase().includes(keyword.toLowerCase()));
|
|
143
|
+
if (correctionKeywordFound) {
|
|
144
|
+
// User correction detected - trigger deviation handling
|
|
145
|
+
return {
|
|
146
|
+
continue: false,
|
|
147
|
+
stopReason: 'User correction detected in Apply phase',
|
|
148
|
+
systemMessage: `⚠️ DEVIATION TRIGGER: 检测到用户纠正关键词,当前阶段为 ${phase}
|
|
149
|
+
请按偏离修正流程处理:
|
|
150
|
+
1. 先修改 spec 文件
|
|
151
|
+
2. 用户确认后再修改代码
|
|
152
|
+
3. 用户确认无误后触发知识沉淀`,
|
|
153
|
+
hookSpecificOutput: {
|
|
154
|
+
additionalContext: `User input: ${promptText.slice(0, 200)}`,
|
|
155
|
+
options: [
|
|
156
|
+
'修改 Spec 文件',
|
|
157
|
+
'直接修改代码(跳过 Spec)',
|
|
158
|
+
'取消修改',
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const extraRequestPattern = /(另外|额外|顺便|再加|同时|also|additionally|by the way|one more)/i;
|
|
164
|
+
const editIntentPattern = /(修改|改|新增|删除|重构|optimi[sz]e|add|change|edit|update|refactor|remove)/i;
|
|
165
|
+
if (extraRequestPattern.test(promptText) && editIntentPattern.test(promptText)) {
|
|
166
|
+
return {
|
|
167
|
+
continue: false,
|
|
168
|
+
stopReason: 'Post-prompt additional requirement detected',
|
|
169
|
+
systemMessage: '⚠️ 检测到 Apply 阶段追加需求,请先更新 proposal/tasks(Reverse Sync)后再实施。',
|
|
170
|
+
hookSpecificOutput: {
|
|
171
|
+
additionalContext: `Prompt: ${promptText.slice(0, 300)}`,
|
|
172
|
+
options: ['更新提案后继续实施', '标记为Bug修复豁免', '取消追加需求'],
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
continue: true,
|
|
178
|
+
systemMessage: '✓ Post-prompt deviation check passed',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// Propose phase → block all code modifications (Iron Law 1: No Spec No Code)
|
|
182
|
+
// But check if we can transition to Apply phase
|
|
183
|
+
if (phase === 'propose') {
|
|
184
|
+
// Allow proposal file modifications
|
|
185
|
+
if (filePath.includes('zhuanspec/changes/') || filePath.includes('zhuanspec/specs/')) {
|
|
186
|
+
return {
|
|
187
|
+
continue: true,
|
|
188
|
+
systemMessage: '✓ Proposal file modification allowed in propose phase',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// Check if this is a code file that might trigger Apply phase transition
|
|
192
|
+
const isCodeFile = !filePath.includes('zhuanspec/') &&
|
|
193
|
+
/\.(java|ts|tsx|js|jsx|py|go|rs|sql|kt|scala|c|cpp|cs)$/i.test(filePath);
|
|
194
|
+
if (isCodeFile && changeId && trigger === 'pre-tool') {
|
|
195
|
+
// Try to run pre-apply check to see if we can transition to Apply phase
|
|
196
|
+
const changeDir = path.join(cwd, 'zhuanspec', 'changes', changeId);
|
|
197
|
+
const preApplyResult = await checkPreApplyConditions(changeDir);
|
|
198
|
+
if (preApplyResult.continue) {
|
|
199
|
+
// Pre-apply passed - allow code modification and suggest phase transition
|
|
200
|
+
return {
|
|
201
|
+
continue: true,
|
|
202
|
+
systemMessage: `✓ PRE-APPLY CHECK PASSED. Ready to transition to Apply phase.\n\nAll proposal requirements met:\n- proposal.md exists ✓\n- tasks.md exists ✓\n- specs directory non-empty ✓\n- validate --strict passed ✓\n- Plan approved ✓\n\nYou may now proceed with implementation. Consider updating ZHUANSPEC_PHASE to 'apply'.`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Pre-apply failed - block and show what's missing
|
|
206
|
+
const checks = preApplyResult.checks;
|
|
207
|
+
const missingItems = [];
|
|
208
|
+
if (!checks?.proposalExists)
|
|
209
|
+
missingItems.push('proposal.md');
|
|
210
|
+
if (!checks?.tasksExists)
|
|
211
|
+
missingItems.push('tasks.md');
|
|
212
|
+
if (!checks?.specsExists)
|
|
213
|
+
missingItems.push('specs directory (non-empty)');
|
|
214
|
+
if (!checks?.validationPassed)
|
|
215
|
+
missingItems.push('validate --strict passing');
|
|
216
|
+
if (!checks?.approved)
|
|
217
|
+
missingItems.push('.approved file (Plan approval)');
|
|
218
|
+
return {
|
|
219
|
+
continue: false,
|
|
220
|
+
stopReason: 'Pre-apply check failed: Cannot transition to Apply phase',
|
|
221
|
+
systemMessage: `⚠️ BLOCKED: Cannot modify "${filePath}" - Pre-apply check failed\n\nMissing requirements:\n${missingItems.map(m => `- ${m}`).join('\n')}\n\nPlease complete Propose phase requirements before implementing code.`,
|
|
222
|
+
hookSpecificOutput: {
|
|
223
|
+
additionalContext: 'ZhuanSpec Iron Law 1: No Spec No Code - Complete proposal before implementation.',
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// Block other non-code file modifications in propose phase
|
|
228
|
+
return {
|
|
229
|
+
continue: false,
|
|
230
|
+
stopReason: 'Propose phase: No code modifications allowed (No Spec No Code)',
|
|
231
|
+
systemMessage: `⚠️ BLOCKED: Propose phase - Cannot modify "${filePath}"\nOnly proposal files (zhuanspec/changes/, zhuanspec/specs/) can be modified.\nPlease wait for proposal approval before implementing.`,
|
|
232
|
+
hookSpecificOutput: {
|
|
233
|
+
additionalContext: 'ZhuanSpec Iron Law 1: No Spec No Code - Implementation cannot proceed until proposal is approved.',
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// Apply phase → check deviation
|
|
238
|
+
if (phase === 'apply') {
|
|
239
|
+
let estimatedFiles = [];
|
|
240
|
+
let currentTaskFiles = [];
|
|
241
|
+
// Parse proposal for estimated changes
|
|
242
|
+
if (await FileSystemUtils.fileExists(proposalPath)) {
|
|
243
|
+
estimatedFiles = await parseEstimatedChanges(proposalPath);
|
|
244
|
+
}
|
|
245
|
+
// Parse tasks for current task scope
|
|
246
|
+
if (await FileSystemUtils.fileExists(tasksPath)) {
|
|
247
|
+
currentTaskFiles = await parseCurrentTaskFiles(tasksPath, currentTask);
|
|
248
|
+
}
|
|
249
|
+
// Normalize paths for comparison
|
|
250
|
+
const normalizedTarget = normalizePath(filePath);
|
|
251
|
+
const normalizedEstimated = estimatedFiles.map(normalizePath);
|
|
252
|
+
const normalizedCurrentTask = currentTaskFiles.map(normalizePath);
|
|
253
|
+
// Check if file is in scope
|
|
254
|
+
const inEstimatedScope = normalizedEstimated.some(f => normalizedTarget.includes(f) || f.includes(normalizedTarget));
|
|
255
|
+
const inCurrentTaskScope = normalizedCurrentTask.some(f => normalizedTarget.includes(f) || f.includes(normalizedTarget));
|
|
256
|
+
if (inEstimatedScope || inCurrentTaskScope) {
|
|
257
|
+
return {
|
|
258
|
+
continue: true,
|
|
259
|
+
systemMessage: `✓ File "${filePath}" is within proposal scope`,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// File is out of scope → deviation detected
|
|
263
|
+
const deviationId = `dev-${Date.now()}`;
|
|
264
|
+
return {
|
|
265
|
+
continue: false,
|
|
266
|
+
stopReason: 'Deviation detected: File not in approved proposal scope',
|
|
267
|
+
systemMessage: `⚠️ DEVIATION DETECTED: "${filePath}" is not in the approved proposal scope.\n\nProposal scope: [${estimatedFiles.join(', ') || 'not specified'}]\nCurrent task scope: [${currentTaskFiles.join(', ') || 'not specified'}]\n\nPlease choose how to handle this deviation:`,
|
|
268
|
+
hookSpecificOutput: {
|
|
269
|
+
deviationId,
|
|
270
|
+
additionalContext: `
|
|
271
|
+
Deviation detected for file: ${filePath}
|
|
272
|
+
|
|
273
|
+
Options:
|
|
274
|
+
1. 更新提案后继续实施 - Update proposal to include this file, then continue
|
|
275
|
+
2. 此修改是Bug修复 - Mark as bug fix (exempt from proposal flow)
|
|
276
|
+
3. 取消修改 - Cancel this modification
|
|
277
|
+
4. 其他 - Other (provide explanation)
|
|
278
|
+
|
|
279
|
+
ZhuanSpec Iron Law 3: Reverse Sync - When deviation is found, update proposal first.
|
|
280
|
+
`,
|
|
281
|
+
options: [
|
|
282
|
+
'更新提案后继续实施',
|
|
283
|
+
'此修改是Bug修复',
|
|
284
|
+
'取消修改',
|
|
285
|
+
'其他',
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
// Default: allow
|
|
291
|
+
return {
|
|
292
|
+
continue: true,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async function parseGitChangedFiles(repoRoot) {
|
|
296
|
+
try {
|
|
297
|
+
const output = execSync('git diff --name-only', {
|
|
298
|
+
cwd: repoRoot,
|
|
299
|
+
encoding: 'utf-8',
|
|
300
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
301
|
+
});
|
|
302
|
+
return output.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// fall through
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const status = execSync('git status --porcelain', {
|
|
309
|
+
cwd: repoRoot,
|
|
310
|
+
encoding: 'utf-8',
|
|
311
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
312
|
+
});
|
|
313
|
+
return status
|
|
314
|
+
.split('\n')
|
|
315
|
+
.map((line) => line.replace(/^\s*[A-Z?]+\s+/, '').trim())
|
|
316
|
+
.filter(Boolean);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// ignore
|
|
320
|
+
}
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
async function parseEstimatedChanges(proposalPath) {
|
|
324
|
+
try {
|
|
325
|
+
const content = await FileSystemUtils.readFile(proposalPath);
|
|
326
|
+
const files = [];
|
|
327
|
+
// Look for "影响范围" or "Affected code" sections
|
|
328
|
+
const lines = content.split('\n');
|
|
329
|
+
let inAffectedSection = false;
|
|
330
|
+
for (const line of lines) {
|
|
331
|
+
if (line.includes('影响范围') || line.includes('Affected code') || line.includes('Affected files')) {
|
|
332
|
+
inAffectedSection = true;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (inAffectedSection && line.startsWith('##')) {
|
|
336
|
+
// End of section
|
|
337
|
+
inAffectedSection = false;
|
|
338
|
+
}
|
|
339
|
+
if (inAffectedSection && line.trim().startsWith('-')) {
|
|
340
|
+
// Extract file path from bullet point
|
|
341
|
+
const match = line.match(/-\s*(.+)/);
|
|
342
|
+
if (match) {
|
|
343
|
+
files.push(match[1].trim());
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return files;
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function parseCurrentTaskFiles(tasksPath, currentTask) {
|
|
354
|
+
if (!currentTask) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const content = await FileSystemUtils.readFile(tasksPath);
|
|
359
|
+
const files = [];
|
|
360
|
+
// Find the current task section
|
|
361
|
+
const lines = content.split('\n');
|
|
362
|
+
let inCurrentTask = false;
|
|
363
|
+
for (const line of lines) {
|
|
364
|
+
// Match task line: - [ ] X.Y description or - [x] X.Y description
|
|
365
|
+
const taskMatch = line.match(/-\s*\[[x ]\]\s*(\d+\.\d+)\s*(.+)/);
|
|
366
|
+
if (taskMatch) {
|
|
367
|
+
const taskId = taskMatch[1];
|
|
368
|
+
if (taskId === currentTask) {
|
|
369
|
+
inCurrentTask = true;
|
|
370
|
+
// Extract files mentioned in task description
|
|
371
|
+
const description = taskMatch[2];
|
|
372
|
+
// Look for file patterns in description
|
|
373
|
+
const filePatterns = description.match(/[a-zA-Z0-9_\-/]+\.(java|ts|tsx|js|jsx|py|go|rs|md|yaml|json|sql)/g);
|
|
374
|
+
if (filePatterns) {
|
|
375
|
+
files.push(...filePatterns);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
inCurrentTask = false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Also check for file mentions within task details
|
|
383
|
+
if (inCurrentTask) {
|
|
384
|
+
const filePatterns = line.match(/[a-zA-Z0-9_\-/]+\.(java|ts|tsx|js|jsx|py|go|rs|md|yaml|json|sql)/g);
|
|
385
|
+
if (filePatterns) {
|
|
386
|
+
files.push(...filePatterns);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return files;
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return [];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function normalizePath(p) {
|
|
397
|
+
return p
|
|
398
|
+
.replace(/\\/g, '/')
|
|
399
|
+
.replace(/^\.\/|^\/+/, '')
|
|
400
|
+
.toLowerCase();
|
|
401
|
+
}
|
|
402
|
+
export { PITFALL_KEYWORDS, USER_CORRECTION_KEYWORDS, BEST_PRACTICE_KEYWORDS, IMPLICIT_CONVENTION_KEYWORDS };
|
|
403
|
+
//# sourceMappingURL=deviation-check.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deviation Handler - 偏离修正流程处理
|
|
3
|
+
*
|
|
4
|
+
* 当用户在 Apply 阶段输入纠正关键词时触发偏离修正流程:
|
|
5
|
+
* 1. 先修改 spec 文件,同步用户确认
|
|
6
|
+
* 2. 用户确认后修改代码,完成后验证
|
|
7
|
+
* 3. 用户确认无误后触发知识沉淀
|
|
8
|
+
*/
|
|
9
|
+
export interface DeviationContext {
|
|
10
|
+
changeId: string;
|
|
11
|
+
phase: string;
|
|
12
|
+
userInput: string;
|
|
13
|
+
detectedKeywords: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface DeviationAction {
|
|
16
|
+
type: 'modify-spec' | 'modify-code' | 'knowledge-collect' | 'cancel';
|
|
17
|
+
targetFile?: string;
|
|
18
|
+
content?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Handle deviation correction flow
|
|
22
|
+
*
|
|
23
|
+
* @param context - Deviation context with changeId, phase, userInput
|
|
24
|
+
* @returns Action recommendation for the AI
|
|
25
|
+
*/
|
|
26
|
+
export declare function handleDeviation(context: DeviationContext): Promise<DeviationAction>;
|
|
27
|
+
/**
|
|
28
|
+
* Complete deviation handling after user confirms
|
|
29
|
+
*
|
|
30
|
+
* @param deviationId - The deviation ID to mark as resolved
|
|
31
|
+
* @param specChanged - List of spec files that were changed
|
|
32
|
+
* @param codeChanged - List of code files that were changed
|
|
33
|
+
*/
|
|
34
|
+
export declare function completeDeviation(deviationId: string, changeId: string, specChanged: string[], codeChanged: string[]): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Trigger knowledge collection after deviation is resolved
|
|
37
|
+
*
|
|
38
|
+
* @param changeId - The change ID
|
|
39
|
+
* @param knowledgeContent - Knowledge content to write
|
|
40
|
+
*/
|
|
41
|
+
export declare function triggerKnowledgeCollection(changeId: string, knowledgeContent: string): Promise<void>;
|
|
42
|
+
export { USER_CORRECTION_KEYWORDS } from './deviation-check.js';
|
|
43
|
+
//# sourceMappingURL=deviation-handler.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deviation Handler - 偏离修正流程处理
|
|
3
|
+
*
|
|
4
|
+
* 当用户在 Apply 阶段输入纠正关键词时触发偏离修正流程:
|
|
5
|
+
* 1. 先修改 spec 文件,同步用户确认
|
|
6
|
+
* 2. 用户确认后修改代码,完成后验证
|
|
7
|
+
* 3. 用户确认无误后触发知识沉淀
|
|
8
|
+
*/
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
11
|
+
import { getBeijingTime } from './record-progress.js';
|
|
12
|
+
/**
|
|
13
|
+
* Handle deviation correction flow
|
|
14
|
+
*
|
|
15
|
+
* @param context - Deviation context with changeId, phase, userInput
|
|
16
|
+
* @returns Action recommendation for the AI
|
|
17
|
+
*/
|
|
18
|
+
export async function handleDeviation(context) {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
const zhuanspecDir = path.join(cwd, 'zhuanspec');
|
|
21
|
+
const changeDir = path.join(zhuanspecDir, 'changes', context.changeId);
|
|
22
|
+
const progressPath = path.join(changeDir, 'metrics', 'progress.json');
|
|
23
|
+
// Record deviation in progress.json
|
|
24
|
+
const deviationId = `dev-${Date.now()}`;
|
|
25
|
+
const deviationRecord = {
|
|
26
|
+
deviationId,
|
|
27
|
+
detectedAt: getBeijingTime(),
|
|
28
|
+
triggerType: 'user-input',
|
|
29
|
+
userInput: context.userInput.slice(0, 500),
|
|
30
|
+
specChanged: [],
|
|
31
|
+
codeChanged: [],
|
|
32
|
+
resolvedAt: undefined,
|
|
33
|
+
};
|
|
34
|
+
// Update progress.json with deviation record
|
|
35
|
+
if (await FileSystemUtils.fileExists(progressPath)) {
|
|
36
|
+
const progressData = JSON.parse(await FileSystemUtils.readFile(progressPath));
|
|
37
|
+
progressData.deviationRecords = progressData.deviationRecords || [];
|
|
38
|
+
progressData.deviationRecords.push(deviationRecord);
|
|
39
|
+
progressData.deviationCount = (progressData.deviationCount || 0) + 1;
|
|
40
|
+
await FileSystemUtils.writeFile(progressPath, JSON.stringify(progressData, null, 2));
|
|
41
|
+
}
|
|
42
|
+
// Step 1: Modify spec file first
|
|
43
|
+
return {
|
|
44
|
+
type: 'modify-spec',
|
|
45
|
+
targetFile: 'specs/',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Complete deviation handling after user confirms
|
|
50
|
+
*
|
|
51
|
+
* @param deviationId - The deviation ID to mark as resolved
|
|
52
|
+
* @param specChanged - List of spec files that were changed
|
|
53
|
+
* @param codeChanged - List of code files that were changed
|
|
54
|
+
*/
|
|
55
|
+
export async function completeDeviation(deviationId, changeId, specChanged, codeChanged) {
|
|
56
|
+
const cwd = process.cwd();
|
|
57
|
+
const zhuanspecDir = path.join(cwd, 'zhuanspec');
|
|
58
|
+
const changeDir = path.join(zhuanspecDir, 'changes', changeId);
|
|
59
|
+
const progressPath = path.join(changeDir, 'metrics', 'progress.json');
|
|
60
|
+
if (await FileSystemUtils.fileExists(progressPath)) {
|
|
61
|
+
const progressData = JSON.parse(await FileSystemUtils.readFile(progressPath));
|
|
62
|
+
const deviation = progressData.deviationRecords?.find((d) => d.deviationId === deviationId);
|
|
63
|
+
if (deviation) {
|
|
64
|
+
deviation.specChanged = specChanged;
|
|
65
|
+
deviation.codeChanged = codeChanged;
|
|
66
|
+
deviation.resolvedAt = getBeijingTime();
|
|
67
|
+
await FileSystemUtils.writeFile(progressPath, JSON.stringify(progressData, null, 2));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Trigger knowledge collection after deviation is resolved
|
|
73
|
+
*
|
|
74
|
+
* @param changeId - The change ID
|
|
75
|
+
* @param knowledgeContent - Knowledge content to write
|
|
76
|
+
*/
|
|
77
|
+
export async function triggerKnowledgeCollection(changeId, knowledgeContent) {
|
|
78
|
+
const cwd = process.cwd();
|
|
79
|
+
const zhuanspecDir = path.join(cwd, 'zhuanspec');
|
|
80
|
+
const knowledgePath = path.join(zhuanspecDir, 'knowledge', 'best-practices');
|
|
81
|
+
// Write knowledge file
|
|
82
|
+
const knowledgeFile = path.join(knowledgePath, `deviation-${changeId}.md`);
|
|
83
|
+
await FileSystemUtils.createDirectory(knowledgePath);
|
|
84
|
+
await FileSystemUtils.writeFile(knowledgeFile, knowledgeContent);
|
|
85
|
+
// Update knowledge index
|
|
86
|
+
const indexPath = path.join(zhuanspecDir, 'knowledge', 'index.md');
|
|
87
|
+
if (await FileSystemUtils.fileExists(indexPath)) {
|
|
88
|
+
let indexContent = await FileSystemUtils.readFile(indexPath);
|
|
89
|
+
// Add new entry under Best Practices section
|
|
90
|
+
const entry = `- [偏离处理经验](best-practices/deviation-${changeId}.md) — 偏离修正流程沉淀`;
|
|
91
|
+
if (!indexContent.includes(entry)) {
|
|
92
|
+
indexContent = indexContent.replace('## Best Practices\n', `## Best Practices\n${entry}\n`);
|
|
93
|
+
await FileSystemUtils.writeFile(indexPath, indexContent);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export { USER_CORRECTION_KEYWORDS } from './deviation-check.js';
|
|
98
|
+
//# sourceMappingURL=deviation-handler.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionStart Hook - Initialize ZhuanSpec environment
|
|
3
|
+
*
|
|
4
|
+
* This hook runs when Claude Code starts a new session:
|
|
5
|
+
* 1. Loads project.md and knowledge/ files
|
|
6
|
+
* 2. Sets environment variables for phase tracking
|
|
7
|
+
* 3. Retrieves relevant implicit conventions and change patterns
|
|
8
|
+
*/
|
|
9
|
+
interface InitHookOptions {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function initHook(options: InitHookOptions): Promise<void>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=init.d.ts.map
|