specsmd 0.0.0-dev.6 → 0.0.0-dev.60

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 (73) hide show
  1. package/README.md +10 -2
  2. package/flows/aidlc/commands/construction-agent.md +5 -1
  3. package/flows/aidlc/commands/inception-agent.md +4 -0
  4. package/flows/aidlc/commands/master-agent.md +4 -0
  5. package/flows/aidlc/commands/operations-agent.md +4 -0
  6. package/flows/aidlc/memory-bank.yaml +2 -1
  7. package/{scripts → flows/aidlc/scripts}/artifact-validator.js +3 -3
  8. package/{scripts → flows/aidlc/scripts}/bolt-complete.js +35 -4
  9. package/{scripts → flows/aidlc/scripts}/status-integrity.js +4 -4
  10. package/flows/aidlc/skills/construction/bolt-list.md +1 -1
  11. package/flows/aidlc/skills/construction/bolt-start.md +2 -2
  12. package/flows/aidlc/skills/construction/bolt-status.md +1 -1
  13. package/flows/aidlc/skills/construction/prototype-apply.md +305 -0
  14. package/flows/aidlc/skills/inception/bolt-plan.md +15 -2
  15. package/flows/aidlc/skills/inception/vibe-to-spec.md +406 -0
  16. package/flows/aidlc/skills/master/analyze-context.md +1 -1
  17. package/flows/aidlc/templates/construction/bolt-template.md +22 -1
  18. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt.md +73 -11
  19. package/flows/aidlc/templates/construction/bolt-types/simple-construction-bolt.md +5 -0
  20. package/flows/aidlc/templates/standards/decision-index-template.md +32 -0
  21. package/flows/fire/README.md +19 -0
  22. package/flows/fire/agents/builder/agent.md +272 -0
  23. package/flows/fire/agents/builder/skills/run-execute/SKILL.md +455 -0
  24. package/flows/fire/agents/builder/skills/run-execute/scripts/complete-run.js +549 -0
  25. package/flows/fire/agents/builder/skills/run-execute/scripts/init-run.js +454 -0
  26. package/flows/fire/agents/builder/skills/run-execute/templates/plan.md.hbs +61 -0
  27. package/flows/fire/agents/builder/skills/run-execute/templates/test-report.md.hbs +81 -0
  28. package/flows/fire/agents/builder/skills/run-plan/SKILL.md +376 -0
  29. package/flows/fire/agents/builder/skills/run-status/SKILL.md +94 -0
  30. package/flows/fire/agents/builder/skills/walkthrough-generate/SKILL.md +140 -0
  31. package/flows/fire/agents/builder/skills/walkthrough-generate/scripts/render-walkthrough.ts +755 -0
  32. package/flows/fire/agents/builder/skills/walkthrough-generate/templates/walkthrough.md.hbs +77 -0
  33. package/flows/fire/agents/orchestrator/agent.md +113 -0
  34. package/flows/fire/agents/orchestrator/skills/project-init/SKILL.md +150 -0
  35. package/flows/fire/agents/orchestrator/skills/project-init/templates/coding-standards.md.hbs +149 -0
  36. package/flows/fire/agents/orchestrator/skills/project-init/templates/system-architecture.md.hbs +101 -0
  37. package/flows/fire/agents/orchestrator/skills/project-init/templates/tech-stack.md.hbs +136 -0
  38. package/flows/fire/agents/orchestrator/skills/project-init/templates/testing-standards.md.hbs +94 -0
  39. package/flows/fire/agents/orchestrator/skills/route/SKILL.md +123 -0
  40. package/flows/fire/agents/orchestrator/skills/status/SKILL.md +99 -0
  41. package/flows/fire/agents/planner/agent.md +122 -0
  42. package/flows/fire/agents/planner/skills/design-doc-generate/SKILL.md +213 -0
  43. package/flows/fire/agents/planner/skills/design-doc-generate/templates/design.md.hbs +76 -0
  44. package/flows/fire/agents/planner/skills/intent-capture/SKILL.md +155 -0
  45. package/flows/fire/agents/planner/skills/intent-capture/templates/brief.md.hbs +40 -0
  46. package/flows/fire/agents/planner/skills/work-item-decompose/SKILL.md +194 -0
  47. package/flows/fire/agents/planner/skills/work-item-decompose/templates/work-item.md.hbs +40 -0
  48. package/flows/fire/commands/fire-builder.md +56 -0
  49. package/flows/fire/commands/fire-planner.md +48 -0
  50. package/flows/fire/commands/fire.md +46 -0
  51. package/flows/fire/memory-bank.yaml +164 -0
  52. package/flows/fire/quick-start.md +130 -0
  53. package/flows/simple/README.md +190 -0
  54. package/flows/simple/agents/agent.md +404 -0
  55. package/flows/simple/commands/agent.md +60 -0
  56. package/flows/simple/context-config.yaml +34 -0
  57. package/flows/simple/memory-bank.yaml +66 -0
  58. package/flows/simple/quick-start.md +231 -0
  59. package/flows/simple/skills/design.md +96 -0
  60. package/flows/simple/skills/execute.md +190 -0
  61. package/flows/simple/skills/requirements.md +94 -0
  62. package/flows/simple/skills/tasks.md +136 -0
  63. package/flows/simple/templates/design-template.md +138 -0
  64. package/flows/simple/templates/requirements-template.md +85 -0
  65. package/flows/simple/templates/tasks-template.md +104 -0
  66. package/lib/analytics/tracker.js +6 -2
  67. package/lib/constants.js +17 -8
  68. package/lib/installer.js +5 -15
  69. package/lib/installers/KiroInstaller.js +55 -0
  70. package/lib/installers/OpenCodeInstaller.js +9 -1
  71. package/lib/installers/ToolInstaller.js +4 -1
  72. package/lib/installers/WindsurfInstaller.js +0 -54
  73. package/package.json +3 -52
@@ -0,0 +1,549 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * FIRE Run Completion Script
5
+ *
6
+ * Supports both single and batch/wide runs.
7
+ *
8
+ * For single runs: Completes the run and clears active_run.
9
+ * For batch/wide runs:
10
+ * - --complete-item: Marks current work item done, moves to next
11
+ * - --complete-run: Marks all items done and finalizes entire run
12
+ *
13
+ * Usage:
14
+ * Complete current item: node complete-run.js <rootPath> <runId> --complete-item [options]
15
+ * Complete entire run: node complete-run.js <rootPath> <runId> --complete-run [options]
16
+ * Complete (single/auto): node complete-run.js <rootPath> <runId> [options]
17
+ *
18
+ * Options:
19
+ * --files-created=JSON - JSON array of {path, purpose}
20
+ * --files-modified=JSON - JSON array of {path, changes}
21
+ * --decisions=JSON - JSON array of {decision, choice, rationale}
22
+ * --tests=N - Number of tests added
23
+ * --coverage=N - Coverage percentage
24
+ */
25
+
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+ const yaml = require('yaml');
29
+
30
+ // =============================================================================
31
+ // Error Helper
32
+ // =============================================================================
33
+
34
+ function fireError(message, code, suggestion) {
35
+ const err = new Error(`FIRE Error [${code}]: ${message} ${suggestion}`);
36
+ err.code = code;
37
+ err.suggestion = suggestion;
38
+ return err;
39
+ }
40
+
41
+ // =============================================================================
42
+ // Validation
43
+ // =============================================================================
44
+
45
+ function validateInputs(rootPath, runId) {
46
+ if (!rootPath || typeof rootPath !== 'string' || rootPath.trim() === '') {
47
+ throw fireError('rootPath is required.', 'COMPLETE_001', 'Provide a valid project root path.');
48
+ }
49
+
50
+ if (!runId || typeof runId !== 'string' || runId.trim() === '') {
51
+ throw fireError('runId is required.', 'COMPLETE_002', 'Provide the run ID to complete.');
52
+ }
53
+
54
+ if (!fs.existsSync(rootPath)) {
55
+ throw fireError(
56
+ `Project root not found: "${rootPath}".`,
57
+ 'COMPLETE_003',
58
+ 'Ensure the path exists and is accessible.'
59
+ );
60
+ }
61
+ }
62
+
63
+ function validateFireProject(rootPath, runId) {
64
+ const fireDir = path.join(rootPath, '.specs-fire');
65
+ const statePath = path.join(fireDir, 'state.yaml');
66
+ const runsPath = path.join(fireDir, 'runs');
67
+ const runPath = path.join(runsPath, runId);
68
+ const runLogPath = path.join(runPath, 'run.md');
69
+
70
+ if (!fs.existsSync(fireDir)) {
71
+ throw fireError(
72
+ `FIRE project not initialized at: "${rootPath}".`,
73
+ 'COMPLETE_010',
74
+ 'Run fire-init first to initialize the project.'
75
+ );
76
+ }
77
+
78
+ if (!fs.existsSync(statePath)) {
79
+ throw fireError(
80
+ `State file not found at: "${statePath}".`,
81
+ 'COMPLETE_011',
82
+ 'The project may be corrupted. Try re-initializing.'
83
+ );
84
+ }
85
+
86
+ if (!fs.existsSync(runPath)) {
87
+ throw fireError(
88
+ `Run folder not found: "${runPath}".`,
89
+ 'COMPLETE_012',
90
+ `Ensure run "${runId}" was properly initialized.`
91
+ );
92
+ }
93
+
94
+ if (!fs.existsSync(runLogPath)) {
95
+ throw fireError(
96
+ `Run log not found: "${runLogPath}".`,
97
+ 'COMPLETE_013',
98
+ `The run may have been partially initialized.`
99
+ );
100
+ }
101
+
102
+ return { statePath, runPath, runLogPath };
103
+ }
104
+
105
+ // =============================================================================
106
+ // State Operations
107
+ // =============================================================================
108
+
109
+ function readState(statePath) {
110
+ try {
111
+ const content = fs.readFileSync(statePath, 'utf8');
112
+ const state = yaml.parse(content);
113
+ if (!state || typeof state !== 'object') {
114
+ throw fireError('State file is empty or invalid.', 'COMPLETE_020', 'Check state.yaml format.');
115
+ }
116
+ return state;
117
+ } catch (err) {
118
+ if (err.code && err.code.startsWith('COMPLETE_')) throw err;
119
+ throw fireError(
120
+ `Failed to read state file: ${err.message}`,
121
+ 'COMPLETE_021',
122
+ 'Check file permissions and YAML syntax.'
123
+ );
124
+ }
125
+ }
126
+
127
+ function writeState(statePath, state) {
128
+ try {
129
+ fs.writeFileSync(statePath, yaml.stringify(state));
130
+ } catch (err) {
131
+ throw fireError(
132
+ `Failed to write state file: ${err.message}`,
133
+ 'COMPLETE_022',
134
+ 'Check file permissions and disk space.'
135
+ );
136
+ }
137
+ }
138
+
139
+ // =============================================================================
140
+ // Run Log Operations
141
+ // =============================================================================
142
+
143
+ function updateRunLog(runLogPath, activeRun, params, completedTime, isFullCompletion) {
144
+ let content;
145
+ try {
146
+ content = fs.readFileSync(runLogPath, 'utf8');
147
+ } catch (err) {
148
+ throw fireError(
149
+ `Failed to read run log: ${err.message}`,
150
+ 'COMPLETE_030',
151
+ 'Check file permissions.'
152
+ );
153
+ }
154
+
155
+ // If full completion, update run status
156
+ if (isFullCompletion) {
157
+ content = content.replace(/status: in_progress/, 'status: completed');
158
+ content = content.replace(/completed: null/, `completed: ${completedTime}`);
159
+ }
160
+
161
+ // Update work items status in frontmatter
162
+ if (activeRun.work_items && Array.isArray(activeRun.work_items)) {
163
+ for (const item of activeRun.work_items) {
164
+ // Update status in YAML frontmatter
165
+ const statusPattern = new RegExp(`(id: ${item.id}\\n\\s+intent: [^\\n]+\\n\\s+mode: [^\\n]+\\n\\s+status: )(\\w+)`);
166
+ content = content.replace(statusPattern, `$1${item.status}`);
167
+
168
+ // Update status in markdown body
169
+ const bodyPattern = new RegExp(`(\\*\\*${item.id}\\*\\* \\([^)]+\\) — )(\\w+)`);
170
+ content = content.replace(bodyPattern, `$1${item.status}`);
171
+ }
172
+
173
+ // Update current_item in frontmatter
174
+ content = content.replace(/current_item: [^\n]+/, `current_item: ${activeRun.current_item || 'none'}`);
175
+
176
+ // Update Current Item section
177
+ if (activeRun.current_item) {
178
+ const currentItem = activeRun.work_items.find(i => i.id === activeRun.current_item);
179
+ if (currentItem) {
180
+ content = content.replace(/## Current Item\n[^\n]+/, `## Current Item\n${currentItem.id} (${currentItem.mode})`);
181
+ }
182
+ } else {
183
+ content = content.replace(/## Current Item\n[^\n]+/, `## Current Item\n(all completed)`);
184
+ }
185
+ }
186
+
187
+ // Format file lists (only on full completion)
188
+ if (isFullCompletion) {
189
+ const filesCreatedText = params.filesCreated.length > 0
190
+ ? params.filesCreated.map(f => `- \`${f.path}\`: ${f.purpose || '(no purpose)'}`).join('\n')
191
+ : '(none)';
192
+
193
+ const filesModifiedText = params.filesModified.length > 0
194
+ ? params.filesModified.map(f => `- \`${f.path}\`: ${f.changes || '(no changes)'}`).join('\n')
195
+ : '(none)';
196
+
197
+ const decisionsText = params.decisions.length > 0
198
+ ? params.decisions.map(d => `- **${d.decision}**: ${d.choice} (${d.rationale || 'no rationale'})`).join('\n')
199
+ : '(none)';
200
+
201
+ // Replace placeholder sections
202
+ content = content.replace('## Files Created\n(none yet)', `## Files Created\n${filesCreatedText}`);
203
+ content = content.replace('## Files Modified\n(none yet)', `## Files Modified\n${filesModifiedText}`);
204
+ content = content.replace('## Decisions\n(none yet)', `## Decisions\n${decisionsText}`);
205
+
206
+ // Add summary if not present
207
+ if (!content.includes('## Summary')) {
208
+ const itemCount = activeRun.work_items ? activeRun.work_items.length : 1;
209
+ content += `
210
+
211
+ ## Summary
212
+
213
+ - Work items completed: ${itemCount}
214
+ - Files created: ${params.filesCreated.length}
215
+ - Files modified: ${params.filesModified.length}
216
+ - Tests added: ${params.testsAdded}
217
+ - Coverage: ${params.coverage}%
218
+ - Completed: ${completedTime}
219
+ `;
220
+ }
221
+ }
222
+
223
+ try {
224
+ fs.writeFileSync(runLogPath, content);
225
+ } catch (err) {
226
+ throw fireError(
227
+ `Failed to write run log: ${err.message}`,
228
+ 'COMPLETE_031',
229
+ 'Check file permissions.'
230
+ );
231
+ }
232
+ }
233
+
234
+ // =============================================================================
235
+ // Complete Current Item (for batch runs)
236
+ // =============================================================================
237
+
238
+ function completeCurrentItem(rootPath, runId, params = {}) {
239
+ const completionParams = {
240
+ filesCreated: params.filesCreated || [],
241
+ filesModified: params.filesModified || [],
242
+ decisions: params.decisions || [],
243
+ testsAdded: params.testsAdded || 0,
244
+ coverage: params.coverage || 0,
245
+ };
246
+
247
+ validateInputs(rootPath, runId);
248
+ const { statePath, runLogPath } = validateFireProject(rootPath, runId);
249
+ const state = readState(statePath);
250
+
251
+ if (!state.active_run) {
252
+ throw fireError(
253
+ 'No active run found in state.yaml.',
254
+ 'COMPLETE_040',
255
+ 'The run may have already been completed or was never started.'
256
+ );
257
+ }
258
+
259
+ if (state.active_run.id !== runId) {
260
+ throw fireError(
261
+ `Run ID mismatch. Active run is "${state.active_run.id}" but trying to complete "${runId}".`,
262
+ 'COMPLETE_041',
263
+ `Complete the active run "${state.active_run.id}" first.`
264
+ );
265
+ }
266
+
267
+ const completedTime = new Date().toISOString();
268
+ const workItems = state.active_run.work_items || [];
269
+ const currentItemId = state.active_run.current_item;
270
+
271
+ // Find and mark current item as completed
272
+ let currentItemIndex = -1;
273
+ for (let i = 0; i < workItems.length; i++) {
274
+ if (workItems[i].id === currentItemId) {
275
+ workItems[i].status = 'completed';
276
+ workItems[i].completed_at = completedTime;
277
+ currentItemIndex = i;
278
+ break;
279
+ }
280
+ }
281
+
282
+ if (currentItemIndex === -1) {
283
+ throw fireError(
284
+ `Current item "${currentItemId}" not found in work items.`,
285
+ 'COMPLETE_050',
286
+ 'The run state may be corrupted.'
287
+ );
288
+ }
289
+
290
+ // Find next pending item
291
+ let nextItem = null;
292
+ for (let i = currentItemIndex + 1; i < workItems.length; i++) {
293
+ if (workItems[i].status === 'pending') {
294
+ workItems[i].status = 'in_progress';
295
+ nextItem = workItems[i];
296
+ break;
297
+ }
298
+ }
299
+
300
+ // Update state
301
+ state.active_run.work_items = workItems;
302
+ state.active_run.current_item = nextItem ? nextItem.id : null;
303
+
304
+ // Update run log
305
+ updateRunLog(runLogPath, state.active_run, completionParams, completedTime, false);
306
+
307
+ // Save state
308
+ writeState(statePath, state);
309
+
310
+ return {
311
+ success: true,
312
+ runId: runId,
313
+ completedItem: currentItemId,
314
+ nextItem: nextItem ? nextItem.id : null,
315
+ remainingItems: workItems.filter(i => i.status === 'pending').length,
316
+ allItemsCompleted: nextItem === null,
317
+ completedAt: completedTime,
318
+ };
319
+ }
320
+
321
+ // =============================================================================
322
+ // Complete Entire Run
323
+ // =============================================================================
324
+
325
+ function completeRun(rootPath, runId, params = {}) {
326
+ const completionParams = {
327
+ filesCreated: params.filesCreated || [],
328
+ filesModified: params.filesModified || [],
329
+ decisions: params.decisions || [],
330
+ testsAdded: params.testsAdded || 0,
331
+ coverage: params.coverage || 0,
332
+ };
333
+
334
+ validateInputs(rootPath, runId);
335
+ const { statePath, runLogPath } = validateFireProject(rootPath, runId);
336
+ const state = readState(statePath);
337
+
338
+ if (!state.active_run) {
339
+ throw fireError(
340
+ 'No active run found in state.yaml.',
341
+ 'COMPLETE_040',
342
+ 'The run may have already been completed or was never started.'
343
+ );
344
+ }
345
+
346
+ if (state.active_run.id !== runId) {
347
+ throw fireError(
348
+ `Run ID mismatch. Active run is "${state.active_run.id}" but trying to complete "${runId}".`,
349
+ 'COMPLETE_041',
350
+ `Complete the active run "${state.active_run.id}" first.`
351
+ );
352
+ }
353
+
354
+ const completedTime = new Date().toISOString();
355
+ const workItems = state.active_run.work_items || [];
356
+ const scope = state.active_run.scope || 'single';
357
+
358
+ // Mark all items as completed
359
+ for (const item of workItems) {
360
+ if (item.status !== 'completed') {
361
+ item.status = 'completed';
362
+ item.completed_at = completedTime;
363
+ }
364
+ }
365
+
366
+ state.active_run.work_items = workItems;
367
+ state.active_run.current_item = null;
368
+
369
+ // Update run log
370
+ updateRunLog(runLogPath, state.active_run, completionParams, completedTime, true);
371
+
372
+ // Build completed run record
373
+ const completedRun = {
374
+ id: runId,
375
+ scope: scope,
376
+ work_items: workItems.map(i => ({
377
+ id: i.id,
378
+ intent: i.intent,
379
+ mode: i.mode,
380
+ })),
381
+ completed: completedTime,
382
+ };
383
+
384
+ // Get existing runs history or initialize
385
+ const existingRuns = state.runs || { completed: [] };
386
+ const existingCompleted = Array.isArray(existingRuns.completed) ? existingRuns.completed : [];
387
+
388
+ // Check for duplicate (idempotency)
389
+ const alreadyRecorded = existingCompleted.some(r => r.id === runId);
390
+
391
+ // Update work item status in intents
392
+ if (Array.isArray(state.intents)) {
393
+ for (const workItem of workItems) {
394
+ for (const intent of state.intents) {
395
+ if (intent.id === workItem.intent && Array.isArray(intent.work_items)) {
396
+ for (const wi of intent.work_items) {
397
+ if (wi.id === workItem.id) {
398
+ wi.status = 'completed';
399
+ wi.run_id = runId;
400
+ break;
401
+ }
402
+ }
403
+ }
404
+ }
405
+ }
406
+ }
407
+
408
+ // Update state
409
+ state.active_run = null;
410
+ state.runs = {
411
+ completed: alreadyRecorded ? existingCompleted : [...existingCompleted, completedRun],
412
+ };
413
+
414
+ // Save state
415
+ writeState(statePath, state);
416
+
417
+ return {
418
+ success: true,
419
+ runId: runId,
420
+ scope: scope,
421
+ workItemsCompleted: workItems.length,
422
+ completedAt: completedTime,
423
+ filesCreated: completionParams.filesCreated.length,
424
+ filesModified: completionParams.filesModified.length,
425
+ testsAdded: completionParams.testsAdded,
426
+ coverage: completionParams.coverage,
427
+ };
428
+ }
429
+
430
+ // =============================================================================
431
+ // CLI Argument Parsing
432
+ // =============================================================================
433
+
434
+ function parseArgs(args) {
435
+ const result = {
436
+ rootPath: args[0],
437
+ runId: args[1],
438
+ completeItem: false,
439
+ completeRunFlag: false,
440
+ filesCreated: [],
441
+ filesModified: [],
442
+ decisions: [],
443
+ testsAdded: 0,
444
+ coverage: 0,
445
+ };
446
+
447
+ for (let i = 2; i < args.length; i++) {
448
+ const arg = args[i];
449
+ if (arg === '--complete-item') {
450
+ result.completeItem = true;
451
+ } else if (arg === '--complete-run') {
452
+ result.completeRunFlag = true;
453
+ } else if (arg.startsWith('--files-created=')) {
454
+ try {
455
+ result.filesCreated = JSON.parse(arg.substring('--files-created='.length));
456
+ } catch (e) {
457
+ console.error('Warning: Could not parse --files-created JSON');
458
+ }
459
+ } else if (arg.startsWith('--files-modified=')) {
460
+ try {
461
+ result.filesModified = JSON.parse(arg.substring('--files-modified='.length));
462
+ } catch (e) {
463
+ console.error('Warning: Could not parse --files-modified JSON');
464
+ }
465
+ } else if (arg.startsWith('--decisions=')) {
466
+ try {
467
+ result.decisions = JSON.parse(arg.substring('--decisions='.length));
468
+ } catch (e) {
469
+ console.error('Warning: Could not parse --decisions JSON');
470
+ }
471
+ } else if (arg.startsWith('--tests=')) {
472
+ result.testsAdded = parseInt(arg.substring('--tests='.length), 10) || 0;
473
+ } else if (arg.startsWith('--coverage=')) {
474
+ result.coverage = parseFloat(arg.substring('--coverage='.length)) || 0;
475
+ }
476
+ }
477
+
478
+ return result;
479
+ }
480
+
481
+ function printUsage() {
482
+ console.error('Usage:');
483
+ console.error(' Complete current item: node complete-run.js <rootPath> <runId> --complete-item [options]');
484
+ console.error(' Complete entire run: node complete-run.js <rootPath> <runId> --complete-run [options]');
485
+ console.error(' Auto (single runs): node complete-run.js <rootPath> <runId> [options]');
486
+ console.error('');
487
+ console.error('Arguments:');
488
+ console.error(' rootPath - Project root directory');
489
+ console.error(' runId - Run ID to complete (e.g., run-003)');
490
+ console.error('');
491
+ console.error('Flags:');
492
+ console.error(' --complete-item - Complete only the current work item (batch/wide runs)');
493
+ console.error(' --complete-run - Complete the entire run');
494
+ console.error('');
495
+ console.error('Options:');
496
+ console.error(' --files-created=JSON - JSON array of {path, purpose}');
497
+ console.error(' --files-modified=JSON - JSON array of {path, changes}');
498
+ console.error(' --decisions=JSON - JSON array of {decision, choice, rationale}');
499
+ console.error(' --tests=N - Number of tests added');
500
+ console.error(' --coverage=N - Coverage percentage');
501
+ console.error('');
502
+ console.error('Examples:');
503
+ console.error(' node complete-run.js /project run-003 --complete-item');
504
+ console.error(' node complete-run.js /project run-003 --complete-run --tests=5 --coverage=85');
505
+ }
506
+
507
+ // =============================================================================
508
+ // CLI Interface
509
+ // =============================================================================
510
+
511
+ if (require.main === module) {
512
+ const args = process.argv.slice(2);
513
+
514
+ if (args.length < 2) {
515
+ printUsage();
516
+ process.exit(1);
517
+ }
518
+
519
+ const params = parseArgs(args);
520
+
521
+ try {
522
+ let result;
523
+ if (params.completeItem) {
524
+ result = completeCurrentItem(params.rootPath, params.runId, {
525
+ filesCreated: params.filesCreated,
526
+ filesModified: params.filesModified,
527
+ decisions: params.decisions,
528
+ testsAdded: params.testsAdded,
529
+ coverage: params.coverage,
530
+ });
531
+ } else {
532
+ // Default: complete entire run
533
+ result = completeRun(params.rootPath, params.runId, {
534
+ filesCreated: params.filesCreated,
535
+ filesModified: params.filesModified,
536
+ decisions: params.decisions,
537
+ testsAdded: params.testsAdded,
538
+ coverage: params.coverage,
539
+ });
540
+ }
541
+ console.log(JSON.stringify(result, null, 2));
542
+ process.exit(0);
543
+ } catch (err) {
544
+ console.error(err.message);
545
+ process.exit(1);
546
+ }
547
+ }
548
+
549
+ module.exports = { completeRun, completeCurrentItem };