brain-dev 0.1.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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/agents/brain-checker.md +33 -0
  4. package/agents/brain-debugger.md +35 -0
  5. package/agents/brain-executor.md +37 -0
  6. package/agents/brain-mapper.md +44 -0
  7. package/agents/brain-planner.md +49 -0
  8. package/agents/brain-researcher.md +47 -0
  9. package/agents/brain-synthesizer.md +43 -0
  10. package/agents/brain-verifier.md +41 -0
  11. package/bin/brain-tools.cjs +185 -0
  12. package/bin/lib/adr.cjs +283 -0
  13. package/bin/lib/agents.cjs +152 -0
  14. package/bin/lib/anti-patterns.cjs +183 -0
  15. package/bin/lib/audit.cjs +268 -0
  16. package/bin/lib/commands/adr.cjs +126 -0
  17. package/bin/lib/commands/complete.cjs +270 -0
  18. package/bin/lib/commands/config.cjs +306 -0
  19. package/bin/lib/commands/discuss.cjs +237 -0
  20. package/bin/lib/commands/execute.cjs +415 -0
  21. package/bin/lib/commands/health.cjs +103 -0
  22. package/bin/lib/commands/map.cjs +101 -0
  23. package/bin/lib/commands/new-project.cjs +885 -0
  24. package/bin/lib/commands/pause.cjs +142 -0
  25. package/bin/lib/commands/phase-manage.cjs +357 -0
  26. package/bin/lib/commands/plan.cjs +451 -0
  27. package/bin/lib/commands/progress.cjs +167 -0
  28. package/bin/lib/commands/quick.cjs +447 -0
  29. package/bin/lib/commands/resume.cjs +196 -0
  30. package/bin/lib/commands/storm.cjs +590 -0
  31. package/bin/lib/commands/verify.cjs +504 -0
  32. package/bin/lib/commands.cjs +263 -0
  33. package/bin/lib/complexity.cjs +138 -0
  34. package/bin/lib/complexity.test.cjs +108 -0
  35. package/bin/lib/config.cjs +452 -0
  36. package/bin/lib/core.cjs +62 -0
  37. package/bin/lib/detect.cjs +603 -0
  38. package/bin/lib/git.cjs +112 -0
  39. package/bin/lib/health.cjs +356 -0
  40. package/bin/lib/init.cjs +310 -0
  41. package/bin/lib/logger.cjs +100 -0
  42. package/bin/lib/platform.cjs +58 -0
  43. package/bin/lib/requirements.cjs +158 -0
  44. package/bin/lib/roadmap.cjs +228 -0
  45. package/bin/lib/security.cjs +237 -0
  46. package/bin/lib/state.cjs +353 -0
  47. package/bin/lib/templates.cjs +48 -0
  48. package/bin/templates/advocate.md +182 -0
  49. package/bin/templates/checkpoint.md +55 -0
  50. package/bin/templates/debugger.md +148 -0
  51. package/bin/templates/discuss.md +60 -0
  52. package/bin/templates/executor.md +201 -0
  53. package/bin/templates/mapper.md +129 -0
  54. package/bin/templates/plan-checker.md +134 -0
  55. package/bin/templates/planner.md +165 -0
  56. package/bin/templates/researcher.md +78 -0
  57. package/bin/templates/storm.html +376 -0
  58. package/bin/templates/synthesis.md +30 -0
  59. package/bin/templates/verifier.md +181 -0
  60. package/commands/brain/adr.md +34 -0
  61. package/commands/brain/complete.md +37 -0
  62. package/commands/brain/config.md +37 -0
  63. package/commands/brain/discuss.md +35 -0
  64. package/commands/brain/execute.md +38 -0
  65. package/commands/brain/health.md +33 -0
  66. package/commands/brain/map.md +35 -0
  67. package/commands/brain/new-project.md +38 -0
  68. package/commands/brain/pause.md +26 -0
  69. package/commands/brain/plan.md +38 -0
  70. package/commands/brain/progress.md +28 -0
  71. package/commands/brain/quick.md +51 -0
  72. package/commands/brain/resume.md +28 -0
  73. package/commands/brain/storm.md +30 -0
  74. package/commands/brain/verify.md +39 -0
  75. package/hooks/bootstrap.sh +54 -0
  76. package/hooks/post-tool-use.sh +45 -0
  77. package/hooks/statusline.sh +130 -0
  78. package/package.json +36 -0
@@ -0,0 +1,268 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { parseRequirements } = require('./requirements.cjs');
6
+ const { atomicWriteSync } = require('./state.cjs');
7
+ const { parseRoadmap } = require('./roadmap.cjs');
8
+ const { output, error } = require('./core.cjs');
9
+
10
+ /**
11
+ * Build a requirement matrix Map for the given scoped requirement IDs.
12
+ * Each entry: { required: true, verified: false, built: false }
13
+ * @param {string[]} scopedReqIds
14
+ * @returns {Map<string, {required: boolean, verified: boolean, built: boolean}>}
15
+ */
16
+ function buildRequirementMatrix(scopedReqIds) {
17
+ const matrix = new Map();
18
+ for (const reqId of scopedReqIds) {
19
+ matrix.set(reqId, { required: true, verified: false, built: false });
20
+ }
21
+ return matrix;
22
+ }
23
+
24
+ /**
25
+ * Walk .brain/phases/VERIFICATION.md files and mark verified=true in matrix.
26
+ * Uses same regex as complete.cjs: /- \[x\] (\S+)/g
27
+ * @param {string} brainDir
28
+ * @param {Map} matrix
29
+ */
30
+ function crossReferenceVerifications(brainDir, matrix) {
31
+ const phasesDir = path.join(brainDir, 'phases');
32
+ if (!fs.existsSync(phasesDir)) return;
33
+
34
+ for (const dir of fs.readdirSync(phasesDir)) {
35
+ const verPath = path.join(phasesDir, dir, 'VERIFICATION.md');
36
+ if (!fs.existsSync(verPath)) continue;
37
+
38
+ const content = fs.readFileSync(verPath, 'utf8');
39
+ const regex = /- \[x\] (\S+)/g;
40
+ let match;
41
+ while ((match = regex.exec(content)) !== null) {
42
+ const reqId = match[1];
43
+ if (matrix.has(reqId)) {
44
+ matrix.get(reqId).verified = true;
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Walk .brain/phases/SUMMARY-*.md files and mark built=true in matrix.
52
+ * Uses same regex as complete.cjs: /requirements-completed:\s*\[([^\]]*)\]/
53
+ * @param {string} brainDir
54
+ * @param {Map} matrix
55
+ */
56
+ function crossReferenceSummaries(brainDir, matrix) {
57
+ const phasesDir = path.join(brainDir, 'phases');
58
+ if (!fs.existsSync(phasesDir)) return;
59
+
60
+ for (const dir of fs.readdirSync(phasesDir)) {
61
+ const phaseDir = path.join(phasesDir, dir);
62
+ if (!fs.statSync(phaseDir).isDirectory()) continue;
63
+
64
+ for (const f of fs.readdirSync(phaseDir)) {
65
+ if (!/^SUMMARY-\d+\.md$/.test(f)) continue;
66
+
67
+ const content = fs.readFileSync(path.join(phaseDir, f), 'utf8');
68
+ const match = content.match(/requirements-completed:\s*\[([^\]]*)\]/);
69
+ if (!match) continue;
70
+
71
+ const reqIds = match[1].split(',').map(s => s.trim()).filter(Boolean);
72
+ for (const reqId of reqIds) {
73
+ if (matrix.has(reqId)) {
74
+ matrix.get(reqId).built = true;
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Find discrepancies in the requirement matrix.
83
+ * @param {Map} matrix
84
+ * @returns {Array<{reqId: string, type: string, detail: string}>}
85
+ */
86
+ function findDiscrepancies(matrix) {
87
+ const discrepancies = [];
88
+
89
+ for (const [reqId, entry] of matrix) {
90
+ if (entry.required && !entry.verified) {
91
+ discrepancies.push({
92
+ reqId,
93
+ type: 'not-verified',
94
+ detail: `Requirement ${reqId} exists but has no verification evidence`
95
+ });
96
+ }
97
+ if (entry.required && !entry.built) {
98
+ discrepancies.push({
99
+ reqId,
100
+ type: 'not-built',
101
+ detail: `Requirement ${reqId} exists but has no summary/build evidence`
102
+ });
103
+ }
104
+ if (!entry.required && entry.verified) {
105
+ discrepancies.push({
106
+ reqId,
107
+ type: 'orphan',
108
+ detail: `Verification found for ${reqId} but it is not a scoped requirement`
109
+ });
110
+ }
111
+ }
112
+
113
+ return discrepancies;
114
+ }
115
+
116
+ /**
117
+ * Generate a Markdown audit report.
118
+ * @param {Map} matrix
119
+ * @param {Array} discrepancies
120
+ * @param {string} version
121
+ * @returns {string}
122
+ */
123
+ function generateReport(matrix, discrepancies, version) {
124
+ const total = [...matrix.values()].filter(e => e.required).length;
125
+ const verified = [...matrix.values()].filter(e => e.required && e.verified).length;
126
+ const built = [...matrix.values()].filter(e => e.required && e.built).length;
127
+ const date = new Date().toISOString().slice(0, 10);
128
+ const verdict = discrepancies.length === 0 ? 'PASS' : 'FAIL';
129
+
130
+ const lines = [
131
+ '---',
132
+ `version: ${version}`,
133
+ `date: ${date}`,
134
+ `total: ${total}`,
135
+ `verified: ${verified}`,
136
+ `built: ${built}`,
137
+ `discrepancy_count: ${discrepancies.length}`,
138
+ `verdict: ${verdict}`,
139
+ '---',
140
+ '',
141
+ `# Milestone Audit: ${version}`,
142
+ '',
143
+ '## Requirement Matrix',
144
+ '',
145
+ '| Req | Required | Verified | Built | Status |',
146
+ '|-----|----------|----------|-------|--------|'
147
+ ];
148
+
149
+ for (const [reqId, entry] of matrix) {
150
+ const status = entry.required && entry.verified && entry.built
151
+ ? 'OK'
152
+ : entry.required ? 'GAP' : 'ORPHAN';
153
+ lines.push(
154
+ `| ${reqId} | ${entry.required ? 'Yes' : 'No'} | ${entry.verified ? 'Yes' : 'No'} | ${entry.built ? 'Yes' : 'No'} | ${status} |`
155
+ );
156
+ }
157
+
158
+ lines.push('');
159
+
160
+ if (discrepancies.length > 0) {
161
+ lines.push('## Discrepancies');
162
+ lines.push('');
163
+ for (const d of discrepancies) {
164
+ lines.push(`- **${d.reqId}** (${d.type}): ${d.detail}`);
165
+ }
166
+ lines.push('');
167
+ }
168
+
169
+ lines.push('## Summary');
170
+ lines.push('');
171
+ lines.push(`Verdict: **${verdict}**`);
172
+ lines.push('');
173
+ if (verdict === 'PASS') {
174
+ lines.push('All scoped requirements have both verification and build evidence.');
175
+ } else {
176
+ lines.push(`${discrepancies.length} discrepancy(ies) found. Resolve gaps before milestone completion.`);
177
+ }
178
+ lines.push('');
179
+
180
+ return lines.join('\n');
181
+ }
182
+
183
+ /**
184
+ * Write audit report file to .brain/audits/.
185
+ * Creates directory if missing.
186
+ * @param {string} brainDir
187
+ * @param {string} version
188
+ * @param {string} report
189
+ */
190
+ function writeAuditFile(brainDir, version, report) {
191
+ const auditsDir = path.join(brainDir, 'audits');
192
+ if (!fs.existsSync(auditsDir)) {
193
+ fs.mkdirSync(auditsDir, { recursive: true });
194
+ }
195
+ const filePath = path.join(auditsDir, `${version}-audit.md`);
196
+ atomicWriteSync(filePath, report);
197
+ }
198
+
199
+ /**
200
+ * Run the full audit flow.
201
+ * @param {string} brainDir
202
+ * @param {object} state - Brain state object
203
+ * @param {object} opts - Options
204
+ * @returns {{ action: string, discrepancies: Array, report_path: string, verifiedReqIds: string[] }}
205
+ */
206
+ function runAudit(brainDir, state, opts = {}) {
207
+ // 1. Parse requirements
208
+ const { requirements } = parseRequirements(brainDir);
209
+ const allReqIds = requirements.map(r => r.id);
210
+
211
+ // 2. Scope to completed/verified phases (reuse logic from complete.cjs)
212
+ const completedPhaseReqs = new Set();
213
+ try {
214
+ const roadmap = parseRoadmap(brainDir);
215
+ for (const phase of roadmap.phases) {
216
+ if (['Complete', 'complete', 'Verified', 'verified'].includes(phase.status)) {
217
+ for (const req of phase.requirements) {
218
+ completedPhaseReqs.add(req);
219
+ }
220
+ }
221
+ }
222
+ } catch {
223
+ // If roadmap parse fails, use all requirements
224
+ allReqIds.forEach(r => completedPhaseReqs.add(r));
225
+ }
226
+
227
+ const scopedReqIds = allReqIds.filter(r => completedPhaseReqs.has(r));
228
+
229
+ // 3. Build matrix and cross-reference
230
+ const matrix = buildRequirementMatrix(scopedReqIds);
231
+ crossReferenceVerifications(brainDir, matrix);
232
+ crossReferenceSummaries(brainDir, matrix);
233
+
234
+ // 4. Find discrepancies
235
+ const discrepancies = findDiscrepancies(matrix);
236
+
237
+ // 5. Generate and write report
238
+ const version = state.milestone ? state.milestone.current : 'v1.0';
239
+ const report = generateReport(matrix, discrepancies, version);
240
+ writeAuditFile(brainDir, version, report);
241
+
242
+ // 6. Collect verifiedReqIds (both verified AND built)
243
+ const verifiedReqIds = [];
244
+ for (const [reqId, entry] of matrix) {
245
+ if (entry.required && entry.verified && entry.built) {
246
+ verifiedReqIds.push(reqId);
247
+ }
248
+ }
249
+
250
+ const reportPath = path.join(brainDir, 'audits', `${version}-audit.md`);
251
+
252
+ return {
253
+ action: 'audit-complete',
254
+ discrepancies,
255
+ report_path: reportPath,
256
+ verifiedReqIds
257
+ };
258
+ }
259
+
260
+ module.exports = {
261
+ buildRequirementMatrix,
262
+ crossReferenceVerifications,
263
+ crossReferenceSummaries,
264
+ findDiscrepancies,
265
+ generateReport,
266
+ writeAuditFile,
267
+ runAudit
268
+ };
@@ -0,0 +1,126 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { output, error, success } = require('../core.cjs');
5
+ const { createADR, listADRs, searchADRs } = require('../adr.cjs');
6
+
7
+ /**
8
+ * Parse --key value pairs from args array.
9
+ * @param {string[]} args
10
+ * @returns {object} Parsed key-value pairs
11
+ */
12
+ function parseFlags(args) {
13
+ const flags = {};
14
+ for (let i = 0; i < args.length; i++) {
15
+ if (args[i].startsWith('--') && i + 1 < args.length) {
16
+ const key = args[i].slice(2);
17
+ flags[key] = args[i + 1];
18
+ i++;
19
+ }
20
+ }
21
+ return flags;
22
+ }
23
+
24
+ /**
25
+ * Show ADR command usage.
26
+ */
27
+ function showUsage() {
28
+ const text = [
29
+ 'Usage: brain-dev adr <subcommand> [options]',
30
+ '',
31
+ 'Subcommands:',
32
+ ' create Create a new Architecture Decision Record',
33
+ ' --title <text> Decision title (required)',
34
+ ' --context <text> Why this decision came up',
35
+ ' --decision <text> What was chosen',
36
+ ' --alternatives <t> What was rejected',
37
+ ' --consequences <t> Impact of the decision',
38
+ ' --phase <n> Phase number',
39
+ ' --plan <n> Plan number',
40
+ '',
41
+ ' list [--phase <n>] List all ADRs (optionally filtered by phase)',
42
+ '',
43
+ ' search <keyword> Search ADR content by keyword'
44
+ ].join('\n');
45
+ console.log(text);
46
+ }
47
+
48
+ /**
49
+ * Run the ADR command.
50
+ * @param {string[]} args - Command arguments
51
+ * @param {object} opts
52
+ * @param {string} opts.brainDir - Path to .brain/ directory
53
+ */
54
+ function run(args = [], opts = {}) {
55
+ const brainDir = opts && opts.brainDir
56
+ ? opts.brainDir
57
+ : path.join(process.cwd(), '.brain');
58
+
59
+ const subcommand = args && args[0];
60
+
61
+ if (!subcommand || subcommand === 'help') {
62
+ showUsage();
63
+ return;
64
+ }
65
+
66
+ switch (subcommand) {
67
+ case 'create': {
68
+ const flags = parseFlags(args.slice(1));
69
+ if (!flags.title) {
70
+ error('--title is required for adr create');
71
+ return;
72
+ }
73
+ const result = createADR(brainDir, {
74
+ title: flags.title,
75
+ context: flags.context || '',
76
+ decision: flags.decision || '',
77
+ alternatives: flags.alternatives || '',
78
+ consequences: flags.consequences || '',
79
+ phase: flags.phase || '',
80
+ plan: flags.plan || '',
81
+ deciders: flags.deciders || 'agent'
82
+ });
83
+ output(result, `Created ${result.id} at ${result.path}`);
84
+ break;
85
+ }
86
+
87
+ case 'list': {
88
+ const flags = parseFlags(args.slice(1));
89
+ const adrs = listADRs(brainDir, flags.phase ? { phase: flags.phase } : undefined);
90
+ if (adrs.length === 0) {
91
+ output({ adrs: [] }, 'No ADRs found.');
92
+ return;
93
+ }
94
+ const lines = adrs.map(a =>
95
+ `${String(a.id).padStart(3, '0')} ${a.status.padEnd(12)} ${a.title}`
96
+ );
97
+ output({ adrs }, lines.join('\n'));
98
+ break;
99
+ }
100
+
101
+ case 'search': {
102
+ const keyword = args[1];
103
+ if (!keyword) {
104
+ error('Usage: brain-dev adr search <keyword>');
105
+ return;
106
+ }
107
+ const results = searchADRs(brainDir, keyword);
108
+ if (results.length === 0) {
109
+ output({ results: [] }, `No ADRs matching "${keyword}".`);
110
+ return;
111
+ }
112
+ const lines = results.map(a =>
113
+ `${String(a.id).padStart(3, '0')} ${a.status.padEnd(12)} ${a.title}`
114
+ );
115
+ output({ results }, lines.join('\n'));
116
+ break;
117
+ }
118
+
119
+ default:
120
+ error(`Unknown adr subcommand: '${subcommand}'. Use: create, list, search`);
121
+ showUsage();
122
+ break;
123
+ }
124
+ }
125
+
126
+ module.exports = { run };
@@ -0,0 +1,270 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { readState, writeState } = require('../state.cjs');
6
+ const { parseRoadmap, writeRoadmap } = require('../roadmap.cjs');
7
+ const { gitTag } = require('../git.cjs');
8
+ const { output, error, success } = require('../core.cjs');
9
+ const audit = require('../audit.cjs');
10
+ const requirements = require('../requirements.cjs');
11
+
12
+ /**
13
+ * Find a phase directory under .brain/phases/ matching a phase number.
14
+ * @param {string} brainDir
15
+ * @param {number} phaseNumber
16
+ * @returns {string|null}
17
+ */
18
+ function findPhaseDir(brainDir, phaseNumber) {
19
+ const phasesDir = path.join(brainDir, 'phases');
20
+ if (!fs.existsSync(phasesDir)) return null;
21
+
22
+ const padded = String(phaseNumber).padStart(2, '0');
23
+ const match = fs.readdirSync(phasesDir).find(d => d.startsWith(`${padded}-`));
24
+ return match ? path.join(phasesDir, match) : null;
25
+ }
26
+
27
+ /**
28
+ * Scan phase directory for PLAN and SUMMARY files.
29
+ * @param {string} phaseDir
30
+ * @returns {{ plans: number[], summaries: number[] }}
31
+ */
32
+ function scanPhaseArtifacts(phaseDir) {
33
+ if (!phaseDir || !fs.existsSync(phaseDir)) return { plans: [], summaries: [] };
34
+
35
+ const files = fs.readdirSync(phaseDir);
36
+ const plans = [];
37
+ const summaries = [];
38
+
39
+ for (const f of files) {
40
+ const planMatch = f.match(/^PLAN-(\d+)\.md$/);
41
+ if (planMatch) plans.push(parseInt(planMatch[1], 10));
42
+
43
+ const sumMatch = f.match(/^SUMMARY-(\d+)\.md$/);
44
+ if (sumMatch) summaries.push(parseInt(sumMatch[1], 10));
45
+ }
46
+
47
+ return { plans, summaries };
48
+ }
49
+
50
+ /**
51
+ * Extract requirements-completed from SUMMARY frontmatter.
52
+ * @param {string} content
53
+ * @returns {string[]}
54
+ */
55
+ function extractCompletedReqs(content) {
56
+ const match = content.match(/requirements-completed:\s*\[([^\]]*)\]/);
57
+ if (!match) return [];
58
+ return match[1].split(',').map(s => s.trim()).filter(Boolean);
59
+ }
60
+
61
+ /**
62
+ * Extract verified requirement IDs from VERIFICATION.md.
63
+ * @param {string} content
64
+ * @returns {string[]}
65
+ */
66
+ function extractVerifiedReqs(content) {
67
+ const reqs = [];
68
+ const regex = /- \[x\] (\S+)/g;
69
+ let match;
70
+ while ((match = regex.exec(content)) !== null) {
71
+ reqs.push(match[1]);
72
+ }
73
+ return reqs;
74
+ }
75
+
76
+ /**
77
+ * Extract all requirement IDs from REQUIREMENTS.md.
78
+ * Looks for ## REQ-ID: patterns.
79
+ * @param {string} content
80
+ * @returns {string[]}
81
+ */
82
+ function extractAllReqIds(content) {
83
+ const ids = [];
84
+ const regex = /^## (\S+):/gm;
85
+ let match;
86
+ while ((match = regex.exec(content)) !== null) {
87
+ ids.push(match[1]);
88
+ }
89
+ return ids;
90
+ }
91
+
92
+ /**
93
+ * Increment a semver-like version string.
94
+ * v1.0 -> v1.1, v1.9 -> v1.10
95
+ * @param {string} version
96
+ * @returns {string}
97
+ */
98
+ function incrementVersion(version) {
99
+ const match = version.match(/^v?(\d+)\.(\d+)$/);
100
+ if (!match) return 'v1.1';
101
+ const major = parseInt(match[1], 10);
102
+ const minor = parseInt(match[2], 10);
103
+ return `v${major}.${minor + 1}`;
104
+ }
105
+
106
+ /**
107
+ * Run the complete command.
108
+ *
109
+ * Default (no flags): Perform 3-source audit for milestone completion.
110
+ * --phase N: Mark a single phase as complete.
111
+ *
112
+ * @param {string[]} args
113
+ * @param {object} [opts] - Options (brainDir, skipGitTag for testing)
114
+ * @returns {object}
115
+ */
116
+ async function run(args = [], opts = {}) {
117
+ const brainDir = opts.brainDir || path.join(process.cwd(), '.brain');
118
+ const state = readState(brainDir);
119
+
120
+ if (!state) {
121
+ error("No brain state found. Run 'brain-dev init' first.");
122
+ return { error: 'no-state' };
123
+ }
124
+
125
+ // --audit-only: run audit without triggering milestone completion
126
+ if (args.includes('--audit-only')) {
127
+ return audit.runAudit(brainDir, state, opts);
128
+ }
129
+
130
+ const phaseIdx = args.indexOf('--phase');
131
+ if (phaseIdx >= 0) {
132
+ return handlePhaseComplete(args, phaseIdx, brainDir, state);
133
+ }
134
+
135
+ return handleMilestoneComplete(brainDir, state, args, opts);
136
+ }
137
+
138
+ /**
139
+ * Handle --phase N: mark single phase as complete.
140
+ */
141
+ function handlePhaseComplete(args, phaseIdx, brainDir, state) {
142
+ const phaseNumber = parseInt(args[phaseIdx + 1], 10);
143
+ const phaseDir = findPhaseDir(brainDir, phaseNumber);
144
+
145
+ if (!phaseDir) {
146
+ error(`Phase ${phaseNumber} directory not found.`);
147
+ return { error: 'phase-not-found' };
148
+ }
149
+
150
+ const { plans, summaries } = scanPhaseArtifacts(phaseDir);
151
+
152
+ // Check all plans have summaries
153
+ const missingPlans = plans.filter(p => !summaries.includes(p));
154
+ if (missingPlans.length > 0) {
155
+ error(`Plans not yet executed: ${missingPlans.join(', ')}. Run /brain:execute first.`);
156
+ return { error: 'incomplete-plans', missing: missingPlans, nextAction: '/brain:execute' };
157
+ }
158
+
159
+ // Check verification was run (VERIFICATION.md should exist)
160
+ const verificationPath = path.join(phaseDir, 'VERIFICATION.md');
161
+ if (!fs.existsSync(verificationPath) && state.phase.status !== 'verified') {
162
+ error(`Phase ${phaseNumber} not verified. Run /brain:verify first.`);
163
+ return { error: 'not-verified', nextAction: '/brain:verify' };
164
+ }
165
+
166
+ // Mark phase as complete in state (handle both string and object formats)
167
+ if (Array.isArray(state.phase.phases)) {
168
+ const idx = state.phase.phases.findIndex(p =>
169
+ typeof p === 'object' ? p.number === phaseNumber : false
170
+ );
171
+ if (idx >= 0 && typeof state.phase.phases[idx] === 'object') {
172
+ state.phase.phases[idx].status = 'complete';
173
+ }
174
+ }
175
+
176
+ // Advance to next phase
177
+ const nextPhase = phaseNumber + 1;
178
+ const totalPhases = state.phase.total || (Array.isArray(state.phase.phases) ? state.phase.phases.length : 0);
179
+ const hasNextPhase = totalPhases > 0 && nextPhase <= totalPhases;
180
+ if (hasNextPhase) {
181
+ state.phase.current = nextPhase;
182
+ state.phase.status = 'pending';
183
+ } else {
184
+ state.phase.status = 'complete';
185
+ }
186
+ writeState(brainDir, state);
187
+
188
+ // Update roadmap
189
+ try {
190
+ const roadmap = parseRoadmap(brainDir);
191
+ const roadmapPhase = roadmap.phases.find(p => p.number === phaseNumber);
192
+ if (roadmapPhase) {
193
+ roadmapPhase.status = 'Complete';
194
+ }
195
+ writeRoadmap(brainDir, roadmap);
196
+ } catch (e) {
197
+ error(`Warning: Could not update roadmap: ${e.message}`);
198
+ }
199
+
200
+ const nextActionCmd = hasNextPhase ? '/brain:discuss' : '/brain:complete';
201
+ const msg = hasNextPhase
202
+ ? `Phase ${phaseNumber} complete. Next: Phase ${nextPhase}. Run ${nextActionCmd} to continue.`
203
+ : `Phase ${phaseNumber} complete. All phases done! Run /brain:complete for milestone completion.`;
204
+ output({ action: 'phase-complete', phase: phaseNumber, nextPhase: hasNextPhase ? nextPhase : null, nextAction: nextActionCmd }, `[brain] ${msg}`);
205
+ return { action: 'phase-complete', phase: phaseNumber, nextPhase: hasNextPhase ? nextPhase : null, nextAction: nextActionCmd };
206
+ }
207
+
208
+ /**
209
+ * Handle milestone completion with 3-source audit.
210
+ * Now delegates cross-referencing to audit.cjs and wires requirements status updates.
211
+ */
212
+ function handleMilestoneComplete(brainDir, state, args = [], opts = {}) {
213
+ // Run the full audit
214
+ const auditResult = audit.runAudit(brainDir, state, opts);
215
+ const hasForce = args.includes('--force');
216
+
217
+ if (auditResult.discrepancies.length > 0 && !hasForce) {
218
+ const gaps = auditResult.discrepancies.map(d => d.reqId);
219
+ const uniqueGaps = [...new Set(gaps)];
220
+ const msg = [
221
+ `Gaps detected: ${uniqueGaps.length} requirement(s) not fully covered.`,
222
+ `Missing: ${uniqueGaps.join(', ')}`,
223
+ `Run gap closure: /brain:plan --gaps, then /brain:execute, then /brain:verify, then /brain:complete again.`,
224
+ `Or use --force to override.`
225
+ ].join('\n');
226
+
227
+ output({ action: 'gaps-found', gaps: uniqueGaps, audit: auditResult }, `[brain] ${msg}`);
228
+ return { action: 'gaps-found', gaps: uniqueGaps, audit: auditResult };
229
+ }
230
+
231
+ // No gaps (or --force override) - proceed with milestone completion
232
+
233
+ // Update requirements status for verified requirements
234
+ for (const reqId of auditResult.verifiedReqIds) {
235
+ try {
236
+ requirements.updateStatus(brainDir, reqId, true);
237
+ requirements.updateTraceability(brainDir, reqId, { status: 'Complete' });
238
+ output(`Updated requirement ${reqId} to Complete`);
239
+ } catch (e) {
240
+ error(`Warning: Could not update requirement ${reqId}: ${e.message}`);
241
+ }
242
+ }
243
+
244
+ const version = state.milestone.current;
245
+
246
+ // Create git tag (unless skipped for testing)
247
+ if (!opts.skipGitTag) {
248
+ const projectDir = path.dirname(brainDir);
249
+ gitTag(version, `Milestone: ${state.milestone.name || version}`, projectDir);
250
+ }
251
+
252
+ // Archive milestone
253
+ state.milestone.history.push({
254
+ version,
255
+ name: state.milestone.name,
256
+ completed: new Date().toISOString().slice(0, 10)
257
+ });
258
+ state.milestone.current = incrementVersion(version);
259
+ state.milestone.name = null;
260
+ writeState(brainDir, state);
261
+
262
+ const forceWarning = hasForce && auditResult.discrepancies.length > 0
263
+ ? ' (forced with discrepancies)'
264
+ : '';
265
+ const msg = `Milestone ${version} complete!${forceWarning}${opts.skipGitTag ? '' : ' Tagged in git.'}`;
266
+ output({ action: 'milestone-complete', version, audit: auditResult }, `[brain] ${msg}`);
267
+ return { action: 'milestone-complete', version, audit: auditResult };
268
+ }
269
+
270
+ module.exports = { run };