specsmd 0.0.0-dev.79 → 0.0.0-dev.80

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.
@@ -44,6 +44,12 @@ You are the **Builder Agent** for FIRE (Fast Intent-Run Engineering).
44
44
 
45
45
  <step n="4" title="Route by State">
46
46
  <check if="active run exists">
47
+ <action>Read runs.active[0] from state.yaml</action>
48
+ <action>Read scope (single/batch/wide) and work_items array</action>
49
+ <action>Count items by status: completed, in_progress, pending</action>
50
+ <output>Active run {id} ({scope}) — {completed_count} done, {remaining_count} remaining</output>
51
+ <mandate>DO NOT treat completed items as needing re-execution</mandate>
52
+ <mandate>ONLY work on the current_item from state.yaml</mandate>
47
53
  <action>Resume execution — invoke run-execute skill</action>
48
54
  </check>
49
55
  <check if="pending work items exist">
@@ -61,28 +61,48 @@ Supports both single-item and multi-item (batch/wide) runs.
61
61
  </check>
62
62
 
63
63
  <check if="runs.active has entries">
64
- <action>Load run state from .specs-fire/runs/{runs.active[0].id}/run.md</action>
65
- <action>Get current_item and its status from state.yaml</action>
66
- <action>Check for existing artifacts and LOAD if present:</action>
67
-
68
- <substep>design.md if exists, LOAD from .specs-fire/intents/{intent}/work-items/{id}-design.md</substep>
69
- <substep>plan.md if exists, LOAD from .specs-fire/runs/{run-id}/plan.md (skip plan generation)</substep>
70
- <substep>test-report.md → if exists, tests already passed (skip to Step 6b)</substep>
71
- <substep>review-report.md if exists, review done (skip to Step 7)</substep>
72
-
73
- <determine_resume_point>
74
- | Artifacts Present | Resume At |
75
- |-------------------|-----------|
76
- | None | Step 3 (Generate Plan) |
77
- | plan.md only | Step 5 (Implementation) |
78
- | plan.md + test-report.md | Step 6b (Code Review) |
79
- | plan.md + test-report.md + review-report.md | Step 7 (Complete Item) |
80
- </determine_resume_point>
64
+ <action>Load active run from state.yaml runs.active[0]</action>
65
+ <action>Read scope (single/batch/wide) and work_items array</action>
66
+
67
+ <substep n="0a" title="Enumerate Work Item Status">
68
+ <action>For EACH work item in runs.active[0].work_items, classify by status:</action>
69
+ <action>Build status summary from state.yaml (NOT from artifact files):</action>
70
+ <format>
71
+ [DONE] {item-id} completed
72
+ [WORKING] {item-id} (phase: {current_phase}) — in_progress
73
+ [PENDING] {item-id} — pending
74
+ </format>
75
+ <action>Count: completed={X}, in_progress={Y}, pending={Z}</action>
76
+ </substep>
77
+
78
+ <substep n="0b" title="Determine Resume Point for Current Item">
79
+ <action>Get current_item from state.yaml</action>
80
+ <action>Read current_phase from the current item's entry in work_items</action>
81
+
82
+ <determine_resume_point>
83
+ Use current_phase from state.yaml to determine resume point:
84
+
85
+ | current_phase | Resume At |
86
+ |---------------|-----------|
87
+ | plan (or unset) | Step 3 (Generate Plan) |
88
+ | execute | Step 5 (Implementation) |
89
+ | test | Step 6 (Run Tests) |
90
+ | review | Step 6b (Code Review) |
91
+ </determine_resume_point>
92
+ </substep>
93
+
94
+ <llm critical="true">
95
+ <mandate>NEVER call --complete-item for items with status "completed" — they are already done</mandate>
96
+ <mandate>NEVER re-execute steps (plan, implement, test) for completed items</mandate>
97
+ <mandate>ONLY work on the current_item identified in state.yaml</mandate>
98
+ <mandate>Use current_phase from state.yaml — do NOT infer phase from artifact file existence</mandate>
99
+ </llm>
81
100
 
82
101
  <output>
83
- Resuming run {run-id} for work item {current_item}.
102
+ Resuming run {run-id} ({scope}) for work item {current_item}.
84
103
  Mode: {mode}
85
- Loaded existing artifacts: {artifact_list}
104
+ Phase: {current_phase}
105
+ Status: {completed_count} done, {in_progress_count} working, {pending_count} pending
86
106
  Resuming at: Step {step_number}
87
107
  </output>
88
108
  </check>
@@ -21,6 +21,7 @@
21
21
  * --decisions=JSON - JSON array of {decision, choice, rationale}
22
22
  * --tests=N - Number of tests added
23
23
  * --coverage=N - Coverage percentage
24
+ * --force - Override phase guard (skip review phase check)
24
25
  */
25
26
 
26
27
  const fs = require('fs');
@@ -391,7 +392,7 @@ function updateRunLog(runLogPath, activeRun, params, completedTime, isFullComple
391
392
  // Complete Current Item (for batch runs)
392
393
  // =============================================================================
393
394
 
394
- function completeCurrentItem(rootPath, runId, params = {}) {
395
+ function completeCurrentItem(rootPath, runId, params = {}, options = {}) {
395
396
  const completionParams = {
396
397
  filesCreated: params.filesCreated || [],
397
398
  filesModified: params.filesModified || [],
@@ -399,6 +400,7 @@ function completeCurrentItem(rootPath, runId, params = {}) {
399
400
  testsAdded: params.testsAdded || 0,
400
401
  coverage: params.coverage || 0,
401
402
  };
403
+ const force = options.force || false;
402
404
 
403
405
  validateInputs(rootPath, runId);
404
406
  const { statePath, runLogPath } = validateFireProject(rootPath, runId);
@@ -440,6 +442,16 @@ function completeCurrentItem(rootPath, runId, params = {}) {
440
442
  );
441
443
  }
442
444
 
445
+ // Phase guard: item must be at 'review' phase before completion
446
+ const currentPhase = workItems[currentItemIndex].current_phase;
447
+ if (!force && currentPhase !== 'review') {
448
+ throw fireError(
449
+ `Cannot complete item "${currentItemId}" — current phase is "${currentPhase || 'unknown'}", not "review".`,
450
+ 'COMPLETE_051',
451
+ 'The item must reach the review phase before completion. Use --force to override.'
452
+ );
453
+ }
454
+
443
455
  // Find next pending item
444
456
  let nextItem = null;
445
457
  for (let i = currentItemIndex + 1; i < workItems.length; i++) {
@@ -497,7 +509,7 @@ function completeCurrentItem(rootPath, runId, params = {}) {
497
509
  // Complete Entire Run
498
510
  // =============================================================================
499
511
 
500
- function completeRun(rootPath, runId, params = {}) {
512
+ function completeRun(rootPath, runId, params = {}, options = {}) {
501
513
  const completionParams = {
502
514
  filesCreated: params.filesCreated || [],
503
515
  filesModified: params.filesModified || [],
@@ -505,6 +517,7 @@ function completeRun(rootPath, runId, params = {}) {
505
517
  testsAdded: params.testsAdded || 0,
506
518
  coverage: params.coverage || 0,
507
519
  };
520
+ const force = options.force || false;
508
521
 
509
522
  validateInputs(rootPath, runId);
510
523
  const { statePath, runLogPath } = validateFireProject(rootPath, runId);
@@ -537,6 +550,21 @@ function completeRun(rootPath, runId, params = {}) {
537
550
  const workItems = activeRun.work_items || [];
538
551
  const scope = activeRun.scope || 'single';
539
552
 
553
+ // Phase guard: all non-completed items must be at 'review' phase
554
+ if (!force) {
555
+ const notReady = workItems.filter(
556
+ item => item.status !== 'completed' && item.current_phase !== 'review'
557
+ );
558
+ if (notReady.length > 0) {
559
+ const list = notReady.map(i => `${i.id} (phase: ${i.current_phase || 'unknown'})`).join(', ');
560
+ throw fireError(
561
+ `Cannot complete run — ${notReady.length} item(s) have not reached review phase: ${list}.`,
562
+ 'COMPLETE_060',
563
+ 'All items must reach the review phase before run completion. Use --force to override.'
564
+ );
565
+ }
566
+ }
567
+
540
568
  // Mark all items as completed
541
569
  for (const item of workItems) {
542
570
  if (item.status !== 'completed') {
@@ -636,6 +664,7 @@ function parseArgs(args) {
636
664
  runId: args[1],
637
665
  completeItem: false,
638
666
  completeRunFlag: false,
667
+ force: false,
639
668
  filesCreated: [],
640
669
  filesModified: [],
641
670
  decisions: [],
@@ -649,6 +678,8 @@ function parseArgs(args) {
649
678
  result.completeItem = true;
650
679
  } else if (arg === '--complete-run') {
651
680
  result.completeRunFlag = true;
681
+ } else if (arg === '--force') {
682
+ result.force = true;
652
683
  } else if (arg.startsWith('--files-created=')) {
653
684
  try {
654
685
  result.filesCreated = JSON.parse(arg.substring('--files-created='.length));
@@ -690,6 +721,7 @@ function printUsage() {
690
721
  console.error('Flags:');
691
722
  console.error(' --complete-item - Complete only the current work item (batch/wide runs)');
692
723
  console.error(' --complete-run - Complete the entire run');
724
+ console.error(' --force - Override phase guard (skip review phase check)');
693
725
  console.error('');
694
726
  console.error('Options:');
695
727
  console.error(' --files-created=JSON - JSON array of {path, purpose}');
@@ -718,6 +750,7 @@ if (require.main === module) {
718
750
  const params = parseArgs(args);
719
751
 
720
752
  try {
753
+ const cliOptions = { force: params.force };
721
754
  let result;
722
755
  if (params.completeItem) {
723
756
  result = completeCurrentItem(params.rootPath, params.runId, {
@@ -726,7 +759,7 @@ if (require.main === module) {
726
759
  decisions: params.decisions,
727
760
  testsAdded: params.testsAdded,
728
761
  coverage: params.coverage,
729
- });
762
+ }, cliOptions);
730
763
  } else {
731
764
  // Default: complete entire run
732
765
  result = completeRun(params.rootPath, params.runId, {
@@ -735,7 +768,7 @@ if (require.main === module) {
735
768
  decisions: params.decisions,
736
769
  testsAdded: params.testsAdded,
737
770
  coverage: params.coverage,
738
- });
771
+ }, cliOptions);
739
772
  }
740
773
  console.log(JSON.stringify(result, null, 2));
741
774
  process.exit(0);
@@ -113,6 +113,18 @@ Plan the scope of a run by discovering available work items and suggesting group
113
113
  </action>
114
114
  </check>
115
115
 
116
+ <check if="state says completed but item is in runs.active[].work_items with current_phase != review">
117
+ <output>
118
+ **Suspect completion**: {work-item-id}
119
+ - state.yaml status: completed
120
+ - But current_phase: {current_phase} (not "review")
121
+ - Item is still in active run {run-id}
122
+ Resetting to in_progress — item was likely marked complete prematurely.
123
+ </output>
124
+ <action>Reset work item status to in_progress in state.yaml</action>
125
+ <action>Update frontmatter to match</action>
126
+ </check>
127
+
116
128
  <check if="in state.yaml but file missing">
117
129
  <output>Warning: {item} in state but file not found on disk</output>
118
130
  </check>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.0.0-dev.79",
3
+ "version": "0.0.0-dev.80",
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": {