brain-dev 2.5.0 → 2.5.1

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const { readState, writeState } = require('../state.cjs');
5
+ const { readState, writeState, syncPhaseStatus } = require('../state.cjs');
6
6
  const { parseRoadmap, writeRoadmap } = require('../roadmap.cjs');
7
7
  const { gitTag } = require('../git.cjs');
8
8
  const { output, error, success } = require('../core.cjs');
@@ -163,7 +163,7 @@ function handlePhaseComplete(args, phaseIdx, brainDir, state) {
163
163
  return { error: 'not-verified', nextAction: '/brain:verify' };
164
164
  }
165
165
 
166
- // Mark phase as complete in state (handle both string and object formats)
166
+ // Mark current phase as complete in per-phase array
167
167
  if (Array.isArray(state.phase.phases)) {
168
168
  const idx = state.phase.phases.findIndex(p =>
169
169
  typeof p === 'object' ? p.number === phaseNumber : false
@@ -179,9 +179,9 @@ function handlePhaseComplete(args, phaseIdx, brainDir, state) {
179
179
  const hasNextPhase = totalPhases > 0 && nextPhase <= totalPhases;
180
180
  if (hasNextPhase) {
181
181
  state.phase.current = nextPhase;
182
- state.phase.status = 'pending';
182
+ syncPhaseStatus(state, 'pending');
183
183
  } else {
184
- state.phase.status = 'complete';
184
+ syncPhaseStatus(state, 'complete');
185
185
  }
186
186
  writeState(brainDir, state);
187
187
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const { readState, writeState, atomicWriteSync } = require('../state.cjs');
5
+ const { readState, writeState, atomicWriteSync, syncPhaseStatus } = require('../state.cjs');
6
6
  const { parseRoadmap } = require('../roadmap.cjs');
7
7
  const { loadTemplate, interpolate } = require('../templates.cjs');
8
8
  const { output, error, success, prefix, pipelineGate } = require('../core.cjs');
@@ -132,7 +132,7 @@ function handleAnalyze(args, brainDir, state) {
132
132
  });
133
133
 
134
134
  // Update status to discussing
135
- state.phase.status = 'discussing';
135
+ syncPhaseStatus(state, 'discussing');
136
136
  writeState(brainDir, state);
137
137
 
138
138
  const result = {
@@ -220,7 +220,7 @@ function handleSave(args, brainDir, state) {
220
220
  atomicWriteSync(contextPath, lines.join('\n'));
221
221
 
222
222
  // Update state: set phase status to "discussed"
223
- state.phase.status = 'discussed';
223
+ syncPhaseStatus(state, 'discussed');
224
224
  writeState(brainDir, state);
225
225
 
226
226
  const result = {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const { readState, writeState } = require('../state.cjs');
5
+ const { readState, writeState, syncPhaseStatus } = require('../state.cjs');
6
6
  const { loadTemplate, interpolate, loadTemplateWithOverlay } = require('../templates.cjs');
7
7
  const { getAgent, resolveModel } = require('../agents.cjs');
8
8
  const { logEvent } = require('../logger.cjs');
@@ -304,7 +304,7 @@ async function run(args = [], opts = {}) {
304
304
 
305
305
  // All plans executed
306
306
  if (!targetPlan) {
307
- state.phase.status = 'executed';
307
+ syncPhaseStatus(state, 'executed');
308
308
  writeState(brainDir, state);
309
309
  const msg = "All plans executed. Run /brain:review before verify.";
310
310
  output({ action: 'all-executed', message: msg, nextAction: '/brain:review' }, `[brain] ${msg}\n${pipelineGate('npx brain-dev review --phase ' + phaseNumber)}`);
@@ -385,7 +385,7 @@ async function run(args = [], opts = {}) {
385
385
  } catch { /* cost tracking failure is non-fatal */ }
386
386
 
387
387
  // Update state to executing
388
- state.phase.status = 'executing';
388
+ syncPhaseStatus(state, 'executing');
389
389
  if (!state.phase.execution_started_at) {
390
390
  state.phase.execution_started_at = new Date().toISOString();
391
391
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const { readState, writeState } = require('../state.cjs');
5
+ const { readState, writeState, syncPhaseStatus } = require('../state.cjs');
6
6
  const { parseRoadmap } = require('../roadmap.cjs');
7
7
  const { loadTemplate, interpolate, loadTemplateWithOverlay } = require('../templates.cjs');
8
8
  const { getAgent, resolveModel } = require('../agents.cjs');
@@ -291,7 +291,7 @@ function handleSingle(args, brainDir, state) {
291
291
  const fullPrompt = prompt + checkerInstruction;
292
292
 
293
293
  // Update state: phase status = "planning"
294
- state.phase.status = 'planning';
294
+ syncPhaseStatus(state, 'planning');
295
295
  writeState(brainDir, state);
296
296
 
297
297
  const result = {
@@ -531,7 +531,7 @@ function handleAll(brainDir, state) {
531
531
  });
532
532
 
533
533
  // Update status to planning
534
- state.phase.status = 'planning';
534
+ syncPhaseStatus(state, 'planning');
535
535
  writeState(brainDir, state);
536
536
 
537
537
  const result = {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const { readState, writeState } = require('../state.cjs');
5
+ const { readState, writeState, syncPhaseStatus } = require('../state.cjs');
6
6
  const { loadTemplateWithOverlay, interpolate } = require('../templates.cjs');
7
7
  const { getAgent, resolveModel } = require('../agents.cjs');
8
8
  const { logEvent } = require('../logger.cjs');
@@ -93,7 +93,7 @@ async function run(args = [], opts = {}) {
93
93
  });
94
94
 
95
95
  // Update state
96
- state.phase.status = 'reviewing';
96
+ syncPhaseStatus(state, 'reviewing');
97
97
  writeState(brainDir, state);
98
98
 
99
99
  const result = {
@@ -145,14 +145,14 @@ function handleResult(brainDir, state, args) {
145
145
  const reviewStatus = statusMatch ? statusMatch[1] : 'passed';
146
146
 
147
147
  if (reviewStatus === 'critical') {
148
- state.phase.status = 'review-failed';
148
+ syncPhaseStatus(state, 'review-failed');
149
149
  writeState(brainDir, state);
150
150
  error('Review found critical issues. Fix them and re-run /brain:review.');
151
151
  output({ action: 'review-failed', phase: phaseNumber, status: reviewStatus }, '');
152
152
  return { action: 'review-failed', phase: phaseNumber };
153
153
  }
154
154
 
155
- state.phase.status = 'reviewed';
155
+ syncPhaseStatus(state, 'reviewed');
156
156
  writeState(brainDir, state);
157
157
 
158
158
  const result = {
@@ -3,7 +3,7 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
  const { parseArgs } = require('node:util');
6
- const { readState, writeState } = require('../state.cjs');
6
+ const { readState, writeState, syncPhaseStatus } = require('../state.cjs');
7
7
  const { output, error, prefix } = require('../core.cjs');
8
8
  const { logEvent } = require('../logger.cjs');
9
9
  const { loadTemplate, interpolate, loadTemplateWithOverlay } = require('../templates.cjs');
@@ -913,7 +913,7 @@ function stepActivate(brainDir, state, storyDir, storyMeta) {
913
913
  // Update state phases
914
914
  state.phase = state.phase || { current: 0, status: 'initialized', total: 0, phases: [] };
915
915
  state.phase.current = 1;
916
- state.phase.status = 'ready';
916
+ syncPhaseStatus(state, 'ready');
917
917
  state.phase.total = roadmapData.phases.length;
918
918
  state.phase.phases = roadmapData.phases.map(p => ({
919
919
  number: p.number,
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const { readState, writeState, atomicWriteSync } = require('../state.cjs');
5
+ const { readState, writeState, atomicWriteSync, syncPhaseStatus } = require('../state.cjs');
6
6
  const { loadTemplate, interpolate, loadTemplateWithOverlay } = require('../templates.cjs');
7
7
  const { getAgent, resolveModel } = require('../agents.cjs');
8
8
  const { logEvent } = require('../logger.cjs');
@@ -320,7 +320,7 @@ async function run(args = [], opts = {}) {
320
320
  const fullPrompt = prompt + depthInstruction + summarySection;
321
321
 
322
322
  // Update state to verifying
323
- state.phase.status = 'verifying';
323
+ syncPhaseStatus(state, 'verifying');
324
324
  writeState(brainDir, state);
325
325
 
326
326
  const result = {
@@ -421,9 +421,7 @@ function handleSaveResults(args, saveIdx, brainDir, state) {
421
421
  }
422
422
 
423
423
  // Update state
424
- state.phase.status = results.passed ? 'verified' : 'verification-failed';
425
- // Clear execution timer after verification completes
426
- state.phase.execution_started_at = null;
424
+ syncPhaseStatus(state, results.passed ? 'verified' : 'verification-failed');
427
425
  writeState(brainDir, state);
428
426
 
429
427
  const msg = results.passed
@@ -4,7 +4,7 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
  const { readLock, isLockStale, clearStaleLock } = require('./lock.cjs');
6
6
  const { readLog } = require('./logger.cjs');
7
- const { readState, writeState, VALID_PHASE_STATUSES } = require('./state.cjs');
7
+ const { readState, writeState, VALID_PHASE_STATUSES, syncPhaseStatus } = require('./state.cjs');
8
8
 
9
9
  /**
10
10
  * Analyze the JSONL execution log for a crashed phase.
@@ -293,11 +293,11 @@ function autoResume(brainDir, logAnalysis, state) {
293
293
  if (state.phase) {
294
294
  if (state.phase.status === 'executing' && logAnalysis.inProgressTask) {
295
295
  // There was work in progress - set to planned so execution can retry
296
- state.phase.status = 'planned';
296
+ syncPhaseStatus(state, 'planned');
297
297
  changes.push(`Reset phase status from "executing" to "planned"`);
298
298
  } else if (state.phase.status === 'executing' && !logAnalysis.inProgressTask) {
299
299
  // All tasks were completed or none in progress
300
- state.phase.status = 'planned';
300
+ syncPhaseStatus(state, 'planned');
301
301
  changes.push(`Reset phase status to "planned" for re-verification`);
302
302
  }
303
303
 
@@ -337,8 +337,7 @@ function rollback(brainDir, staleLock, state) {
337
337
  // Reset phase status
338
338
  if (state.phase) {
339
339
  const previousStatus = state.phase.status;
340
- state.phase.status = 'planned';
341
- state.phase.execution_started_at = null;
340
+ syncPhaseStatus(state, 'planned');
342
341
  changes.push(`Reset phase status from "${previousStatus}" to "planned"`);
343
342
  }
344
343
 
package/bin/lib/state.cjs CHANGED
@@ -517,6 +517,27 @@ const VALID_PHASE_STATUSES = [
517
517
  'partial', 'failed', 'paused', 'complete'
518
518
  ];
519
519
 
520
+ /**
521
+ * Atomically sync phase status across both top-level and per-phase array.
522
+ * Also clears execution_started_at when regressing to pre-execute states.
523
+ * @param {object} state - brain.json state object
524
+ * @param {string} newStatus - New status value
525
+ */
526
+ function syncPhaseStatus(state, newStatus) {
527
+ const preExecuteStatuses = ['pending', 'ready', 'discussing', 'discussed', 'planning', 'planned'];
528
+ if (preExecuteStatuses.includes(newStatus)) {
529
+ state.phase.execution_started_at = null;
530
+ }
531
+
532
+ state.phase.status = newStatus;
533
+ if (Array.isArray(state.phase.phases) && state.phase.current > 0) {
534
+ const idx = state.phase.phases.findIndex(p => p.number === state.phase.current);
535
+ if (idx >= 0) {
536
+ state.phase.phases[idx].status = newStatus;
537
+ }
538
+ }
539
+ }
540
+
520
541
  module.exports = {
521
542
  atomicWriteSync,
522
543
  readState,
@@ -524,5 +545,6 @@ module.exports = {
524
545
  generateStateMd,
525
546
  createDefaultState,
526
547
  migrateState,
548
+ syncPhaseStatus,
527
549
  VALID_PHASE_STATUSES
528
550
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brain-dev",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "AI-powered development workflow orchestrator",
5
5
  "author": "halilcosdu",
6
6
  "license": "MIT",