@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,282 @@
1
+ /**
2
+ * Stop Hook - Summarize Session Progress
3
+ *
4
+ * This hook runs when Claude Code session stops or pauses:
5
+ * 1. Summarize current wave completion status
6
+ * 2. Update ZHUANSPEC_CURRENT_TASK for next task
7
+ * 3. Check if tasks.md is complete → trigger Review phase
8
+ * 4. Generate events.json entries for key events
9
+ */
10
+ import path from 'path';
11
+ import { execSync } from 'child_process';
12
+ import { FileSystemUtils } from '../../utils/file-system.js';
13
+ import { executePostApply } from './post-apply.js';
14
+ import { getBeijingTime } from './record-progress.js';
15
+ export async function summarizeHook(options) {
16
+ const output = await runSummarize();
17
+ if (options.json) {
18
+ console.log(JSON.stringify(output));
19
+ }
20
+ else {
21
+ if (output.systemMessage) {
22
+ console.log(output.systemMessage);
23
+ }
24
+ }
25
+ }
26
+ async function runSummarize() {
27
+ const phase = process.env.ZHUANSPEC_PHASE || 'idle';
28
+ const changeId = process.env.ZHUANSPEC_CHANGE_ID || '';
29
+ const currentTask = process.env.ZHUANSPEC_CURRENT_TASK || '';
30
+ // No active change → nothing to summarize
31
+ if (!changeId || phase === 'idle') {
32
+ return {
33
+ continue: true,
34
+ systemMessage: 'ℹ No active ZhuanSpec change to summarize',
35
+ };
36
+ }
37
+ const cwd = process.cwd();
38
+ const zhuanspecDir = path.join(cwd, 'zhuanspec');
39
+ const changeDir = path.join(zhuanspecDir, 'changes', changeId);
40
+ const metricsDir = path.join(changeDir, 'metrics');
41
+ // Load progress data
42
+ const progressPath = path.join(metricsDir, 'progress.json');
43
+ let progressData = null;
44
+ if (await FileSystemUtils.fileExists(progressPath)) {
45
+ try {
46
+ progressData = JSON.parse(await FileSystemUtils.readFile(progressPath));
47
+ }
48
+ catch {
49
+ // Ignore parse errors
50
+ }
51
+ }
52
+ // Load tasks.md to analyze wave structure
53
+ const tasksPath = path.join(changeDir, 'tasks.md');
54
+ let tasks = [];
55
+ let waves = [];
56
+ if (await FileSystemUtils.fileExists(tasksPath)) {
57
+ const tasksContent = await FileSystemUtils.readFile(tasksPath);
58
+ tasks = parseTasks(tasksContent);
59
+ waves = computeWaves(tasks);
60
+ }
61
+ // Find current wave
62
+ const currentWave = waves.find(w => w.tasks.includes(currentTask) || w.status === 'in_progress');
63
+ // Calculate completion status
64
+ const allTasksComplete = tasks.length > 0 && tasks.every(t => t.status === 'completed');
65
+ const completedTasksCount = tasks.filter(t => t.status === 'completed').length;
66
+ // Find next task
67
+ let nextTask = '';
68
+ if (!allTasksComplete) {
69
+ const pendingTasks = tasks.filter(t => t.status === 'pending');
70
+ if (pendingTasks.length > 0) {
71
+ nextTask = pendingTasks[0].taskId;
72
+ }
73
+ }
74
+ // Auto review trigger: only once after all apply tasks complete.
75
+ // First, execute post-apply hook for deviation detection
76
+ let autoReviewTriggered = false;
77
+ let autoReviewPassed = false;
78
+ let deviationDetected = false;
79
+ if (phase === 'apply' && allTasksComplete) {
80
+ // Execute post-apply hook to detect deviations
81
+ const postApplyResult = await executePostApply(changeDir, cwd);
82
+ if (postApplyResult.hookSpecificOutput?.deviationDetected) {
83
+ deviationDetected = true;
84
+ // Don't trigger review if deviation detected
85
+ }
86
+ else {
87
+ // No deviation - proceed with auto review
88
+ const autoReviewEnabled = process.env.ZHUANSPEC_AUTO_REVIEW !== 'false';
89
+ const reviewPath = path.join(metricsDir, 'review.json');
90
+ const hasReviewReport = await FileSystemUtils.fileExists(reviewPath);
91
+ if (autoReviewEnabled && !hasReviewReport) {
92
+ try {
93
+ execSync(`zhuanspec review "${changeId}"`, {
94
+ cwd,
95
+ stdio: 'pipe',
96
+ encoding: 'utf-8',
97
+ });
98
+ autoReviewTriggered = true;
99
+ autoReviewPassed = true;
100
+ }
101
+ catch {
102
+ autoReviewTriggered = true;
103
+ autoReviewPassed = false;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ // Calculate metrics
109
+ const sessionDurationMs = progressData
110
+ ? Date.now() - new Date(progressData.startedAt).getTime()
111
+ : 0;
112
+ // Write events.json entry
113
+ const eventsPath = path.join(metricsDir, 'events.json');
114
+ const event = {
115
+ eventId: `evt-${Date.now()}`,
116
+ type: 'session_summary',
117
+ timestamp: getBeijingTime(),
118
+ data: {
119
+ phase,
120
+ currentTask,
121
+ currentWaveId: currentWave?.waveId,
122
+ tasksCompleted: completedTasksCount,
123
+ tasksTotal: tasks.length,
124
+ allTasksComplete,
125
+ nextTask,
126
+ toolCallsCount: progressData?.toolCalls.length || 0,
127
+ filesModifiedCount: progressData?.filesModified.length || 0,
128
+ linesAdded: progressData?.linesAdded || 0,
129
+ sessionDurationMs,
130
+ tokenUsageTotal: progressData?.stats?.tokenUsageTotal || 0,
131
+ contextLoad: progressData?.stats?.contextLoad || 0,
132
+ },
133
+ };
134
+ let eventsLog;
135
+ if (await FileSystemUtils.fileExists(eventsPath)) {
136
+ try {
137
+ eventsLog = JSON.parse(await FileSystemUtils.readFile(eventsPath));
138
+ eventsLog.events.push(event);
139
+ }
140
+ catch {
141
+ eventsLog = {
142
+ changeId,
143
+ sessionId: progressData?.sessionId || 'unknown',
144
+ events: [event],
145
+ };
146
+ }
147
+ }
148
+ else {
149
+ eventsLog = {
150
+ changeId,
151
+ sessionId: progressData?.sessionId || 'unknown',
152
+ events: [event],
153
+ };
154
+ }
155
+ await FileSystemUtils.writeFile(eventsPath, JSON.stringify(eventsLog, null, 2));
156
+ // Timeout detection - check if task has been idle for too long
157
+ const TIMEOUT_THRESHOLD_SECONDS = Number(process.env.ZHUANSPEC_TIMEOUT_SECONDS) || 300;
158
+ let timeoutWarning = '';
159
+ if (progressData && currentTask && phase === 'apply') {
160
+ const lastActiveTime = new Date(progressData.lastUpdatedAt).getTime();
161
+ const idleSeconds = Math.round((Date.now() - lastActiveTime) / 1000);
162
+ if (idleSeconds > TIMEOUT_THRESHOLD_SECONDS) {
163
+ const idleMinutes = Math.round(idleSeconds / 60);
164
+ timeoutWarning = `⚠️ 超时警告: 任务 ${currentTask} 可能卡住\n最后活跃: ${idleMinutes}m ago\n建议: 检查任务执行状态\n`;
165
+ }
166
+ }
167
+ // Build summary message
168
+ let systemMessage = `📊 ZhuanSpec Session Summary\n\n`;
169
+ systemMessage += `Phase: ${phase}\n`;
170
+ systemMessage += `Change: ${changeId}\n`;
171
+ if (currentTask) {
172
+ systemMessage += `Current Task: ${currentTask}\n`;
173
+ }
174
+ if (tasks.length > 0) {
175
+ systemMessage += `Tasks Progress: ${completedTasksCount}/${tasks.length} completed\n`;
176
+ if (currentWave) {
177
+ systemMessage += `Current Wave: Wave ${currentWave.waveId} (${currentWave.completedTasks.length}/${currentWave.tasks.length} completed)\n`;
178
+ }
179
+ }
180
+ // Add timeout warning if detected
181
+ if (timeoutWarning) {
182
+ systemMessage += `\n${timeoutWarning}`;
183
+ }
184
+ if (progressData) {
185
+ systemMessage += `\nMetrics:\n`;
186
+ systemMessage += `- Tool Calls: ${progressData.toolCalls.length}\n`;
187
+ systemMessage += `- Files Modified: ${progressData.filesModified.length}\n`;
188
+ systemMessage += `- Lines Added: ${progressData.linesAdded}\n`;
189
+ systemMessage += `- Session Duration: ${Math.round(sessionDurationMs / 1000)}s\n`;
190
+ systemMessage += `- Token Usage: ${progressData.stats?.tokenUsageTotal || 0}\n`;
191
+ systemMessage += `- Context Load: ${progressData.stats?.contextLoad || 0}\n`;
192
+ }
193
+ if (allTasksComplete) {
194
+ systemMessage += `\n✅ All tasks completed! Ready for Review phase.\n`;
195
+ if (deviationDetected) {
196
+ systemMessage += `⚠️ DEVIATION DETECTED. Please update proposal (Reverse Sync) before proceeding to Review.\n`;
197
+ }
198
+ else if (autoReviewTriggered) {
199
+ systemMessage += autoReviewPassed
200
+ ? `✅ Auto review executed successfully.\n`
201
+ : `⚠️ Auto review executed but failed. Please check zhuanspec review output.\n`;
202
+ }
203
+ }
204
+ else if (nextTask) {
205
+ systemMessage += `\n➡️ Next Task: ${nextTask}\n`;
206
+ }
207
+ return {
208
+ continue: true,
209
+ systemMessage,
210
+ hookSpecificOutput: {
211
+ waveCompleted: currentWave?.waveId,
212
+ nextTask,
213
+ allTasksComplete,
214
+ autoReviewTriggered,
215
+ metrics: {
216
+ totalToolCalls: progressData?.toolCalls.length || 0,
217
+ totalFilesModified: progressData?.filesModified.length || 0,
218
+ totalLinesAdded: progressData?.linesAdded || 0,
219
+ sessionDurationMs,
220
+ tokenUsageTotal: progressData?.stats?.tokenUsageTotal || 0,
221
+ contextLoad: progressData?.stats?.contextLoad || 0,
222
+ },
223
+ },
224
+ };
225
+ }
226
+ function parseTasks(content) {
227
+ const tasks = [];
228
+ const lines = content.split('\n');
229
+ let currentWave = 0;
230
+ for (const line of lines) {
231
+ // Match wave headers: ### Wave 1, ### Wave 2, etc.
232
+ const waveMatch = line.match(/^###\s*Wave\s*(\d+)/i);
233
+ if (waveMatch) {
234
+ currentWave = parseInt(waveMatch[1], 10);
235
+ continue;
236
+ }
237
+ // Match task lines: - [ ] X.Y description or - [x] X.Y description
238
+ const taskMatch = line.match(/^-\s*\[([x ])\]\s*(\d+\.\d+)\s*(.+)/);
239
+ if (taskMatch) {
240
+ const statusChar = taskMatch[1];
241
+ const taskId = taskMatch[2];
242
+ tasks.push({
243
+ taskId,
244
+ status: statusChar === 'x' ? 'completed' : 'pending',
245
+ wave: currentWave,
246
+ });
247
+ }
248
+ }
249
+ return tasks;
250
+ }
251
+ function computeWaves(tasks) {
252
+ const wavesMap = new Map();
253
+ for (const task of tasks) {
254
+ if (!wavesMap.has(task.wave)) {
255
+ wavesMap.set(task.wave, {
256
+ waveId: task.wave,
257
+ tasks: [],
258
+ completedTasks: [],
259
+ status: 'pending',
260
+ });
261
+ }
262
+ const wave = wavesMap.get(task.wave);
263
+ wave.tasks.push(task.taskId);
264
+ if (task.status === 'completed') {
265
+ wave.completedTasks.push(task.taskId);
266
+ }
267
+ }
268
+ // Determine wave status
269
+ for (const [waveId, wave] of wavesMap) {
270
+ if (wave.completedTasks.length === wave.tasks.length) {
271
+ wave.status = 'completed';
272
+ }
273
+ else if (wave.completedTasks.length > 0) {
274
+ wave.status = 'in_progress';
275
+ }
276
+ else {
277
+ wave.status = 'pending';
278
+ }
279
+ }
280
+ return Array.from(wavesMap.values()).sort((a, b) => a.waveId - b.waveId);
281
+ }
282
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * User Input Hook
3
+ *
4
+ * Records user input summary during ZhuanSpec workflow.
5
+ * Tracks user interventions, corrections, confirmations, and additional requests.
6
+ */
7
+ interface UserInputOutput {
8
+ continue: boolean;
9
+ systemMessage?: string;
10
+ hookSpecificOutput?: {
11
+ inputRecorded: boolean;
12
+ triggerType: string;
13
+ summaryLength: number;
14
+ };
15
+ }
16
+ /**
17
+ * Main hook function - records user input
18
+ */
19
+ export declare function userInputHook(content: string, responseTimeMs?: number): Promise<UserInputOutput>;
20
+ /**
21
+ * CLI entry point
22
+ */
23
+ export declare function runUserInputHook(): Promise<void>;
24
+ export {};
25
+ //# sourceMappingURL=user-input-hook.d.ts.map
@@ -0,0 +1,179 @@
1
+ /**
2
+ * User Input Hook
3
+ *
4
+ * Records user input summary during ZhuanSpec workflow.
5
+ * Tracks user interventions, corrections, confirmations, and additional requests.
6
+ */
7
+ import path from 'path';
8
+ import { FileSystemUtils } from '../../utils/file-system.js';
9
+ import { getBeijingTime } from './record-progress.js';
10
+ /**
11
+ * Detect trigger type from user input content
12
+ */
13
+ function detectTriggerType(content) {
14
+ const lowerContent = content.toLowerCase();
15
+ // Correction patterns
16
+ if (lowerContent.includes('不对') || lowerContent.includes('错误') ||
17
+ lowerContent.includes('修复') || lowerContent.includes('修改') ||
18
+ lowerContent.includes('问题') || lowerContent.includes('bug')) {
19
+ return 'correction';
20
+ }
21
+ // Additional patterns
22
+ if (lowerContent.includes('另外') || lowerContent.includes('还有') ||
23
+ lowerContent.includes('追加') || lowerContent.includes('补充') ||
24
+ lowerContent.includes('加上')) {
25
+ return 'additional';
26
+ }
27
+ // Confirmation patterns
28
+ if (lowerContent.includes('好的') || lowerContent.includes('确认') ||
29
+ lowerContent.includes('同意') || lowerContent.includes('批准') ||
30
+ lowerContent.includes('继续') || lowerContent.includes('plan approved')) {
31
+ return 'confirmation';
32
+ }
33
+ // Clarification patterns
34
+ if (lowerContent.includes('?') || lowerContent.includes('为什么') ||
35
+ lowerContent.includes('怎么') || lowerContent.includes('如何') ||
36
+ lowerContent.includes('什么') || lowerContent.includes('澄清')) {
37
+ return 'clarification';
38
+ }
39
+ // Initial request (typically long first message)
40
+ if (content.length > 200) {
41
+ return 'initial';
42
+ }
43
+ return 'other';
44
+ }
45
+ /**
46
+ * Extract file references from user input
47
+ */
48
+ function extractAffectedFiles(content) {
49
+ const filePatterns = [
50
+ /(?:file|文件|路径):\s*([^\s,]+)/gi,
51
+ /([a-zA-Z0-9_/-]+\.(?:java|ts|js|md|json|xml))/gi,
52
+ /zhuanspec\/[^\s]+/gi
53
+ ];
54
+ const files = [];
55
+ for (const pattern of filePatterns) {
56
+ const matches = content.match(pattern) || [];
57
+ files.push(...matches);
58
+ }
59
+ return [...new Set(files)].slice(0, 10); // Limit to 10 unique files
60
+ }
61
+ /**
62
+ * Generate summary from user input (max 200 chars)
63
+ */
64
+ function generateSummary(content) {
65
+ // Truncate to 200 chars for summary
66
+ if (content.length <= 200) {
67
+ return content.trim();
68
+ }
69
+ // Extract first sentence or first 200 chars
70
+ const firstSentence = content.split(/[.!?\n]/)[0];
71
+ if (firstSentence.length <= 200) {
72
+ return firstSentence.trim();
73
+ }
74
+ return content.slice(0, 197) + '...';
75
+ }
76
+ /**
77
+ * Main hook function - records user input
78
+ */
79
+ export async function userInputHook(content, responseTimeMs) {
80
+ const phase = process.env.ZHUANSPEC_PHASE || 'idle';
81
+ const changeId = process.env.ZHUANSPEC_CHANGE_ID || '';
82
+ // Skip if not in active phase
83
+ if (!changeId || phase === 'idle') {
84
+ return { continue: true };
85
+ }
86
+ // Skip empty content
87
+ if (!content || content.trim().length < 10) {
88
+ return { continue: true };
89
+ }
90
+ const cwd = process.cwd();
91
+ const zhuanspecDir = path.join(cwd, 'zhuanspec');
92
+ const metricsDir = path.join(zhuanspecDir, 'changes', changeId, 'metrics');
93
+ // Ensure metrics directory exists
94
+ await FileSystemUtils.createDirectory(metricsDir);
95
+ const userInputPath = path.join(metricsDir, 'user_inputs.json');
96
+ // Create input record
97
+ const inputRecord = {
98
+ inputId: `input-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
99
+ timestamp: getBeijingTime(),
100
+ phase,
101
+ summary: generateSummary(content),
102
+ triggerType: detectTriggerType(content),
103
+ affectedFiles: extractAffectedFiles(content),
104
+ responseTimeMs
105
+ };
106
+ // Load existing user inputs or create new
107
+ let userInputs;
108
+ if (await FileSystemUtils.fileExists(userInputPath)) {
109
+ try {
110
+ userInputs = JSON.parse(await FileSystemUtils.readFile(userInputPath));
111
+ userInputs.inputs.push(inputRecord);
112
+ }
113
+ catch {
114
+ userInputs = {
115
+ changeId,
116
+ sessionId: `sess-${Date.now()}`,
117
+ inputs: [inputRecord]
118
+ };
119
+ }
120
+ }
121
+ else {
122
+ userInputs = {
123
+ changeId,
124
+ sessionId: `sess-${Date.now()}`,
125
+ inputs: [inputRecord]
126
+ };
127
+ }
128
+ // Write user inputs file
129
+ await FileSystemUtils.writeFile(userInputPath, JSON.stringify(userInputs, null, 2));
130
+ return {
131
+ continue: true,
132
+ systemMessage: `📝 User input recorded: ${inputRecord.triggerType} (${inputRecord.summary.length} chars)`,
133
+ hookSpecificOutput: {
134
+ inputRecorded: true,
135
+ triggerType: inputRecord.triggerType,
136
+ summaryLength: inputRecord.summary.length
137
+ }
138
+ };
139
+ }
140
+ /**
141
+ * CLI entry point
142
+ */
143
+ export async function runUserInputHook() {
144
+ // Read stdin for user message
145
+ let stdinData = {};
146
+ try {
147
+ const stdinContent = await readStdin();
148
+ if (stdinContent) {
149
+ stdinData = JSON.parse(stdinContent);
150
+ }
151
+ }
152
+ catch {
153
+ // Ignore parse errors
154
+ }
155
+ const content = stdinData.user_message || '';
156
+ const responseTimeMs = typeof stdinData.response_time_ms === 'number'
157
+ ? stdinData.response_time_ms
158
+ : undefined;
159
+ if (!content) {
160
+ console.log(JSON.stringify({ continue: true }));
161
+ return;
162
+ }
163
+ const output = await userInputHook(content, responseTimeMs);
164
+ console.log(JSON.stringify(output));
165
+ }
166
+ async function readStdin() {
167
+ return new Promise((resolve) => {
168
+ let data = '';
169
+ process.stdin.setEncoding('utf8');
170
+ process.stdin.on('data', chunk => data += chunk);
171
+ process.stdin.on('end', () => resolve(data));
172
+ process.stdin.on('error', () => resolve(null));
173
+ });
174
+ }
175
+ // CLI execution
176
+ if (require.main === module) {
177
+ runUserInputHook();
178
+ }
179
+ //# sourceMappingURL=user-input-hook.js.map
@@ -69,7 +69,15 @@ export declare class InitCommand {
69
69
  private writeTemplateFiles;
70
70
  private configureAITools;
71
71
  private configureRootAgentsStub;
72
+ /**
73
+ * Generate Claude Code hooks configuration for ZhuanSpec workflow.
74
+ * Creates .claude/settings.json with hooks for SessionStart, PreToolUse, PostToolUse, and Stop.
75
+ */
76
+ private generateClaudeSettings;
77
+ private readExistingSettings;
78
+ private mergeSettings;
72
79
  private displaySuccessMessage;
80
+ private displayWorkflowDiagram;
73
81
  private formatToolNames;
74
82
  private renderBanner;
75
83
  private startSpinner;