specsmd 0.0.0-dev.78 → 0.0.0-dev.79

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 (36) hide show
  1. package/README.md +172 -236
  2. package/flows/aidlc/scripts/{artifact-validator.js → artifact-validator.cjs} +3 -3
  3. package/flows/aidlc/scripts/{bolt-complete.js → bolt-complete.cjs} +3 -3
  4. package/flows/aidlc/scripts/{status-integrity.js → status-integrity.cjs} +5 -5
  5. package/flows/aidlc/skills/construction/bolt-start.md +3 -3
  6. package/flows/aidlc/skills/construction/prototype-apply.md +6 -0
  7. package/flows/aidlc/skills/inception/vibe-to-spec.md +4 -0
  8. package/flows/fire/agents/builder/agent.md +19 -17
  9. package/flows/fire/agents/builder/skills/code-review/SKILL.md +9 -6
  10. package/flows/fire/agents/builder/skills/code-review/references/auto-fix-rules.md +6 -0
  11. package/flows/fire/agents/builder/skills/run-execute/SKILL.md +181 -62
  12. package/flows/fire/agents/builder/skills/run-execute/scripts/{complete-run.js → complete-run.cjs} +274 -75
  13. package/flows/fire/agents/builder/skills/run-execute/scripts/{init-run.js → init-run.cjs} +22 -19
  14. package/flows/fire/agents/builder/skills/run-execute/scripts/update-phase.cjs +239 -0
  15. package/flows/fire/agents/builder/skills/run-plan/SKILL.md +44 -46
  16. package/flows/fire/agents/builder/skills/run-status/SKILL.md +3 -1
  17. package/flows/fire/agents/builder/skills/walkthrough-generate/SKILL.md +131 -7
  18. package/flows/fire/agents/builder/skills/walkthrough-generate/templates/walkthrough.md.hbs +99 -0
  19. package/flows/fire/agents/orchestrator/agent.md +22 -5
  20. package/flows/fire/agents/orchestrator/skills/project-init/SKILL.md +2 -1
  21. package/flows/fire/agents/orchestrator/skills/route/SKILL.md +11 -6
  22. package/flows/fire/agents/orchestrator/skills/status/SKILL.md +609 -13
  23. package/flows/fire/agents/planner/agent.md +5 -0
  24. package/flows/fire/agents/planner/skills/design-doc-generate/SKILL.md +1 -0
  25. package/flows/fire/agents/planner/skills/intent-capture/SKILL.md +1 -0
  26. package/flows/fire/agents/planner/skills/work-item-decompose/SKILL.md +1 -0
  27. package/flows/fire/commands/fire-builder.md +1 -1
  28. package/flows/fire/commands/fire-planner.md +1 -1
  29. package/flows/fire/commands/fire.md +1 -1
  30. package/flows/fire/memory-bank.yaml +29 -12
  31. package/flows/fire/quick-start.md +16 -0
  32. package/lib/constants.js +6 -3
  33. package/lib/installer.js +14 -0
  34. package/package.json +1 -1
  35. package/flows/fire/agents/builder/skills/walkthrough-generate/scripts/render-walkthrough.ts +0 -755
  36. package/flows/fire/agents/orchestrator/skills/project-migrate/SKILL.md +0 -234
@@ -5,15 +5,15 @@
5
5
  *
6
6
  * Supports both single and batch/wide runs.
7
7
  *
8
- * For single runs: Completes the run and clears active_run.
8
+ * For single runs: Completes the run and removes from runs.active[].
9
9
  * For batch/wide runs:
10
10
  * - --complete-item: Marks current work item done, moves to next
11
11
  * - --complete-run: Marks all items done and finalizes entire run
12
12
  *
13
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]
14
+ * Complete current item: node complete-run.cjs <rootPath> <runId> --complete-item [options]
15
+ * Complete entire run: node complete-run.cjs <rootPath> <runId> --complete-run [options]
16
+ * Complete (single/auto): node complete-run.cjs <rootPath> <runId> [options]
17
17
  *
18
18
  * Options:
19
19
  * --files-created=JSON - JSON array of {path, purpose}
@@ -102,6 +102,128 @@ function validateFireProject(rootPath, runId) {
102
102
  return { statePath, runPath, runLogPath };
103
103
  }
104
104
 
105
+ // =============================================================================
106
+ // Frontmatter Helpers
107
+ // =============================================================================
108
+
109
+ /**
110
+ * Parse YAML frontmatter from markdown content.
111
+ * Returns { frontmatter: object, body: string } or null if no frontmatter.
112
+ */
113
+ function parseFrontmatter(content) {
114
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
115
+ if (!match) return null;
116
+
117
+ try {
118
+ const frontmatter = yaml.parse(match[1]);
119
+ const body = content.slice(match[0].length);
120
+ return { frontmatter, body };
121
+ } catch (err) {
122
+ return null;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Reconstruct markdown content from frontmatter and body.
128
+ */
129
+ function buildMarkdownWithFrontmatter(frontmatter, body) {
130
+ return `---\n${yaml.stringify(frontmatter)}---${body}`;
131
+ }
132
+
133
+ // =============================================================================
134
+ // Markdown Frontmatter Sync
135
+ // =============================================================================
136
+
137
+ /**
138
+ * Update work item markdown file frontmatter with new status.
139
+ */
140
+ function updateWorkItemMarkdown(rootPath, intentId, workItemId, status, runId, completedAt) {
141
+ const filePath = path.join(rootPath, '.specs-fire', 'intents', intentId, 'work-items', `${workItemId}.md`);
142
+
143
+ if (!fs.existsSync(filePath)) {
144
+ // File doesn't exist - not an error, just skip
145
+ return false;
146
+ }
147
+
148
+ try {
149
+ const content = fs.readFileSync(filePath, 'utf8');
150
+ const parsed = parseFrontmatter(content);
151
+
152
+ if (!parsed) {
153
+ // No valid frontmatter - skip
154
+ return false;
155
+ }
156
+
157
+ // Update frontmatter fields
158
+ parsed.frontmatter.status = status;
159
+ if (runId) parsed.frontmatter.run_id = runId;
160
+ if (completedAt && status === 'completed') {
161
+ parsed.frontmatter.completed_at = completedAt;
162
+ }
163
+
164
+ const newContent = buildMarkdownWithFrontmatter(parsed.frontmatter, parsed.body);
165
+ fs.writeFileSync(filePath, newContent);
166
+ return true;
167
+ } catch (err) {
168
+ // Log but don't fail - markdown sync is best-effort
169
+ console.error(`Warning: Could not update work item markdown ${filePath}: ${err.message}`);
170
+ return false;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Update intent brief.md frontmatter based on work item statuses.
176
+ */
177
+ function updateIntentMarkdown(rootPath, intentId, state) {
178
+ const filePath = path.join(rootPath, '.specs-fire', 'intents', intentId, 'brief.md');
179
+
180
+ if (!fs.existsSync(filePath)) {
181
+ return false;
182
+ }
183
+
184
+ try {
185
+ // Determine intent status from its work items
186
+ const intent = state.intents?.find(i => i.id === intentId);
187
+ if (!intent || !Array.isArray(intent.work_items)) {
188
+ return false;
189
+ }
190
+
191
+ const allCompleted = intent.work_items.every(wi => wi.status === 'completed');
192
+ const anyInProgress = intent.work_items.some(wi => wi.status === 'in_progress');
193
+
194
+ let newStatus = 'pending';
195
+ if (allCompleted) {
196
+ newStatus = 'completed';
197
+ } else if (anyInProgress || intent.work_items.some(wi => wi.status === 'completed')) {
198
+ newStatus = 'in_progress';
199
+ }
200
+
201
+ const content = fs.readFileSync(filePath, 'utf8');
202
+ const parsed = parseFrontmatter(content);
203
+
204
+ if (!parsed) {
205
+ return false;
206
+ }
207
+
208
+ // Only update if status actually changed
209
+ if (parsed.frontmatter.status === newStatus) {
210
+ return false;
211
+ }
212
+
213
+ parsed.frontmatter.status = newStatus;
214
+ if (allCompleted) {
215
+ parsed.frontmatter.completed_at = new Date().toISOString();
216
+ }
217
+
218
+ const newContent = buildMarkdownWithFrontmatter(parsed.frontmatter, parsed.body);
219
+ fs.writeFileSync(filePath, newContent);
220
+ return true;
221
+ } catch (err) {
222
+ console.error(`Warning: Could not update intent markdown ${filePath}: ${err.message}`);
223
+ return false;
224
+ }
225
+ }
226
+
105
227
  // =============================================================================
106
228
  // State Operations
107
229
  // =============================================================================
@@ -140,6 +262,10 @@ function writeState(statePath, state) {
140
262
  // Run Log Operations
141
263
  // =============================================================================
142
264
 
265
+ /**
266
+ * Update run.md using proper YAML parsing instead of fragile regex.
267
+ * This ensures frontmatter updates work regardless of field order or formatting.
268
+ */
143
269
  function updateRunLog(runLogPath, activeRun, params, completedTime, isFullCompletion) {
144
270
  let content;
145
271
  try {
@@ -152,35 +278,62 @@ function updateRunLog(runLogPath, activeRun, params, completedTime, isFullComple
152
278
  );
153
279
  }
154
280
 
155
- // If full completion, update run status
281
+ // Parse frontmatter using YAML (robust approach)
282
+ const parsed = parseFrontmatter(content);
283
+ if (!parsed) {
284
+ throw fireError(
285
+ 'Invalid run.md format - no valid YAML frontmatter found.',
286
+ 'COMPLETE_032',
287
+ 'Ensure run.md has valid ---\\n...\\n--- frontmatter.'
288
+ );
289
+ }
290
+
291
+ let { frontmatter, body } = parsed;
292
+
293
+ // Update frontmatter fields
156
294
  if (isFullCompletion) {
157
- content = content.replace(/status: in_progress/, 'status: completed');
158
- content = content.replace(/completed: null/, `completed: ${completedTime}`);
295
+ frontmatter.status = 'completed';
296
+ frontmatter.completed = completedTime;
159
297
  }
160
298
 
161
- // Update work items status in frontmatter
299
+ frontmatter.current_item = activeRun.current_item || null;
300
+
301
+ // Update work_items array in frontmatter
162
302
  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
- }
303
+ frontmatter.work_items = activeRun.work_items.map(item => ({
304
+ id: item.id,
305
+ intent: item.intent,
306
+ mode: item.mode,
307
+ status: item.status,
308
+ }));
309
+ }
310
+
311
+ // Update markdown body sections
312
+ // Update Work Items section
313
+ if (activeRun.work_items && Array.isArray(activeRun.work_items)) {
314
+ const workItemsLines = activeRun.work_items.map((item, i) =>
315
+ `${i + 1}. **${item.id}** (${item.mode}) — ${item.status}`
316
+ ).join('\n');
172
317
 
173
- // Update current_item in frontmatter
174
- content = content.replace(/current_item: [^\n]+/, `current_item: ${activeRun.current_item || 'none'}`);
318
+ body = body.replace(
319
+ /## Work Items\n[\s\S]*?(?=\n## )/,
320
+ `## Work Items\n${workItemsLines}\n\n`
321
+ );
175
322
 
176
323
  // Update Current Item section
177
324
  if (activeRun.current_item) {
178
325
  const currentItem = activeRun.work_items.find(i => i.id === activeRun.current_item);
179
326
  if (currentItem) {
180
- content = content.replace(/## Current Item\n[^\n]+/, `## Current Item\n${currentItem.id} (${currentItem.mode})`);
327
+ body = body.replace(
328
+ /## Current Item\n[^\n]+/,
329
+ `## Current Item\n${currentItem.id} (${currentItem.mode})`
330
+ );
181
331
  }
182
332
  } else {
183
- content = content.replace(/## Current Item\n[^\n]+/, `## Current Item\n(all completed)`);
333
+ body = body.replace(
334
+ /## Current Item\n[^\n]+/,
335
+ `## Current Item\n(all completed)`
336
+ );
184
337
  }
185
338
  }
186
339
 
@@ -199,14 +352,14 @@ function updateRunLog(runLogPath, activeRun, params, completedTime, isFullComple
199
352
  : '(none)';
200
353
 
201
354
  // 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}`);
355
+ body = body.replace('## Files Created\n(none yet)', `## Files Created\n${filesCreatedText}`);
356
+ body = body.replace('## Files Modified\n(none yet)', `## Files Modified\n${filesModifiedText}`);
357
+ body = body.replace('## Decisions\n(none yet)', `## Decisions\n${decisionsText}`);
205
358
 
206
359
  // Add summary if not present
207
- if (!content.includes('## Summary')) {
360
+ if (!body.includes('## Summary')) {
208
361
  const itemCount = activeRun.work_items ? activeRun.work_items.length : 1;
209
- content += `
362
+ body += `
210
363
 
211
364
  ## Summary
212
365
 
@@ -220,8 +373,11 @@ function updateRunLog(runLogPath, activeRun, params, completedTime, isFullComple
220
373
  }
221
374
  }
222
375
 
376
+ // Reconstruct content with updated frontmatter and body
377
+ const newContent = buildMarkdownWithFrontmatter(frontmatter, body);
378
+
223
379
  try {
224
- fs.writeFileSync(runLogPath, content);
380
+ fs.writeFileSync(runLogPath, newContent);
225
381
  } catch (err) {
226
382
  throw fireError(
227
383
  `Failed to write run log: ${err.message}`,
@@ -248,25 +404,22 @@ function completeCurrentItem(rootPath, runId, params = {}) {
248
404
  const { statePath, runLogPath } = validateFireProject(rootPath, runId);
249
405
  const state = readState(statePath);
250
406
 
251
- if (!state.active_run) {
407
+ // Find run in active runs list
408
+ const activeRuns = state.runs?.active || [];
409
+ const runIndex = activeRuns.findIndex(r => r.id === runId);
410
+
411
+ if (runIndex === -1) {
252
412
  throw fireError(
253
- 'No active run found in state.yaml.',
413
+ `Run "${runId}" not found in active runs.`,
254
414
  'COMPLETE_040',
255
415
  'The run may have already been completed or was never started.'
256
416
  );
257
417
  }
258
418
 
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
-
419
+ const activeRun = activeRuns[runIndex];
267
420
  const completedTime = new Date().toISOString();
268
- const workItems = state.active_run.work_items || [];
269
- const currentItemId = state.active_run.current_item;
421
+ const workItems = activeRun.work_items || [];
422
+ const currentItemId = activeRun.current_item;
270
423
 
271
424
  // Find and mark current item as completed
272
425
  let currentItemIndex = -1;
@@ -292,17 +445,39 @@ function completeCurrentItem(rootPath, runId, params = {}) {
292
445
  for (let i = currentItemIndex + 1; i < workItems.length; i++) {
293
446
  if (workItems[i].status === 'pending') {
294
447
  workItems[i].status = 'in_progress';
448
+ workItems[i].current_phase = 'plan';
295
449
  nextItem = workItems[i];
296
450
  break;
297
451
  }
298
452
  }
299
453
 
300
- // Update state
301
- state.active_run.work_items = workItems;
302
- state.active_run.current_item = nextItem ? nextItem.id : null;
454
+ // Update active run in list
455
+ activeRun.work_items = workItems;
456
+ activeRun.current_item = nextItem ? nextItem.id : null;
457
+ state.runs.active[runIndex] = activeRun;
303
458
 
304
459
  // Update run log
305
- updateRunLog(runLogPath, state.active_run, completionParams, completedTime, false);
460
+ updateRunLog(runLogPath, activeRun, completionParams, completedTime, false);
461
+
462
+ // Sync markdown frontmatter for completed work item
463
+ const completedWorkItem = workItems.find(wi => wi.id === currentItemId);
464
+ if (completedWorkItem) {
465
+ updateWorkItemMarkdown(
466
+ rootPath,
467
+ completedWorkItem.intent,
468
+ currentItemId,
469
+ 'completed',
470
+ runId,
471
+ completedTime
472
+ );
473
+ // Update intent status based on its work items
474
+ updateIntentMarkdown(rootPath, completedWorkItem.intent, state);
475
+ }
476
+
477
+ // Also update next item's markdown to in_progress
478
+ if (nextItem) {
479
+ updateWorkItemMarkdown(rootPath, nextItem.intent, nextItem.id, 'in_progress', null, null);
480
+ }
306
481
 
307
482
  // Save state
308
483
  writeState(statePath, state);
@@ -335,25 +510,32 @@ function completeRun(rootPath, runId, params = {}) {
335
510
  const { statePath, runLogPath } = validateFireProject(rootPath, runId);
336
511
  const state = readState(statePath);
337
512
 
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
- );
513
+ // Initialize runs structure if needed
514
+ if (!state.runs) {
515
+ state.runs = { active: [], completed: [] };
516
+ }
517
+ if (!Array.isArray(state.runs.active)) {
518
+ state.runs.active = [];
519
+ }
520
+ if (!Array.isArray(state.runs.completed)) {
521
+ state.runs.completed = [];
344
522
  }
345
523
 
346
- if (state.active_run.id !== runId) {
524
+ // Find run in active runs list
525
+ const runIndex = state.runs.active.findIndex(r => r.id === runId);
526
+
527
+ if (runIndex === -1) {
347
528
  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.`
529
+ `Run "${runId}" not found in active runs.`,
530
+ 'COMPLETE_040',
531
+ 'The run may have already been completed or was never started.'
351
532
  );
352
533
  }
353
534
 
535
+ const activeRun = state.runs.active[runIndex];
354
536
  const completedTime = new Date().toISOString();
355
- const workItems = state.active_run.work_items || [];
356
- const scope = state.active_run.scope || 'single';
537
+ const workItems = activeRun.work_items || [];
538
+ const scope = activeRun.scope || 'single';
357
539
 
358
540
  // Mark all items as completed
359
541
  for (const item of workItems) {
@@ -363,11 +545,11 @@ function completeRun(rootPath, runId, params = {}) {
363
545
  }
364
546
  }
365
547
 
366
- state.active_run.work_items = workItems;
367
- state.active_run.current_item = null;
548
+ activeRun.work_items = workItems;
549
+ activeRun.current_item = null;
368
550
 
369
551
  // Update run log
370
- updateRunLog(runLogPath, state.active_run, completionParams, completedTime, true);
552
+ updateRunLog(runLogPath, activeRun, completionParams, completedTime, true);
371
553
 
372
554
  // Build completed run record
373
555
  const completedRun = {
@@ -378,17 +560,15 @@ function completeRun(rootPath, runId, params = {}) {
378
560
  intent: i.intent,
379
561
  mode: i.mode,
380
562
  })),
563
+ started: activeRun.started,
381
564
  completed: completedTime,
382
565
  };
383
566
 
384
- // Get existing runs history or initialize
385
- const existingRuns = state.runs || { completed: [] };
386
- const existingCompleted = Array.isArray(existingRuns.completed) ? existingRuns.completed : [];
387
-
388
567
  // Check for duplicate (idempotency)
389
- const alreadyRecorded = existingCompleted.some(r => r.id === runId);
568
+ const alreadyRecorded = state.runs.completed.some(r => r.id === runId);
390
569
 
391
- // Update work item status in intents
570
+ // Update work item status in intents (state.yaml)
571
+ const affectedIntents = new Set();
392
572
  if (Array.isArray(state.intents)) {
393
573
  for (const workItem of workItems) {
394
574
  for (const intent of state.intents) {
@@ -397,6 +577,8 @@ function completeRun(rootPath, runId, params = {}) {
397
577
  if (wi.id === workItem.id) {
398
578
  wi.status = 'completed';
399
579
  wi.run_id = runId;
580
+ wi.completed_at = completedTime;
581
+ affectedIntents.add(intent.id);
400
582
  break;
401
583
  }
402
584
  }
@@ -405,15 +587,32 @@ function completeRun(rootPath, runId, params = {}) {
405
587
  }
406
588
  }
407
589
 
408
- // Update state
409
- state.active_run = null;
410
- state.runs = {
411
- completed: alreadyRecorded ? existingCompleted : [...existingCompleted, completedRun],
412
- };
590
+ // Remove from active runs and add to completed
591
+ state.runs.active.splice(runIndex, 1);
592
+ if (!alreadyRecorded) {
593
+ state.runs.completed.push(completedRun);
594
+ }
413
595
 
414
- // Save state
596
+ // Save state first (so markdown sync has correct state)
415
597
  writeState(statePath, state);
416
598
 
599
+ // Sync markdown frontmatter for all completed work items
600
+ for (const workItem of workItems) {
601
+ updateWorkItemMarkdown(
602
+ rootPath,
603
+ workItem.intent,
604
+ workItem.id,
605
+ 'completed',
606
+ runId,
607
+ workItem.completed_at || completedTime
608
+ );
609
+ }
610
+
611
+ // Update intent markdown for all affected intents
612
+ for (const intentId of affectedIntents) {
613
+ updateIntentMarkdown(rootPath, intentId, state);
614
+ }
615
+
417
616
  return {
418
617
  success: true,
419
618
  runId: runId,
@@ -480,9 +679,9 @@ function parseArgs(args) {
480
679
 
481
680
  function printUsage() {
482
681
  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]');
682
+ console.error(' Complete current item: node complete-run.cjs <rootPath> <runId> --complete-item [options]');
683
+ console.error(' Complete entire run: node complete-run.cjs <rootPath> <runId> --complete-run [options]');
684
+ console.error(' Auto (single runs): node complete-run.cjs <rootPath> <runId> [options]');
486
685
  console.error('');
487
686
  console.error('Arguments:');
488
687
  console.error(' rootPath - Project root directory');
@@ -500,8 +699,8 @@ function printUsage() {
500
699
  console.error(' --coverage=N - Coverage percentage');
501
700
  console.error('');
502
701
  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');
702
+ console.error(' node complete-run.cjs /project run-003 --complete-item');
703
+ console.error(' node complete-run.cjs /project run-003 --complete-run --tests=5 --coverage=85');
505
704
  }
506
705
 
507
706
  // =============================================================================
@@ -11,12 +11,12 @@
11
11
  * - existing run folders in .specs-fire/runs/
12
12
  *
13
13
  * Usage:
14
- * Single item: node init-run.js <rootPath> <workItemId> <intentId> <mode>
15
- * Batch/Wide: node init-run.js <rootPath> --batch '<workItemsJson>'
14
+ * Single item: node init-run.cjs <rootPath> <workItemId> <intentId> <mode>
15
+ * Batch/Wide: node init-run.cjs <rootPath> --batch '<workItemsJson>'
16
16
  *
17
17
  * Examples:
18
- * node init-run.js /project login-endpoint user-auth confirm
19
- * node init-run.js /project --batch '[{"id":"wi-1","intent":"int-1","mode":"autopilot"}]'
18
+ * node init-run.cjs /project login-endpoint user-auth confirm
19
+ * node init-run.cjs /project --batch '[{"id":"wi-1","intent":"int-1","mode":"autopilot"}]'
20
20
  */
21
21
 
22
22
  const fs = require('fs');
@@ -317,13 +317,15 @@ function initRun(rootPath, workItems, scope) {
317
317
  // Read state
318
318
  const state = readState(statePath);
319
319
 
320
- // Check for existing active run
321
- if (state.active_run) {
322
- throw fireError(
323
- `A run is already active: "${state.active_run.id}".`,
324
- 'INIT_080',
325
- `Complete or cancel run "${state.active_run.id}" before starting a new one.`
326
- );
320
+ // Initialize runs structure if needed
321
+ if (!state.runs) {
322
+ state.runs = { active: [], completed: [] };
323
+ }
324
+ if (!Array.isArray(state.runs.active)) {
325
+ state.runs.active = [];
326
+ }
327
+ if (!Array.isArray(state.runs.completed)) {
328
+ state.runs.completed = [];
327
329
  }
328
330
 
329
331
  // Generate run ID (checks both history AND file system)
@@ -337,22 +339,23 @@ function initRun(rootPath, workItems, scope) {
337
339
  const startTime = new Date().toISOString();
338
340
  createRunLog(runPath, runId, workItems, detectedScope, startTime);
339
341
 
340
- // Prepare work items for state with status tracking
342
+ // Prepare work items for state with status and phase tracking
341
343
  const stateWorkItems = workItems.map((item, index) => ({
342
344
  id: item.id,
343
345
  intent: item.intent,
344
346
  mode: item.mode,
345
347
  status: index === 0 ? 'in_progress' : 'pending',
348
+ current_phase: index === 0 ? 'plan' : null,
346
349
  }));
347
350
 
348
- // Update state with active run
349
- state.active_run = {
351
+ // Add to active runs list (supports multiple parallel runs)
352
+ state.runs.active.push({
350
353
  id: runId,
351
354
  scope: detectedScope,
352
355
  work_items: stateWorkItems,
353
356
  current_item: workItems[0].id,
354
357
  started: startTime,
355
- };
358
+ });
356
359
 
357
360
  // Save state
358
361
  writeState(statePath, state);
@@ -375,8 +378,8 @@ function initRun(rootPath, workItems, scope) {
375
378
 
376
379
  function printUsage() {
377
380
  console.error('Usage:');
378
- console.error(' Single item: node init-run.js <rootPath> <workItemId> <intentId> <mode>');
379
- console.error(' Batch/Wide: node init-run.js <rootPath> --batch \'<workItemsJson>\' [--scope=<scope>]');
381
+ console.error(' Single item: node init-run.cjs <rootPath> <workItemId> <intentId> <mode>');
382
+ console.error(' Batch/Wide: node init-run.cjs <rootPath> --batch \'<workItemsJson>\' [--scope=<scope>]');
380
383
  console.error('');
381
384
  console.error('Arguments:');
382
385
  console.error(' rootPath - Project root directory');
@@ -392,8 +395,8 @@ function printUsage() {
392
395
  console.error(' [{"id": "wi-1", "intent": "int-1", "mode": "autopilot"}, ...]');
393
396
  console.error('');
394
397
  console.error('Examples:');
395
- console.error(' node init-run.js /project login-endpoint user-auth confirm');
396
- console.error(' node init-run.js /project --batch \'[{"id":"wi-1","intent":"int-1","mode":"autopilot"}]\'');
398
+ console.error(' node init-run.cjs /project login-endpoint user-auth confirm');
399
+ console.error(' node init-run.cjs /project --batch \'[{"id":"wi-1","intent":"int-1","mode":"autopilot"}]\'');
397
400
  }
398
401
 
399
402
  if (require.main === module) {