@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,278 @@
1
+ /**
2
+ * Progress Command - Display current execution status
3
+ *
4
+ * This command reads progress.json and tasks.md to show:
5
+ * - Current phase and wave status
6
+ * - Task completion percentage with visual progress bar
7
+ * - Estimated remaining time
8
+ */
9
+ import path from 'path';
10
+ import { promises as fs } from 'fs';
11
+ import { FileSystemUtils } from '../utils/file-system.js';
12
+ import { PHASE_ORDER } from '../utils/phase-utils.js';
13
+ import { initializeProgress } from '../core/hooks/record-progress.js';
14
+ export class ProgressCommand {
15
+ /**
16
+ * Show progress for a specific change
17
+ * Usage: zhuanspec progress show <change-id>
18
+ */
19
+ async show(changeId, options) {
20
+ const cwd = process.cwd();
21
+ const zhuanspecDir = path.join(cwd, 'zhuanspec');
22
+ const changeDir = path.join(zhuanspecDir, 'changes', changeId);
23
+ const metricsDir = path.join(changeDir, 'metrics');
24
+ if (!await FileSystemUtils.directoryExists(changeDir)) {
25
+ throw new Error(`Change '${changeId}' not found`);
26
+ }
27
+ // Read progress.json
28
+ const progressPath = path.join(metricsDir, 'progress.json');
29
+ let progress = null;
30
+ if (await FileSystemUtils.fileExists(progressPath)) {
31
+ try {
32
+ progress = JSON.parse(await FileSystemUtils.readFile(progressPath));
33
+ }
34
+ catch {
35
+ // Ignore parse errors
36
+ }
37
+ }
38
+ // Read tasks.md
39
+ const tasksPath = path.join(changeDir, 'tasks.md');
40
+ let tasks = [];
41
+ let waves = [];
42
+ if (await FileSystemUtils.fileExists(tasksPath)) {
43
+ const tasksContent = await FileSystemUtils.readFile(tasksPath);
44
+ tasks = this.parseTasks(tasksContent);
45
+ waves = this.computeWaves(tasks);
46
+ }
47
+ if (options?.json) {
48
+ this.outputJson(changeId, progress, tasks, waves);
49
+ }
50
+ else {
51
+ this.outputFormatted(changeId, progress, tasks, waves);
52
+ }
53
+ }
54
+ async execute(options) {
55
+ const cwd = process.cwd();
56
+ const zhuanspecDir = path.join(cwd, 'zhuanspec');
57
+ // Check if zhuanspec directory exists
58
+ if (!await FileSystemUtils.fileExists(zhuanspecDir)) {
59
+ console.log('โ„น๏ธ No ZhuanSpec project found in current directory');
60
+ return;
61
+ }
62
+ // Get active change from environment or find most recent
63
+ const changeId = process.env.ZHUANSPEC_CHANGE_ID || await this.findMostRecentChange(zhuanspecDir);
64
+ if (!changeId) {
65
+ console.log('โ„น๏ธ No active ZhuanSpec change');
66
+ return;
67
+ }
68
+ const changeDir = path.join(zhuanspecDir, 'changes', changeId);
69
+ const metricsDir = path.join(changeDir, 'metrics');
70
+ // Read progress.json
71
+ const progressPath = path.join(metricsDir, 'progress.json');
72
+ let progress = null;
73
+ if (await FileSystemUtils.fileExists(progressPath)) {
74
+ try {
75
+ progress = JSON.parse(await FileSystemUtils.readFile(progressPath));
76
+ }
77
+ catch {
78
+ // Ignore parse errors
79
+ }
80
+ }
81
+ // Read tasks.md
82
+ const tasksPath = path.join(changeDir, 'tasks.md');
83
+ let tasks = [];
84
+ let waves = [];
85
+ if (await FileSystemUtils.fileExists(tasksPath)) {
86
+ const tasksContent = await FileSystemUtils.readFile(tasksPath);
87
+ tasks = this.parseTasks(tasksContent);
88
+ waves = this.computeWaves(tasks);
89
+ }
90
+ if (options?.json) {
91
+ this.outputJson(changeId, progress, tasks, waves);
92
+ }
93
+ else {
94
+ this.outputFormatted(changeId, progress, tasks, waves);
95
+ }
96
+ }
97
+ async findMostRecentChange(zhuanspecDir) {
98
+ const changesDir = path.join(zhuanspecDir, 'changes');
99
+ if (!await FileSystemUtils.fileExists(changesDir)) {
100
+ return null;
101
+ }
102
+ const entries = await fs.readdir(changesDir, { withFileTypes: true });
103
+ const changeDirs = entries
104
+ .filter(e => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'archive')
105
+ .map(e => e.name);
106
+ if (changeDirs.length === 0) {
107
+ return null;
108
+ }
109
+ // Return most recently modified
110
+ const changeStats = await Promise.all(changeDirs.map(async (name) => {
111
+ const dirPath = path.join(changesDir, name);
112
+ const stats = await fs.stat(dirPath);
113
+ return { name, mtime: stats.mtime.getTime() };
114
+ }));
115
+ changeStats.sort((a, b) => b.mtime - a.mtime);
116
+ return changeStats[0].name;
117
+ }
118
+ parseTasks(content) {
119
+ const tasks = [];
120
+ const lines = content.split('\n');
121
+ let currentWave = 0;
122
+ for (const line of lines) {
123
+ // Match wave headers: ### Wave 1, ### Wave 2, etc.
124
+ const waveMatch = line.match(/^###\s*Wave\s*(\d+)/i);
125
+ if (waveMatch) {
126
+ currentWave = parseInt(waveMatch[1], 10);
127
+ continue;
128
+ }
129
+ // Match task lines: - [ ] X.Y description or - [x] X.Y description
130
+ const taskMatch = line.match(/^-\s*\[([x ])\]\s*(\d+\.\d+)\s*(.+)/);
131
+ if (taskMatch) {
132
+ const statusChar = taskMatch[1];
133
+ const taskId = taskMatch[2];
134
+ const description = taskMatch[3].trim();
135
+ tasks.push({
136
+ taskId,
137
+ status: statusChar === 'x' ? 'completed' : 'pending',
138
+ wave: currentWave,
139
+ description: description.split('@')[0].trim(), // Remove @skill/@depends tags
140
+ });
141
+ }
142
+ }
143
+ return tasks;
144
+ }
145
+ computeWaves(tasks) {
146
+ const wavesMap = new Map();
147
+ for (const task of tasks) {
148
+ if (!wavesMap.has(task.wave)) {
149
+ wavesMap.set(task.wave, {
150
+ waveId: task.wave,
151
+ tasks: [],
152
+ completedTasks: [],
153
+ status: 'pending',
154
+ });
155
+ }
156
+ const wave = wavesMap.get(task.wave);
157
+ wave.tasks.push(task);
158
+ if (task.status === 'completed') {
159
+ wave.completedTasks.push(task);
160
+ }
161
+ }
162
+ // Determine wave status
163
+ for (const [, wave] of wavesMap) {
164
+ if (wave.completedTasks.length === wave.tasks.length) {
165
+ wave.status = 'completed';
166
+ }
167
+ else if (wave.completedTasks.length > 0) {
168
+ wave.status = 'in_progress';
169
+ }
170
+ else {
171
+ wave.status = 'pending';
172
+ }
173
+ }
174
+ return Array.from(wavesMap.values()).sort((a, b) => a.waveId - b.waveId);
175
+ }
176
+ outputJson(changeId, progress, tasks, waves) {
177
+ const output = {
178
+ changeId,
179
+ phase: progress?.phase || 'unknown',
180
+ currentTask: progress?.currentTask || null,
181
+ tasks: {
182
+ total: tasks.length,
183
+ completed: tasks.filter(t => t.status === 'completed').length,
184
+ pending: tasks.filter(t => t.status === 'pending').length,
185
+ },
186
+ waves: waves.map(w => ({
187
+ waveId: w.waveId,
188
+ status: w.status,
189
+ totalTasks: w.tasks.length,
190
+ completedTasks: w.completedTasks.length,
191
+ })),
192
+ progress: progress,
193
+ };
194
+ console.log(JSON.stringify(output, null, 2));
195
+ }
196
+ outputFormatted(changeId, progress, tasks, waves) {
197
+ const phase = progress?.phase || 'unknown';
198
+ const currentTask = progress?.currentTask || '';
199
+ const completedCount = tasks.filter(t => t.status === 'completed').length;
200
+ const totalTasks = tasks.length;
201
+ // Phase index
202
+ const phases = ['idle', 'propose', 'apply', 'review', 'archive'];
203
+ const phaseIndex = phases.indexOf(phase) + 1;
204
+ // Progress percentage
205
+ const percentage = totalTasks > 0 ? (completedCount / totalTasks) * 100 : 0;
206
+ const progressBar = this.generateProgressBar(percentage, 12);
207
+ // Duration
208
+ const durationMs = progress?.stats?.durationMs?.[phase] || 0;
209
+ const durationMin = Math.round(durationMs / 60000);
210
+ const estimatedRemaining = totalTasks > 0 && completedCount > 0
211
+ ? Math.round((durationMin / completedCount) * (totalTasks - completedCount))
212
+ : 0;
213
+ console.log('');
214
+ console.log(`๐Ÿ“Š ๅ˜ๆ›ด่ฟ›ๅบฆ: ${changeId}`);
215
+ console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”');
216
+ console.log('');
217
+ console.log(`Phase: ${phase} (${phaseIndex}/${phases.length})`);
218
+ console.log('');
219
+ // Wave status
220
+ for (const wave of waves) {
221
+ const waveStatusEmoji = wave.status === 'completed' ? 'โœ…' : wave.status === 'in_progress' ? '๐Ÿ”„' : 'โณ';
222
+ const waveStatusText = wave.status === 'completed' ? 'ๅฎŒๆˆ' : wave.status === 'in_progress' ? '่ฟ›่กŒไธญ' : 'ๅพ…ๅผ€ๅง‹';
223
+ console.log(`Wave ${wave.waveId}: ${waveStatusEmoji} ${waveStatusText}`);
224
+ for (const task of wave.tasks) {
225
+ const taskStatusEmoji = task.status === 'completed' ? 'โœ…' : task.status === 'in_progress' ? '๐Ÿ”„' : 'โณ';
226
+ const isCurrent = task.taskId === currentTask;
227
+ const currentIndicator = isCurrent ? ' โ† ๅฝ“ๅ‰' : '';
228
+ console.log(` ${taskStatusEmoji} ${task.taskId} ${task.description}${currentIndicator}`);
229
+ }
230
+ console.log('');
231
+ }
232
+ // Overall progress
233
+ console.log(`ๆ•ดไฝ“: ${progressBar} ${percentage.toFixed(1)}% (${completedCount}/${totalTasks})`);
234
+ // Duration info
235
+ if (durationMin > 0) {
236
+ console.log(`่€—ๆ—ถ: ${durationMin}m | ้ข„่ฎกๅ‰ฉไฝ™: ~${estimatedRemaining}m`);
237
+ }
238
+ console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”');
239
+ console.log('');
240
+ // Next task
241
+ const nextTask = tasks.find(t => t.status === 'pending');
242
+ if (nextTask) {
243
+ console.log(`โžก๏ธ ไธ‹ไธ€ไธชไปปๅŠก: ${nextTask.taskId}`);
244
+ }
245
+ else if (completedCount === totalTasks && totalTasks > 0) {
246
+ console.log('โœ… ๅ…จ้ƒจไปปๅŠกๅฎŒๆˆ๏ผŒๅ‡†ๅค‡่ฟ›ๅ…ฅ Review ้˜ถๆฎต');
247
+ }
248
+ }
249
+ generateProgressBar(percentage, blocks) {
250
+ const filled = Math.round((percentage / 100) * blocks);
251
+ const empty = blocks - filled;
252
+ return 'โ–ˆ'.repeat(filled) + 'โ–‘'.repeat(empty);
253
+ }
254
+ /**
255
+ * Set phase for a change
256
+ * Usage: zhuanspec progress set-phase <change-id> <phase>
257
+ */
258
+ async setPhase(changeId, phase) {
259
+ const cwd = process.cwd();
260
+ const changeDir = path.join(cwd, 'zhuanspec', 'changes', changeId);
261
+ if (!await FileSystemUtils.directoryExists(changeDir)) {
262
+ throw new Error(`Change '${changeId}' not found`);
263
+ }
264
+ // Validate phase
265
+ const normalizedPhase = phase.toLowerCase();
266
+ if (!PHASE_ORDER.includes(normalizedPhase)) {
267
+ throw new Error(`Invalid phase: ${phase}. Valid phases: ${PHASE_ORDER.join(', ')}`);
268
+ }
269
+ // Update progress.json
270
+ await initializeProgress(changeId, normalizedPhase);
271
+ // Read and display result
272
+ const progressPath = path.join(changeDir, 'metrics', 'progress.json');
273
+ const progress = JSON.parse(await FileSystemUtils.readFile(progressPath));
274
+ console.log(`\nโœ“ Phase set: ${changeId} -> ${normalizedPhase}`);
275
+ console.log(`โœ“ Progress file: ${progressPath}\n`);
276
+ }
277
+ }
278
+ //# sourceMappingURL=progress.js.map
@@ -0,0 +1,32 @@
1
+ export declare class ReviewCommand {
2
+ execute(changeName?: string, options?: {
3
+ json?: boolean;
4
+ testCommand?: string;
5
+ coverageThreshold?: string;
6
+ }): Promise<void>;
7
+ private selectChangeInteractively;
8
+ private collectRequirementNames;
9
+ private collectTouchedFiles;
10
+ private collectTouchedFileContent;
11
+ private runTests;
12
+ private buildSummary;
13
+ private buildSpecConsistency;
14
+ private printSummary;
15
+ /**
16
+ * Fetch Sonar issues using MCP
17
+ */
18
+ private fetchSonarIssues;
19
+ /**
20
+ * Collect all Scenarios from delta specs
21
+ */
22
+ private collectScenarios;
23
+ /**
24
+ * Find test files in the project
25
+ */
26
+ private findTestFiles;
27
+ /**
28
+ * Convert Scenario name to possible test method names
29
+ */
30
+ private scenarioToTestMethodNames;
31
+ }
32
+ //# sourceMappingURL=review.d.ts.map