brain-dev 2.3.1 → 2.5.0

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.
package/README.md CHANGED
@@ -30,6 +30,7 @@ Brain adds structure without adding friction:
30
30
  /brain:discuss → Captures architectural decisions before coding
31
31
  /brain:plan → Creates verified execution plans with TDD
32
32
  /brain:execute → Builds with per-task commits and deviation handling
33
+ /brain:review → Mandatory code review before verification
33
34
  /brain:verify → 3-level verification against must-haves
34
35
  /brain:complete → Advances to next phase with audit trail
35
36
  ```
@@ -56,7 +57,7 @@ Brain provides four levels of work, from lightweight to comprehensive:
56
57
 
57
58
  ```
58
59
  quick → plan → execute → commit (minutes)
59
- new-task → discuss → plan → execute → verify (hours)
60
+ new-task → discuss → plan → execute → review → verify (hours)
60
61
  story → research → requirements → roadmap → phases (days/weeks)
61
62
  new-project → detect → map → suggest next step (one-time setup)
62
63
  ```
@@ -94,7 +95,7 @@ For features that need more than a quick fix but less than a full story:
94
95
  /brain:new-task "add payment integration with Stripe"
95
96
  ```
96
97
 
97
- Brain runs the full pipeline: discuss → plan → execute → verify → complete.
98
+ Brain runs the full pipeline: discuss → plan → execute → review → verify → complete.
98
99
 
99
100
  ```bash
100
101
  /brain:new-task --research "add rate limiting" # With light research
@@ -150,7 +151,7 @@ No configuration needed — Brain detects your stack and injects expertise into
150
151
  ## Lifecycle
151
152
 
152
153
  ```
153
- init → new-project → story → discuss → plan → execute → verify → complete
154
+ init → new-project → story → discuss → plan → execute → review → verify → complete
154
155
  ↑ │
155
156
  └──────────── next story/phase ───────────────┘
156
157
  ```
@@ -173,14 +174,14 @@ Run phases autonomously without human intervention:
173
174
  /brain:execute --auto --stop # Stop running auto session
174
175
  ```
175
176
 
176
- Auto mode chains: discuss → plan → execute → verify → complete → next phase, with budget limits, stuck detection, and crash recovery built in.
177
+ Auto mode chains: discuss → plan → execute → review → verify → complete → next phase, with budget limits, stuck detection, and crash recovery built in.
177
178
 
178
179
  ## State Machine
179
180
 
180
181
  Brain tracks progress through a deterministic state machine:
181
182
 
182
183
  ```
183
- pending → discussing → discussed → planning → executing → executed → verifying → verified → complete
184
+ pending → discussing → discussed → planning → executing → executed → reviewing → reviewed → verifying → verified → complete
184
185
  ```
185
186
 
186
187
  Every command knows what comes next. `brain-dev progress` always tells you the right next step.
@@ -223,6 +224,7 @@ No supply chain risk. No transitive vulnerabilities. No `node_modules` bloat.
223
224
  | `/brain:discuss` | Capture architectural decisions |
224
225
  | `/brain:plan` | Create verified execution plans |
225
226
  | `/brain:execute` | Build according to plans with TDD |
227
+ | `/brain:review` | Mandatory code review before verification |
226
228
  | `/brain:verify` | 3-level verification against must-haves |
227
229
  | `/brain:complete` | Mark phase done, advance to next |
228
230
  | `/brain:quick` | Quick task — skip the ceremony |
@@ -2,11 +2,11 @@
2
2
 
3
3
  /**
4
4
  * Agent registry for brain orchestration.
5
- * Defines the 7 core agents and their metadata.
5
+ * Defines the 11 core agents and their metadata.
6
6
  * Constant registry with discovery and validation functions.
7
7
  */
8
8
 
9
- const MAX_AGENTS = 9;
9
+ const MAX_AGENTS = 11;
10
10
 
11
11
  const AGENTS = {
12
12
  researcher: {
@@ -73,6 +73,20 @@ const AGENTS = {
73
73
  model: 'inherit',
74
74
  description: 'Maps codebase across focus areas producing structured Markdown documentation',
75
75
  focus: ['tech', 'arch', 'quality', 'concerns']
76
+ },
77
+ 'requirements-generator': {
78
+ template: 'requirements-generator',
79
+ inputs: ['project_md', 'summary_content', 'stack_expertise', 'codebase_context'],
80
+ outputs: ['REQUIREMENTS.md'],
81
+ model: 'inherit',
82
+ description: 'Generates structured requirements with acceptance criteria from research findings and project context'
83
+ },
84
+ 'roadmap-architect': {
85
+ template: 'roadmap-architect',
86
+ inputs: ['requirements_content', 'summary_content', 'project_md', 'stack_expertise'],
87
+ outputs: ['ROADMAP.md'],
88
+ model: 'inherit',
89
+ description: 'Creates strategically-phased roadmap with technical dependency analysis from requirements and research'
76
90
  }
77
91
  };
78
92
 
@@ -6,7 +6,7 @@ const { readState, writeState } = 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');
9
- const { output, error } = require('../core.cjs');
9
+ const { output, error, pipelineGate } = require('../core.cjs');
10
10
  const { recordInvocation, estimateTokens } = require('../cost.cjs');
11
11
  const { acquireLock, releaseLock } = require('../lock.cjs');
12
12
  const { generateExpertise, getDetectedFramework } = require('../stack-expert.cjs');
@@ -306,9 +306,9 @@ async function run(args = [], opts = {}) {
306
306
  if (!targetPlan) {
307
307
  state.phase.status = 'executed';
308
308
  writeState(brainDir, state);
309
- const msg = "All plans executed. Run /brain:verify to check the work.";
310
- output({ action: 'all-executed', message: msg, nextAction: '/brain:verify' }, `[brain] ${msg}`);
311
- return { action: 'all-executed', message: msg, nextAction: '/brain:verify' };
309
+ const msg = "All plans executed. Run /brain:review before verify.";
310
+ output({ action: 'all-executed', message: msg, nextAction: '/brain:review' }, `[brain] ${msg}\n${pipelineGate('npx brain-dev review --phase ' + phaseNumber)}`);
311
+ return { action: 'all-executed', message: msg, nextAction: '/brain:review' };
312
312
  }
313
313
 
314
314
  // Read plan content
@@ -409,10 +409,8 @@ async function run(args = [], opts = {}) {
409
409
  buildDebuggerSpawnInstructions(taskSlug, errorCtx, taskCtx, attemptedFixes, state)
410
410
  };
411
411
 
412
- // Build verify command with optional --auto-recover
413
- const verifyCmd = autoRecover
414
- ? `brain-dev verify --phase ${phaseNumber} --auto-recover`
415
- : `brain-dev verify --phase ${phaseNumber}`;
412
+ // Build review command (review is mandatory before verify)
413
+ const reviewCmd = `brain-dev review --phase ${phaseNumber}`;
416
414
 
417
415
  const humanLines = [
418
416
  `[brain] Executor instructions generated for Plan ${padded} in Phase ${phaseNumber}`,
@@ -425,7 +423,8 @@ async function run(args = [], opts = {}) {
425
423
  if (autoRecover) {
426
424
  humanLines.push('[brain] Auto-recovery: enabled (verify will auto-diagnose failures)');
427
425
  }
428
- humanLines.push(`[brain] Verify with: ${verifyCmd}`);
426
+ humanLines.push(`[brain] After execution, review with: ${reviewCmd}`);
427
+ humanLines.push(pipelineGate(`npx brain-dev review --phase ${phaseNumber}`));
429
428
  humanLines.push('');
430
429
  humanLines.push(fullPrompt);
431
430
  output(result, humanLines.join('\n'));
@@ -5,7 +5,7 @@ const path = require('node:path');
5
5
  const { parseArgs } = require('node:util');
6
6
  const { readState, writeState } = require('../state.cjs');
7
7
  const { logEvent } = require('../logger.cjs');
8
- const { output, error, prefix } = require('../core.cjs');
8
+ const { output, error, prefix, pipelineGate } = require('../core.cjs');
9
9
  const { recordInvocation, estimateTokens } = require('../cost.cjs');
10
10
 
11
11
  async function run(args = [], opts = {}) {
@@ -364,7 +364,8 @@ function handleContinue(brainDir, state) {
364
364
  prefix(`Status: ${newStatus}`),
365
365
  prefix(`Next step: ${nextStep}`),
366
366
  '',
367
- steps[nextStep]
367
+ steps[nextStep],
368
+ nextStep === 'review' ? pipelineGate('npx brain-dev review') : ''
368
369
  ].join('\n');
369
370
 
370
371
  output({
@@ -6,7 +6,7 @@ const { readState, writeState } = require('../state.cjs');
6
6
  const { loadTemplate, interpolate } = require('../templates.cjs');
7
7
  const { resolveModel } = require('../agents.cjs');
8
8
  const { logEvent } = require('../logger.cjs');
9
- const { output, error, success } = require('../core.cjs');
9
+ const { output, error, success, pipelineGate } = require('../core.cjs');
10
10
  const { generateExpertise } = require('../stack-expert.cjs');
11
11
 
12
12
  /**
@@ -265,7 +265,7 @@ function handleExecute(args, brainDir, state) {
265
265
  } catch { /* ignore */ }
266
266
 
267
267
  const nextCmd = isFull
268
- ? `npx brain-dev quick --verify --task ${taskNum}`
268
+ ? `npx brain-dev review`
269
269
  : `npx brain-dev quick --complete --task ${taskNum}`;
270
270
 
271
271
  logEvent(brainDir, 0, { type: 'quick-execute', task: taskNum });
@@ -279,9 +279,10 @@ function handleExecute(args, brainDir, state) {
279
279
  'Do NOT execute the tasks yourself.',
280
280
  '',
281
281
  `After executor completes, run: ${nextCmd}`,
282
+ isFull ? pipelineGate('npx brain-dev review') : '',
282
283
  '',
283
284
  prompt
284
- ].join('\n');
285
+ ].filter(Boolean).join('\n');
285
286
 
286
287
  const result = {
287
288
  action: 'spawn-quick-executor',
@@ -313,6 +314,17 @@ function handleVerify(args, brainDir, state) {
313
314
  return { error: 'task-not-found' };
314
315
  }
315
316
 
317
+ // Hard gate: review must be completed before verify
318
+ const reviewPath = path.join(taskDir, 'REVIEW.md');
319
+ if (!fs.existsSync(reviewPath)) {
320
+ error('Review is mandatory before verify. Run: /brain:review first.');
321
+ output(
322
+ { error: 'review-required', nextAction: '/brain:review' },
323
+ `[brain] BLOCKED: Run /brain:review before quick --verify\n${pipelineGate('npx brain-dev review')}`
324
+ );
325
+ return { error: 'review-required' };
326
+ }
327
+
316
328
  const planPath = path.join(taskDir, 'PLAN-1.md');
317
329
  const verificationPath = path.join(taskDir, 'VERIFICATION-1.md');
318
330
 
@@ -6,14 +6,15 @@ const { parseArgs } = require('node:util');
6
6
  const { readState, writeState } = require('../state.cjs');
7
7
  const { output, error, prefix } = require('../core.cjs');
8
8
  const { logEvent } = require('../logger.cjs');
9
- const { loadTemplate, interpolate } = require('../templates.cjs');
9
+ const { loadTemplate, interpolate, loadTemplateWithOverlay } = require('../templates.cjs');
10
10
  const { getAgent, resolveModel } = require('../agents.cjs');
11
11
  const { generateExpertise } = require('../stack-expert.cjs');
12
12
  const {
13
13
  RESEARCH_AREAS, CORE_QUESTIONS, buildBrownfieldQuestions,
14
14
  readDetection, buildCodebaseContext, generateProjectMd,
15
- generateRequirementsMd, extractFeatures, extractSection
15
+ generateRequirementsMd, validateRequirementsMd, extractFeatures, extractSection
16
16
  } = require('../story-helpers.cjs');
17
+ const { getDetectedFramework } = require('../stack-expert.cjs');
17
18
 
18
19
  /**
19
20
  * Get research areas with optional framework-specific additions.
@@ -318,10 +319,55 @@ function handleContinue(brainDir, state, values) {
318
319
  }
319
320
 
320
321
  if (!hasRequirementsMd) {
322
+ if (storyMeta.status === 'requirements-generating') {
323
+ const humanLines = prefix('Requirements agent is generating... Run --continue after it finishes.');
324
+ output({ action: 'requirements-pending' }, humanLines);
325
+ return { action: 'requirements-pending' };
326
+ }
321
327
  return stepRequirements(brainDir, storyDir, storyMeta, state);
322
328
  }
323
329
 
330
+ // Validate requirements quality before proceeding to roadmap.
331
+ // Only validate agent-generated requirements (hasSummaryMd).
332
+ // Programmatic fallback (--no-research) skips validation because its output
333
+ // format intentionally lacks acceptance criteria.
334
+ if (hasRequirementsMd && !hasRoadmapMd && !storyMeta.requirementsValidated && hasSummaryMd) {
335
+ const reqContent = fs.readFileSync(path.join(storyDir, 'REQUIREMENTS.md'), 'utf8');
336
+ const validation = validateRequirementsMd(reqContent);
337
+
338
+ if (!validation.valid && (storyMeta.requirementsRetries || 0) < 2) {
339
+ fs.unlinkSync(path.join(storyDir, 'REQUIREMENTS.md'));
340
+ storyMeta.requirementsRetries = (storyMeta.requirementsRetries || 0) + 1;
341
+ storyMeta.requirementsFeedback = validation.issues;
342
+
343
+ logEvent(brainDir, 0, { type: 'requirements-retry', story: storyMeta.num, attempt: storyMeta.requirementsRetries, issues: validation.issues.length });
344
+
345
+ const humanLines = [
346
+ prefix(`Requirements quality check failed (attempt ${storyMeta.requirementsRetries}/2):`),
347
+ ...validation.issues.map(i => prefix(` - ${i}`)),
348
+ '',
349
+ prefix('Re-spawning requirements agent with feedback...')
350
+ ].join('\n');
351
+ output({ action: 'requirements-retry', issues: validation.issues, stats: validation.stats }, humanLines);
352
+
353
+ return stepRequirements(brainDir, storyDir, storyMeta, state);
354
+ }
355
+
356
+ storyMeta.requirementsValidated = true;
357
+ fs.writeFileSync(path.join(storyDir, 'story.json'), JSON.stringify(storyMeta, null, 2));
358
+
359
+ if (!validation.valid) {
360
+ const humanLines = prefix(`Warning: REQUIREMENTS.md has ${validation.issues.length} quality issues after ${storyMeta.requirementsRetries} retries. Proceeding anyway.`);
361
+ output({ action: 'requirements-quality-warning', issues: validation.issues, stats: validation.stats }, humanLines);
362
+ }
363
+ }
364
+
324
365
  if (!hasRoadmapMd) {
366
+ if (storyMeta.status === 'roadmap-generating') {
367
+ const humanLines = prefix('Roadmap agent is generating... Run --continue after it finishes.');
368
+ output({ action: 'roadmap-pending' }, humanLines);
369
+ return { action: 'roadmap-pending' };
370
+ }
325
371
  return stepRoadmap(brainDir, storyDir, storyMeta, state);
326
372
  }
327
373
 
@@ -526,9 +572,8 @@ function stepSynthesize(brainDir, storyDir, storyMeta, state) {
526
572
  // ---------------------------------------------------------------------------
527
573
 
528
574
  function stepRequirements(brainDir, storyDir, storyMeta, state) {
529
- // Read PROJECT.md for features
575
+ // Read PROJECT.md
530
576
  const projectMd = fs.readFileSync(path.join(storyDir, 'PROJECT.md'), 'utf8');
531
- const features = extractFeatures(projectMd);
532
577
 
533
578
  // Read research summary if available
534
579
  const summaryPath = path.join(storyDir, 'research', 'SUMMARY.md');
@@ -537,36 +582,107 @@ function stepRequirements(brainDir, storyDir, storyMeta, state) {
537
582
  summaryContent = fs.readFileSync(summaryPath, 'utf8');
538
583
  }
539
584
 
540
- // Generate REQUIREMENTS.md
541
- const { requirementsMd, categories } = generateRequirementsMd(features, summaryContent);
542
- fs.writeFileSync(path.join(storyDir, 'REQUIREMENTS.md'), requirementsMd, 'utf8');
585
+ // No research → use programmatic fallback
586
+ const skipResearch = storyMeta.noResearch === true || !summaryContent;
587
+ if (skipResearch) {
588
+ const features = extractFeatures(projectMd);
589
+ const { requirementsMd, categories } = generateRequirementsMd(features, summaryContent);
590
+ fs.writeFileSync(path.join(storyDir, 'REQUIREMENTS.md'), requirementsMd, 'utf8');
591
+
592
+ storyMeta.status = 'requirements';
593
+ fs.writeFileSync(path.join(storyDir, 'story.json'), JSON.stringify(storyMeta, null, 2));
594
+
595
+ const activeIdx = (state.stories.active || []).findIndex(s => s.dirName === storyMeta.dirName);
596
+ if (activeIdx >= 0) state.stories.active[activeIdx].status = 'requirements';
597
+ writeState(brainDir, state);
598
+
599
+ logEvent(brainDir, 0, { type: 'story-requirements', story: storyMeta.num, categories: categories.length });
600
+
601
+ const humanLines = [
602
+ prefix(`Story: ${storyMeta.version} ${storyMeta.title}`),
603
+ prefix('Step: Requirements Generated (programmatic fallback)'),
604
+ '',
605
+ prefix(`REQUIREMENTS.md written with ${categories.length} categories:`),
606
+ ...categories.map(c => prefix(` - ${c.name} (${c.items.length} requirements)`)),
607
+ '',
608
+ prefix('Review REQUIREMENTS.md and confirm.'),
609
+ prefix('Then run: brain-dev story --continue')
610
+ ].join('\n');
611
+
612
+ const result = {
613
+ action: 'requirements-generated',
614
+ content: requirementsMd,
615
+ categories,
616
+ instruction: 'Review and confirm'
617
+ };
618
+
619
+ output(result, humanLines);
620
+ return result;
621
+ }
622
+
623
+ // Research exists → spawn requirements-generator agent
624
+ const outputPath = path.join(storyDir, 'REQUIREMENTS.md');
625
+ const framework = getDetectedFramework(brainDir);
626
+ const template = loadTemplateWithOverlay('requirements-generator', framework);
627
+ const stackExpertise = generateExpertise(brainDir, 'general');
628
+ const detection = readDetection(brainDir);
629
+ const codebaseContext = detection ? buildCodebaseContext(detection) : '';
630
+
631
+ // Build feedback section if retrying
632
+ let feedbackSection = '';
633
+ if (storyMeta.requirementsFeedback && storyMeta.requirementsFeedback.length > 0) {
634
+ feedbackSection = '\n\n## PREVIOUS ATTEMPT FEEDBACK\n\n' +
635
+ 'Your previous output was rejected for these quality issues:\n' +
636
+ storyMeta.requirementsFeedback.map(i => `- ${i}`).join('\n') +
637
+ '\n\nFix ALL these issues in your next attempt.';
638
+ }
639
+
640
+ const prompt = interpolate(template, {
641
+ project_md: projectMd,
642
+ summary_content: summaryContent,
643
+ stack_expertise: stackExpertise || '',
644
+ codebase_context: codebaseContext || '_No codebase context available._',
645
+ output_path: outputPath
646
+ }) + feedbackSection;
647
+
648
+ const model = resolveModel('requirements-generator', state);
543
649
 
544
650
  // Update status
545
- storyMeta.status = 'requirements';
651
+ storyMeta.status = 'requirements-generating';
546
652
  fs.writeFileSync(path.join(storyDir, 'story.json'), JSON.stringify(storyMeta, null, 2));
547
653
 
548
654
  const activeIdx = (state.stories.active || []).findIndex(s => s.dirName === storyMeta.dirName);
549
- if (activeIdx >= 0) state.stories.active[activeIdx].status = 'requirements';
655
+ if (activeIdx >= 0) state.stories.active[activeIdx].status = 'requirements-generating';
550
656
  writeState(brainDir, state);
551
657
 
552
- logEvent(brainDir, 0, { type: 'story-requirements', story: storyMeta.num, categories: categories.length });
658
+ logEvent(brainDir, 0, { type: 'story-requirements-spawn', story: storyMeta.num, retry: storyMeta.requirementsRetries || 0 });
553
659
 
554
660
  const humanLines = [
555
661
  prefix(`Story: ${storyMeta.version} ${storyMeta.title}`),
556
- prefix('Step: Requirements Generated'),
662
+ prefix('Step: Spawning Requirements Generator Agent'),
663
+ storyMeta.requirementsRetries ? prefix(`Retry: ${storyMeta.requirementsRetries}/2`) : '',
557
664
  '',
558
- prefix(`REQUIREMENTS.md written with ${categories.length} categories:`),
559
- ...categories.map(c => prefix(` - ${c.name} (${c.items.length} requirements)`)),
665
+ prefix('The agent will read your research summary and project context'),
666
+ prefix('to generate detailed, specific requirements with acceptance criteria.'),
560
667
  '',
561
- prefix('Review REQUIREMENTS.md and confirm.'),
562
- prefix('Then run: brain-dev story --continue')
563
- ].join('\n');
668
+ prefix(`Output: ${outputPath}`),
669
+ prefix(`Model: ${model}`),
670
+ '',
671
+ 'IMPORTANT: Use the Agent tool to spawn the requirements-generator agent.',
672
+ 'Do NOT generate requirements yourself.',
673
+ '',
674
+ 'After agent completes, run: brain-dev story --continue',
675
+ '',
676
+ prompt
677
+ ].filter(Boolean).join('\n');
564
678
 
565
679
  const result = {
566
- action: 'requirements-generated',
567
- content: requirementsMd,
568
- categories,
569
- instruction: 'Review and confirm'
680
+ action: 'spawn-requirements-agent',
681
+ prompt,
682
+ model,
683
+ outputPath,
684
+ retry: storyMeta.requirementsRetries || 0,
685
+ instruction: 'Spawn requirements-generator agent, then run --continue'
570
686
  };
571
687
 
572
688
  output(result, humanLines);
@@ -582,6 +698,82 @@ function stepRoadmap(brainDir, storyDir, storyMeta, state) {
582
698
  const reqPath = path.join(storyDir, 'REQUIREMENTS.md');
583
699
  const reqContent = fs.readFileSync(reqPath, 'utf8');
584
700
 
701
+ // Read research summary if available
702
+ const summaryPath = path.join(storyDir, 'research', 'SUMMARY.md');
703
+ let summaryContent = '';
704
+ if (fs.existsSync(summaryPath)) {
705
+ summaryContent = fs.readFileSync(summaryPath, 'utf8');
706
+ }
707
+
708
+ // No research → use programmatic fallback
709
+ const skipResearch = storyMeta.noResearch === true || !summaryContent;
710
+ if (skipResearch) {
711
+ return stepRoadmapProgrammatic(brainDir, storyDir, storyMeta, state, reqContent);
712
+ }
713
+
714
+ // Research exists → spawn roadmap-architect agent
715
+ const projectMd = fs.readFileSync(path.join(storyDir, 'PROJECT.md'), 'utf8');
716
+ const outputPath = path.join(storyDir, 'ROADMAP.md');
717
+ const framework = getDetectedFramework(brainDir);
718
+ const template = loadTemplateWithOverlay('roadmap-architect', framework);
719
+ const stackExpertise = generateExpertise(brainDir, 'general');
720
+
721
+ const prompt = interpolate(template, {
722
+ requirements_content: reqContent,
723
+ summary_content: summaryContent,
724
+ project_md: projectMd,
725
+ stack_expertise: stackExpertise || '',
726
+ output_path: outputPath,
727
+ story_version: storyMeta.version || '',
728
+ story_title: storyMeta.title || ''
729
+ });
730
+
731
+ const model = resolveModel('roadmap-architect', state);
732
+
733
+ // Update status
734
+ storyMeta.status = 'roadmap-generating';
735
+ fs.writeFileSync(path.join(storyDir, 'story.json'), JSON.stringify(storyMeta, null, 2));
736
+
737
+ const activeIdx = (state.stories.active || []).findIndex(s => s.dirName === storyMeta.dirName);
738
+ if (activeIdx >= 0) state.stories.active[activeIdx].status = 'roadmap-generating';
739
+ writeState(brainDir, state);
740
+
741
+ logEvent(brainDir, 0, { type: 'story-roadmap-spawn', story: storyMeta.num });
742
+
743
+ const humanLines = [
744
+ prefix(`Story: ${storyMeta.version} ${storyMeta.title}`),
745
+ prefix('Step: Spawning Roadmap Architect Agent'),
746
+ '',
747
+ prefix('The agent will analyze requirements and research findings'),
748
+ prefix('to create a strategically-phased roadmap with real dependencies.'),
749
+ '',
750
+ prefix(`Output: ${outputPath}`),
751
+ prefix(`Model: ${model}`),
752
+ '',
753
+ 'IMPORTANT: Use the Agent tool to spawn the roadmap-architect agent.',
754
+ 'Do NOT generate the roadmap yourself.',
755
+ '',
756
+ 'After agent completes, run: brain-dev story --continue',
757
+ '',
758
+ prompt
759
+ ].join('\n');
760
+
761
+ const result = {
762
+ action: 'spawn-roadmap-agent',
763
+ prompt,
764
+ model,
765
+ outputPath,
766
+ instruction: 'Spawn roadmap-architect agent, then run --continue'
767
+ };
768
+
769
+ output(result, humanLines);
770
+ return result;
771
+ }
772
+
773
+ /**
774
+ * Programmatic fallback for roadmap generation (no research).
775
+ */
776
+ function stepRoadmapProgrammatic(brainDir, storyDir, storyMeta, state, reqContent) {
585
777
  // Parse categories from REQUIREMENTS.md headings
586
778
  const categoryRegex = /## (.+)\n([\s\S]*?)(?=\n## |$)/g;
587
779
  const phases = [];
@@ -591,8 +783,8 @@ function stepRoadmap(brainDir, storyDir, storyMeta, state) {
591
783
  while ((catMatch = categoryRegex.exec(reqContent)) !== null) {
592
784
  const catName = catMatch[1].trim();
593
785
  if (catName === 'Requirements' || catName.toLowerCase() === 'requirements') continue;
786
+ if (catName === 'Traceability' || catName.toLowerCase() === 'traceability') continue;
594
787
 
595
- // Extract requirement IDs from this category
596
788
  const catBody = catMatch[2];
597
789
  const reqIds = [];
598
790
  const reqLineRegex = /- \*\*(\d+\.\d+)\*\*/g;
@@ -601,7 +793,6 @@ function stepRoadmap(brainDir, storyDir, storyMeta, state) {
601
793
  reqIds.push(reqMatch[1]);
602
794
  }
603
795
 
604
- // Dependencies: each phase depends on the previous one (except phase 1)
605
796
  const dependsOn = phaseNum > 1 ? [phaseNum - 1] : [];
606
797
 
607
798
  phases.push({
@@ -617,7 +808,6 @@ function stepRoadmap(brainDir, storyDir, storyMeta, state) {
617
808
  phaseNum++;
618
809
  }
619
810
 
620
- // Fallback: if no phases extracted, create a single phase
621
811
  if (phases.length === 0) {
622
812
  phases.push({
623
813
  number: 1,
@@ -630,7 +820,6 @@ function stepRoadmap(brainDir, storyDir, storyMeta, state) {
630
820
  });
631
821
  }
632
822
 
633
- // Generate ROADMAP.md
634
823
  const roadmapLines = [
635
824
  '# Roadmap',
636
825
  '',
@@ -650,7 +839,6 @@ function stepRoadmap(brainDir, storyDir, storyMeta, state) {
650
839
  roadmapLines.push('');
651
840
  }
652
841
 
653
- // Progress table
654
842
  roadmapLines.push('## Progress');
655
843
  roadmapLines.push('');
656
844
  roadmapLines.push('| Phase | Status | Plans |');
@@ -663,7 +851,6 @@ function stepRoadmap(brainDir, storyDir, storyMeta, state) {
663
851
  const roadmapMd = roadmapLines.join('\n');
664
852
  fs.writeFileSync(path.join(storyDir, 'ROADMAP.md'), roadmapMd, 'utf8');
665
853
 
666
- // Update status
667
854
  storyMeta.status = 'roadmap';
668
855
  fs.writeFileSync(path.join(storyDir, 'story.json'), JSON.stringify(storyMeta, null, 2));
669
856
 
@@ -675,7 +862,7 @@ function stepRoadmap(brainDir, storyDir, storyMeta, state) {
675
862
 
676
863
  const humanLines = [
677
864
  prefix(`Story: ${storyMeta.version} ${storyMeta.title}`),
678
- prefix('Step: Roadmap Generated'),
865
+ prefix('Step: Roadmap Generated (programmatic fallback)'),
679
866
  '',
680
867
  prefix(`ROADMAP.md written with ${phases.length} phases:`),
681
868
  ...phases.map(p => prefix(` Phase ${p.number}: ${p.name} (depends on: ${p.dependsOn.length === 0 ? 'none' : p.dependsOn.join(', ')})`)),
@@ -6,7 +6,7 @@ const { readState, writeState, atomicWriteSync } = 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');
9
- const { output, error, success } = require('../core.cjs');
9
+ const { output, error, success, pipelineGate } = require('../core.cjs');
10
10
  const antiPatterns = require('../anti-patterns.cjs');
11
11
  const { buildDebuggerSpawnInstructions } = require('./execute.cjs');
12
12
  const { isPathWithinRoot } = require('../security.cjs');
@@ -203,6 +203,17 @@ async function run(args = [], opts = {}) {
203
203
  return { error: 'phase-not-found' };
204
204
  }
205
205
 
206
+ // Hard gate: review must be completed before verify
207
+ const reviewPath = path.join(phaseDir, 'REVIEW.md');
208
+ if (!fs.existsSync(reviewPath)) {
209
+ error('Review is mandatory before verify. Run: /brain:review first.');
210
+ output(
211
+ { error: 'review-required', nextAction: '/brain:review' },
212
+ `[brain] BLOCKED: Run /brain:review before /brain:verify\n${pipelineGate('npx brain-dev review --phase ' + phaseNumber)}`
213
+ );
214
+ return { error: 'review-required' };
215
+ }
216
+
206
217
  // Scan for PLAN-*.md files and extract must_haves from each
207
218
  const files = fs.readdirSync(phaseDir);
208
219
  const planFiles = files.filter(f => /^PLAN-\d+\.md$/.test(f)).sort();
@@ -56,7 +56,9 @@ const PROFILES = {
56
56
  models: {
57
57
  'plan-checker': 'claude-sonnet-4-20250514',
58
58
  verifier: 'claude-sonnet-4-20250514',
59
- researcher: 'claude-sonnet-4-20250514'
59
+ researcher: 'claude-sonnet-4-20250514',
60
+ 'requirements-generator': 'claude-sonnet-4-20250514',
61
+ 'roadmap-architect': 'claude-sonnet-4-20250514'
60
62
  }
61
63
  },
62
64
  budget: {
@@ -67,7 +69,9 @@ const PROFILES = {
67
69
  'plan-checker': 'claude-haiku-4-20250514',
68
70
  verifier: 'claude-haiku-4-20250514',
69
71
  debugger: 'claude-haiku-4-20250514',
70
- mapper: 'claude-haiku-4-20250514'
72
+ mapper: 'claude-haiku-4-20250514',
73
+ 'requirements-generator': 'claude-sonnet-4-20250514',
74
+ 'roadmap-architect': 'claude-haiku-4-20250514'
71
75
  }
72
76
  }
73
77
  };
@@ -426,6 +426,74 @@ function extractSection(content, heading) {
426
426
  return match ? match[1].trim() : '';
427
427
  }
428
428
 
429
+ /**
430
+ * Validate REQUIREMENTS.md quality after LLM generation.
431
+ * Checks for acceptance criteria, description length, banned phrases, and structure.
432
+ * @param {string} content - REQUIREMENTS.md content
433
+ * @returns {{ valid: boolean, issues: string[], stats: { requirements: number, withAcceptance: number, categories: number, acceptanceRate: number } }}
434
+ */
435
+ function validateRequirementsMd(content) {
436
+ const issues = [];
437
+
438
+ // Parse requirements (checkbox format: - [ ] **N.M**: Description)
439
+ const reqRegex = /- \[[ x]\] \*\*(\S+)\*\*:?\s*(.+)/g;
440
+ let reqCount = 0;
441
+ let withAcceptance = 0;
442
+ let match;
443
+
444
+ while ((match = reqRegex.exec(content)) !== null) {
445
+ reqCount++;
446
+ const reqId = match[1];
447
+ const reqText = match[2];
448
+
449
+ // Check for acceptance criterion following this requirement
450
+ const reqIdx = content.indexOf(match[0]);
451
+ const nextLines = content.slice(reqIdx + match[0].length).split('\n').slice(0, 4);
452
+ const hasAcceptance = nextLines.some(l =>
453
+ l.trim().startsWith('- Acceptance:') || l.trim().startsWith('Acceptance:'));
454
+
455
+ if (hasAcceptance) withAcceptance++;
456
+ else issues.push(`${reqId}: missing acceptance criterion`);
457
+
458
+ // Check description length
459
+ if (reqText.split(/\s+/).length < 5) {
460
+ issues.push(`${reqId}: description too short (< 5 words)`);
461
+ }
462
+
463
+ // Check for banned vague phrases
464
+ const banned = [
465
+ 'implement properly', 'handle correctly', 'make it work',
466
+ 'ensure quality', 'integrate well', 'setup recommended'
467
+ ];
468
+ for (const phrase of banned) {
469
+ if (reqText.toLowerCase().includes(phrase)) {
470
+ issues.push(`${reqId}: contains banned vague phrase "${phrase}"`);
471
+ }
472
+ }
473
+ }
474
+
475
+ // Check category count (exclude special sections like Traceability, Requirements)
476
+ const allHeadings = content.match(/^## [A-Z].*/gm) || [];
477
+ const catCount = allHeadings.filter(h =>
478
+ !h.match(/^## (Traceability|Requirements|REQUIREMENTS)/)).length;
479
+ if (catCount < 2) issues.push('Too few categories (expected at least 2)');
480
+
481
+ // Check minimum requirement count
482
+ if (reqCount < 3) issues.push(`Only ${reqCount} requirements found (expected at least 3)`);
483
+
484
+ const valid = issues.length === 0;
485
+ return {
486
+ valid,
487
+ issues,
488
+ stats: {
489
+ requirements: reqCount,
490
+ withAcceptance,
491
+ categories: catCount,
492
+ acceptanceRate: reqCount > 0 ? Math.round(withAcceptance / reqCount * 100) : 0
493
+ }
494
+ };
495
+ }
496
+
429
497
  module.exports = {
430
498
  RESEARCH_AREAS,
431
499
  CORE_QUESTIONS,
@@ -434,6 +502,7 @@ module.exports = {
434
502
  buildCodebaseContext,
435
503
  generateProjectMd,
436
504
  generateRequirementsMd,
505
+ validateRequirementsMd,
437
506
  extractFeatures,
438
507
  extractSection
439
508
  };
@@ -243,6 +243,13 @@ Include sections:
243
243
  - On failure after retry: `## EXECUTION FAILED`
244
244
  - On checkpoint (user input needed): `## CHECKPOINT REACHED`
245
245
 
246
+ ## After Execution Completes
247
+
248
+ When you output EXECUTION COMPLETE:
249
+ - The orchestrator will run /brain:review BEFORE /brain:verify
250
+ - Review is MANDATORY — it cannot be skipped for any reason
251
+ - Do NOT suggest running verify directly after execution
252
+
246
253
  ## Pipeline Enforcement Reminders
247
254
 
248
255
  **EXECUTION FAILED:** When you output this marker:
@@ -0,0 +1,115 @@
1
+ # Requirements Generator Agent
2
+
3
+ You are a requirements engineering specialist. Your job is to analyze research findings and project context to produce specific, measurable, testable requirements.
4
+
5
+ ## Technology Context
6
+ {{stack_expertise}}
7
+
8
+ ## Project Context
9
+ {{project_md}}
10
+
11
+ ## Research Summary
12
+ {{summary_content}}
13
+
14
+ ## Codebase Context
15
+ {{codebase_context}}
16
+
17
+ ## Your Task
18
+
19
+ Generate a structured REQUIREMENTS.md by analyzing the research summary and project context above. Every requirement must be **grounded in research findings** — no generic filler.
20
+
21
+ ## Output Format
22
+
23
+ Write the file to: `{{output_path}}`
24
+
25
+ Use this EXACT format:
26
+
27
+ ```markdown
28
+ # Requirements
29
+
30
+ ## [Category Name]
31
+
32
+ Goal: [1-sentence goal grounded in specific research findings]
33
+
34
+ - [ ] **N.M**: [Specific, measurable requirement — minimum 5 words]
35
+ - Acceptance: [Testable criterion that can become an assert/expect statement]
36
+ - Source: [Which research area identified this need]
37
+ - Priority: P0|P1|P2
38
+
39
+ ## Traceability
40
+
41
+ | Requirement | Category | Priority | Source |
42
+ |-------------|----------|----------|--------|
43
+ | N.M | Category | P0/P1/P2 | Research area |
44
+ ```
45
+
46
+ ### Category Rules
47
+ - Derive categories from ACTUAL research themes and project structure
48
+ - Do NOT use hardcoded generic names like "Infrastructure" or "Quality & Security"
49
+ - Each category needs a Goal line grounded in specific research findings
50
+ - Minimum 2 categories, maximum 8
51
+
52
+ ### Requirement Rules
53
+ 1. **Checkbox format required**: `- [ ] **N.M**: Description`
54
+ - N = category number (1, 2, 3...)
55
+ - M = requirement within category (1, 2, 3...)
56
+ 2. **Every requirement MUST have an Acceptance line** — testable criterion
57
+ 3. **Description minimum 5 words** — be specific about WHAT, not just "implement X"
58
+ 4. **Source must reference research** — which research finding drives this requirement
59
+ 5. **Priority**: P0 = must have, P1 = should have, P2 = nice to have
60
+
61
+ ### Banned Vague Phrases (DO NOT USE)
62
+ - "implement properly"
63
+ - "handle correctly"
64
+ - "make it work"
65
+ - "ensure quality"
66
+ - "integrate well"
67
+ - "setup recommended technology stack"
68
+ - "implement testing strategy"
69
+ - "implement security requirements"
70
+ - "implement recommended architecture patterns"
71
+
72
+ ### Bad vs Good Examples
73
+
74
+ **BAD:**
75
+ ```
76
+ - [ ] **1.1**: Setup recommended technology stack
77
+ - [ ] **2.1**: Implement security requirements
78
+ - [ ] **3.1**: Handle errors properly
79
+ ```
80
+
81
+ **GOOD:**
82
+ ```
83
+ - [ ] **1.1**: User authentication via JWT with RS256 signing, 24h token expiration
84
+ - Acceptance: POST /auth/login with valid credentials returns 200 with JWT containing user_id and exp claims; expired tokens return 401
85
+ - Source: Security research — session management recommendations
86
+ - Priority: P0
87
+
88
+ - [ ] **1.2**: PostgreSQL schema with users table including email uniqueness constraint and bcrypt password hashing
89
+ - Acceptance: Migration creates users table with UNIQUE index on email column; passwords stored as bcrypt hashes with cost factor 12
90
+ - Source: Stack research — database recommendations
91
+ - Priority: P0
92
+
93
+ - [ ] **2.1**: Rate limiting on authentication endpoints at 5 requests per minute per IP
94
+ - Acceptance: 6th login attempt within 60 seconds returns 429 with Retry-After header
95
+ - Source: Security research — brute force protection
96
+ - Priority: P1
97
+ ```
98
+
99
+ ### Cross-Cutting Concerns
100
+ Include requirements from ALL research areas, not just features:
101
+ - Security findings → security requirements
102
+ - Performance findings → performance requirements
103
+ - Testing findings → testing requirements
104
+ - Architecture findings → structural requirements
105
+ - Pitfalls findings → defensive requirements
106
+
107
+ ## Output Marker
108
+
109
+ When complete, output:
110
+ ```
111
+ ## REQUIREMENTS COMPLETE
112
+ Categories: [count]
113
+ Requirements: [total count]
114
+ P0: [count] | P1: [count] | P2: [count]
115
+ ```
@@ -0,0 +1,104 @@
1
+ # Roadmap Architect Agent
2
+
3
+ You are a technical roadmap architect. Your job is to create a strategically-phased development plan with REAL technical dependencies — not a linear chain of categories.
4
+
5
+ ## Technology Context
6
+ {{stack_expertise}}
7
+
8
+ ## Requirements
9
+ {{requirements_content}}
10
+
11
+ ## Research Summary
12
+ {{summary_content}}
13
+
14
+ ## Project Context
15
+ {{project_md}}
16
+
17
+ ## Your Task
18
+
19
+ Analyze requirements and research to create a phased ROADMAP.md with technical dependency analysis, risk assessment, and strategic sequencing.
20
+
21
+ ## Dependency Analysis Rules
22
+
23
+ 1. **Identify TECHNICAL dependencies** between requirements:
24
+ - Database schema must exist before CRUD endpoints
25
+ - Auth infrastructure before protected routes
26
+ - Shared models/types before their consumers
27
+ - Configuration/environment setup before feature code
28
+ - Core utilities before features that use them
29
+
30
+ 2. **Group by TECHNICAL AFFINITY**, not by requirement category:
31
+ - Requirements sharing the same domain model belong together
32
+ - Infrastructure requirements precede feature requirements
33
+ - Integration requirements follow their component requirements
34
+ - Testing setup can parallel with early implementation
35
+
36
+ 3. **Dependency graph must be a DAG**:
37
+ - No circular dependencies
38
+ - Phase N can only depend on phases < N
39
+ - Multiple phases CAN share dependencies (fan-in)
40
+ - A phase CAN enable multiple later phases (fan-out)
41
+ - NOT all phases need to be sequential — identify parallelizable work
42
+
43
+ ## Output Format
44
+
45
+ Write the file to: `{{output_path}}`
46
+
47
+ Use this EXACT format:
48
+
49
+ ```markdown
50
+ # Roadmap
51
+
52
+ Story: {{story_version}} {{story_title}}
53
+
54
+ ## Phases
55
+
56
+ ### Phase N: [Descriptive Name — NOT category name]
57
+
58
+ - **Goal:** [Specific goal grounded in requirements — NOT "Implement X requirements"]
59
+ - **Depends on:** [Comma-separated phase numbers based on TECHNICAL analysis, or None]
60
+ - **Requirements:** [Comma-separated requirement IDs from REQUIREMENTS.md]
61
+ - **Risk:** [Primary risk from research for this phase]
62
+ - **Estimated complexity:** Low|Medium|High
63
+ - **Status:** Pending
64
+
65
+ ## Progress
66
+
67
+ | Phase | Status | Plans |
68
+ |-------|--------|-------|
69
+ | N | Pending | 0/0 |
70
+
71
+ ## Phase Rationale
72
+
73
+ [2-3 sentences explaining WHY phases are ordered this way, citing specific technical dependencies discovered in research]
74
+ ```
75
+
76
+ ## Phase Goal Anti-Patterns (DO NOT USE)
77
+
78
+ - "Implement core features requirements" — circular, says nothing
79
+ - "Setup infrastructure" — too vague
80
+ - "Implement X requirements" — restates the category name
81
+ - "Build the system" — meaningless
82
+
83
+ ## Good Phase Goal Examples
84
+
85
+ - "Establish database schema, authentication flow, and API foundation that all feature phases depend on"
86
+ - "Build real-time notification pipeline using WebSocket connections with Redis pub/sub backing"
87
+ - "Add rate limiting, input validation, and CSRF protection across all public endpoints"
88
+
89
+ ## Phase Sizing Guidelines
90
+
91
+ - Each phase should have 3-8 requirements
92
+ - If a phase has 10+ requirements, it's too large — split it
93
+ - If a phase has 1-2 requirements, merge with a related phase
94
+ - Aim for 3-6 phases total for most projects
95
+
96
+ ## Output Marker
97
+
98
+ When complete, output:
99
+ ```
100
+ ## ROADMAP COMPLETE
101
+ Phases: [count]
102
+ Dependency depth: [max chain length]
103
+ Parallelizable: [phases with same/no dependencies that could run concurrently]
104
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brain-dev",
3
- "version": "2.3.1",
3
+ "version": "2.5.0",
4
4
  "description": "AI-powered development workflow orchestrator",
5
5
  "author": "halilcosdu",
6
6
  "license": "MIT",