specsmd 0.1.9 → 0.1.10

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.
@@ -248,25 +248,22 @@ function completeCurrentItem(rootPath, runId, params = {}) {
248
248
  const { statePath, runLogPath } = validateFireProject(rootPath, runId);
249
249
  const state = readState(statePath);
250
250
 
251
- if (!state.active_run) {
251
+ // Find run in active runs list
252
+ const activeRuns = state.runs?.active || [];
253
+ const runIndex = activeRuns.findIndex(r => r.id === runId);
254
+
255
+ if (runIndex === -1) {
252
256
  throw fireError(
253
- 'No active run found in state.yaml.',
257
+ `Run "${runId}" not found in active runs.`,
254
258
  'COMPLETE_040',
255
259
  'The run may have already been completed or was never started.'
256
260
  );
257
261
  }
258
262
 
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
-
263
+ const activeRun = activeRuns[runIndex];
267
264
  const completedTime = new Date().toISOString();
268
- const workItems = state.active_run.work_items || [];
269
- const currentItemId = state.active_run.current_item;
265
+ const workItems = activeRun.work_items || [];
266
+ const currentItemId = activeRun.current_item;
270
267
 
271
268
  // Find and mark current item as completed
272
269
  let currentItemIndex = -1;
@@ -297,12 +294,13 @@ function completeCurrentItem(rootPath, runId, params = {}) {
297
294
  }
298
295
  }
299
296
 
300
- // Update state
301
- state.active_run.work_items = workItems;
302
- state.active_run.current_item = nextItem ? nextItem.id : null;
297
+ // Update active run in list
298
+ activeRun.work_items = workItems;
299
+ activeRun.current_item = nextItem ? nextItem.id : null;
300
+ state.runs.active[runIndex] = activeRun;
303
301
 
304
302
  // Update run log
305
- updateRunLog(runLogPath, state.active_run, completionParams, completedTime, false);
303
+ updateRunLog(runLogPath, activeRun, completionParams, completedTime, false);
306
304
 
307
305
  // Save state
308
306
  writeState(statePath, state);
@@ -335,25 +333,32 @@ function completeRun(rootPath, runId, params = {}) {
335
333
  const { statePath, runLogPath } = validateFireProject(rootPath, runId);
336
334
  const state = readState(statePath);
337
335
 
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
- );
336
+ // Initialize runs structure if needed
337
+ if (!state.runs) {
338
+ state.runs = { active: [], completed: [] };
339
+ }
340
+ if (!Array.isArray(state.runs.active)) {
341
+ state.runs.active = [];
342
+ }
343
+ if (!Array.isArray(state.runs.completed)) {
344
+ state.runs.completed = [];
344
345
  }
345
346
 
346
- if (state.active_run.id !== runId) {
347
+ // Find run in active runs list
348
+ const runIndex = state.runs.active.findIndex(r => r.id === runId);
349
+
350
+ if (runIndex === -1) {
347
351
  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.`
352
+ `Run "${runId}" not found in active runs.`,
353
+ 'COMPLETE_040',
354
+ 'The run may have already been completed or was never started.'
351
355
  );
352
356
  }
353
357
 
358
+ const activeRun = state.runs.active[runIndex];
354
359
  const completedTime = new Date().toISOString();
355
- const workItems = state.active_run.work_items || [];
356
- const scope = state.active_run.scope || 'single';
360
+ const workItems = activeRun.work_items || [];
361
+ const scope = activeRun.scope || 'single';
357
362
 
358
363
  // Mark all items as completed
359
364
  for (const item of workItems) {
@@ -363,11 +368,11 @@ function completeRun(rootPath, runId, params = {}) {
363
368
  }
364
369
  }
365
370
 
366
- state.active_run.work_items = workItems;
367
- state.active_run.current_item = null;
371
+ activeRun.work_items = workItems;
372
+ activeRun.current_item = null;
368
373
 
369
374
  // Update run log
370
- updateRunLog(runLogPath, state.active_run, completionParams, completedTime, true);
375
+ updateRunLog(runLogPath, activeRun, completionParams, completedTime, true);
371
376
 
372
377
  // Build completed run record
373
378
  const completedRun = {
@@ -378,15 +383,12 @@ function completeRun(rootPath, runId, params = {}) {
378
383
  intent: i.intent,
379
384
  mode: i.mode,
380
385
  })),
386
+ started: activeRun.started,
381
387
  completed: completedTime,
382
388
  };
383
389
 
384
- // Get existing runs history or initialize
385
- const existingRuns = state.runs || { completed: [] };
386
- const existingCompleted = Array.isArray(existingRuns.completed) ? existingRuns.completed : [];
387
-
388
390
  // Check for duplicate (idempotency)
389
- const alreadyRecorded = existingCompleted.some(r => r.id === runId);
391
+ const alreadyRecorded = state.runs.completed.some(r => r.id === runId);
390
392
 
391
393
  // Update work item status in intents
392
394
  if (Array.isArray(state.intents)) {
@@ -405,11 +407,11 @@ function completeRun(rootPath, runId, params = {}) {
405
407
  }
406
408
  }
407
409
 
408
- // Update state
409
- state.active_run = null;
410
- state.runs = {
411
- completed: alreadyRecorded ? existingCompleted : [...existingCompleted, completedRun],
412
- };
410
+ // Remove from active runs and add to completed
411
+ state.runs.active.splice(runIndex, 1);
412
+ if (!alreadyRecorded) {
413
+ state.runs.completed.push(completedRun);
414
+ }
413
415
 
414
416
  // Save state
415
417
  writeState(statePath, state);
@@ -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)
@@ -345,14 +347,14 @@ function initRun(rootPath, workItems, scope) {
345
347
  status: index === 0 ? 'in_progress' : 'pending',
346
348
  }));
347
349
 
348
- // Update state with active run
349
- state.active_run = {
350
+ // Add to active runs list (supports multiple parallel runs)
351
+ state.runs.active.push({
350
352
  id: runId,
351
353
  scope: detectedScope,
352
354
  work_items: stateWorkItems,
353
355
  current_item: workItems[0].id,
354
356
  started: startTime,
355
- };
357
+ });
356
358
 
357
359
  // Save state
358
360
  writeState(statePath, state);
@@ -55,14 +55,16 @@ state:
55
55
  - title: "Intent title"
56
56
  - status: "pending | in_progress | completed"
57
57
  - work_items: "List of work items"
58
- active_run:
59
- - id: "Current run ID"
60
- - scope: "single | batch | wide"
61
- - work_items: "List of work items in this run"
62
- - current_item: "Work item currently being executed"
63
- - started: "ISO 8601 timestamp"
64
58
  runs:
59
+ - active: "List of currently active runs (supports multiple parallel runs)"
65
60
  - completed: "List of completed runs with id, work_item, intent, completed timestamp"
61
+ # Each run (active or completed) has:
62
+ # - id: "Run ID (e.g., run-001)"
63
+ # - scope: "single | batch | wide"
64
+ # - work_items: "List of work items in this run"
65
+ # - current_item: "Work item currently being executed (active runs only)"
66
+ # - started: "ISO 8601 timestamp"
67
+ # - completed: "ISO 8601 timestamp (completed runs only)"
66
68
 
67
69
  # Data Conventions
68
70
  conventions:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Multi-agent orchestration system for AI-native software development. Delivers AI-DLC, Agile, and custom SDLC flows as markdown-based agent systems.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {