@brainforge/core 3.1.5 → 3.1.6

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 (3) hide show
  1. package/dist/index.d.ts +651 -3
  2. package/dist/index.js +3812 -46
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,26 +1,189 @@
1
1
  // src/state/manager.ts
2
+ import fs3 from "fs/promises";
3
+ import path3 from "path";
4
+
5
+ // src/project/workspace.ts
6
+ import fs2 from "fs/promises";
7
+ import path2 from "path";
8
+
9
+ // src/project/project-config.ts
2
10
  import fs from "fs/promises";
3
11
  import path from "path";
12
+ var DEFAULT_CONFIG = {
13
+ version: 2,
14
+ mode: "simple",
15
+ runtime: "claude-code",
16
+ parallelization: {
17
+ enabled: false,
18
+ maxAgents: 4
19
+ },
20
+ automation: {
21
+ autoCommit: false,
22
+ verifyEachPhase: true,
23
+ shipEachPhase: false,
24
+ stopOnFailure: true
25
+ },
26
+ memory: {
27
+ contextPacketMaxTokens: 12e3,
28
+ summarizeAfterEachRun: true,
29
+ keepRawLogs: true
30
+ },
31
+ student: {
32
+ enabled: false,
33
+ level: "intermediate",
34
+ professorMode: true
35
+ },
36
+ safety: {
37
+ redactSecrets: true,
38
+ requireCleanGit: false,
39
+ dryRunByDefault: false
40
+ },
41
+ dashboard: {
42
+ enabled: true,
43
+ port: 3742
44
+ }
45
+ };
46
+ var CONFIG_FILE = path.join(".brainforge", "config.json");
47
+ async function loadConfig(cwd) {
48
+ try {
49
+ const raw = await fs.readFile(path.join(cwd, CONFIG_FILE), "utf-8");
50
+ const parsed = JSON.parse(raw);
51
+ return deepMerge(DEFAULT_CONFIG, parsed);
52
+ } catch {
53
+ return structuredClone(DEFAULT_CONFIG);
54
+ }
55
+ }
56
+ async function saveConfig(cwd, config) {
57
+ const dest = path.join(cwd, CONFIG_FILE);
58
+ await fs.mkdir(path.dirname(dest), { recursive: true });
59
+ await fs.writeFile(dest, JSON.stringify(config, null, 2), "utf-8");
60
+ }
61
+ function deepMerge(base, override) {
62
+ const result = { ...base };
63
+ for (const [key, val] of Object.entries(override)) {
64
+ if (val !== null && val !== void 0 && typeof val === "object" && !Array.isArray(val) && key in result && typeof result[key] === "object") {
65
+ result[key] = deepMerge(result[key], val);
66
+ } else if (val !== void 0) {
67
+ result[key] = val;
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+
73
+ // src/project/workspace.ts
74
+ var BRAINFORGE_DIR = ".brainforge";
75
+ var SUBDIRS = [
76
+ "memory/context-packets",
77
+ "codebase",
78
+ "phases",
79
+ "runs",
80
+ "skills",
81
+ "forensics"
82
+ ];
83
+ var Workspace = class {
84
+ constructor(cwd) {
85
+ this.cwd = cwd;
86
+ this.brainforgeDir = path2.join(cwd, BRAINFORGE_DIR);
87
+ }
88
+ cwd;
89
+ brainforgeDir;
90
+ async ensureDirectories() {
91
+ await fs2.mkdir(this.brainforgeDir, { recursive: true });
92
+ for (const rel of SUBDIRS) {
93
+ await fs2.mkdir(path2.join(this.brainforgeDir, rel), { recursive: true });
94
+ }
95
+ await this.ensureInitialFiles();
96
+ }
97
+ async loadConfig() {
98
+ return loadConfig(this.cwd);
99
+ }
100
+ async saveConfig(config) {
101
+ return saveConfig(this.cwd, config);
102
+ }
103
+ async initConfig(overrides) {
104
+ const config = { ...DEFAULT_CONFIG, ...overrides };
105
+ await saveConfig(this.cwd, config);
106
+ return config;
107
+ }
108
+ async configExists() {
109
+ return fileExists(path2.join(this.brainforgeDir, "config.json"));
110
+ }
111
+ async stateExists() {
112
+ return fileExists(path2.join(this.brainforgeDir, "core.json"));
113
+ }
114
+ phasePath(phaseId) {
115
+ return path2.join(this.brainforgeDir, "phases", phaseId);
116
+ }
117
+ memoryPath() {
118
+ return path2.join(this.brainforgeDir, "memory");
119
+ }
120
+ codebasePath() {
121
+ return path2.join(this.brainforgeDir, "codebase");
122
+ }
123
+ runsPath() {
124
+ return path2.join(this.brainforgeDir, "runs");
125
+ }
126
+ runPath(runId) {
127
+ return path2.join(this.brainforgeDir, "runs", runId);
128
+ }
129
+ skillsPath() {
130
+ return path2.join(this.brainforgeDir, "skills");
131
+ }
132
+ forensicsPath() {
133
+ return path2.join(this.brainforgeDir, "forensics");
134
+ }
135
+ async ensurePhaseDir(phaseId) {
136
+ const dir = this.phasePath(phaseId);
137
+ await fs2.mkdir(path2.join(dir, "artifacts"), { recursive: true });
138
+ }
139
+ async ensureRunDir(runId) {
140
+ const dir = this.runPath(runId);
141
+ await fs2.mkdir(path2.join(dir, "agent-results"), { recursive: true });
142
+ }
143
+ async ensureInitialFiles() {
144
+ const memoryIndex = path2.join(this.brainforgeDir, "memory", "index.json");
145
+ if (!await fileExists(memoryIndex)) {
146
+ await fs2.writeFile(
147
+ memoryIndex,
148
+ JSON.stringify({ version: 1, createdAt: (/* @__PURE__ */ new Date()).toISOString(), entries: [] }, null, 2),
149
+ "utf-8"
150
+ );
151
+ }
152
+ const skillsInstalled = path2.join(this.brainforgeDir, "skills", "installed.json");
153
+ if (!await fileExists(skillsInstalled)) {
154
+ await fs2.writeFile(
155
+ skillsInstalled,
156
+ JSON.stringify({ version: 1, runtimes: [], installedAt: null }, null, 2),
157
+ "utf-8"
158
+ );
159
+ }
160
+ }
161
+ };
162
+ async function fileExists(p) {
163
+ return fs2.access(p).then(() => true).catch(() => false);
164
+ }
165
+
166
+ // src/state/manager.ts
4
167
  var STATE_DIR = ".brainforge";
5
168
  var STATE_FILE = "core.json";
6
169
  var StateManager = class {
7
170
  constructor(workingDir) {
8
171
  this.workingDir = workingDir;
9
- this.statePath = path.join(workingDir, STATE_DIR, STATE_FILE);
172
+ this.statePath = path3.join(workingDir, STATE_DIR, STATE_FILE);
10
173
  }
11
174
  workingDir;
12
175
  statePath;
13
176
  state = null;
14
177
  async load() {
15
- const raw = await fs.readFile(this.statePath, "utf-8");
178
+ const raw = await fs3.readFile(this.statePath, "utf-8");
16
179
  this.state = JSON.parse(raw);
17
180
  return this.state;
18
181
  }
19
182
  async save() {
20
183
  if (!this.state) throw new Error("No state loaded \u2014 call init() or load() first.");
21
184
  this.state.metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
22
- await fs.mkdir(path.dirname(this.statePath), { recursive: true });
23
- await fs.writeFile(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
185
+ await fs3.mkdir(path3.dirname(this.statePath), { recursive: true });
186
+ await fs3.writeFile(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
24
187
  await this.writeDerivedViews();
25
188
  }
26
189
  async init(project) {
@@ -36,8 +199,33 @@ var StateManager = class {
36
199
  }
37
200
  };
38
201
  await this.save();
202
+ try {
203
+ const workspace = new Workspace(this.workingDir);
204
+ await workspace.ensureDirectories();
205
+ if (!await workspace.configExists()) {
206
+ const modeMap = {
207
+ student: "student",
208
+ pro: "pro"
209
+ };
210
+ await workspace.initConfig({
211
+ mode: modeMap[project.type] ?? "simple",
212
+ student: {
213
+ enabled: project.type === "student",
214
+ level: project.studentLevel ?? "intermediate",
215
+ professorMode: true
216
+ }
217
+ });
218
+ }
219
+ } catch {
220
+ }
39
221
  return this.state;
40
222
  }
223
+ async loadWorkspaceConfig() {
224
+ return loadConfig(this.workingDir);
225
+ }
226
+ async saveWorkspaceConfig(config) {
227
+ return saveConfig(this.workingDir, config);
228
+ }
41
229
  get() {
42
230
  if (!this.state) throw new Error("State not loaded \u2014 call load() first.");
43
231
  return this.state;
@@ -94,7 +282,7 @@ var StateManager = class {
94
282
  `---`,
95
283
  `*Generated by BrainForge AI v3 \u2014 ${s.metadata.updatedAt}*`
96
284
  ].join("\n");
97
- await fs.writeFile(path.join(this.workingDir, "PROJECT.md"), content, "utf-8");
285
+ await fs3.writeFile(path3.join(this.workingDir, "PROJECT.md"), content, "utf-8");
98
286
  }
99
287
  async writeStateMd() {
100
288
  const s = this.state;
@@ -118,13 +306,148 @@ Tasks: ${done}/${p.tasks.length}`;
118
306
  "",
119
307
  phaseBlocks.length ? phaseBlocks.join("\n\n") : "No phases yet."
120
308
  ].join("\n");
121
- await fs.writeFile(path.join(this.workingDir, "STATE.md"), content, "utf-8");
309
+ await fs3.writeFile(path3.join(this.workingDir, "STATE.md"), content, "utf-8");
122
310
  }
123
311
  };
124
312
 
313
+ // src/project/migrations.ts
314
+ import fs4 from "fs/promises";
315
+ import path4 from "path";
316
+ async function detectAndMigrate(cwd) {
317
+ const workspace = new Workspace(cwd);
318
+ const actions = [];
319
+ await workspace.ensureDirectories();
320
+ if (!await workspace.configExists()) {
321
+ const config = await inferConfigFromState(cwd);
322
+ await saveConfig(cwd, config);
323
+ actions.push("Created .brainforge/config.json");
324
+ }
325
+ const oldMap = path4.join(cwd, ".brainforge", "codebase-map.json");
326
+ if (await fileExists2(oldMap)) {
327
+ actions.push("Found legacy codebase-map.json (preserved)");
328
+ }
329
+ return { ran: actions.length > 0, actions };
330
+ }
331
+ async function inferConfigFromState(cwd) {
332
+ const config = structuredClone(DEFAULT_CONFIG);
333
+ try {
334
+ const raw = await fs4.readFile(path4.join(cwd, ".brainforge", "core.json"), "utf-8");
335
+ const state = JSON.parse(raw);
336
+ const type = state.project?.type;
337
+ if (type === "student") {
338
+ config.mode = "student";
339
+ config.student.enabled = true;
340
+ const level = state.project?.studentLevel;
341
+ if (level === "beginner" || level === "intermediate" || level === "advanced") {
342
+ config.student.level = level;
343
+ }
344
+ } else if (type === "pro") {
345
+ config.mode = "pro";
346
+ }
347
+ } catch {
348
+ }
349
+ return config;
350
+ }
351
+ async function fileExists2(p) {
352
+ return fs4.access(p).then(() => true).catch(() => false);
353
+ }
354
+
355
+ // src/project/validators.ts
356
+ var VALID_MODES = ["student", "simple", "pro"];
357
+ var VALID_PHASE_STATUSES = ["pending", "active", "completed", "blocked"];
358
+ var VALID_TASK_STATUSES = ["todo", "in-progress", "done", "skipped"];
359
+ var VALID_PROJECT_TYPES = ["student", "junior", "solo", "personal", "pro"];
360
+ var VALID_LANGUAGES = ["typescript", "javascript", "python", "java", "go", "php", "rust", "csharp", "ruby"];
361
+ function validateConfig(config) {
362
+ const errors = [];
363
+ const warnings = [];
364
+ if (!config || typeof config !== "object") {
365
+ return { valid: false, errors: ["config must be an object"], warnings };
366
+ }
367
+ const c = config;
368
+ if (c.version !== void 0 && typeof c.version !== "number") {
369
+ errors.push("config.version must be a number");
370
+ }
371
+ if (c.mode && !VALID_MODES.includes(c.mode)) {
372
+ errors.push(`config.mode must be one of: ${VALID_MODES.join(", ")}`);
373
+ }
374
+ if (c.parallelization?.maxAgents !== void 0) {
375
+ const n = c.parallelization.maxAgents;
376
+ if (typeof n !== "number" || n < 1 || n > 20) {
377
+ warnings.push("config.parallelization.maxAgents should be between 1 and 20");
378
+ }
379
+ }
380
+ if (c.dashboard?.port !== void 0) {
381
+ const p = c.dashboard.port;
382
+ if (typeof p !== "number" || p < 1024 || p > 65535) {
383
+ errors.push("config.dashboard.port must be between 1024 and 65535");
384
+ }
385
+ }
386
+ if (c.memory?.contextPacketMaxTokens !== void 0) {
387
+ const t = c.memory.contextPacketMaxTokens;
388
+ if (typeof t !== "number" || t < 1e3 || t > 2e5) {
389
+ warnings.push("config.memory.contextPacketMaxTokens should be between 1000 and 200000");
390
+ }
391
+ }
392
+ return { valid: errors.length === 0, errors, warnings };
393
+ }
394
+ function validatePhase(phase) {
395
+ const errors = [];
396
+ const warnings = [];
397
+ if (!phase || typeof phase !== "object") {
398
+ return { valid: false, errors: ["phase must be an object"], warnings };
399
+ }
400
+ const p = phase;
401
+ if (!p.id || typeof p.id !== "string") errors.push("phase.id is required");
402
+ if (!p.name || typeof p.name !== "string") errors.push("phase.name is required");
403
+ if (!p.status) {
404
+ errors.push("phase.status is required");
405
+ } else if (!VALID_PHASE_STATUSES.includes(p.status)) {
406
+ errors.push(`phase.status must be one of: ${VALID_PHASE_STATUSES.join(", ")}`);
407
+ }
408
+ if (!Array.isArray(p.tasks)) {
409
+ warnings.push("phase.tasks should be an array");
410
+ }
411
+ return { valid: errors.length === 0, errors, warnings };
412
+ }
413
+ function validateTask(task) {
414
+ const errors = [];
415
+ const warnings = [];
416
+ if (!task || typeof task !== "object") {
417
+ return { valid: false, errors: ["task must be an object"], warnings };
418
+ }
419
+ const t = task;
420
+ if (!t.id || typeof t.id !== "string") errors.push("task.id is required");
421
+ if (!t.title || typeof t.title !== "string") errors.push("task.title is required");
422
+ if (!t.phaseId || typeof t.phaseId !== "string") errors.push("task.phaseId is required");
423
+ if (t.status && !VALID_TASK_STATUSES.includes(t.status)) {
424
+ errors.push(`task.status must be one of: ${VALID_TASK_STATUSES.join(", ")}`);
425
+ }
426
+ return { valid: errors.length === 0, errors, warnings };
427
+ }
428
+ function validateProjectConfig(config) {
429
+ const errors = [];
430
+ const warnings = [];
431
+ if (!config || typeof config !== "object") {
432
+ return { valid: false, errors: ["projectConfig must be an object"], warnings };
433
+ }
434
+ const c = config;
435
+ if (!c.name || typeof c.name !== "string") errors.push("project.name is required");
436
+ if (!c.description || typeof c.description !== "string") errors.push("project.description is required");
437
+ if (!c.type || !VALID_PROJECT_TYPES.includes(c.type)) {
438
+ errors.push(`project.type must be one of: ${VALID_PROJECT_TYPES.join(", ")}`);
439
+ }
440
+ if (!c.language || !VALID_LANGUAGES.includes(c.language)) {
441
+ errors.push(`project.language must be one of: ${VALID_LANGUAGES.join(", ")}`);
442
+ }
443
+ return { valid: errors.length === 0, errors, warnings };
444
+ }
445
+
125
446
  // src/state/state-machine.ts
126
447
  function reduce(state, action) {
127
448
  switch (action.type) {
449
+ case "ADD_PHASE":
450
+ return { ...state, phases: [...state.phases, { description: "", ...action.phase }] };
128
451
  case "SET_ACTIVE_PHASE":
129
452
  return { ...state, activePhaseId: action.phaseId };
130
453
  case "COMPLETE_PHASE": {
@@ -142,6 +465,15 @@ function reduce(state, action) {
142
465
  }));
143
466
  return { ...state, phases };
144
467
  }
468
+ case "UPDATE_TASK_STATUS": {
469
+ const phases = state.phases.map((p) => ({
470
+ ...p,
471
+ tasks: p.tasks.map(
472
+ (t) => t.id === action.taskId ? { ...t, status: action.status, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : t
473
+ )
474
+ }));
475
+ return { ...state, phases };
476
+ }
145
477
  case "SET_CONTEXT":
146
478
  return { ...state, context: { ...state.context, [action.key]: action.value } };
147
479
  default:
@@ -235,8 +567,8 @@ var PromptEngine = class {
235
567
  };
236
568
 
237
569
  // src/mapper/mapper.ts
238
- import fs2 from "fs/promises";
239
- import path2 from "path";
570
+ import fs5 from "fs/promises";
571
+ import path5 from "path";
240
572
 
241
573
  // src/mapper/extractors.ts
242
574
  function extractFromFile(filePath, content, language) {
@@ -441,14 +773,17 @@ function emptyNode(filePath, language) {
441
773
  }
442
774
 
443
775
  // src/mapper/mapper.ts
444
- var MAP_FILE = path2.join(".brainforge", "codebase-map.json");
776
+ var MAP_FILE = path5.join(".brainforge", "codebase-map.json");
445
777
  var LANGUAGE_EXTENSIONS = {
446
778
  typescript: [".ts", ".tsx"],
447
779
  javascript: [".js", ".jsx", ".mjs", ".cjs"],
448
780
  python: [".py"],
449
781
  java: [".java"],
450
782
  go: [".go"],
451
- php: [".php"]
783
+ php: [".php"],
784
+ rust: [".rs"],
785
+ csharp: [".cs"],
786
+ ruby: [".rb"]
452
787
  };
453
788
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".brainforge", "dist", "build", "coverage", "__pycache__", ".venv", "vendor"]);
454
789
  var CodebaseMapper = class {
@@ -462,7 +797,7 @@ var CodebaseMapper = class {
462
797
  const fileNodes = [];
463
798
  for (const filePath of sourceFiles) {
464
799
  try {
465
- const content = await fs2.readFile(path2.join(this.workingDir, filePath), "utf-8");
800
+ const content = await fs5.readFile(path5.join(this.workingDir, filePath), "utf-8");
466
801
  fileNodes.push(extractFromFile(filePath, content, language));
467
802
  } catch {
468
803
  }
@@ -482,7 +817,7 @@ var CodebaseMapper = class {
482
817
  return map;
483
818
  }
484
819
  async load() {
485
- const raw = await fs2.readFile(path2.join(this.workingDir, MAP_FILE), "utf-8");
820
+ const raw = await fs5.readFile(path5.join(this.workingDir, MAP_FILE), "utf-8");
486
821
  return JSON.parse(raw);
487
822
  }
488
823
  findRelevantFiles(map, query) {
@@ -507,10 +842,10 @@ var CodebaseMapper = class {
507
842
  return results;
508
843
  }
509
844
  async walk(root, rel, extensions, results) {
510
- const dir = rel ? path2.join(root, rel) : root;
845
+ const dir = rel ? path5.join(root, rel) : root;
511
846
  let entries;
512
847
  try {
513
- entries = await fs2.readdir(dir, { withFileTypes: true });
848
+ entries = await fs5.readdir(dir, { withFileTypes: true });
514
849
  } catch {
515
850
  return;
516
851
  }
@@ -525,9 +860,9 @@ var CodebaseMapper = class {
525
860
  }
526
861
  }
527
862
  async saveMap(map) {
528
- const dest = path2.join(this.workingDir, MAP_FILE);
529
- await fs2.mkdir(path2.dirname(dest), { recursive: true });
530
- await fs2.writeFile(dest, JSON.stringify(map, null, 2), "utf-8");
863
+ const dest = path5.join(this.workingDir, MAP_FILE);
864
+ await fs5.mkdir(path5.dirname(dest), { recursive: true });
865
+ await fs5.writeFile(dest, JSON.stringify(map, null, 2), "utf-8");
531
866
  }
532
867
  };
533
868
  function tokenize(text) {
@@ -708,8 +1043,8 @@ ${project.type === "student" ? "- Write code you can explain line-by-line \u2014
708
1043
  };
709
1044
 
710
1045
  // src/professor/scanner.ts
711
- import fs3 from "fs/promises";
712
- import path3 from "path";
1046
+ import fs6 from "fs/promises";
1047
+ import path6 from "path";
713
1048
 
714
1049
  // src/professor/patterns.ts
715
1050
  var PATTERNS = [
@@ -860,7 +1195,10 @@ var LANGUAGE_EXTENSIONS2 = {
860
1195
  python: [".py"],
861
1196
  java: [".java"],
862
1197
  go: [".go"],
863
- php: [".php"]
1198
+ php: [".php"],
1199
+ rust: [".rs"],
1200
+ csharp: [".cs"],
1201
+ ruby: [".rb"]
864
1202
  };
865
1203
  var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
866
1204
  "node_modules",
@@ -886,7 +1224,7 @@ var ProfessorScanner = class {
886
1224
  const files = await this.findFiles(extensions);
887
1225
  const matches = [];
888
1226
  for (const relPath of files) {
889
- const content = await fs3.readFile(path3.join(this.workingDir, relPath), "utf-8").catch(() => null);
1227
+ const content = await fs6.readFile(path6.join(this.workingDir, relPath), "utf-8").catch(() => null);
890
1228
  if (!content) continue;
891
1229
  const lines = content.split("\n");
892
1230
  for (const pattern of applicable) {
@@ -926,10 +1264,10 @@ var ProfessorScanner = class {
926
1264
  return results;
927
1265
  }
928
1266
  async walk(rel, extensions, results) {
929
- const dir = rel ? path3.join(this.workingDir, rel) : this.workingDir;
1267
+ const dir = rel ? path6.join(this.workingDir, rel) : this.workingDir;
930
1268
  let entries;
931
1269
  try {
932
- entries = await fs3.readdir(dir, { withFileTypes: true });
1270
+ entries = await fs6.readdir(dir, { withFileTypes: true });
933
1271
  } catch {
934
1272
  return;
935
1273
  }
@@ -975,15 +1313,15 @@ var ProfessorScanner = class {
975
1313
  ""
976
1314
  );
977
1315
  }
978
- const dest = path3.join(this.workingDir, "PROFESSOR_REPORT.md");
979
- await fs3.writeFile(dest, lines.join("\n"), "utf-8");
1316
+ const dest = path6.join(this.workingDir, "PROFESSOR_REPORT.md");
1317
+ await fs6.writeFile(dest, lines.join("\n"), "utf-8");
980
1318
  }
981
1319
  };
982
1320
 
983
1321
  // src/server/server.ts
984
1322
  import http from "http";
985
- import fs4 from "fs/promises";
986
- import path4 from "path";
1323
+ import fs7 from "fs/promises";
1324
+ import path7 from "path";
987
1325
  import { fileURLToPath } from "url";
988
1326
  import { WebSocketServer, WebSocket } from "ws";
989
1327
  var MIME = {
@@ -999,8 +1337,8 @@ var BrainForgeServer = class {
999
1337
  constructor(workingDir, port = 3742, dashboardDir) {
1000
1338
  this.workingDir = workingDir;
1001
1339
  this.port = port;
1002
- const here = path4.dirname(fileURLToPath(import.meta.url));
1003
- this.dashboardDir = dashboardDir ?? path4.resolve(here, "../../../dashboard/dist");
1340
+ const here = path7.dirname(fileURLToPath(import.meta.url));
1341
+ this.dashboardDir = dashboardDir ?? path7.resolve(here, "../../../dashboard/dist");
1004
1342
  }
1005
1343
  workingDir;
1006
1344
  port;
@@ -1076,7 +1414,7 @@ var BrainForgeServer = class {
1076
1414
  }
1077
1415
  async fileJson(res, rel) {
1078
1416
  try {
1079
- const raw = await fs4.readFile(path4.join(this.workingDir, rel), "utf-8");
1417
+ const raw = await fs7.readFile(path7.join(this.workingDir, rel), "utf-8");
1080
1418
  res.writeHead(200, { "Content-Type": "application/json" });
1081
1419
  res.end(raw);
1082
1420
  } catch {
@@ -1086,7 +1424,7 @@ var BrainForgeServer = class {
1086
1424
  }
1087
1425
  async fileText(res, rel) {
1088
1426
  try {
1089
- const raw = await fs4.readFile(path4.join(this.workingDir, rel), "utf-8");
1427
+ const raw = await fs7.readFile(path7.join(this.workingDir, rel), "utf-8");
1090
1428
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1091
1429
  res.end(raw);
1092
1430
  } catch {
@@ -1095,10 +1433,10 @@ var BrainForgeServer = class {
1095
1433
  }
1096
1434
  }
1097
1435
  async static(res, pathname) {
1098
- let target = path4.join(this.dashboardDir, pathname === "/" ? "index.html" : pathname);
1436
+ let target = path7.join(this.dashboardDir, pathname === "/" ? "index.html" : pathname);
1099
1437
  let found = await exists(target);
1100
1438
  if (!found) {
1101
- target = path4.join(this.dashboardDir, "index.html");
1439
+ target = path7.join(this.dashboardDir, "index.html");
1102
1440
  found = await exists(target);
1103
1441
  }
1104
1442
  if (!found) {
@@ -1106,13 +1444,13 @@ var BrainForgeServer = class {
1106
1444
  res.end("Dashboard not built. Run: npm run build --workspace=packages/dashboard");
1107
1445
  return;
1108
1446
  }
1109
- const ext = path4.extname(target);
1447
+ const ext = path7.extname(target);
1110
1448
  res.writeHead(200, { "Content-Type": MIME[ext] ?? "application/octet-stream" });
1111
- res.end(await fs4.readFile(target));
1449
+ res.end(await fs7.readFile(target));
1112
1450
  }
1113
1451
  };
1114
1452
  async function exists(p) {
1115
- return fs4.access(p).then(() => true).catch(() => false);
1453
+ return fs7.access(p).then(() => true).catch(() => false);
1116
1454
  }
1117
1455
  function readBody(req) {
1118
1456
  return new Promise((resolve, reject) => {
@@ -1126,8 +1464,8 @@ function readBody(req) {
1126
1464
  }
1127
1465
 
1128
1466
  // src/defense/defense.ts
1129
- import fs5 from "fs/promises";
1130
- import path5 from "path";
1467
+ import fs8 from "fs/promises";
1468
+ import path8 from "path";
1131
1469
 
1132
1470
  // src/defense/question-bank.ts
1133
1471
  var STATIC_QUESTIONS = [
@@ -1355,8 +1693,8 @@ var MockDefense = class {
1355
1693
  ""
1356
1694
  );
1357
1695
  }
1358
- await fs5.writeFile(
1359
- path5.join(this.workingDir, "DEFENSE_PREP.md"),
1696
+ await fs8.writeFile(
1697
+ path8.join(this.workingDir, "DEFENSE_PREP.md"),
1360
1698
  lines.join("\n"),
1361
1699
  "utf-8"
1362
1700
  );
@@ -1364,8 +1702,8 @@ var MockDefense = class {
1364
1702
  };
1365
1703
 
1366
1704
  // src/skills/registry.ts
1367
- import fs6 from "fs/promises";
1368
- import path6 from "path";
1705
+ import fs9 from "fs/promises";
1706
+ import path9 from "path";
1369
1707
  import Handlebars2 from "handlebars";
1370
1708
 
1371
1709
  // src/skills/built-in.ts
@@ -4887,11 +5225,11 @@ var SkillRegistry = class {
4887
5225
  }
4888
5226
  async loadUserSkills(skillsDir) {
4889
5227
  try {
4890
- const entries = await fs6.readdir(skillsDir, { withFileTypes: true });
5228
+ const entries = await fs9.readdir(skillsDir, { withFileTypes: true });
4891
5229
  for (const entry of entries) {
4892
5230
  if (!entry.name.endsWith(".json")) continue;
4893
5231
  try {
4894
- const raw = await fs6.readFile(path6.join(skillsDir, entry.name), "utf-8");
5232
+ const raw = await fs9.readFile(path9.join(skillsDir, entry.name), "utf-8");
4895
5233
  const skill = JSON.parse(raw);
4896
5234
  this.register(skill);
4897
5235
  } catch {
@@ -4902,7 +5240,7 @@ var SkillRegistry = class {
4902
5240
  }
4903
5241
  async loadSkillsFile(filePath) {
4904
5242
  try {
4905
- const raw = await fs6.readFile(filePath, "utf-8");
5243
+ const raw = await fs9.readFile(filePath, "utf-8");
4906
5244
  const data = JSON.parse(raw);
4907
5245
  const skills = Array.isArray(data) ? data : [data];
4908
5246
  for (const skill of skills) {
@@ -5141,22 +5479,3450 @@ var AdapterManager = class _AdapterManager {
5141
5479
  return _AdapterManager.create(state.aiConfig);
5142
5480
  }
5143
5481
  };
5482
+
5483
+ // src/runtime/runtime-registry.ts
5484
+ import os from "os";
5485
+ import path10 from "path";
5486
+ var HOME = os.homedir();
5487
+ var RUNTIME_REGISTRY = [
5488
+ {
5489
+ id: "claude-code",
5490
+ name: "Claude Code",
5491
+ commandPrefix: "/brg-",
5492
+ globalConfigPath: path10.join(HOME, ".claude", "commands"),
5493
+ localConfigPath: path10.join(".claude", "commands"),
5494
+ commandFileExt: ".md",
5495
+ capabilities: ["slash-commands"],
5496
+ notes: "Full slash command support. Commands appear in /brg-* namespace.",
5497
+ supportLevel: "full"
5498
+ },
5499
+ {
5500
+ id: "codex",
5501
+ name: "OpenAI Codex CLI",
5502
+ commandPrefix: "/brg-",
5503
+ globalConfigPath: path10.join(HOME, ".codex", "prompts"),
5504
+ localConfigPath: path10.join(".codex", "prompts"),
5505
+ commandFileExt: ".md",
5506
+ capabilities: ["slash-commands"],
5507
+ notes: "Partial support \u2014 config path may vary by version.",
5508
+ supportLevel: "partial"
5509
+ },
5510
+ {
5511
+ id: "opencode",
5512
+ name: "OpenCode",
5513
+ commandPrefix: "/brg:",
5514
+ globalConfigPath: path10.join(HOME, ".config", "opencode", "prompts"),
5515
+ localConfigPath: path10.join(".opencode", "prompts"),
5516
+ commandFileExt: ".md",
5517
+ capabilities: ["slash-commands"],
5518
+ notes: "Uses colon syntax: /brg:new. Config path may vary.",
5519
+ supportLevel: "partial"
5520
+ },
5521
+ {
5522
+ id: "gemini-cli",
5523
+ name: "Gemini CLI",
5524
+ commandPrefix: "/brg:",
5525
+ globalConfigPath: path10.join(HOME, ".gemini", "prompts"),
5526
+ localConfigPath: path10.join(".gemini", "prompts"),
5527
+ commandFileExt: ".md",
5528
+ capabilities: ["slash-commands"],
5529
+ notes: "Uses colon syntax: /brg:new.",
5530
+ supportLevel: "partial"
5531
+ },
5532
+ {
5533
+ id: "cursor",
5534
+ name: "Cursor",
5535
+ commandPrefix: "@brg-",
5536
+ globalConfigPath: path10.join(HOME, ".cursor", "rules"),
5537
+ localConfigPath: path10.join(".cursor", "rules"),
5538
+ commandFileExt: ".mdc",
5539
+ capabilities: ["rules"],
5540
+ notes: "Rules-based approach. Commands are referenced via @brg-* in chat.",
5541
+ supportLevel: "partial"
5542
+ },
5543
+ {
5544
+ id: "windsurf",
5545
+ name: "Windsurf",
5546
+ commandPrefix: "@brg-",
5547
+ globalConfigPath: path10.join(HOME, ".codeium", "windsurf", "rules"),
5548
+ localConfigPath: path10.join(".windsurf", "rules"),
5549
+ commandFileExt: ".md",
5550
+ capabilities: ["rules"],
5551
+ notes: "Rules installed in .windsurf/rules/. Reference with @brg-* in chat.",
5552
+ supportLevel: "partial"
5553
+ },
5554
+ {
5555
+ id: "copilot",
5556
+ name: "GitHub Copilot",
5557
+ commandPrefix: "brg-",
5558
+ globalConfigPath: "",
5559
+ localConfigPath: path10.join(".github"),
5560
+ commandFileExt: ".md",
5561
+ capabilities: ["instructions"],
5562
+ notes: "Appends workflow instructions to .github/copilot-instructions.md.",
5563
+ supportLevel: "partial"
5564
+ },
5565
+ {
5566
+ id: "vscode",
5567
+ name: "VS Code",
5568
+ commandPrefix: "brg-",
5569
+ globalConfigPath: "",
5570
+ localConfigPath: path10.join(".vscode"),
5571
+ commandFileExt: ".json",
5572
+ capabilities: ["snippets"],
5573
+ notes: "Writes workspace snippets to .vscode/brainforge.code-snippets.",
5574
+ supportLevel: "partial"
5575
+ },
5576
+ {
5577
+ id: "generic",
5578
+ name: "Generic (Markdown Prompts)",
5579
+ commandPrefix: "brg-",
5580
+ globalConfigPath: "",
5581
+ localConfigPath: path10.join(".brainforge", "prompts"),
5582
+ commandFileExt: ".md",
5583
+ capabilities: ["slash-commands"],
5584
+ notes: "Always works. Writes .md prompt files you can copy-paste into any AI tool.",
5585
+ supportLevel: "fallback"
5586
+ }
5587
+ ];
5588
+ var REGISTRY_MAP = new Map(
5589
+ RUNTIME_REGISTRY.map((r) => [r.id, r])
5590
+ );
5591
+ function getRuntime(id) {
5592
+ return REGISTRY_MAP.get(id);
5593
+ }
5594
+ function listRuntimes() {
5595
+ return RUNTIME_REGISTRY;
5596
+ }
5597
+
5598
+ // src/runtime/runtime-detector.ts
5599
+ import fs10 from "fs/promises";
5600
+ import path11 from "path";
5601
+ import { exec } from "child_process";
5602
+ import { promisify } from "util";
5603
+ var execAsync = promisify(exec);
5604
+ async function detectRuntimes(cwd) {
5605
+ const results = await Promise.all([
5606
+ detectClaudeCode(cwd),
5607
+ detectCodex(cwd),
5608
+ detectOpenCode(cwd),
5609
+ detectGeminiCli(cwd),
5610
+ detectCursor(cwd),
5611
+ detectWindsurf(cwd),
5612
+ detectCopilot(cwd),
5613
+ detectVSCode(cwd),
5614
+ detectGeneric()
5615
+ ]);
5616
+ return results;
5617
+ }
5618
+ async function detectClaudeCode(cwd) {
5619
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "claude-code");
5620
+ const [hasGlobal, hasLocal] = await Promise.all([
5621
+ dirExists(rt.globalConfigPath),
5622
+ dirExists(path11.join(cwd, ".claude"))
5623
+ ]);
5624
+ const inPath = await commandExists("claude");
5625
+ const detected = hasGlobal || hasLocal || inPath;
5626
+ return {
5627
+ id: "claude-code",
5628
+ name: rt.name,
5629
+ detected,
5630
+ method: detected ? hasGlobal ? "global config dir" : hasLocal ? "local .claude/" : "PATH" : void 0,
5631
+ configPath: hasGlobal ? rt.globalConfigPath : hasLocal ? path11.join(cwd, ".claude", "commands") : void 0,
5632
+ supportLevel: rt.supportLevel
5633
+ };
5634
+ }
5635
+ async function detectCodex(cwd) {
5636
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "codex");
5637
+ const [hasGlobal, hasLocal, inPath] = await Promise.all([
5638
+ dirExists(rt.globalConfigPath),
5639
+ dirExists(path11.join(cwd, ".codex")),
5640
+ commandExists("codex")
5641
+ ]);
5642
+ const detected = hasGlobal || hasLocal || inPath;
5643
+ return { id: "codex", name: rt.name, detected, method: detected ? "detected" : void 0, supportLevel: rt.supportLevel };
5644
+ }
5645
+ async function detectOpenCode(cwd) {
5646
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "opencode");
5647
+ const [hasGlobal, inPath] = await Promise.all([
5648
+ dirExists(rt.globalConfigPath),
5649
+ commandExists("opencode")
5650
+ ]);
5651
+ const detected = hasGlobal || inPath;
5652
+ return { id: "opencode", name: rt.name, detected, method: detected ? "detected" : void 0, supportLevel: rt.supportLevel };
5653
+ }
5654
+ async function detectGeminiCli(cwd) {
5655
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "gemini-cli");
5656
+ const [hasGlobal, inPath] = await Promise.all([
5657
+ dirExists(rt.globalConfigPath),
5658
+ commandExists("gemini")
5659
+ ]);
5660
+ const detected = hasGlobal || inPath;
5661
+ return { id: "gemini-cli", name: rt.name, detected, method: detected ? "detected" : void 0, supportLevel: rt.supportLevel };
5662
+ }
5663
+ async function detectCursor(cwd) {
5664
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "cursor");
5665
+ const [hasLocal, inPath] = await Promise.all([
5666
+ dirExists(path11.join(cwd, ".cursor")),
5667
+ commandExists("cursor")
5668
+ ]);
5669
+ const detected = hasLocal || inPath;
5670
+ return { id: "cursor", name: rt.name, detected, method: detected ? "detected" : void 0, supportLevel: rt.supportLevel };
5671
+ }
5672
+ async function detectWindsurf(cwd) {
5673
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "windsurf");
5674
+ const hasLocal = await dirExists(path11.join(cwd, ".windsurf"));
5675
+ const inPath = await commandExists("windsurf");
5676
+ const detected = hasLocal || inPath;
5677
+ return { id: "windsurf", name: rt.name, detected, method: detected ? "detected" : void 0, supportLevel: rt.supportLevel };
5678
+ }
5679
+ async function detectCopilot(cwd) {
5680
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "copilot");
5681
+ const hasFile = await fileExists3(path11.join(cwd, ".github", "copilot-instructions.md"));
5682
+ const hasGithub = await dirExists(path11.join(cwd, ".github"));
5683
+ const detected = hasFile || hasGithub;
5684
+ return { id: "copilot", name: rt.name, detected, method: detected ? "detected" : void 0, supportLevel: rt.supportLevel };
5685
+ }
5686
+ async function detectVSCode(cwd) {
5687
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "vscode");
5688
+ const [hasLocal, inPath] = await Promise.all([
5689
+ dirExists(path11.join(cwd, ".vscode")),
5690
+ commandExists("code")
5691
+ ]);
5692
+ const detected = hasLocal || inPath;
5693
+ return { id: "vscode", name: rt.name, detected, method: detected ? "detected" : void 0, supportLevel: rt.supportLevel };
5694
+ }
5695
+ function detectGeneric() {
5696
+ const rt = RUNTIME_REGISTRY.find((r) => r.id === "generic");
5697
+ return { id: "generic", name: rt.name, detected: true, method: "always available", supportLevel: "fallback" };
5698
+ }
5699
+ async function dirExists(p) {
5700
+ try {
5701
+ const stat = await fs10.stat(p);
5702
+ return stat.isDirectory();
5703
+ } catch {
5704
+ return false;
5705
+ }
5706
+ }
5707
+ async function fileExists3(p) {
5708
+ try {
5709
+ await fs10.access(p);
5710
+ return true;
5711
+ } catch {
5712
+ return false;
5713
+ }
5714
+ }
5715
+ async function commandExists(cmd) {
5716
+ try {
5717
+ const which = process.platform === "win32" ? "where" : "which";
5718
+ await execAsync(`${which} ${cmd}`);
5719
+ return true;
5720
+ } catch {
5721
+ return false;
5722
+ }
5723
+ }
5724
+
5725
+ // src/runtime/runtime-installer.ts
5726
+ import path21 from "path";
5727
+
5728
+ // src/runtime/adapters/command-definitions.ts
5729
+ var COMMAND_DEFINITIONS = [
5730
+ {
5731
+ name: "new",
5732
+ description: "Create a new BrainForge AI project brief",
5733
+ cliCommand: "brg new",
5734
+ instruction: `Create a new BrainForge AI project brief for the current project.
5735
+
5736
+ Run \`brg new\` in the terminal (or \`brg new --from "[description]"\` if the user described what they want to build).
5737
+
5738
+ After completion, read \`.brainforge/phases/*/DISCUSSION.md\` and summarize the brief for the user. Then suggest: \`brg requirements\` to formalize requirements.`
5739
+ },
5740
+ {
5741
+ name: "map",
5742
+ description: "Scan the codebase and generate an architecture map",
5743
+ cliCommand: "brg map",
5744
+ instruction: `Scan the current codebase and generate an architecture map.
5745
+
5746
+ Run \`brg map --refresh\` in the terminal. After completion, read \`.brainforge/codebase/MAP.md\` and summarize the detected stack, structure, and any risks for the user.`
5747
+ },
5748
+ {
5749
+ name: "discuss",
5750
+ description: "Generate a discussion prompt for the current phase",
5751
+ cliCommand: "brg discuss",
5752
+ instruction: `Generate a discussion prompt for the current project phase.
5753
+
5754
+ Run \`brg discuss\` in the terminal. Read the output prompt and use it to ask the user clarifying questions about their project goals and approach.`
5755
+ },
5756
+ {
5757
+ name: "plan",
5758
+ description: "Plan tasks by scanning the codebase",
5759
+ cliCommand: "brg plan",
5760
+ instruction: `Generate a task plan for the current project phase.
5761
+
5762
+ Run \`brg plan\` in the terminal. After completion, show the user the generated tasks and ask if they want to adjust priorities, descriptions, or risk levels before proceeding.`
5763
+ },
5764
+ {
5765
+ name: "execute",
5766
+ description: "Generate execution prompts for the active phase",
5767
+ cliCommand: "brg execute",
5768
+ instruction: `Generate execution prompts for the active phase's tasks.
5769
+
5770
+ Run \`brg execute --prompt-only\` in the terminal. After completion:
5771
+ 1. Read \`.brainforge/phases/*/EXECUTION.md\` and implement the tasks described.
5772
+ 2. For multi-agent mode, check \`.brainforge/phases/*/artifacts/\` for wave-based prompts.
5773
+ 3. After implementing, run \`brg verify\` to check your work.`
5774
+ },
5775
+ {
5776
+ name: "verify",
5777
+ description: "Verify the active phase is correctly implemented",
5778
+ cliCommand: "brg verify",
5779
+ instruction: `Verify that the active phase is correctly implemented.
5780
+
5781
+ Run \`brg verify\` in the terminal to generate the verification checklist. Then:
5782
+ 1. Read \`.brainforge/phases/*/VERIFY_PROMPT.md\` and follow the checklist.
5783
+ 2. Run all verification commands listed.
5784
+ 3. Report any bugs found. If all checks pass, run \`brg ship\`.`
5785
+ },
5786
+ {
5787
+ name: "ship",
5788
+ description: "Close the active phase and update project memory",
5789
+ cliCommand: "brg ship",
5790
+ instruction: `Close the active phase and update project memory.
5791
+
5792
+ First confirm with the user that the phase is verified and all acceptance criteria pass. Then run \`brg ship\` in the terminal.
5793
+
5794
+ After shipping, show the user the CHANGELOG entry and suggest the next phase.`
5795
+ },
5796
+ {
5797
+ name: "progress",
5798
+ description: "Show a visual progress overview",
5799
+ cliCommand: "brg progress",
5800
+ instruction: `Show the current project progress.
5801
+
5802
+ Run \`brg progress\` in the terminal and summarize the output for the user: how many phases are done, what's the active phase, and what the next action is.`
5803
+ },
5804
+ {
5805
+ name: "resume",
5806
+ description: "Generate a context packet to resume work",
5807
+ cliCommand: "brg resume",
5808
+ instruction: `Generate a context packet to resume work after a session break.
5809
+
5810
+ Run \`brg resume --rebuild\` in the terminal to get a fresh context packet. Read the output and use it as your working context to continue where you left off.`
5811
+ },
5812
+ {
5813
+ name: "quick",
5814
+ description: "Generate a targeted mini-task prompt",
5815
+ cliCommand: "brg quick",
5816
+ instruction: `Generate a targeted prompt for a small, focused change.
5817
+
5818
+ Ask the user what they want to change (e.g., "Fix the mobile nav menu overflow bug"). Then run \`brg quick "[description]"\` in the terminal and implement the change described in the output file.`
5819
+ },
5820
+ {
5821
+ name: "debug",
5822
+ description: "Diagnose a bug using structured debugging workflow",
5823
+ cliCommand: "brg debug",
5824
+ instruction: `Diagnose a bug using BrainForge's structured debugging workflow.
5825
+
5826
+ Ask the user to describe the bug (error message, reproduction steps, expected vs actual behavior). Then run \`brg debug "[description]"\` in the terminal and follow the diagnostic plan in the output.`
5827
+ },
5828
+ {
5829
+ name: "fix",
5830
+ description: "Execute a fix plan for a diagnosed bug",
5831
+ cliCommand: "brg fix",
5832
+ instruction: `Execute a fix plan for a diagnosed bug.
5833
+
5834
+ Read \`.brainforge/phases/*/BUGS.md\` to understand the bug. Then run \`brg fix\` in the terminal and implement the minimal fix described. Add a regression test after fixing.`
5835
+ },
5836
+ {
5837
+ name: "auto",
5838
+ description: "Run the full automated workflow pipeline",
5839
+ cliCommand: "brg auto",
5840
+ instruction: `Run the full BrainForge workflow pipeline automatically.
5841
+
5842
+ Confirm with the user before proceeding. Run \`brg auto --dry-run\` first to show the plan, then \`brg auto\` to execute. Stop on any verification failure.`
5843
+ },
5844
+ {
5845
+ name: "settings",
5846
+ description: "View or edit BrainForge project settings",
5847
+ cliCommand: "brg settings",
5848
+ instruction: `View or update BrainForge project settings.
5849
+
5850
+ Run \`brg settings --show\` to display current settings. Ask the user what they'd like to change, then run \`brg settings\` for interactive editing.`
5851
+ },
5852
+ {
5853
+ name: "forensics",
5854
+ description: "Diagnose broken workflow state",
5855
+ cliCommand: "brg forensics",
5856
+ instruction: `Diagnose and repair a broken BrainForge workflow state.
5857
+
5858
+ Run \`brg forensics\` in the terminal. Read the diagnostic report and fix any issues found: missing files, invalid JSON, status mismatches.`
5859
+ },
5860
+ {
5861
+ name: "professor",
5862
+ description: "Explain implemented code for academic defense",
5863
+ cliCommand: "brg discuss --professor",
5864
+ instruction: `Generate a defense-ready explanation of the implemented code.
5865
+
5866
+ Ask the user which phase or feature to explain. Generate a clear, structured explanation of: what was built, why these technical choices were made, how the key algorithms work, and anticipated examiner questions.`
5867
+ },
5868
+ {
5869
+ name: "explain",
5870
+ description: "Explain a piece of code in plain language",
5871
+ cliCommand: "brg ask",
5872
+ instruction: `Explain a piece of code in plain language at the user's level.
5873
+
5874
+ Ask the user: "Which file or function should I explain?" and "What's your programming experience level?" Then provide a clear, jargon-appropriate explanation with analogies where helpful.`
5875
+ },
5876
+ {
5877
+ name: "ui-phase",
5878
+ description: "Generate a UI design contract before frontend work",
5879
+ cliCommand: "brg new --mode ui",
5880
+ instruction: `Generate a UI design contract before implementing frontend work.
5881
+
5882
+ Ask the user about: component layout, color/spacing tokens, responsive breakpoints, accessibility requirements, and interaction states. Write the design contract to \`.brainforge/phases/*/UI_CONTRACT.md\`.`
5883
+ },
5884
+ {
5885
+ name: "ui-review",
5886
+ description: "Audit a UI implementation against the design contract",
5887
+ cliCommand: "brg verify --ui",
5888
+ instruction: `Audit the UI implementation against the design contract.
5889
+
5890
+ Read \`.brainforge/phases/*/UI_CONTRACT.md\`. Check the implementation against: layout accuracy, color/spacing consistency, accessibility (contrast, keyboard nav, ARIA), responsive behavior, and error states. Write findings to UI_REVIEW.md.`
5891
+ },
5892
+ {
5893
+ name: "security-review",
5894
+ description: "Run a security audit on the current codebase",
5895
+ cliCommand: "brg verify --security",
5896
+ instruction: `Run a security audit on the current codebase.
5897
+
5898
+ Check for OWASP Top 10 vulnerabilities: injection, broken auth, sensitive data exposure, insecure direct object references, security misconfiguration, XSS, insecure deserialization, vulnerable components, insufficient logging. Write findings to SECURITY.md.`
5899
+ },
5900
+ {
5901
+ name: "test-review",
5902
+ description: "Review test coverage and quality",
5903
+ cliCommand: "brg verify --tests",
5904
+ instruction: `Review test coverage and quality for the current phase.
5905
+
5906
+ Run the test suite. Check: coverage percentage, missing test cases, tests that only check happy paths, integration vs unit test balance. Suggest specific tests to add for untested paths.`
5907
+ },
5908
+ {
5909
+ name: "agents",
5910
+ description: "List available AI agents and their roles",
5911
+ cliCommand: "brg agents list",
5912
+ instruction: `Show the available BrainForge AI agents and help the user choose the right one.
5913
+
5914
+ Run \`brg agents list\` in the terminal. Ask the user what task they need help with and suggest the most appropriate agent. Then run \`brg agents describe [id]\` for the chosen agent.`
5915
+ }
5916
+ ];
5917
+ var COMMANDS_BY_NAME = new Map(
5918
+ COMMAND_DEFINITIONS.map((c) => [c.name, c])
5919
+ );
5920
+
5921
+ // src/runtime/adapters/claude-code.ts
5922
+ import fs11 from "fs/promises";
5923
+ import path12 from "path";
5924
+ async function installClaudeCode(commands, targetDir) {
5925
+ await fs11.mkdir(targetDir, { recursive: true });
5926
+ const files = [];
5927
+ for (const cmd of commands) {
5928
+ const filename = `brg-${cmd.name}.md`;
5929
+ const filePath = path12.join(targetDir, filename);
5930
+ const content = [
5931
+ `# /brg-${cmd.name}`,
5932
+ "",
5933
+ `> ${cmd.description}`,
5934
+ "",
5935
+ cmd.instruction,
5936
+ "",
5937
+ "---",
5938
+ `*BrainForge AI slash command \u2014 run \`${cmd.cliCommand}\`*`
5939
+ ].join("\n");
5940
+ await fs11.writeFile(filePath, content, "utf-8");
5941
+ files.push(filePath);
5942
+ }
5943
+ return files;
5944
+ }
5945
+
5946
+ // src/runtime/adapters/codex.ts
5947
+ import fs12 from "fs/promises";
5948
+ import path13 from "path";
5949
+ async function installCodex(commands, targetDir) {
5950
+ await fs12.mkdir(targetDir, { recursive: true });
5951
+ const files = [];
5952
+ for (const cmd of commands) {
5953
+ const filename = `brg-${cmd.name}.md`;
5954
+ const filePath = path13.join(targetDir, filename);
5955
+ const content = [
5956
+ `# /brg-${cmd.name} \u2014 ${cmd.description}`,
5957
+ "",
5958
+ cmd.instruction,
5959
+ "",
5960
+ `CLI: \`${cmd.cliCommand}\``
5961
+ ].join("\n");
5962
+ await fs12.writeFile(filePath, content, "utf-8");
5963
+ files.push(filePath);
5964
+ }
5965
+ return files;
5966
+ }
5967
+
5968
+ // src/runtime/adapters/opencode.ts
5969
+ import fs13 from "fs/promises";
5970
+ import path14 from "path";
5971
+ async function installOpenCode(commands, targetDir) {
5972
+ await fs13.mkdir(targetDir, { recursive: true });
5973
+ const files = [];
5974
+ for (const cmd of commands) {
5975
+ const filename = `brg-${cmd.name}.md`;
5976
+ const filePath = path14.join(targetDir, filename);
5977
+ const content = [
5978
+ `# /brg:${cmd.name}`,
5979
+ "",
5980
+ `> ${cmd.description}`,
5981
+ "",
5982
+ cmd.instruction,
5983
+ "",
5984
+ `CLI: \`${cmd.cliCommand}\``
5985
+ ].join("\n");
5986
+ await fs13.writeFile(filePath, content, "utf-8");
5987
+ files.push(filePath);
5988
+ }
5989
+ return files;
5990
+ }
5991
+
5992
+ // src/runtime/adapters/gemini-cli.ts
5993
+ import fs14 from "fs/promises";
5994
+ import path15 from "path";
5995
+ async function installGeminiCli(commands, targetDir) {
5996
+ await fs14.mkdir(targetDir, { recursive: true });
5997
+ const files = [];
5998
+ for (const cmd of commands) {
5999
+ const filename = `brg-${cmd.name}.md`;
6000
+ const filePath = path15.join(targetDir, filename);
6001
+ const content = [
6002
+ `# /brg:${cmd.name}`,
6003
+ "",
6004
+ `> ${cmd.description}`,
6005
+ "",
6006
+ cmd.instruction,
6007
+ "",
6008
+ `CLI: \`${cmd.cliCommand}\``,
6009
+ "",
6010
+ "> Note: Use /brg:" + cmd.name + " in Gemini CLI."
6011
+ ].join("\n");
6012
+ await fs14.writeFile(filePath, content, "utf-8");
6013
+ files.push(filePath);
6014
+ }
6015
+ return files;
6016
+ }
6017
+
6018
+ // src/runtime/adapters/cursor.ts
6019
+ import fs15 from "fs/promises";
6020
+ import path16 from "path";
6021
+ async function installCursor(commands, targetDir) {
6022
+ await fs15.mkdir(targetDir, { recursive: true });
6023
+ const files = [];
6024
+ for (const cmd of commands) {
6025
+ const filename = `brg-${cmd.name}.mdc`;
6026
+ const filePath = path16.join(targetDir, filename);
6027
+ const content = [
6028
+ "---",
6029
+ `description: BrainForge AI \u2014 ${cmd.description}`,
6030
+ "globs: []",
6031
+ "alwaysApply: false",
6032
+ "---",
6033
+ "",
6034
+ `# BrainForge: ${cmd.name}`,
6035
+ "",
6036
+ cmd.instruction,
6037
+ "",
6038
+ `Run in terminal: \`${cmd.cliCommand}\``
6039
+ ].join("\n");
6040
+ await fs15.writeFile(filePath, content, "utf-8");
6041
+ files.push(filePath);
6042
+ }
6043
+ return files;
6044
+ }
6045
+
6046
+ // src/runtime/adapters/windsurf.ts
6047
+ import fs16 from "fs/promises";
6048
+ import path17 from "path";
6049
+ async function installWindsurf(commands, targetDir) {
6050
+ await fs16.mkdir(targetDir, { recursive: true });
6051
+ const files = [];
6052
+ for (const cmd of commands) {
6053
+ const filename = `brg-${cmd.name}.md`;
6054
+ const filePath = path17.join(targetDir, filename);
6055
+ const content = [
6056
+ `# BrainForge: ${cmd.name}`,
6057
+ "",
6058
+ `> ${cmd.description}`,
6059
+ "",
6060
+ cmd.instruction,
6061
+ "",
6062
+ `Terminal: \`${cmd.cliCommand}\``
6063
+ ].join("\n");
6064
+ await fs16.writeFile(filePath, content, "utf-8");
6065
+ files.push(filePath);
6066
+ }
6067
+ return files;
6068
+ }
6069
+
6070
+ // src/runtime/adapters/copilot.ts
6071
+ import fs17 from "fs/promises";
6072
+ import path18 from "path";
6073
+ async function installCopilot(commands, targetDir) {
6074
+ await fs17.mkdir(targetDir, { recursive: true });
6075
+ const instructionsPath = path18.join(targetDir, "copilot-instructions.md");
6076
+ let existing = "";
6077
+ try {
6078
+ existing = await fs17.readFile(instructionsPath, "utf-8");
6079
+ } catch {
6080
+ }
6081
+ const MARKER_START = "<!-- brainforge-start -->";
6082
+ const MARKER_END = "<!-- brainforge-end -->";
6083
+ const block = [
6084
+ MARKER_START,
6085
+ "",
6086
+ "# BrainForge AI Workflow Commands",
6087
+ "",
6088
+ "The following BrainForge CLI commands are available in this project:",
6089
+ "",
6090
+ ...commands.map((c) => `- \`${c.cliCommand}\` \u2014 ${c.description}`),
6091
+ "",
6092
+ "## Usage examples",
6093
+ "",
6094
+ ...commands.slice(0, 5).map(
6095
+ (c) => `### ${c.name}
6096
+ ${c.instruction.split("\n")[0]}
6097
+
6098
+ Run: \`${c.cliCommand}\``
6099
+ ),
6100
+ "",
6101
+ MARKER_END
6102
+ ].join("\n");
6103
+ let newContent;
6104
+ if (existing.includes(MARKER_START)) {
6105
+ newContent = existing.replace(
6106
+ new RegExp(`${MARKER_START}[\\s\\S]*?${MARKER_END}`),
6107
+ block
6108
+ );
6109
+ } else {
6110
+ newContent = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + block + "\n";
6111
+ }
6112
+ await fs17.writeFile(instructionsPath, newContent, "utf-8");
6113
+ return [instructionsPath];
6114
+ }
6115
+
6116
+ // src/runtime/adapters/vscode.ts
6117
+ import fs18 from "fs/promises";
6118
+ import path19 from "path";
6119
+ async function installVSCode(commands, targetDir) {
6120
+ await fs18.mkdir(targetDir, { recursive: true });
6121
+ const snippetsPath = path19.join(targetDir, "brainforge.code-snippets");
6122
+ const snippets = {};
6123
+ for (const cmd of commands) {
6124
+ const key = `brg-${cmd.name}`;
6125
+ snippets[key] = {
6126
+ scope: "markdown,plaintext",
6127
+ prefix: `brg-${cmd.name}`,
6128
+ body: [cmd.instruction.split("\n")[0], "", `Run: \`${cmd.cliCommand}\``],
6129
+ description: cmd.description
6130
+ };
6131
+ }
6132
+ await fs18.writeFile(snippetsPath, JSON.stringify(snippets, null, 2), "utf-8");
6133
+ return [snippetsPath];
6134
+ }
6135
+
6136
+ // src/runtime/adapters/generic.ts
6137
+ import fs19 from "fs/promises";
6138
+ import path20 from "path";
6139
+ async function installGeneric(commands, targetDir) {
6140
+ await fs19.mkdir(targetDir, { recursive: true });
6141
+ const files = [];
6142
+ for (const cmd of commands) {
6143
+ const filename = `brg-${cmd.name}.md`;
6144
+ const filePath = path20.join(targetDir, filename);
6145
+ const content = [
6146
+ `# brg-${cmd.name}`,
6147
+ "",
6148
+ `**${cmd.description}**`,
6149
+ "",
6150
+ "## Instructions",
6151
+ "",
6152
+ cmd.instruction,
6153
+ "",
6154
+ "## CLI command",
6155
+ "",
6156
+ `\`\`\`bash`,
6157
+ cmd.cliCommand,
6158
+ `\`\`\``,
6159
+ "",
6160
+ "---",
6161
+ "*Copy and paste this into your AI tool.*"
6162
+ ].join("\n");
6163
+ await fs19.writeFile(filePath, content, "utf-8");
6164
+ files.push(filePath);
6165
+ }
6166
+ const indexPath = path20.join(targetDir, "INDEX.md");
6167
+ const indexContent = [
6168
+ "# BrainForge AI \u2014 Prompt Library",
6169
+ "",
6170
+ "Copy any of the files below into your AI tool to run that workflow.",
6171
+ "",
6172
+ ...commands.map((c) => `- [brg-${c.name}.md](brg-${c.name}.md) \u2014 ${c.description}`)
6173
+ ].join("\n");
6174
+ await fs19.writeFile(indexPath, indexContent, "utf-8");
6175
+ files.push(indexPath);
6176
+ return files;
6177
+ }
6178
+
6179
+ // src/runtime/runtime-installer.ts
6180
+ async function installRuntime(runtimeId, opts) {
6181
+ const rt = getRuntime(runtimeId);
6182
+ if (!rt) throw new Error(`Unknown runtime: ${runtimeId}`);
6183
+ const targetDir = opts.global && rt.globalConfigPath ? rt.globalConfigPath : path21.join(opts.cwd, rt.localConfigPath);
6184
+ const commands = COMMAND_DEFINITIONS;
6185
+ let filesWritten;
6186
+ switch (runtimeId) {
6187
+ case "claude-code":
6188
+ filesWritten = await installClaudeCode(commands, targetDir);
6189
+ break;
6190
+ case "codex":
6191
+ filesWritten = await installCodex(commands, targetDir);
6192
+ break;
6193
+ case "opencode":
6194
+ filesWritten = await installOpenCode(commands, targetDir);
6195
+ break;
6196
+ case "gemini-cli":
6197
+ filesWritten = await installGeminiCli(commands, targetDir);
6198
+ break;
6199
+ case "cursor":
6200
+ filesWritten = await installCursor(commands, targetDir);
6201
+ break;
6202
+ case "windsurf":
6203
+ filesWritten = await installWindsurf(commands, targetDir);
6204
+ break;
6205
+ case "copilot":
6206
+ filesWritten = await installCopilot(commands, targetDir);
6207
+ break;
6208
+ case "vscode":
6209
+ filesWritten = await installVSCode(commands, targetDir);
6210
+ break;
6211
+ case "generic":
6212
+ filesWritten = await installGeneric(commands, targetDir);
6213
+ break;
6214
+ default:
6215
+ throw new Error(`No adapter for runtime: ${runtimeId}`);
6216
+ }
6217
+ return {
6218
+ runtimeId,
6219
+ runtimeName: rt.name,
6220
+ filesWritten,
6221
+ targetDir,
6222
+ commandCount: commands.length
6223
+ };
6224
+ }
6225
+
6226
+ // src/agents/agent-registry.ts
6227
+ var AGENT_REGISTRY = [
6228
+ {
6229
+ id: "orchestrator",
6230
+ name: "Orchestrator",
6231
+ purpose: "Coordinates all other agents. Reads project goals, assigns tasks, validates wave plan.",
6232
+ inputs: ["PROJECT.md", "STATE.md", "REQUIREMENTS.md", "ROADMAP.md"],
6233
+ outputs: ["AGENTS.json", "wave plan", "task assignments"],
6234
+ forbidden: ["writing source code", "making architectural decisions alone"],
6235
+ qualityChecks: ["all tasks assigned", "no circular dependencies", "wave plan is valid"],
6236
+ taskTypes: ["general"]
6237
+ },
6238
+ {
6239
+ id: "researcher",
6240
+ name: "Researcher",
6241
+ purpose: "Researches domain knowledge, third-party libraries, and technical solutions.",
6242
+ inputs: ["topic", "stack", "constraints"],
6243
+ outputs: ["RESEARCH.md"],
6244
+ forbidden: ["writing production code", "making final technology choices"],
6245
+ qualityChecks: ["sources cited", "trade-offs documented", "recommendation is concrete"],
6246
+ taskTypes: ["general", "docs"]
6247
+ },
6248
+ {
6249
+ id: "codebase-mapper",
6250
+ name: "Codebase Mapper",
6251
+ purpose: "Maps the existing codebase architecture and produces human-readable summaries.",
6252
+ inputs: ["source files", "package.json", "tsconfig.json"],
6253
+ outputs: ["MAP.md", "STACK.json", "CONVENTIONS.md"],
6254
+ forbidden: ["modifying source code", "making architectural changes"],
6255
+ qualityChecks: ["entry points identified", "key modules listed", "stack fully detected"],
6256
+ taskTypes: ["general"]
6257
+ },
6258
+ {
6259
+ id: "requirements-analyst",
6260
+ name: "Requirements Analyst",
6261
+ purpose: "Extracts and structures requirements from DISCUSSION.md into REQUIREMENTS.md.",
6262
+ inputs: ["DISCUSSION.md", "project goals", "constraints"],
6263
+ outputs: ["REQUIREMENTS.md"],
6264
+ forbidden: ["inventing features not discussed", "technical implementation details"],
6265
+ qualityChecks: ["FRs numbered", "NFRs included", "out-of-scope section present", "no ambiguous language"],
6266
+ taskTypes: ["general", "docs"]
6267
+ },
6268
+ {
6269
+ id: "architect",
6270
+ name: "Architect",
6271
+ purpose: "Designs the system structure: modules, data flow, API contracts, database schema.",
6272
+ inputs: ["REQUIREMENTS.md", "MAP.md", "STACK.json"],
6273
+ outputs: ["ARCHITECTURE.md", "API_CONTRACTS.md", "SCHEMA.md"],
6274
+ forbidden: ["writing implementation code", "skipping documentation"],
6275
+ qualityChecks: ["all FRs covered", "data flow described", "external integrations listed"],
6276
+ taskTypes: ["backend", "database", "general"]
6277
+ },
6278
+ {
6279
+ id: "planner",
6280
+ name: "Planner",
6281
+ purpose: "Breaks down a phase into atomic tasks with dependencies and agent assignments.",
6282
+ inputs: ["REQUIREMENTS.md", "ARCHITECTURE.md", "phase goal"],
6283
+ outputs: ["TASKS.json", "AGENTS.json", "dependency graph"],
6284
+ forbidden: ["inventing scope", "assigning more than one agent per task"],
6285
+ qualityChecks: ["each task has clear acceptance criteria", "dependencies are acyclic", "risk levels set"],
6286
+ taskTypes: ["general"]
6287
+ },
6288
+ {
6289
+ id: "backend-engineer",
6290
+ name: "Backend Engineer",
6291
+ purpose: "Implements API endpoints, business logic, services, and backend infrastructure.",
6292
+ inputs: ["PLAN.md", "task object", "ARCHITECTURE.md", "CONVENTIONS.md"],
6293
+ outputs: ["source code", "task completion report"],
6294
+ forbidden: ["modifying frontend code", "skipping error handling", "hardcoding secrets"],
6295
+ qualityChecks: ["tests written", "input validated", "errors handled", "no hardcoded secrets"],
6296
+ taskTypes: ["backend", "general"]
6297
+ },
6298
+ {
6299
+ id: "frontend-engineer",
6300
+ name: "Frontend Engineer",
6301
+ purpose: "Implements UI components, pages, forms, and client-side logic.",
6302
+ inputs: ["PLAN.md", "task object", "UI_CONTRACT.md", "CONVENTIONS.md"],
6303
+ outputs: ["source code", "task completion report"],
6304
+ forbidden: ["modifying backend code", "skipping accessibility", "inline styles without system tokens"],
6305
+ qualityChecks: ["responsive design", "accessible (ARIA)", "error states handled", "no raw API calls from components"],
6306
+ taskTypes: ["frontend", "general"]
6307
+ },
6308
+ {
6309
+ id: "fullstack-engineer",
6310
+ name: "Full-Stack Engineer",
6311
+ purpose: "Implements tasks spanning both frontend and backend in the same feature slice.",
6312
+ inputs: ["PLAN.md", "task object", "ARCHITECTURE.md", "CONVENTIONS.md"],
6313
+ outputs: ["source code", "task completion report"],
6314
+ forbidden: ["skipping tests", "mixing concerns", "leaving TODO comments in shipped code"],
6315
+ qualityChecks: ["both layers implemented", "E2E flow tested", "no dead code"],
6316
+ taskTypes: ["frontend", "backend", "general"]
6317
+ },
6318
+ {
6319
+ id: "database-engineer",
6320
+ name: "Database Engineer",
6321
+ purpose: "Designs and implements database schemas, migrations, indexes, and queries.",
6322
+ inputs: ["SCHEMA.md", "task object", "STACK.json"],
6323
+ outputs: ["migration files", "schema updates", "seed data"],
6324
+ forbidden: ["destructive migrations without backup plan", "N+1 queries", "missing indexes on FK columns"],
6325
+ qualityChecks: ["migration is reversible", "indexes on query-critical columns", "no data loss"],
6326
+ taskTypes: ["database"]
6327
+ },
6328
+ {
6329
+ id: "devops-engineer",
6330
+ name: "DevOps Engineer",
6331
+ purpose: "Implements CI/CD pipelines, Docker configs, deployment scripts, and infrastructure.",
6332
+ inputs: ["PLAN.md", "task object", "STACK.json", "deployment target"],
6333
+ outputs: ["pipeline files", "Docker configs", "deployment scripts"],
6334
+ forbidden: ["hardcoding credentials", "skipping health checks", "deploying untested code"],
6335
+ qualityChecks: ["secrets in env vars", "health check endpoint used", "rollback plan documented"],
6336
+ taskTypes: ["devops", "general"]
6337
+ },
6338
+ {
6339
+ id: "security-reviewer",
6340
+ name: "Security Reviewer",
6341
+ purpose: "Audits code for OWASP Top 10 vulnerabilities, authentication issues, and data exposure.",
6342
+ inputs: ["source code", "PLAN.md", "API_CONTRACTS.md"],
6343
+ outputs: ["SECURITY.md", "security findings with severity"],
6344
+ forbidden: ["introducing new code", "ignoring high/critical findings"],
6345
+ qualityChecks: ["OWASP Top 10 checked", "auth/authz reviewed", "input sanitization verified"],
6346
+ taskTypes: ["security", "backend"]
6347
+ },
6348
+ {
6349
+ id: "test-engineer",
6350
+ name: "Test Engineer",
6351
+ purpose: "Writes unit, integration, and E2E tests with strong coverage.",
6352
+ inputs: ["source code", "task result", "REQUIREMENTS.md"],
6353
+ outputs: ["test files", "test run results"],
6354
+ forbidden: ["mocking the database for integration tests", "testing implementation details", "skipping edge cases"],
6355
+ qualityChecks: ["happy path tested", "error paths tested", "coverage > 80%"],
6356
+ taskTypes: ["test"]
6357
+ },
6358
+ {
6359
+ id: "ui-reviewer",
6360
+ name: "UI Reviewer",
6361
+ purpose: "Audits UI implementation against design contract, accessibility, and visual quality.",
6362
+ inputs: ["source code", "UI_CONTRACT.md", "screenshots"],
6363
+ outputs: ["UI_REVIEW.md", "list of issues with severity"],
6364
+ forbidden: ["rewriting frontend code", "approving inaccessible UIs"],
6365
+ qualityChecks: ["contrast ratio passes", "keyboard navigation works", "responsive on mobile"],
6366
+ taskTypes: ["frontend"]
6367
+ },
6368
+ {
6369
+ id: "debugger",
6370
+ name: "Debugger",
6371
+ purpose: "Diagnoses bugs using scientific method: hypothesis \u2192 test \u2192 fix \u2192 verify.",
6372
+ inputs: ["bug description", "error logs", "source code", "reproduction steps"],
6373
+ outputs: ["BUGS.md", "FIX_PLAN.md", "root cause analysis"],
6374
+ forbidden: ["guessing without evidence", "fixing symptoms without root cause", "introducing new bugs"],
6375
+ qualityChecks: ["root cause identified", "fix is minimal", "regression test added"],
6376
+ taskTypes: ["backend", "frontend", "general"]
6377
+ },
6378
+ {
6379
+ id: "verifier",
6380
+ name: "Verifier",
6381
+ purpose: "Verifies that a phase is truly done: runs acceptance criteria, checks tests, reports bugs.",
6382
+ inputs: ["PLAN.md", "TASKS.json", "source code", "test results"],
6383
+ outputs: ["VERIFY.md", "BUGS.md"],
6384
+ forbidden: ["approving a phase with critical bugs", "skipping any acceptance criterion"],
6385
+ qualityChecks: ["every acceptance criterion checked", "all tests pass", "no regression"],
6386
+ taskTypes: ["general"]
6387
+ },
6388
+ {
6389
+ id: "docs-writer",
6390
+ name: "Docs Writer",
6391
+ purpose: "Updates README, API docs, architecture docs, and CHANGELOG after implementation.",
6392
+ inputs: ["source code", "REQUIREMENTS.md", "SHIP.md"],
6393
+ outputs: ["updated docs", "CHANGELOG entry"],
6394
+ forbidden: ["inventing features not built", "outdated examples"],
6395
+ qualityChecks: ["code examples run", "API docs match implementation", "CHANGELOG entry present"],
6396
+ taskTypes: ["docs"]
6397
+ },
6398
+ {
6399
+ id: "professor",
6400
+ name: "Professor",
6401
+ purpose: "Explains the implemented code for academic defense: how it works, why it was built this way.",
6402
+ inputs: ["source code", "task object", "REQUIREMENTS.md"],
6403
+ outputs: ["DEFENSE.md", "oral defense talking points"],
6404
+ forbidden: ["oversimplifying", "missing connections to course concepts"],
6405
+ qualityChecks: ['explains the "why"', "connects to theory", "anticipates examiner questions"],
6406
+ taskTypes: ["general"]
6407
+ },
6408
+ {
6409
+ id: "student-explainer",
6410
+ name: "Student Explainer",
6411
+ purpose: "Simplifies complex code explanations to match the student's declared knowledge level.",
6412
+ inputs: ["source code", "student level", "task context"],
6413
+ outputs: ["plain-language explanation", "annotated code snippets"],
6414
+ forbidden: ["using advanced jargon without definition", "condescending tone"],
6415
+ qualityChecks: ["level-appropriate vocabulary", "analogy used for complex concepts", 'no unanswered "why" questions'],
6416
+ taskTypes: ["general"]
6417
+ },
6418
+ {
6419
+ id: "ship-manager",
6420
+ name: "Ship Manager",
6421
+ purpose: "Closes a phase safely: verifies VERIFY.md passes, writes SHIP.md, updates CHANGELOG and memory.",
6422
+ inputs: ["VERIFY.md", "PLAN.md", "TASKS.json", "BUGS.md"],
6423
+ outputs: ["SHIP.md", "CHANGELOG entry", "memory summary"],
6424
+ forbidden: ["shipping with open critical bugs", "skipping memory update"],
6425
+ qualityChecks: ["no critical bugs in BUGS.md", "CHANGELOG entry accurate", "memory entry written"],
6426
+ taskTypes: ["general"]
6427
+ }
6428
+ ];
6429
+ var REGISTRY_MAP2 = new Map(
6430
+ AGENT_REGISTRY.map((a) => [a.id, a])
6431
+ );
6432
+ function getAgent(id) {
6433
+ return REGISTRY_MAP2.get(id);
6434
+ }
6435
+ function listAgents() {
6436
+ return AGENT_REGISTRY;
6437
+ }
6438
+
6439
+ // src/agents/wave-planner.ts
6440
+ var WavePlanner = class {
6441
+ plan(tasks) {
6442
+ const pending = tasks.filter((t) => t.status !== "done");
6443
+ if (pending.length === 0) return [];
6444
+ const assigned = /* @__PURE__ */ new Set();
6445
+ const waves = [];
6446
+ const grouped = /* @__PURE__ */ new Map();
6447
+ const ungrouped = [];
6448
+ for (const t of pending) {
6449
+ if (t.parallelGroup) {
6450
+ const g = grouped.get(t.parallelGroup) ?? [];
6451
+ g.push(t);
6452
+ grouped.set(t.parallelGroup, g);
6453
+ } else {
6454
+ ungrouped.push(t);
6455
+ }
6456
+ }
6457
+ const sorted = topologicalSort(ungrouped);
6458
+ let waveIndex = 1;
6459
+ const remaining = [...sorted];
6460
+ while (remaining.length > 0) {
6461
+ const waveTasks = [];
6462
+ for (const task of remaining) {
6463
+ const deps = task.dependsOn ?? [];
6464
+ const depsResolved = deps.every((dep) => assigned.has(dep));
6465
+ if (depsResolved) waveTasks.push(task);
6466
+ }
6467
+ if (waveTasks.length === 0) {
6468
+ waveTasks.push(...remaining);
6469
+ }
6470
+ for (const t of waveTasks) {
6471
+ assigned.add(t.id);
6472
+ const idx = remaining.indexOf(t);
6473
+ if (idx !== -1) remaining.splice(idx, 1);
6474
+ }
6475
+ waves.push({
6476
+ index: waveIndex++,
6477
+ label: `Wave ${waveIndex - 1}`,
6478
+ tasks: waveTasks,
6479
+ canParallelize: waveTasks.length > 1
6480
+ });
6481
+ }
6482
+ for (const [group, groupTasks] of grouped) {
6483
+ waves.push({
6484
+ index: waveIndex++,
6485
+ label: `Wave ${waveIndex - 1} (group: ${group})`,
6486
+ tasks: groupTasks,
6487
+ canParallelize: true
6488
+ });
6489
+ }
6490
+ return waves;
6491
+ }
6492
+ assignAgents(tasks) {
6493
+ const assignments = /* @__PURE__ */ new Map();
6494
+ for (const task of tasks) {
6495
+ if (task.agent) {
6496
+ assignments.set(task.id, task.agent);
6497
+ continue;
6498
+ }
6499
+ assignments.set(task.id, inferAgent(task));
6500
+ }
6501
+ return assignments;
6502
+ }
6503
+ };
6504
+ function inferAgent(task) {
6505
+ const title = task.title.toLowerCase();
6506
+ const desc = (task.description ?? "").toLowerCase();
6507
+ const text = title + " " + desc;
6508
+ if (task.type === "database" || /migrat|schema|model|orm|sequelize|prisma|drizzle/.test(text)) return "database-engineer";
6509
+ if (task.type === "devops" || /ci\/cd|pipeline|docker|deploy|kubernetes|helm|infra/.test(text)) return "devops-engineer";
6510
+ if (task.type === "security" || /auth|jwt|oauth|rbac|permission|security|encrypt/.test(text)) return "security-reviewer";
6511
+ if (task.type === "test" || /test|spec|coverage|vitest|jest|playwright|cypress/.test(text)) return "test-engineer";
6512
+ if (task.type === "docs" || /readme|doc|changelog|comment|jsdoc/.test(text)) return "docs-writer";
6513
+ if (task.type === "frontend" || /component|ui|page|layout|style|css|form|modal|button|nav/.test(text)) return "frontend-engineer";
6514
+ if (task.type === "backend" || /api|endpoint|route|controller|service|handler|webhook|cron/.test(text)) return "backend-engineer";
6515
+ return "fullstack-engineer";
6516
+ }
6517
+ function topologicalSort(tasks) {
6518
+ const sorted = [];
6519
+ const visited = /* @__PURE__ */ new Set();
6520
+ const taskMap = new Map(tasks.map((t) => [t.id, t]));
6521
+ function visit(task) {
6522
+ if (visited.has(task.id)) return;
6523
+ visited.add(task.id);
6524
+ for (const depId of task.dependsOn ?? []) {
6525
+ const dep = taskMap.get(depId);
6526
+ if (dep) visit(dep);
6527
+ }
6528
+ sorted.push(task);
6529
+ }
6530
+ for (const task of tasks) visit(task);
6531
+ return sorted;
6532
+ }
6533
+
6534
+ // src/agents/parallel-orchestrator.ts
6535
+ import fs20 from "fs/promises";
6536
+ import path22 from "path";
6537
+
6538
+ // src/agents/agent-prompts.ts
6539
+ function buildAgentPrompt(agent, task, ctx) {
6540
+ const stackLine = ctx.stack ? `${ctx.stack.language}${ctx.stack.framework ? " / " + ctx.stack.framework : ""}` : "unknown";
6541
+ const lines = [
6542
+ `# Agent: ${agent.name}`,
6543
+ `> Project: ${ctx.projectName} | Phase: ${ctx.phaseName} | Stack: ${stackLine}`,
6544
+ "",
6545
+ `## Your role`,
6546
+ agent.purpose,
6547
+ "",
6548
+ `## Task`,
6549
+ `**${task.title}**`,
6550
+ "",
6551
+ task.description ? task.description + "\n" : ""
6552
+ ];
6553
+ if (task.acceptanceCriteria?.length) {
6554
+ lines.push("## Acceptance criteria");
6555
+ for (const c of task.acceptanceCriteria) lines.push(`- [ ] ${c}`);
6556
+ lines.push("");
6557
+ }
6558
+ if (task.verificationCommands?.length) {
6559
+ lines.push("## Verification commands");
6560
+ for (const cmd of task.verificationCommands) lines.push(`\`\`\`bash
6561
+ ${cmd}
6562
+ \`\`\``);
6563
+ lines.push("");
6564
+ }
6565
+ lines.push("## Your inputs");
6566
+ for (const input of agent.inputs) lines.push(`- ${input}`);
6567
+ lines.push("");
6568
+ lines.push("## Expected outputs");
6569
+ for (const output of agent.outputs) lines.push(`- ${output}`);
6570
+ lines.push("");
6571
+ lines.push("## What you must NOT do");
6572
+ for (const f of agent.forbidden) lines.push(`- ${f}`);
6573
+ lines.push("");
6574
+ lines.push("## Quality checks before you report done");
6575
+ for (const q of agent.qualityChecks) lines.push(`- [ ] ${q}`);
6576
+ lines.push("");
6577
+ if (task.riskLevel === "high") {
6578
+ lines.push(`> **Risk level: ${task.riskLevel}** \u2014 extra care required. Document trade-offs.`);
6579
+ lines.push("");
6580
+ }
6581
+ lines.push("---");
6582
+ lines.push(`*BrainForge AI \u2014 Agent: ${agent.id} \u2014 paste into your AI tool.*`);
6583
+ return lines.filter((l) => l !== null && l !== void 0).join("\n");
6584
+ }
6585
+ function buildWaveSummary(wave, tasks, phase, ctx) {
6586
+ const lines = [
6587
+ `# Wave ${wave} \u2014 ${ctx.phaseName}`,
6588
+ `> ${tasks.length} task(s) can run in parallel in this wave.`,
6589
+ "",
6590
+ "## Tasks in this wave",
6591
+ ""
6592
+ ];
6593
+ for (const t of tasks) {
6594
+ const agentLabel = t.agent ? ` \u2192 agent: ${t.agent}` : "";
6595
+ lines.push(`- **${t.title}**${agentLabel}`);
6596
+ if (t.description) lines.push(` ${t.description.split("\n")[0]}`);
6597
+ }
6598
+ lines.push("");
6599
+ lines.push("## Parallelization note");
6600
+ lines.push("These tasks are independent and can be worked on simultaneously in separate AI sessions.");
6601
+ lines.push("Each task has its own prompt file in this wave directory.");
6602
+ return lines.join("\n");
6603
+ }
6604
+
6605
+ // src/agents/parallel-orchestrator.ts
6606
+ var ParallelOrchestrator = class {
6607
+ constructor(cwd) {
6608
+ this.cwd = cwd;
6609
+ this.phasesDir = path22.join(cwd, ".brainforge", "phases");
6610
+ }
6611
+ cwd;
6612
+ phasesDir;
6613
+ async orchestrate(phase, stack) {
6614
+ const planner = new WavePlanner();
6615
+ const waves = planner.plan(phase.tasks);
6616
+ const agentMap = planner.assignAgents(phase.tasks);
6617
+ const artifactsDir = path22.join(this.phasesDir, phase.id, "artifacts");
6618
+ await fs20.mkdir(artifactsDir, { recursive: true });
6619
+ const ctx = {
6620
+ projectName: path22.basename(this.cwd),
6621
+ phaseName: phase.name,
6622
+ stack
6623
+ };
6624
+ const writtenFiles = [];
6625
+ for (const wave of waves) {
6626
+ const waveDir = path22.join(artifactsDir, `wave-${String(wave.index).padStart(3, "0")}`);
6627
+ await fs20.mkdir(waveDir, { recursive: true });
6628
+ const summaryContent = buildWaveSummary(wave.index, wave.tasks, phase, ctx);
6629
+ const summaryPath = path22.join(waveDir, "WAVE.md");
6630
+ await fs20.writeFile(summaryPath, summaryContent, "utf-8");
6631
+ writtenFiles.push(path22.relative(this.cwd, summaryPath).replace(/\\/g, "/"));
6632
+ for (const task of wave.tasks) {
6633
+ const agentId = agentMap.get(task.id) ?? "fullstack-engineer";
6634
+ const agent = getAgent(agentId) ?? getAgent("fullstack-engineer");
6635
+ const content = buildAgentPrompt(agent, task, ctx);
6636
+ const slug = task.id.replace(/[^a-z0-9-]/gi, "-").slice(0, 40);
6637
+ const filePath = path22.join(waveDir, `${slug}.md`);
6638
+ await fs20.writeFile(filePath, content, "utf-8");
6639
+ writtenFiles.push(path22.relative(this.cwd, filePath).replace(/\\/g, "/"));
6640
+ }
6641
+ }
6642
+ const indexContent = buildArtifactsIndex(phase, waves, agentMap);
6643
+ const indexPath = path22.join(artifactsDir, "INDEX.md");
6644
+ await fs20.writeFile(indexPath, indexContent, "utf-8");
6645
+ writtenFiles.push(path22.relative(this.cwd, indexPath).replace(/\\/g, "/"));
6646
+ return {
6647
+ waves: waves.length,
6648
+ totalTasks: phase.tasks.filter((t) => t.status !== "done").length,
6649
+ files: writtenFiles,
6650
+ waveDir: path22.relative(this.cwd, artifactsDir).replace(/\\/g, "/")
6651
+ };
6652
+ }
6653
+ };
6654
+ function buildArtifactsIndex(phase, waves, agentMap) {
6655
+ const lines = [
6656
+ `# Execution Artifacts \u2014 ${phase.name}`,
6657
+ `> ${waves.length} wave(s) | ${phase.tasks.filter((t) => t.status !== "done").length} tasks`,
6658
+ "",
6659
+ "## How to use",
6660
+ "1. Start with Wave 1. Open each task file and paste it into your AI tool.",
6661
+ "2. Tasks in the same wave can run in parallel (separate AI sessions or tabs).",
6662
+ "3. Move to the next wave only when all tasks in the current wave are done.",
6663
+ ""
6664
+ ];
6665
+ for (const wave of waves) {
6666
+ lines.push(`## ${wave.label}${wave.canParallelize ? " (parallel)" : ""}`);
6667
+ for (const task of wave.tasks) {
6668
+ const agentId = agentMap.get(task.id) ?? "fullstack-engineer";
6669
+ lines.push(`- [${agentId}] ${task.title}`);
6670
+ }
6671
+ lines.push("");
6672
+ }
6673
+ return lines.join("\n");
6674
+ }
6675
+
6676
+ // src/agents/conflict-detector.ts
6677
+ function detectConflicts(tasks) {
6678
+ const warnings = [];
6679
+ for (let i = 0; i < tasks.length; i++) {
6680
+ for (let j = i + 1; j < tasks.length; j++) {
6681
+ const a = tasks[i];
6682
+ const b = tasks[j];
6683
+ if (a.files?.length && b.files?.length) {
6684
+ const overlap = a.files.filter((f) => b.files.includes(f));
6685
+ if (overlap.length > 0) {
6686
+ warnings.push({
6687
+ taskA: a.id,
6688
+ taskB: b.id,
6689
+ reason: `Both tasks modify the same file(s)`,
6690
+ filePaths: overlap
6691
+ });
6692
+ continue;
6693
+ }
6694
+ }
6695
+ const aText = taskText(a);
6696
+ const bText = taskText(b);
6697
+ if (/schema|migrat|model/.test(aText) && /schema|migrat|model/.test(bText)) {
6698
+ warnings.push({
6699
+ taskA: a.id,
6700
+ taskB: b.id,
6701
+ reason: "Both tasks may modify database schema \u2014 coordinate to avoid migration conflicts"
6702
+ });
6703
+ continue;
6704
+ }
6705
+ if (/\bauth\b/.test(aText) && /\bauth\b/.test(bText)) {
6706
+ warnings.push({
6707
+ taskA: a.id,
6708
+ taskB: b.id,
6709
+ reason: "Both tasks touch authentication \u2014 ensure consistent token/session handling"
6710
+ });
6711
+ }
6712
+ }
6713
+ }
6714
+ return warnings;
6715
+ }
6716
+ function taskText(task) {
6717
+ return (task.title + " " + (task.description ?? "")).toLowerCase();
6718
+ }
6719
+
6720
+ // src/agents/result-merger.ts
6721
+ function mergeResults(waves, results) {
6722
+ const totalTasks = waves.reduce((n, w) => n + w.tasks.length, 0);
6723
+ let completed = 0;
6724
+ let failed = 0;
6725
+ const allBlockers = [];
6726
+ for (const result of results) {
6727
+ completed += result.completedTaskIds.length;
6728
+ failed += result.failedTaskIds.length;
6729
+ allBlockers.push(...result.blockers);
6730
+ }
6731
+ const blocked = allBlockers.length > 0;
6732
+ const summary = [
6733
+ `${completed}/${totalTasks} tasks completed across ${waves.length} wave(s).`,
6734
+ failed > 0 ? `${failed} task(s) failed.` : "",
6735
+ blocked ? `Blockers: ${allBlockers.join("; ")}` : ""
6736
+ ].filter(Boolean).join(" ");
6737
+ return { totalWaves: waves.length, totalTasks, completed, failed, blocked, summary };
6738
+ }
6739
+ function buildMergeReport(tasks, results) {
6740
+ const completedIds = new Set(results.flatMap((r) => r.completedTaskIds));
6741
+ const failedIds = new Set(results.flatMap((r) => r.failedTaskIds));
6742
+ const lines = ["# Merge Report", ""];
6743
+ for (const task of tasks) {
6744
+ const icon = completedIds.has(task.id) ? "\u2713" : failedIds.has(task.id) ? "\u2717" : "\u25CB";
6745
+ lines.push(`- [${icon}] ${task.title}`);
6746
+ }
6747
+ const blockers = results.flatMap((r) => r.blockers);
6748
+ if (blockers.length > 0) {
6749
+ lines.push("", "## Blockers");
6750
+ for (const b of blockers) lines.push(`- ${b}`);
6751
+ }
6752
+ return lines.join("\n");
6753
+ }
6754
+
6755
+ // src/workflow/workflow-engine.ts
6756
+ var WorkflowEngine = class {
6757
+ buildCreatePhaseAction(input) {
6758
+ return {
6759
+ type: "ADD_PHASE",
6760
+ phase: {
6761
+ id: input.id,
6762
+ name: input.name,
6763
+ status: "pending",
6764
+ tasks: input.tasks.map((t) => ({ ...t, status: "todo" }))
6765
+ }
6766
+ };
6767
+ }
6768
+ buildSwitchPhaseAction(phaseId) {
6769
+ return { type: "SET_ACTIVE_PHASE", phaseId };
6770
+ }
6771
+ buildStartTaskAction(taskId) {
6772
+ return { type: "UPDATE_TASK_STATUS", taskId, status: "in-progress" };
6773
+ }
6774
+ buildCompleteTaskAction(taskId) {
6775
+ return { type: "UPDATE_TASK_STATUS", taskId, status: "done" };
6776
+ }
6777
+ buildCompletePhaseAction(phaseId) {
6778
+ return { type: "COMPLETE_PHASE", phaseId };
6779
+ }
6780
+ isPhaseComplete(phase) {
6781
+ return phase.tasks.length > 0 && phase.tasks.every((t) => t.status === "done");
6782
+ }
6783
+ getNextAction(state) {
6784
+ if (state.phases.length === 0) return "Run `brg plan` to create your first phase.";
6785
+ const active = state.phases.find((p) => p.id === state.activePhaseId);
6786
+ if (!active) {
6787
+ const pending = state.phases.find((p) => p.status === "pending");
6788
+ return pending ? `Switch to phase "${pending.name}" with \`brg phase switch ${pending.id}\`` : "All phases complete \u2014 run `brg ship` to finalize.";
6789
+ }
6790
+ const inProg = active.tasks.filter((t) => t.status === "in-progress");
6791
+ const todo = active.tasks.filter((t) => t.status === "todo");
6792
+ if (inProg.length > 0) return `Continue ${inProg.length} in-progress task(s) \u2014 run \`brg execute --prompt-only\``;
6793
+ if (todo.length > 0) return `Execute ${todo.length} remaining task(s) \u2014 run \`brg execute --prompt-only\``;
6794
+ return `All tasks done \u2014 run \`brg verify\` to verify phase "${active.name}"`;
6795
+ }
6796
+ slugify(name) {
6797
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
6798
+ }
6799
+ generatePhaseId(name, existingIds) {
6800
+ const base = this.slugify(name);
6801
+ if (!existingIds.includes(base)) return base;
6802
+ let n = 2;
6803
+ while (existingIds.includes(`${base}-${n}`)) n++;
6804
+ return `${base}-${n}`;
6805
+ }
6806
+ };
6807
+
6808
+ // src/workflow/phase-engine.ts
6809
+ import fs21 from "fs/promises";
6810
+ import path23 from "path";
6811
+ var PhaseEngine = class {
6812
+ constructor(cwd) {
6813
+ this.cwd = cwd;
6814
+ }
6815
+ cwd;
6816
+ phaseDir(phaseId) {
6817
+ return path23.join(this.cwd, ".brainforge", "phases", phaseId);
6818
+ }
6819
+ async ensurePhaseDir(phaseId) {
6820
+ await fs21.mkdir(this.phaseDir(phaseId), { recursive: true });
6821
+ }
6822
+ async writeStatus(phaseId, step) {
6823
+ await this.ensurePhaseDir(phaseId);
6824
+ const status = { phaseId, step, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
6825
+ await fs21.writeFile(
6826
+ path23.join(this.phaseDir(phaseId), "STATUS.json"),
6827
+ JSON.stringify(status, null, 2),
6828
+ "utf-8"
6829
+ );
6830
+ }
6831
+ async readStatus(phaseId) {
6832
+ try {
6833
+ const raw = await fs21.readFile(path23.join(this.phaseDir(phaseId), "STATUS.json"), "utf-8");
6834
+ return JSON.parse(raw);
6835
+ } catch {
6836
+ return null;
6837
+ }
6838
+ }
6839
+ async writeArtifact(phaseId, filename, content) {
6840
+ await this.ensurePhaseDir(phaseId);
6841
+ const filePath = path23.join(this.phaseDir(phaseId), filename);
6842
+ await fs21.writeFile(filePath, content, "utf-8");
6843
+ return filePath;
6844
+ }
6845
+ async readArtifact(phaseId, filename) {
6846
+ try {
6847
+ return await fs21.readFile(path23.join(this.phaseDir(phaseId), filename), "utf-8");
6848
+ } catch {
6849
+ return null;
6850
+ }
6851
+ }
6852
+ async listArtifacts(phaseId) {
6853
+ try {
6854
+ const entries = await fs21.readdir(this.phaseDir(phaseId));
6855
+ return entries;
6856
+ } catch {
6857
+ return [];
6858
+ }
6859
+ }
6860
+ relPath(phaseId, filename) {
6861
+ return path23.relative(
6862
+ this.cwd,
6863
+ path23.join(this.phaseDir(phaseId), filename)
6864
+ ).replace(/\\/g, "/");
6865
+ }
6866
+ };
6867
+
6868
+ // src/workflow/execution-engine.ts
6869
+ var ExecutionEngine = class {
6870
+ buildTaskPrompt(phase, task, ctx) {
6871
+ const stackLine = ctx.stack ? `${ctx.stack.language}${ctx.stack.framework ? " / " + ctx.stack.framework : ""}${ctx.stack.runtime ? " on " + ctx.stack.runtime : ""}` : "not detected";
6872
+ const lines = [
6873
+ `# Task: ${task.title}`,
6874
+ `> Project: ${ctx.projectName} | Phase: ${phase.name}`,
6875
+ `> Stack: ${stackLine}`,
6876
+ "",
6877
+ "## What to implement",
6878
+ task.description ?? task.title,
6879
+ ""
6880
+ ];
6881
+ if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
6882
+ lines.push("## Acceptance criteria");
6883
+ for (const c of task.acceptanceCriteria) {
6884
+ lines.push(`- [ ] ${c}`);
6885
+ }
6886
+ lines.push("");
6887
+ }
6888
+ if (task.verificationCommands && task.verificationCommands.length > 0) {
6889
+ lines.push("## Verification commands");
6890
+ for (const cmd of task.verificationCommands) {
6891
+ lines.push(`\`\`\`bash
6892
+ ${cmd}
6893
+ \`\`\``);
6894
+ }
6895
+ lines.push("");
6896
+ }
6897
+ if (ctx.conventions) {
6898
+ lines.push("## Code conventions to follow");
6899
+ lines.push(`- Naming: ${ctx.conventions.namingStyle}`);
6900
+ lines.push(`- Module style: ${ctx.conventions.moduleStyle}`);
6901
+ if (ctx.conventions.typescriptStrict) lines.push("- TypeScript strict mode enabled");
6902
+ lines.push("");
6903
+ }
6904
+ lines.push("## Done criteria");
6905
+ lines.push("Mark the task complete when all acceptance criteria pass and tests are green.");
6906
+ lines.push("");
6907
+ lines.push("---");
6908
+ lines.push("*Generated by BrainForge AI \u2014 paste this into your AI tool to implement.*");
6909
+ return {
6910
+ taskId: task.id,
6911
+ taskTitle: task.title,
6912
+ content: lines.join("\n")
6913
+ };
6914
+ }
6915
+ buildPhasePrompt(phase, ctx) {
6916
+ const stackLine = ctx.stack ? `${ctx.stack.language}${ctx.stack.framework ? " / " + ctx.stack.framework : ""}` : "not detected";
6917
+ const pendingTasks = phase.tasks.filter((t) => t.status !== "done");
6918
+ const doneTasks = phase.tasks.filter((t) => t.status === "done");
6919
+ const lines = [
6920
+ `# Execute Phase: ${phase.name}`,
6921
+ `> Project: ${ctx.projectName} | Stack: ${stackLine}`,
6922
+ `> Progress: ${doneTasks.length}/${phase.tasks.length} tasks done`,
6923
+ ""
6924
+ ];
6925
+ if (ctx.phaseGoal) {
6926
+ lines.push("## Phase goal");
6927
+ lines.push(ctx.phaseGoal);
6928
+ lines.push("");
6929
+ }
6930
+ if (ctx.conventions) {
6931
+ lines.push("## Conventions");
6932
+ lines.push(`- Naming: ${ctx.conventions.namingStyle} | Modules: ${ctx.conventions.moduleStyle}`);
6933
+ if (ctx.conventions.typescriptStrict) lines.push("- TypeScript strict mode");
6934
+ if (ctx.conventions.entryPoint) lines.push(`- Entry point: \`${ctx.conventions.entryPoint}\``);
6935
+ lines.push("");
6936
+ }
6937
+ lines.push(`## Tasks to implement (${pendingTasks.length} remaining)`);
6938
+ lines.push("");
6939
+ for (let i = 0; i < pendingTasks.length; i++) {
6940
+ const t = pendingTasks[i];
6941
+ lines.push(`### Task ${i + 1}: ${t.title}`);
6942
+ if (t.description) lines.push(t.description);
6943
+ if (t.acceptanceCriteria?.length) {
6944
+ lines.push("");
6945
+ lines.push("Acceptance criteria:");
6946
+ for (const c of t.acceptanceCriteria) lines.push(`- [ ] ${c}`);
6947
+ }
6948
+ if (t.riskLevel && t.riskLevel !== "low") {
6949
+ lines.push("");
6950
+ lines.push(`> Risk: ${t.riskLevel} \u2014 implement carefully, test thoroughly.`);
6951
+ }
6952
+ lines.push("");
6953
+ }
6954
+ lines.push("## Instructions for your AI tool");
6955
+ lines.push("1. Implement each task in order unless marked as parallelizable.");
6956
+ lines.push("2. After each task, verify its acceptance criteria before proceeding.");
6957
+ lines.push("3. Run tests after every change: failing tests are a hard stop.");
6958
+ lines.push("4. Report completion with: files changed, tests passing, any blockers.");
6959
+ lines.push("");
6960
+ lines.push("---");
6961
+ lines.push("*Generated by BrainForge AI \u2014 paste into your AI tool to execute the phase.*");
6962
+ return lines.join("\n");
6963
+ }
6964
+ buildDryRunSummary(phase) {
6965
+ const pending = phase.tasks.filter((t) => t.status !== "done");
6966
+ const lines = [
6967
+ `[DRY RUN] Phase: ${phase.name}`,
6968
+ `Tasks to execute: ${pending.length}`,
6969
+ ""
6970
+ ];
6971
+ for (const t of pending) {
6972
+ const risk = t.riskLevel ? ` [${t.riskLevel}]` : "";
6973
+ lines.push(` \u2022 ${t.title}${risk}`);
6974
+ }
6975
+ return lines.join("\n");
6976
+ }
6977
+ };
6978
+
6979
+ // src/workflow/verification-engine.ts
6980
+ var VerificationEngine = class {
6981
+ buildVerifyPrompt(phase, stack) {
6982
+ const tasksWithCriteria = phase.tasks.filter((t) => t.acceptanceCriteria?.length);
6983
+ const allCriteria = [];
6984
+ for (const t of phase.tasks) {
6985
+ for (const c of t.acceptanceCriteria ?? []) {
6986
+ allCriteria.push({ task: t, criterion: c });
6987
+ }
6988
+ }
6989
+ const lines = [
6990
+ `# Verify Phase: ${phase.name}`,
6991
+ "",
6992
+ "## Objective",
6993
+ "Check that every task in this phase is correctly implemented and all acceptance criteria pass.",
6994
+ "",
6995
+ "## Checklist",
6996
+ ""
6997
+ ];
6998
+ if (allCriteria.length > 0) {
6999
+ for (const { task, criterion } of allCriteria) {
7000
+ lines.push(`- [ ] **[${task.title}]** ${criterion}`);
7001
+ }
7002
+ } else {
7003
+ for (const t of phase.tasks) {
7004
+ lines.push(`- [ ] ${t.title} \u2014 implemented and working`);
7005
+ }
7006
+ }
7007
+ lines.push("");
7008
+ lines.push("## Verification commands to run");
7009
+ const cmds = phase.tasks.flatMap((t) => t.verificationCommands ?? []);
7010
+ if (cmds.length > 0) {
7011
+ for (const cmd of [...new Set(cmds)]) {
7012
+ lines.push(`\`\`\`bash
7013
+ ${cmd}
7014
+ \`\`\``);
7015
+ }
7016
+ } else {
7017
+ lines.push("_(No verification commands defined \u2014 run your test suite manually.)_");
7018
+ if (stack?.runtime === "node" || stack?.runtime === "bun") {
7019
+ lines.push("```bash\nnpm test\n```");
7020
+ }
7021
+ }
7022
+ lines.push("");
7023
+ lines.push("## Instructions for your AI tool");
7024
+ lines.push("1. Run all verification commands. Report pass/fail for each.");
7025
+ lines.push('2. For each checklist item, confirm it is truly done \u2014 not just "the code exists".');
7026
+ lines.push("3. List any bugs found using the format below:");
7027
+ lines.push("");
7028
+ lines.push("```");
7029
+ lines.push("BUG: [title]");
7030
+ lines.push("Severity: critical | major | minor");
7031
+ lines.push("Task: [task name]");
7032
+ lines.push("Description: [what is wrong]");
7033
+ lines.push("```");
7034
+ lines.push("");
7035
+ lines.push("4. If there are **critical** bugs, do NOT mark the phase as verified.");
7036
+ lines.push('5. If all criteria pass, confirm with: "PHASE VERIFIED \u2713"');
7037
+ lines.push("");
7038
+ lines.push("---");
7039
+ lines.push("*Generated by BrainForge AI \u2014 paste into your AI tool to verify the phase.*");
7040
+ return lines.join("\n");
7041
+ }
7042
+ buildVerifyMd(phase, bugs = []) {
7043
+ const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
7044
+ const criticalBugs = bugs.filter((b) => b.severity === "critical");
7045
+ const blocked = criticalBugs.length > 0;
7046
+ const lines = [
7047
+ `# Verification Report \u2014 ${phase.name}`,
7048
+ `> Generated: ${now} UTC`,
7049
+ `> Status: ${blocked ? "\u{1F534} BLOCKED" : bugs.length > 0 ? "\u{1F7E1} ISSUES FOUND" : "\u{1F7E2} VERIFIED"}`,
7050
+ "",
7051
+ "## Tasks"
7052
+ ];
7053
+ for (const t of phase.tasks) {
7054
+ const icon = t.status === "done" ? "\u2713" : "\u25CB";
7055
+ lines.push(`- [${icon}] ${t.title}`);
7056
+ }
7057
+ if (bugs.length > 0) {
7058
+ lines.push("", "## Bugs Found");
7059
+ for (const bug of bugs) {
7060
+ lines.push(`### ${bug.id}: ${bug.title}`);
7061
+ lines.push(`**Severity:** ${bug.severity}`);
7062
+ if (bug.taskId) lines.push(`**Task:** ${bug.taskId}`);
7063
+ if (bug.description) lines.push("", bug.description);
7064
+ lines.push("");
7065
+ }
7066
+ }
7067
+ if (!blocked) {
7068
+ lines.push("", "## Next step");
7069
+ lines.push("Run `brg ship` to close this phase and update project memory.");
7070
+ } else {
7071
+ lines.push("", "## Next step");
7072
+ lines.push("Fix critical bugs then re-run `brg verify` before shipping.");
7073
+ }
7074
+ return lines.join("\n");
7075
+ }
7076
+ buildBugsMd(bugs) {
7077
+ if (bugs.length === 0) return "# Bugs\n\nNo bugs found in this phase.";
7078
+ const lines = ["# Bug Report", ""];
7079
+ for (const bug of bugs) {
7080
+ lines.push(`## [${bug.severity.toUpperCase()}] ${bug.title}`);
7081
+ if (bug.taskId) lines.push(`**Related task:** ${bug.taskId}`);
7082
+ if (bug.description) lines.push("", bug.description);
7083
+ lines.push("");
7084
+ }
7085
+ return lines.join("\n");
7086
+ }
7087
+ };
7088
+
7089
+ // src/workflow/ship-engine.ts
7090
+ var ShipEngine = class {
7091
+ build(phase, verifyPassed) {
7092
+ const shippedAt = (/* @__PURE__ */ new Date()).toISOString();
7093
+ const tasksCompleted = phase.tasks.filter((t) => t.status === "done").length;
7094
+ return {
7095
+ phaseId: phase.id,
7096
+ phaseName: phase.name,
7097
+ shippedAt,
7098
+ tasksCompleted,
7099
+ memorySummary: this.buildMemorySummary(phase, shippedAt),
7100
+ changelogEntry: this.buildChangelogEntry(phase, shippedAt)
7101
+ };
7102
+ }
7103
+ buildShipMd(phase, result) {
7104
+ return [
7105
+ `# Ship Report \u2014 ${phase.name}`,
7106
+ `> Shipped: ${result.shippedAt.slice(0, 16).replace("T", " ")} UTC`,
7107
+ "",
7108
+ `## Summary`,
7109
+ `- **Tasks completed:** ${result.tasksCompleted} / ${phase.tasks.length}`,
7110
+ `- **Phase status:** shipped \u2713`,
7111
+ "",
7112
+ "## Completed tasks",
7113
+ ...phase.tasks.map((t) => `- \u2713 ${t.title}`),
7114
+ "",
7115
+ "## Memory entry",
7116
+ "```",
7117
+ result.memorySummary,
7118
+ "```",
7119
+ "",
7120
+ "## Changelog entry",
7121
+ "```markdown",
7122
+ result.changelogEntry,
7123
+ "```",
7124
+ "",
7125
+ "## Next step",
7126
+ "Run `brg progress` to see the overall project status."
7127
+ ].join("\n");
7128
+ }
7129
+ buildMemorySummary(phase, shippedAt) {
7130
+ const taskList = phase.tasks.map((t) => ` - ${t.title}`).join("\n");
7131
+ return [
7132
+ `Phase "${phase.name}" shipped on ${shippedAt.slice(0, 10)}.`,
7133
+ "",
7134
+ `Completed ${phase.tasks.length} tasks:`,
7135
+ taskList
7136
+ ].join("\n");
7137
+ }
7138
+ buildChangelogEntry(phase, shippedAt) {
7139
+ const date = shippedAt.slice(0, 10);
7140
+ const lines = [`## [${date}] \u2014 ${phase.name}`, ""];
7141
+ for (const t of phase.tasks) {
7142
+ lines.push(`- ${t.title}`);
7143
+ }
7144
+ return lines.join("\n");
7145
+ }
7146
+ };
7147
+
7148
+ // src/workflow/debug-engine.ts
7149
+ var DebugEngine = class {
7150
+ build(ctx, phase, stack) {
7151
+ const bugId = `bug-${Date.now().toString(36)}`;
7152
+ const title = ctx.description.slice(0, 80);
7153
+ const hypotheses = this.generateHypotheses(ctx, phase, stack);
7154
+ const filesToInspect = this.inferRelevantFiles(ctx, phase);
7155
+ const diagnosticCommands = this.buildDiagnosticCommands(stack);
7156
+ return {
7157
+ bugId,
7158
+ title,
7159
+ hypotheses,
7160
+ filesToInspect,
7161
+ diagnosticCommands,
7162
+ prompt: this.buildDebugPrompt(bugId, ctx, hypotheses, filesToInspect, diagnosticCommands, stack),
7163
+ fixPlanPrompt: this.buildFixPlanPrompt(bugId, ctx, phase)
7164
+ };
7165
+ }
7166
+ buildDebugsMd(plans) {
7167
+ if (plans.length === 0) return "# Bugs\n\nNo bugs reported.";
7168
+ const lines = ["# Bug Report", ""];
7169
+ for (const plan of plans) {
7170
+ lines.push(`## ${plan.bugId}: ${plan.title}`);
7171
+ lines.push("");
7172
+ lines.push("**Status:** open");
7173
+ lines.push("");
7174
+ if (plan.hypotheses.length > 0) {
7175
+ lines.push("**Hypotheses:**");
7176
+ for (const h of plan.hypotheses) lines.push(`- ${h}`);
7177
+ lines.push("");
7178
+ }
7179
+ if (plan.filesToInspect.length > 0) {
7180
+ lines.push("**Files to inspect:**");
7181
+ for (const f of plan.filesToInspect) lines.push(`- \`${f}\``);
7182
+ lines.push("");
7183
+ }
7184
+ }
7185
+ return lines.join("\n");
7186
+ }
7187
+ buildDebugPrompt(bugId, ctx, hypotheses, files, cmds, stack) {
7188
+ const lines = [
7189
+ `# Debug Session \u2014 ${bugId}`,
7190
+ "",
7191
+ "## Bug description",
7192
+ ctx.description,
7193
+ ""
7194
+ ];
7195
+ if (ctx.errorMessage) {
7196
+ lines.push("## Error message");
7197
+ lines.push("```");
7198
+ lines.push(ctx.errorMessage);
7199
+ lines.push("```");
7200
+ lines.push("");
7201
+ }
7202
+ if (ctx.reproSteps) {
7203
+ lines.push("## Steps to reproduce");
7204
+ lines.push(ctx.reproSteps);
7205
+ lines.push("");
7206
+ }
7207
+ if (ctx.expectedBehavior || ctx.actualBehavior) {
7208
+ lines.push("## Expected vs actual");
7209
+ if (ctx.expectedBehavior) lines.push(`**Expected:** ${ctx.expectedBehavior}`);
7210
+ if (ctx.actualBehavior) lines.push(`**Actual:** ${ctx.actualBehavior}`);
7211
+ lines.push("");
7212
+ }
7213
+ lines.push("## Scientific debugging method");
7214
+ lines.push("");
7215
+ lines.push("Follow these steps in order:");
7216
+ lines.push("");
7217
+ lines.push("**1. Reproduce** \u2014 confirm the bug exists with the steps above");
7218
+ lines.push("**2. Isolate** \u2014 narrow down which code path triggers it");
7219
+ lines.push("**3. Hypothesize** \u2014 form a specific theory about root cause");
7220
+ lines.push("**4. Test** \u2014 write a failing test or add logging to confirm/refute");
7221
+ lines.push("**5. Fix** \u2014 apply the minimal change that fixes the root cause");
7222
+ lines.push("**6. Verify** \u2014 confirm the bug is gone and no regressions introduced");
7223
+ lines.push("");
7224
+ if (hypotheses.length > 0) {
7225
+ lines.push("## Likely root causes (check in order)");
7226
+ for (let i = 0; i < hypotheses.length; i++) {
7227
+ lines.push(`${i + 1}. ${hypotheses[i]}`);
7228
+ }
7229
+ lines.push("");
7230
+ }
7231
+ if (files.length > 0) {
7232
+ lines.push("## Files most likely to be involved");
7233
+ for (const f of files) lines.push(`- \`${f}\``);
7234
+ lines.push("");
7235
+ }
7236
+ if (cmds.length > 0) {
7237
+ lines.push("## Diagnostic commands to run");
7238
+ for (const cmd of cmds) lines.push(`\`\`\`bash
7239
+ ${cmd}
7240
+ \`\`\``);
7241
+ lines.push("");
7242
+ }
7243
+ lines.push("## Output format");
7244
+ lines.push("");
7245
+ lines.push("Report your findings as:");
7246
+ lines.push("```");
7247
+ lines.push("ROOT CAUSE: [one clear sentence]");
7248
+ lines.push("FILE: [path/to/file.ts]");
7249
+ lines.push("LINE: [approximate line number or function name]");
7250
+ lines.push("FIX: [description of minimal fix]");
7251
+ lines.push("REGRESSION TEST: [what test would catch this in the future]");
7252
+ lines.push("```");
7253
+ lines.push("");
7254
+ lines.push("---");
7255
+ lines.push("*BrainForge AI debug session \u2014 paste into your AI tool.*");
7256
+ return lines.join("\n");
7257
+ }
7258
+ buildFixPlanPrompt(bugId, ctx, phase) {
7259
+ return [
7260
+ `# Fix Plan \u2014 ${bugId}`,
7261
+ "",
7262
+ `## Bug`,
7263
+ ctx.description,
7264
+ "",
7265
+ "## Instructions",
7266
+ "",
7267
+ "1. Apply the minimal fix identified in the debug session.",
7268
+ "2. Do NOT refactor surrounding code \u2014 only fix the root cause.",
7269
+ "3. Add a regression test that would have caught this bug.",
7270
+ "4. Run the full test suite after the fix.",
7271
+ "5. Report: file changed, lines modified, test added, suite status.",
7272
+ "",
7273
+ phase ? `## Context: Phase "${phase.name}"` : "",
7274
+ phase ? `Active tasks: ${phase.tasks.filter((t) => t.status !== "done").map((t) => t.title).join(", ")}` : "",
7275
+ "",
7276
+ "---",
7277
+ "*BrainForge AI fix plan \u2014 paste into your AI tool.*"
7278
+ ].filter((l) => l !== null).join("\n");
7279
+ }
7280
+ generateHypotheses(ctx, phase, stack) {
7281
+ const text = (ctx.description + " " + (ctx.errorMessage ?? "")).toLowerCase();
7282
+ const hypotheses = [];
7283
+ if (/undefined|null|cannot read|typeerror/i.test(text)) {
7284
+ hypotheses.push("Null/undefined value not handled \u2014 check for missing guard clauses");
7285
+ }
7286
+ if (/404|not found/i.test(text)) {
7287
+ hypotheses.push("Route or resource not found \u2014 check URL pattern and handler registration");
7288
+ }
7289
+ if (/403|401|unauthorized|forbidden/i.test(text)) {
7290
+ hypotheses.push("Auth/permissions issue \u2014 check token validation or role check logic");
7291
+ }
7292
+ if (/500|internal server/i.test(text)) {
7293
+ hypotheses.push("Unhandled exception on server \u2014 check error middleware and async try/catch");
7294
+ }
7295
+ if (/cors/i.test(text)) {
7296
+ hypotheses.push("CORS policy mismatch \u2014 check allowed origins and preflight handling");
7297
+ }
7298
+ if (/import|module|cannot find/i.test(text)) {
7299
+ hypotheses.push("Import path wrong or module missing \u2014 check tsconfig paths and package.json exports");
7300
+ }
7301
+ if (/async|await|promise|timeout/i.test(text)) {
7302
+ hypotheses.push("Async/await issue \u2014 check for missing await, unhandled promise rejection, or race condition");
7303
+ }
7304
+ if (/database|query|sql|orm/i.test(text)) {
7305
+ hypotheses.push("Database query issue \u2014 check query parameters, missing JOIN, or stale migration");
7306
+ }
7307
+ if (/type|interface|property does not exist/i.test(text)) {
7308
+ hypotheses.push("TypeScript type mismatch \u2014 check interface definition matches actual data shape");
7309
+ }
7310
+ if (hypotheses.length === 0) {
7311
+ hypotheses.push("Check the stack trace for the exact call site");
7312
+ hypotheses.push("Add logging before the failure point to capture state");
7313
+ }
7314
+ return hypotheses.slice(0, 4);
7315
+ }
7316
+ inferRelevantFiles(ctx, phase) {
7317
+ const files = /* @__PURE__ */ new Set();
7318
+ if (phase) {
7319
+ for (const task of phase.tasks) {
7320
+ if (task.files) task.files.forEach((f) => files.add(f));
7321
+ }
7322
+ }
7323
+ const text = ctx.description.toLowerCase();
7324
+ if (/auth|login|session|token/.test(text)) files.add("src/middleware/auth.*");
7325
+ if (/api|route|endpoint/.test(text)) files.add("src/routes/**");
7326
+ if (/database|model|schema/.test(text)) files.add("src/models/**");
7327
+ if (/component|ui|render/.test(text)) files.add("src/components/**");
7328
+ return [...files].slice(0, 6);
7329
+ }
7330
+ buildDiagnosticCommands(stack) {
7331
+ const cmds = [];
7332
+ if (!stack) return ["npm test", "npm run build"];
7333
+ if (stack.runtime === "node" || stack.runtime === "bun") {
7334
+ cmds.push("npm test -- --reporter=verbose");
7335
+ cmds.push("npm run build 2>&1 | head -50");
7336
+ }
7337
+ if (stack.runtime === "python") {
7338
+ cmds.push("python -m pytest -x -v 2>&1 | head -50");
7339
+ }
7340
+ if (stack.runtime === "go") {
7341
+ cmds.push("go test ./... -v 2>&1 | head -50");
7342
+ }
7343
+ return cmds.length > 0 ? cmds : ["npm test"];
7344
+ }
7345
+ };
7346
+
7347
+ // src/workflow/auto-engine.ts
7348
+ var AutoEngine = class {
7349
+ buildPlan(state, opts) {
7350
+ const blockers = [];
7351
+ if (state.phases.length === 0) {
7352
+ return {
7353
+ steps: [],
7354
+ totalPhases: 0,
7355
+ phasesToRun: 0,
7356
+ canRun: false,
7357
+ blockers: ["No phases defined \u2014 run `brg new` or `brg phase create` first"],
7358
+ estimatedPrompts: 0
7359
+ };
7360
+ }
7361
+ let phases = state.phases.filter((p) => p.status !== "completed");
7362
+ if (opts.fromPhaseId) {
7363
+ const fromIdx = phases.findIndex((p) => p.id === opts.fromPhaseId);
7364
+ if (fromIdx === -1) blockers.push(`Phase "${opts.fromPhaseId}" not found`);
7365
+ else phases = phases.slice(fromIdx);
7366
+ }
7367
+ if (opts.toPhaseId) {
7368
+ const toIdx = phases.findIndex((p) => p.id === opts.toPhaseId);
7369
+ if (toIdx === -1) blockers.push(`Phase "${opts.toPhaseId}" not found`);
7370
+ else phases = phases.slice(0, toIdx + 1);
7371
+ }
7372
+ const steps = [];
7373
+ let stepNum = 1;
7374
+ steps.push({
7375
+ step: stepNum++,
7376
+ phaseId: "",
7377
+ phaseName: "(global)",
7378
+ action: "map",
7379
+ description: "Scan codebase and generate architecture map",
7380
+ blocked: false
7381
+ });
7382
+ for (const phase of phases) {
7383
+ const pendingTasks = phase.tasks.filter((t) => t.status !== "done");
7384
+ const hasTasks = phase.tasks.length > 0;
7385
+ steps.push({
7386
+ step: stepNum++,
7387
+ phaseId: phase.id,
7388
+ phaseName: phase.name,
7389
+ action: "execute",
7390
+ description: `Execute ${pendingTasks.length} task(s) in "${phase.name}"`,
7391
+ blocked: !hasTasks,
7392
+ blockReason: !hasTasks ? "No tasks defined \u2014 run `brg plan` first" : void 0
7393
+ });
7394
+ if (opts.gates.verifyEachPhase) {
7395
+ steps.push({
7396
+ step: stepNum++,
7397
+ phaseId: phase.id,
7398
+ phaseName: phase.name,
7399
+ action: "verify",
7400
+ description: `Verify "${phase.name}" \u2014 check acceptance criteria`,
7401
+ blocked: false
7402
+ });
7403
+ }
7404
+ if (opts.gates.shipEachPhase) {
7405
+ steps.push({
7406
+ step: stepNum++,
7407
+ phaseId: phase.id,
7408
+ phaseName: phase.name,
7409
+ action: "ship",
7410
+ description: `Ship "${phase.name}" \u2014 close phase and update memory`,
7411
+ blocked: false
7412
+ });
7413
+ }
7414
+ }
7415
+ const canRun = blockers.length === 0 && steps.some((s) => !s.blocked);
7416
+ const estimatedPrompts = steps.filter((s) => s.action === "execute").length;
7417
+ return {
7418
+ steps,
7419
+ totalPhases: state.phases.length,
7420
+ phasesToRun: phases.length,
7421
+ canRun,
7422
+ blockers,
7423
+ estimatedPrompts
7424
+ };
7425
+ }
7426
+ buildDryRunOutput(plan) {
7427
+ const lines = [
7428
+ "# Auto Pipeline \u2014 Dry Run",
7429
+ "",
7430
+ `Phases to run: ${plan.phasesToRun} | Steps: ${plan.steps.length} | Execution prompts: ${plan.estimatedPrompts}`,
7431
+ ""
7432
+ ];
7433
+ if (!plan.canRun) {
7434
+ lines.push("## Blockers (fix before running)");
7435
+ for (const b of plan.blockers) lines.push(` \u2717 ${b}`);
7436
+ lines.push("");
7437
+ }
7438
+ lines.push("## Steps");
7439
+ lines.push("");
7440
+ for (const step of plan.steps) {
7441
+ const status = step.blocked ? "\u26A0 BLOCKED" : "\u25CB";
7442
+ const phaseLabel = step.phaseId ? ` [${step.phaseName}]` : "";
7443
+ lines.push(` ${step.step}. ${status} ${step.action.toUpperCase()}${phaseLabel}`);
7444
+ lines.push(` ${step.description}`);
7445
+ if (step.blockReason) lines.push(` \u21B3 ${step.blockReason}`);
7446
+ }
7447
+ lines.push("");
7448
+ lines.push("Remove --dry-run to execute the pipeline.");
7449
+ return lines.join("\n");
7450
+ }
7451
+ buildAutoSummaryPrompt(plan, state) {
7452
+ const executeSteps = plan.steps.filter((s) => s.action === "execute" && !s.blocked);
7453
+ const lines = [
7454
+ "# Auto Pipeline \u2014 Full Execution",
7455
+ "",
7456
+ `This will execute ${executeSteps.length} phase(s) sequentially.`,
7457
+ "",
7458
+ "## Execution order",
7459
+ ""
7460
+ ];
7461
+ let phaseNum = 1;
7462
+ for (const phase of state.phases.filter((p) => p.status !== "completed")) {
7463
+ const tasks = phase.tasks.filter((t) => t.status !== "done");
7464
+ lines.push(`### Phase ${phaseNum++}: ${phase.name}`);
7465
+ lines.push(`${tasks.length} task(s) to execute:`);
7466
+ for (const t of tasks) lines.push(`- ${t.title}`);
7467
+ lines.push("");
7468
+ }
7469
+ lines.push("## Safety gates");
7470
+ lines.push("- Stop on any critical bug in verify step");
7471
+ lines.push("- Do not ship a phase that fails verification");
7472
+ lines.push("- Report all bugs in BUGS.md before proceeding");
7473
+ lines.push("");
7474
+ lines.push("---");
7475
+ lines.push("*BrainForge AI auto pipeline \u2014 execute phases in order.*");
7476
+ return lines.join("\n");
7477
+ }
7478
+ };
7479
+
7480
+ // src/scanner/codebase-map.ts
7481
+ import fs26 from "fs/promises";
7482
+ import path29 from "path";
7483
+
7484
+ // src/scanner/stack-detector.ts
7485
+ import fs22 from "fs/promises";
7486
+ import path24 from "path";
7487
+ var FRAMEWORK_MARKERS = {
7488
+ "next": ["next"],
7489
+ "nuxt": ["nuxt"],
7490
+ "remix": ["@remix-run/react", "@remix-run/node"],
7491
+ "sveltekit": ["@sveltejs/kit"],
7492
+ "react": ["react"],
7493
+ "vue": ["vue"],
7494
+ "svelte": ["svelte"],
7495
+ "angular": ["@angular/core"],
7496
+ "solid": ["solid-js"],
7497
+ "astro": ["astro"],
7498
+ "express": ["express"],
7499
+ "fastify": ["fastify"],
7500
+ "hono": ["hono"],
7501
+ "nestjs": ["@nestjs/core"],
7502
+ "koa": ["koa"],
7503
+ "elysia": ["elysia"],
7504
+ "django": ["django"],
7505
+ "fastapi": ["fastapi"],
7506
+ "flask": ["flask"],
7507
+ "gin": ["gin-gonic/gin"],
7508
+ "fiber": ["gofiber/fiber"],
7509
+ "actix-web": ["actix-web"],
7510
+ "axum": ["axum"],
7511
+ "laravel": ["laravel/framework"],
7512
+ "spring": ["spring-boot"]
7513
+ };
7514
+ var BUILD_TOOL_FILES = [
7515
+ ["vite.config.ts", "vite"],
7516
+ ["vite.config.js", "vite"],
7517
+ ["webpack.config.js", "webpack"],
7518
+ ["webpack.config.ts", "webpack"],
7519
+ ["rollup.config.js", "rollup"],
7520
+ ["rollup.config.ts", "rollup"],
7521
+ ["esbuild.config.js", "esbuild"],
7522
+ ["tsup.config.ts", "tsup"],
7523
+ ["tsup.config.js", "tsup"],
7524
+ ["turbo.json", "turborepo"],
7525
+ ["nx.json", "nx"]
7526
+ ];
7527
+ var LINTER_MARKERS = {
7528
+ eslint: ["eslint", "@eslint/js"],
7529
+ biome: ["@biomejs/biome"],
7530
+ oxc: ["oxc-parser"],
7531
+ tslint: ["tslint"],
7532
+ ruff: ["ruff"],
7533
+ flake8: ["flake8"]
7534
+ };
7535
+ var FORMATTER_MARKERS = {
7536
+ prettier: ["prettier"],
7537
+ biome: ["@biomejs/biome"],
7538
+ black: ["black"]
7539
+ };
7540
+ var CSS_MARKERS = {
7541
+ tailwindcss: ["tailwindcss"],
7542
+ bootstrap: ["bootstrap"],
7543
+ "mui": ["@mui/material"],
7544
+ "chakra-ui": ["@chakra-ui/react"],
7545
+ "shadcn/ui": ["class-variance-authority", "clsx", "tailwind-merge"],
7546
+ "styled-components": ["styled-components"]
7547
+ };
7548
+ async function detectStack(cwd) {
7549
+ const allDeps = {};
7550
+ let hasTypeScript = false;
7551
+ let hasPython = false;
7552
+ let hasGo = false;
7553
+ let hasRust = false;
7554
+ let hasJava = false;
7555
+ let hasPHP = false;
7556
+ let hasRuby = false;
7557
+ let runtime;
7558
+ try {
7559
+ const raw = await fs22.readFile(path24.join(cwd, "package.json"), "utf-8");
7560
+ const pkg = JSON.parse(raw);
7561
+ Object.assign(allDeps, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
7562
+ if (pkg.engines?.["bun"]) runtime = "bun";
7563
+ else if (pkg.engines?.["deno"]) runtime = "deno";
7564
+ } catch {
7565
+ }
7566
+ const [hasTs, hasPy, hasGoMod, hasCargo, hasPom, hasComposer, hasGemfile] = await Promise.all([
7567
+ fileExists4(cwd, "tsconfig.json"),
7568
+ fileExists4(cwd, "pyproject.toml") || fileExists4(cwd, "requirements.txt") || fileExists4(cwd, "setup.py"),
7569
+ fileExists4(cwd, "go.mod"),
7570
+ fileExists4(cwd, "Cargo.toml"),
7571
+ fileExists4(cwd, "pom.xml"),
7572
+ fileExists4(cwd, "composer.json"),
7573
+ fileExists4(cwd, "Gemfile")
7574
+ ]);
7575
+ if (hasTs || "typescript" in allDeps || "@types/node" in allDeps) hasTypeScript = true;
7576
+ if (hasPy) hasPython = true;
7577
+ if (hasGoMod) hasGo = true;
7578
+ if (hasCargo) hasRust = true;
7579
+ if (hasPom) hasJava = true;
7580
+ if (hasComposer) hasPHP = true;
7581
+ if (hasGemfile) hasRuby = true;
7582
+ let language = "javascript";
7583
+ if (hasTypeScript) language = "typescript";
7584
+ else if (hasPython) language = "python";
7585
+ else if (hasGo) language = "go";
7586
+ else if (hasRust) language = "rust";
7587
+ else if (hasJava) language = "java";
7588
+ else if (hasPHP) language = "php";
7589
+ else if (hasRuby) language = "ruby";
7590
+ const secondaryLanguages = [];
7591
+ if (language !== "typescript" && hasTypeScript) secondaryLanguages.push("typescript");
7592
+ if (language !== "python" && hasPython) secondaryLanguages.push("python");
7593
+ let framework;
7594
+ for (const [name, markers] of Object.entries(FRAMEWORK_MARKERS)) {
7595
+ if (markers.some((m) => m in allDeps)) {
7596
+ framework = name;
7597
+ break;
7598
+ }
7599
+ }
7600
+ if (!runtime) {
7601
+ const [isBun, isDeno] = await Promise.all([
7602
+ fileExists4(cwd, "bun.lockb") || fileExists4(cwd, "bun.lock"),
7603
+ fileExists4(cwd, "deno.json") || fileExists4(cwd, "deno.jsonc")
7604
+ ]);
7605
+ if (isBun) runtime = "bun";
7606
+ else if (isDeno) runtime = "deno";
7607
+ else if (Object.keys(allDeps).length > 0 || hasTypeScript) runtime = "node";
7608
+ else if (hasPython) runtime = "python";
7609
+ else if (hasGo) runtime = "go";
7610
+ else if (hasRust) runtime = "rust";
7611
+ else if (hasJava) runtime = "jvm";
7612
+ }
7613
+ let buildTool;
7614
+ for (const [file, tool] of BUILD_TOOL_FILES) {
7615
+ if (await fileExists4(cwd, file)) {
7616
+ buildTool = tool;
7617
+ break;
7618
+ }
7619
+ }
7620
+ if (!buildTool) {
7621
+ for (const tool of ["tsup", "tsc", "vite", "webpack", "rollup", "esbuild"]) {
7622
+ if (tool in allDeps) {
7623
+ buildTool = tool;
7624
+ break;
7625
+ }
7626
+ }
7627
+ }
7628
+ let linter;
7629
+ for (const [name, markers] of Object.entries(LINTER_MARKERS)) {
7630
+ if (markers.some((m) => m in allDeps)) {
7631
+ linter = name;
7632
+ break;
7633
+ }
7634
+ }
7635
+ let formatter;
7636
+ for (const [name, markers] of Object.entries(FORMATTER_MARKERS)) {
7637
+ if (name !== linter && markers.some((m) => m in allDeps)) {
7638
+ formatter = name;
7639
+ break;
7640
+ }
7641
+ }
7642
+ if (linter === "biome") formatter = "biome";
7643
+ let cssFramework;
7644
+ for (const [name, markers] of Object.entries(CSS_MARKERS)) {
7645
+ if (markers.every((m) => m in allDeps)) {
7646
+ cssFramework = name;
7647
+ break;
7648
+ }
7649
+ if (markers.some((m) => m in allDeps)) {
7650
+ cssFramework = name;
7651
+ break;
7652
+ }
7653
+ }
7654
+ let packageManager;
7655
+ const [hasPnpmLock, hasYarnLock, hasBunLock] = await Promise.all([
7656
+ fileExists4(cwd, "pnpm-lock.yaml"),
7657
+ fileExists4(cwd, "yarn.lock"),
7658
+ fileExists4(cwd, "bun.lockb") || fileExists4(cwd, "bun.lock")
7659
+ ]);
7660
+ if (hasPnpmLock) packageManager = "pnpm";
7661
+ else if (hasYarnLock) packageManager = "yarn";
7662
+ else if (hasBunLock) packageManager = "bun";
7663
+ else if (await fileExists4(cwd, "package-lock.json")) packageManager = "npm";
7664
+ return {
7665
+ language,
7666
+ secondaryLanguages,
7667
+ framework,
7668
+ runtime,
7669
+ buildTool,
7670
+ linter,
7671
+ formatter,
7672
+ packageManager,
7673
+ cssFramework
7674
+ };
7675
+ }
7676
+ async function fileExists4(cwd, ...parts) {
7677
+ try {
7678
+ await fs22.access(path24.join(cwd, ...parts));
7679
+ return true;
7680
+ } catch {
7681
+ return false;
7682
+ }
7683
+ }
7684
+
7685
+ // src/scanner/dependency-analyzer.ts
7686
+ import fs23 from "fs/promises";
7687
+ import path25 from "path";
7688
+ async function analyzeDependencies(cwd) {
7689
+ const handlers = [
7690
+ () => parseNodePackage(cwd),
7691
+ () => parsePythonRequirements(cwd),
7692
+ () => parseGoMod(cwd),
7693
+ () => parseCargoToml(cwd),
7694
+ () => parseComposerJson(cwd)
7695
+ ];
7696
+ for (const handler of handlers) {
7697
+ const result = await handler();
7698
+ if (result) return result;
7699
+ }
7700
+ return { runtime: [], dev: [], total: 0, manifestType: "unknown", highlights: [] };
7701
+ }
7702
+ async function parseNodePackage(cwd) {
7703
+ try {
7704
+ const raw = await fs23.readFile(path25.join(cwd, "package.json"), "utf-8");
7705
+ const pkg = JSON.parse(raw);
7706
+ const runtime = Object.entries(pkg.dependencies ?? {}).map(([name, version]) => ({
7707
+ name,
7708
+ version,
7709
+ dev: false
7710
+ }));
7711
+ const dev = Object.entries(pkg.devDependencies ?? {}).map(([name, version]) => ({
7712
+ name,
7713
+ version,
7714
+ dev: true
7715
+ }));
7716
+ return {
7717
+ runtime,
7718
+ dev,
7719
+ total: runtime.length + dev.length,
7720
+ manifestType: "package.json",
7721
+ highlights: pickHighlights([...runtime, ...dev])
7722
+ };
7723
+ } catch {
7724
+ return null;
7725
+ }
7726
+ }
7727
+ async function parsePythonRequirements(cwd) {
7728
+ try {
7729
+ const raw = await fs23.readFile(path25.join(cwd, "pyproject.toml"), "utf-8");
7730
+ const deps = [];
7731
+ const depSection = raw.match(/\[project\][\s\S]*?dependencies\s*=\s*\[([\s\S]*?)\]/);
7732
+ if (depSection?.[1]) {
7733
+ for (const line of depSection[1].split("\n")) {
7734
+ const clean = line.replace(/["',]/g, "").trim();
7735
+ if (clean && !clean.startsWith("#")) {
7736
+ const m = clean.match(/^([a-zA-Z0-9_.-]+)(.*)$/);
7737
+ if (m) deps.push({ name: m[1], version: m[2].trim() || "*", dev: false });
7738
+ }
7739
+ }
7740
+ }
7741
+ if (deps.length > 0) {
7742
+ return { runtime: deps, dev: [], total: deps.length, manifestType: "pyproject.toml", highlights: pickHighlights(deps) };
7743
+ }
7744
+ } catch {
7745
+ }
7746
+ try {
7747
+ const raw = await fs23.readFile(path25.join(cwd, "requirements.txt"), "utf-8");
7748
+ const deps = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && !l.startsWith("-")).map((l) => {
7749
+ const m = l.match(/^([a-zA-Z0-9_.-]+)([>=<!].+)?$/);
7750
+ return m ? { name: m[1], version: m[2] ?? "*", dev: false } : null;
7751
+ }).filter((d) => d !== null);
7752
+ if (deps.length > 0) {
7753
+ return { runtime: deps, dev: [], total: deps.length, manifestType: "requirements.txt", highlights: pickHighlights(deps) };
7754
+ }
7755
+ } catch {
7756
+ }
7757
+ return null;
7758
+ }
7759
+ async function parseGoMod(cwd) {
7760
+ try {
7761
+ const raw = await fs23.readFile(path25.join(cwd, "go.mod"), "utf-8");
7762
+ const deps = [];
7763
+ for (const line of raw.split("\n")) {
7764
+ const m = line.trim().match(/^([a-zA-Z0-9_./-]+)\s+(v[\d.]+.*)$/);
7765
+ if (m) deps.push({ name: m[1], version: m[2], dev: false });
7766
+ }
7767
+ return { runtime: deps, dev: [], total: deps.length, manifestType: "go.mod", highlights: pickHighlights(deps) };
7768
+ } catch {
7769
+ return null;
7770
+ }
7771
+ }
7772
+ async function parseCargoToml(cwd) {
7773
+ try {
7774
+ const raw = await fs23.readFile(path25.join(cwd, "Cargo.toml"), "utf-8");
7775
+ const deps = [];
7776
+ let inDeps = false;
7777
+ let inDevDeps = false;
7778
+ for (const line of raw.split("\n")) {
7779
+ if (line.trim() === "[dependencies]") {
7780
+ inDeps = true;
7781
+ inDevDeps = false;
7782
+ continue;
7783
+ }
7784
+ if (line.trim() === "[dev-dependencies]") {
7785
+ inDevDeps = true;
7786
+ inDeps = false;
7787
+ continue;
7788
+ }
7789
+ if (line.startsWith("[") && line.trim() !== "[dependencies]" && line.trim() !== "[dev-dependencies]") {
7790
+ inDeps = false;
7791
+ inDevDeps = false;
7792
+ }
7793
+ if ((inDeps || inDevDeps) && line.includes("=")) {
7794
+ const m = line.match(/^([a-zA-Z0-9_-]+)\s*=\s*"([^"]+)"/);
7795
+ if (m) deps.push({ name: m[1], version: m[2], dev: inDevDeps });
7796
+ }
7797
+ }
7798
+ return { runtime: deps.filter((d) => !d.dev), dev: deps.filter((d) => d.dev), total: deps.length, manifestType: "Cargo.toml", highlights: pickHighlights(deps) };
7799
+ } catch {
7800
+ return null;
7801
+ }
7802
+ }
7803
+ async function parseComposerJson(cwd) {
7804
+ try {
7805
+ const raw = await fs23.readFile(path25.join(cwd, "composer.json"), "utf-8");
7806
+ const pkg = JSON.parse(raw);
7807
+ const runtime = Object.entries(pkg.require ?? {}).filter(([name]) => name !== "php").map(([name, version]) => ({ name, version, dev: false }));
7808
+ const dev = Object.entries(pkg["require-dev"] ?? {}).map(([name, version]) => ({
7809
+ name,
7810
+ version,
7811
+ dev: true
7812
+ }));
7813
+ return { runtime, dev, total: runtime.length + dev.length, manifestType: "composer.json", highlights: pickHighlights([...runtime, ...dev]) };
7814
+ } catch {
7815
+ return null;
7816
+ }
7817
+ }
7818
+ var NOTABLE_DEPS = /* @__PURE__ */ new Set([
7819
+ "react",
7820
+ "vue",
7821
+ "next",
7822
+ "nuxt",
7823
+ "svelte",
7824
+ "astro",
7825
+ "angular",
7826
+ "express",
7827
+ "fastify",
7828
+ "hono",
7829
+ "nestjs",
7830
+ "koa",
7831
+ "prisma",
7832
+ "drizzle-orm",
7833
+ "typeorm",
7834
+ "mongoose",
7835
+ "typescript",
7836
+ "vite",
7837
+ "webpack",
7838
+ "esbuild",
7839
+ "tsup",
7840
+ "vitest",
7841
+ "jest",
7842
+ "mocha",
7843
+ "playwright",
7844
+ "cypress",
7845
+ "tailwindcss",
7846
+ "styled-components",
7847
+ "zod",
7848
+ "trpc",
7849
+ "@trpc/server",
7850
+ "fastapi",
7851
+ "django",
7852
+ "flask",
7853
+ "sqlalchemy",
7854
+ "gin",
7855
+ "fiber",
7856
+ "gorm",
7857
+ "axum",
7858
+ "actix-web",
7859
+ "tokio"
7860
+ ]);
7861
+ function pickHighlights(deps) {
7862
+ return deps.filter((d) => NOTABLE_DEPS.has(d.name.toLowerCase())).map((d) => `${d.name}@${d.version}`).slice(0, 8);
7863
+ }
7864
+
7865
+ // src/scanner/convention-detector.ts
7866
+ import fs24 from "fs/promises";
7867
+ import path26 from "path";
7868
+ async function detectConventions(cwd, files) {
7869
+ const srcFiles = files.filter((f) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
7870
+ const namingStyle = detectNamingStyle(srcFiles);
7871
+ const folderPattern = detectFolderPattern(files);
7872
+ const moduleStyle = await detectModuleStyle(cwd);
7873
+ const typescriptStrict = await detectTsStrict(cwd);
7874
+ const entryPoint = detectEntryPoint(files);
7875
+ const [hasEditorConfig, hasHusky, hasLintStaged] = await Promise.all([
7876
+ fileExists5(cwd, ".editorconfig"),
7877
+ fileExists5(cwd, ".husky"),
7878
+ fileExists5(cwd, ".lintstagedrc") || fileExists5(cwd, ".lintstagedrc.json") || fileExists5(cwd, ".lintstagedrc.js")
7879
+ ]);
7880
+ return {
7881
+ namingStyle,
7882
+ folderPattern,
7883
+ moduleStyle,
7884
+ typescriptStrict,
7885
+ hasEditorConfig,
7886
+ hasHusky,
7887
+ hasLintStaged,
7888
+ entryPoint
7889
+ };
7890
+ }
7891
+ function detectNamingStyle(files) {
7892
+ const basenames = files.map((f) => path26.basename(f, path26.extname(f))).filter((b) => b.length > 2 && !/^index$|^main$|^app$/.test(b));
7893
+ const counts = { camel: 0, snake: 0, kebab: 0, pascal: 0 };
7894
+ for (const b of basenames) {
7895
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(b)) counts.pascal++;
7896
+ else if (/^[a-z][a-zA-Z0-9]*$/.test(b) && /[A-Z]/.test(b)) counts.camel++;
7897
+ else if (b.includes("_")) counts.snake++;
7898
+ else if (b.includes("-")) counts.kebab++;
7899
+ }
7900
+ const max = Math.max(...Object.values(counts));
7901
+ if (max === 0) return "mixed";
7902
+ if (counts.pascal === max) return "PascalCase";
7903
+ if (counts.camel === max) return "camelCase";
7904
+ if (counts.snake === max) return "snake_case";
7905
+ if (counts.kebab === max) return "kebab-case";
7906
+ return "mixed";
7907
+ }
7908
+ function detectFolderPattern(files) {
7909
+ const topDirs = /* @__PURE__ */ new Set();
7910
+ for (const f of files) {
7911
+ const parts = f.replace(/\\/g, "/").split("/");
7912
+ if (parts.length > 1) topDirs.add(parts[0]);
7913
+ }
7914
+ const patterns = {
7915
+ "monorepo": ["packages", "apps", "libs"],
7916
+ "src-based": ["src"],
7917
+ "app-based": ["app"],
7918
+ "flat": []
7919
+ };
7920
+ for (const [name, markers] of Object.entries(patterns)) {
7921
+ if (markers.some((m) => topDirs.has(m))) return name;
7922
+ }
7923
+ return "flat";
7924
+ }
7925
+ async function detectModuleStyle(cwd) {
7926
+ try {
7927
+ const raw = await fs24.readFile(path26.join(cwd, "package.json"), "utf-8");
7928
+ const pkg = JSON.parse(raw);
7929
+ if (pkg.type === "module") return "esm";
7930
+ if (pkg.type === "commonjs") return "cjs";
7931
+ return "mixed";
7932
+ } catch {
7933
+ return "unknown";
7934
+ }
7935
+ }
7936
+ async function detectTsStrict(cwd) {
7937
+ try {
7938
+ const raw = await fs24.readFile(path26.join(cwd, "tsconfig.json"), "utf-8");
7939
+ const stripped = raw.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
7940
+ const tsconfig = JSON.parse(stripped);
7941
+ return tsconfig.compilerOptions?.strict === true;
7942
+ } catch {
7943
+ return false;
7944
+ }
7945
+ }
7946
+ function detectEntryPoint(files) {
7947
+ const candidates = [
7948
+ "src/index.ts",
7949
+ "src/index.js",
7950
+ "src/main.ts",
7951
+ "src/main.js",
7952
+ "src/app.ts",
7953
+ "src/app.js",
7954
+ "index.ts",
7955
+ "index.js",
7956
+ "main.ts",
7957
+ "main.js",
7958
+ "app.ts",
7959
+ "app.js",
7960
+ "src/App.tsx",
7961
+ "src/App.jsx"
7962
+ ];
7963
+ return candidates.find((c) => files.some((f) => f.replace(/\\/g, "/").endsWith(c)));
7964
+ }
7965
+ async function fileExists5(cwd, ...parts) {
7966
+ try {
7967
+ await fs24.access(path26.join(cwd, ...parts));
7968
+ return true;
7969
+ } catch {
7970
+ return false;
7971
+ }
7972
+ }
7973
+
7974
+ // src/scanner/risk-detector.ts
7975
+ import fs25 from "fs/promises";
7976
+ import path27 from "path";
7977
+ var LARGE_FILE_THRESHOLD = 500;
7978
+ var HUGE_FILE_THRESHOLD = 1e3;
7979
+ var SECRET_PATTERNS = [
7980
+ /\.env(\.|$)/i,
7981
+ /secrets?\.(json|yaml|yml|toml)$/i,
7982
+ /credentials?\.(json|yaml|yml)$/i,
7983
+ /private[\w-]*\.(pem|key|p12)$/i,
7984
+ /\.pfx$/i
7985
+ ];
7986
+ var INLINE_SECRET_PATTERNS = [
7987
+ /(?:api_key|apikey|secret|password|passwd|token)\s*[:=]\s*['"][^'"]{8,}/i,
7988
+ /(?:sk-|pk_live_|pk_test_|ghp_|ghs_|xoxb-|xoxp-)/
7989
+ ];
7990
+ var TODO_PATTERN = /\b(TODO|FIXME|HACK|XXX|TEMP|BUG)\b/g;
7991
+ async function detectRisks(cwd, files, fileContents) {
7992
+ const items = [];
7993
+ for (const [filePath, content] of fileContents) {
7994
+ const lineCount = content.split("\n").length;
7995
+ if (lineCount > HUGE_FILE_THRESHOLD) {
7996
+ items.push({
7997
+ level: "high",
7998
+ category: "maintainability",
7999
+ message: `File has ${lineCount} lines (>${HUGE_FILE_THRESHOLD} threshold)`,
8000
+ file: filePath
8001
+ });
8002
+ } else if (lineCount > LARGE_FILE_THRESHOLD) {
8003
+ items.push({
8004
+ level: "medium",
8005
+ category: "maintainability",
8006
+ message: `File has ${lineCount} lines (>${LARGE_FILE_THRESHOLD} threshold)`,
8007
+ file: filePath
8008
+ });
8009
+ }
8010
+ }
8011
+ for (const f of files) {
8012
+ const basename = path27.basename(f);
8013
+ if (SECRET_PATTERNS.some((p) => p.test(basename)) && !basename.endsWith(".example")) {
8014
+ items.push({
8015
+ level: "high",
8016
+ category: "security",
8017
+ message: `Potential secrets file committed: ${basename}`,
8018
+ file: f
8019
+ });
8020
+ }
8021
+ }
8022
+ const sourceFiles = [...fileContents.entries()].filter(
8023
+ ([f]) => /\.(ts|tsx|js|jsx|py|go|rs|php|rb)$/.test(f)
8024
+ );
8025
+ for (const [filePath, content] of sourceFiles) {
8026
+ if (content.split("\n").length > 300) continue;
8027
+ for (const pattern of INLINE_SECRET_PATTERNS) {
8028
+ if (pattern.test(content)) {
8029
+ items.push({
8030
+ level: "high",
8031
+ category: "security",
8032
+ message: "Potential hardcoded secret or API key",
8033
+ file: filePath
8034
+ });
8035
+ break;
8036
+ }
8037
+ }
8038
+ }
8039
+ let todoCount = 0;
8040
+ for (const [, content] of fileContents) {
8041
+ const matches = content.match(TODO_PATTERN);
8042
+ if (matches) todoCount += matches.length;
8043
+ }
8044
+ if (todoCount > 20) {
8045
+ items.push({ level: "medium", category: "quality", message: `${todoCount} TODO/FIXME/HACK comments found` });
8046
+ } else if (todoCount > 5) {
8047
+ items.push({ level: "low", category: "quality", message: `${todoCount} TODO/FIXME/HACK comments found` });
8048
+ }
8049
+ if (!files.some((f) => path27.basename(f) === ".gitignore")) {
8050
+ items.push({ level: "medium", category: "hygiene", message: "No .gitignore found" });
8051
+ }
8052
+ const hasTestFiles = files.some(
8053
+ (f) => /\.(test|spec)\.(ts|tsx|js|jsx|py|go|rs)$/.test(f) || f.replace(/\\/g, "/").includes("/__tests__/") || f.replace(/\\/g, "/").includes("/tests/")
8054
+ );
8055
+ if (!hasTestFiles) {
8056
+ items.push({ level: "high", category: "quality", message: "No test files detected" });
8057
+ }
8058
+ if (!files.some((f) => /^readme\.md$/i.test(path27.basename(f)))) {
8059
+ items.push({ level: "low", category: "documentation", message: "No README.md found" });
8060
+ }
8061
+ const hasPackageJson = files.some((f) => path27.basename(f) === "package.json");
8062
+ const hasLockFile = files.some(
8063
+ (f) => ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb", "bun.lock"].includes(path27.basename(f))
8064
+ );
8065
+ if (hasPackageJson && !hasLockFile) {
8066
+ items.push({ level: "medium", category: "hygiene", message: "Node project has no lock file committed" });
8067
+ }
8068
+ const prodFiles = [...fileContents.entries()].filter(
8069
+ ([f]) => /\.(ts|tsx|js|jsx)$/.test(f) && !/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(f) && !f.includes("__tests__")
8070
+ );
8071
+ let consoleLogs = 0;
8072
+ for (const [, content] of prodFiles) {
8073
+ const matches = content.match(/\bconsole\.(log|warn|error|debug)\b/g);
8074
+ if (matches) consoleLogs += matches.length;
8075
+ }
8076
+ if (consoleLogs > 10) {
8077
+ items.push({ level: "low", category: "quality", message: `${consoleLogs} console.log statements in production code` });
8078
+ }
8079
+ const summary = {
8080
+ high: items.filter((i) => i.level === "high").length,
8081
+ medium: items.filter((i) => i.level === "medium").length,
8082
+ low: items.filter((i) => i.level === "low").length
8083
+ };
8084
+ return { items, summary };
8085
+ }
8086
+ async function loadFileContents(cwd, files, maxLines = 1500) {
8087
+ const map = /* @__PURE__ */ new Map();
8088
+ const readable = files.filter(
8089
+ (f) => /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|json|yaml|yml|toml|md|env)$/.test(f) && !f.includes("node_modules") && !f.includes(".brainforge") && !f.includes("dist/") && !f.includes(".git/")
8090
+ );
8091
+ await Promise.all(
8092
+ readable.map(async (rel) => {
8093
+ try {
8094
+ const abs = path27.join(cwd, rel);
8095
+ const raw = await fs25.readFile(abs, "utf-8");
8096
+ const lines = raw.split("\n");
8097
+ map.set(rel, lines.slice(0, maxLines).join("\n"));
8098
+ } catch {
8099
+ }
8100
+ })
8101
+ );
8102
+ return map;
8103
+ }
8104
+
8105
+ // src/scanner/test-detector.ts
8106
+ import path28 from "path";
8107
+ var TEST_FRAMEWORK_MARKERS = {
8108
+ vitest: ["vitest"],
8109
+ jest: ["jest", "@jest/core"],
8110
+ mocha: ["mocha"],
8111
+ jasmine: ["jasmine"],
8112
+ ava: ["ava"],
8113
+ tap: ["tap", "node:test"],
8114
+ playwright: ["@playwright/test"],
8115
+ cypress: ["cypress"],
8116
+ pytest: ["pytest"],
8117
+ unittest: ["unittest"],
8118
+ gotest: ["testing"],
8119
+ cargo: ["#[cfg(test)]", "#[test]"]
8120
+ };
8121
+ var TEST_CONFIG_FILES = [
8122
+ "vitest.config.ts",
8123
+ "vitest.config.js",
8124
+ "jest.config.ts",
8125
+ "jest.config.js",
8126
+ "jest.config.mjs",
8127
+ "mocha.opts",
8128
+ ".mocharc.yml",
8129
+ ".mocharc.json",
8130
+ ".mocharc.js",
8131
+ "playwright.config.ts",
8132
+ "playwright.config.js",
8133
+ "cypress.config.ts",
8134
+ "cypress.config.js",
8135
+ "pytest.ini",
8136
+ "setup.cfg",
8137
+ "pyproject.toml"
8138
+ ];
8139
+ var TEST_FILE_PATTERNS = [
8140
+ /\.(test|spec)\.(ts|tsx|js|jsx|mjs)$/,
8141
+ /\.test\.(go)$/,
8142
+ /\.spec\.(rb)$/,
8143
+ /_test\.(go|rs|py)$/,
8144
+ /test_.*\.(py)$/
8145
+ ];
8146
+ var SOURCE_FILE_PATTERNS = [
8147
+ /\.(ts|tsx|js|jsx|mjs)$/,
8148
+ /\.(py)$/,
8149
+ /\.(go)$/,
8150
+ /\.(rs)$/,
8151
+ /\.(rb)$/
8152
+ ];
8153
+ var SOURCE_EXCLUDES = /\.(test|spec)\.(ts|tsx|js|jsx|mjs)$|__tests__|node_modules|dist\/|\.brainforge/;
8154
+ function detectTests(files, deps, scripts) {
8155
+ let framework;
8156
+ for (const [name, markers] of Object.entries(TEST_FRAMEWORK_MARKERS)) {
8157
+ if (markers.some((m) => m in deps)) {
8158
+ framework = name;
8159
+ break;
8160
+ }
8161
+ }
8162
+ const testFiles = files.filter(
8163
+ (f) => TEST_FILE_PATTERNS.some((p) => p.test(f)) || f.replace(/\\/g, "/").includes("/__tests__/") || f.replace(/\\/g, "/").includes("/tests/") || /\/test\//.test(f.replace(/\\/g, "/"))
8164
+ );
8165
+ const sourceFiles = files.filter(
8166
+ (f) => SOURCE_FILE_PATTERNS.some((p) => p.test(f)) && !SOURCE_EXCLUDES.test(f)
8167
+ );
8168
+ const coverageRatio = sourceFiles.length > 0 ? testFiles.length / sourceFiles.length : 0;
8169
+ const hasConfig = files.some(
8170
+ (f) => TEST_CONFIG_FILES.some((c) => path28.basename(f) === c)
8171
+ );
8172
+ const coverageEnabled = "c8" in deps || "@vitest/coverage-v8" in deps || "@vitest/coverage-istanbul" in deps || Boolean(scripts?.["coverage"]) || Boolean(scripts?.["test:coverage"]);
8173
+ const testCommand = scripts?.["test"] ?? scripts?.["test:unit"];
8174
+ return {
8175
+ framework,
8176
+ testFiles,
8177
+ testCount: testFiles.length,
8178
+ sourceFileCount: sourceFiles.length,
8179
+ coverageRatio,
8180
+ hasConfig,
8181
+ coverageEnabled,
8182
+ testCommand
8183
+ };
8184
+ }
8185
+
8186
+ // src/scanner/codebase-map.ts
8187
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
8188
+ "node_modules",
8189
+ ".git",
8190
+ "dist",
8191
+ "build",
8192
+ ".next",
8193
+ ".nuxt",
8194
+ ".cache",
8195
+ "coverage",
8196
+ ".brainforge",
8197
+ "__pycache__",
8198
+ ".pytest_cache",
8199
+ "target",
8200
+ ".cargo",
8201
+ "vendor",
8202
+ ".venv",
8203
+ "venv",
8204
+ "env"
8205
+ ]);
8206
+ var IGNORE_EXTENSIONS = /* @__PURE__ */ new Set([
8207
+ ".png",
8208
+ ".jpg",
8209
+ ".jpeg",
8210
+ ".gif",
8211
+ ".svg",
8212
+ ".ico",
8213
+ ".webp",
8214
+ ".avif",
8215
+ ".woff",
8216
+ ".woff2",
8217
+ ".ttf",
8218
+ ".eot",
8219
+ ".otf",
8220
+ ".mp4",
8221
+ ".mp3",
8222
+ ".wav",
8223
+ ".ogg",
8224
+ ".pdf",
8225
+ ".zip",
8226
+ ".tar",
8227
+ ".gz",
8228
+ ".7z",
8229
+ ".lock",
8230
+ // skip lockfiles for content
8231
+ ".map"
8232
+ // skip sourcemaps
8233
+ ]);
8234
+ var CodebaseScanner = class {
8235
+ constructor(cwd) {
8236
+ this.cwd = cwd;
8237
+ this.codebaseDir = path29.join(cwd, ".brainforge", "codebase");
8238
+ }
8239
+ cwd;
8240
+ codebaseDir;
8241
+ async scan(deep = false) {
8242
+ const files = await this.walkFiles(this.cwd, deep ? 8 : 5);
8243
+ const relFiles = files.map((f) => path29.relative(this.cwd, f).replace(/\\/g, "/"));
8244
+ const fileContents = await loadFileContents(this.cwd, relFiles);
8245
+ const [stack, dependencies, conventions, risks] = await Promise.all([
8246
+ detectStack(this.cwd),
8247
+ analyzeDependencies(this.cwd),
8248
+ detectConventions(this.cwd, relFiles),
8249
+ detectRisks(this.cwd, relFiles, fileContents)
8250
+ ]);
8251
+ let scripts;
8252
+ try {
8253
+ const raw = await fs26.readFile(path29.join(this.cwd, "package.json"), "utf-8");
8254
+ scripts = JSON.parse(raw).scripts;
8255
+ } catch {
8256
+ }
8257
+ const allDeps = {};
8258
+ for (const d of [...dependencies.runtime, ...dependencies.dev]) {
8259
+ allDeps[d.name] = d.version;
8260
+ }
8261
+ const tests = detectTests(relFiles, allDeps, scripts);
8262
+ const fileEntries = await this.buildFileInventory(files, relFiles, fileContents);
8263
+ const projectName = await this.readProjectName();
8264
+ return {
8265
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8266
+ projectName,
8267
+ stack,
8268
+ dependencies,
8269
+ conventions,
8270
+ risks,
8271
+ tests,
8272
+ files: fileEntries,
8273
+ totalFiles: fileEntries.length,
8274
+ totalLines: fileEntries.reduce((n, f) => n + f.lines, 0)
8275
+ };
8276
+ }
8277
+ async save(result) {
8278
+ await fs26.mkdir(this.codebaseDir, { recursive: true });
8279
+ await Promise.all([
8280
+ fs26.writeFile(path29.join(this.codebaseDir, "STACK.json"), JSON.stringify(result.stack, null, 2), "utf-8"),
8281
+ fs26.writeFile(path29.join(this.codebaseDir, "FILES.json"), JSON.stringify(result.files, null, 2), "utf-8"),
8282
+ fs26.writeFile(path29.join(this.codebaseDir, "MAP.md"), this.toMapMd(result), "utf-8"),
8283
+ fs26.writeFile(path29.join(this.codebaseDir, "CONVENTIONS.md"), this.toConventionsMd(result), "utf-8"),
8284
+ fs26.writeFile(path29.join(this.codebaseDir, "RISKS.md"), this.toRisksMd(result), "utf-8"),
8285
+ fs26.writeFile(path29.join(this.codebaseDir, "TESTS.md"), this.toTestsMd(result), "utf-8")
8286
+ ]);
8287
+ }
8288
+ async loadLatest() {
8289
+ try {
8290
+ const stack = JSON.parse(await fs26.readFile(path29.join(this.codebaseDir, "STACK.json"), "utf-8"));
8291
+ const files = JSON.parse(await fs26.readFile(path29.join(this.codebaseDir, "FILES.json"), "utf-8"));
8292
+ return { stack, files };
8293
+ } catch {
8294
+ return null;
8295
+ }
8296
+ }
8297
+ toMapMd(r) {
8298
+ const s = r.stack;
8299
+ const lines = [
8300
+ `# Codebase Architecture \u2014 ${r.projectName}`,
8301
+ `> Generated: ${r.generatedAt.slice(0, 16).replace("T", " ")} UTC`,
8302
+ "",
8303
+ "## Stack",
8304
+ `- **Language:** ${s.language}${s.secondaryLanguages.length ? " + " + s.secondaryLanguages.join(", ") : ""}`,
8305
+ s.framework ? `- **Framework:** ${s.framework}` : "",
8306
+ s.runtime ? `- **Runtime:** ${s.runtime}` : "",
8307
+ s.buildTool ? `- **Build:** ${s.buildTool}` : "",
8308
+ s.packageManager ? `- **Package Manager:** ${s.packageManager}` : "",
8309
+ s.linter ? `- **Linter:** ${s.linter}` : "",
8310
+ s.formatter ? `- **Formatter:** ${s.formatter}` : "",
8311
+ s.cssFramework ? `- **CSS:** ${s.cssFramework}` : "",
8312
+ "",
8313
+ "## Project Size",
8314
+ `- ${r.totalFiles} files | ${r.totalLines.toLocaleString()} total lines`,
8315
+ `- Module style: ${r.conventions.moduleStyle}`,
8316
+ r.conventions.entryPoint ? `- Entry point: \`${r.conventions.entryPoint}\`` : "",
8317
+ "",
8318
+ "## Dependencies",
8319
+ `${r.dependencies.total} total (${r.dependencies.runtime.length} runtime, ${r.dependencies.dev.length} dev)`
8320
+ ];
8321
+ if (r.dependencies.highlights.length > 0) {
8322
+ lines.push("", "**Key deps:** " + r.dependencies.highlights.join(", "));
8323
+ }
8324
+ lines.push("", "## Key Directories");
8325
+ const byRole = groupFilesByRole(r.files);
8326
+ for (const [role, fileList] of Object.entries(byRole)) {
8327
+ if (fileList.length > 0) lines.push(`- **${role}:** ${fileList.length} files`);
8328
+ }
8329
+ lines.push("", "## Health Summary");
8330
+ const risks = r.risks.summary;
8331
+ if (risks.high > 0) lines.push(`- ${risks.high} HIGH risk item(s)`);
8332
+ if (risks.medium > 0) lines.push(`- ${risks.medium} MEDIUM risk item(s)`);
8333
+ if (risks.low > 0) lines.push(`- ${risks.low} LOW risk item(s)`);
8334
+ if (risks.high === 0 && risks.medium === 0) lines.push("- No critical risks detected");
8335
+ if (r.tests.framework) {
8336
+ lines.push("", "## Tests");
8337
+ lines.push(`- Framework: ${r.tests.framework}`);
8338
+ lines.push(`- ${r.tests.testCount} test files | ${r.tests.sourceFileCount} source files`);
8339
+ if (r.tests.coverageEnabled) lines.push("- Coverage enabled");
8340
+ }
8341
+ return lines.filter((l) => l !== void 0 && l !== null).join("\n");
8342
+ }
8343
+ toConventionsMd(r) {
8344
+ const c = r.conventions;
8345
+ return [
8346
+ `# Conventions \u2014 ${r.projectName}`,
8347
+ "",
8348
+ `- **Naming:** ${c.namingStyle}`,
8349
+ `- **Folder pattern:** ${c.folderPattern}`,
8350
+ `- **Module style:** ${c.moduleStyle}`,
8351
+ `- **TypeScript strict:** ${c.typescriptStrict ? "yes" : "no"}`,
8352
+ `- **EditorConfig:** ${c.hasEditorConfig ? "yes" : "no"}`,
8353
+ `- **Git hooks (Husky):** ${c.hasHusky ? "yes" : "no"}`,
8354
+ `- **Lint-staged:** ${c.hasHusky ? "yes" : "no"}`
8355
+ ].join("\n");
8356
+ }
8357
+ toRisksMd(r) {
8358
+ const s = r.risks.summary;
8359
+ const lines = [
8360
+ `# Risks \u2014 ${r.projectName}`,
8361
+ `> ${s.high} high | ${s.medium} medium | ${s.low} low`,
8362
+ ""
8363
+ ];
8364
+ const byLevel = { high: [], medium: [], low: [] };
8365
+ for (const item of r.risks.items) byLevel[item.level].push(item);
8366
+ for (const [level, items] of Object.entries(byLevel)) {
8367
+ if (items.length === 0) continue;
8368
+ lines.push(`## ${level.toUpperCase()}`);
8369
+ for (const item of items) {
8370
+ lines.push(`- [${item.category}] ${item.message}${item.file ? `
8371
+ \u2192 \`${item.file}\`` : ""}`);
8372
+ }
8373
+ lines.push("");
8374
+ }
8375
+ if (r.risks.items.length === 0) lines.push("No risks detected.");
8376
+ return lines.join("\n");
8377
+ }
8378
+ toTestsMd(r) {
8379
+ const t = r.tests;
8380
+ const ratio = Math.round(t.coverageRatio * 100);
8381
+ const lines = [
8382
+ `# Tests \u2014 ${r.projectName}`,
8383
+ "",
8384
+ `- **Framework:** ${t.framework ?? "not detected"}`,
8385
+ `- **Test files:** ${t.testCount}`,
8386
+ `- **Source files:** ${t.sourceFileCount}`,
8387
+ `- **Test/Source ratio:** ${ratio}%`,
8388
+ `- **Coverage enabled:** ${t.coverageEnabled ? "yes" : "no"}`,
8389
+ `- **Config file:** ${t.hasConfig ? "yes" : "no"}`
8390
+ ];
8391
+ if (t.testCommand) lines.push(`- **Test command:** \`${t.testCommand}\``);
8392
+ if (t.testCount === 0) lines.push("", "> No test files detected \u2014 consider adding tests.");
8393
+ return lines.join("\n");
8394
+ }
8395
+ async buildFileInventory(absPaths, relPaths, contents) {
8396
+ const entries = [];
8397
+ for (let i = 0; i < absPaths.length; i++) {
8398
+ const rel = relPaths[i];
8399
+ const abs = absPaths[i];
8400
+ let size = 0;
8401
+ let lines = 0;
8402
+ try {
8403
+ const stat = await fs26.stat(abs);
8404
+ size = stat.size;
8405
+ const content = contents.get(rel);
8406
+ lines = content ? content.split("\n").length : 0;
8407
+ } catch {
8408
+ }
8409
+ entries.push({ path: rel, size, lines, role: inferRole(rel) });
8410
+ }
8411
+ return entries;
8412
+ }
8413
+ async walkFiles(dir, maxDepth, depth = 0) {
8414
+ if (depth > maxDepth) return [];
8415
+ const entries = await fs26.readdir(dir, { withFileTypes: true });
8416
+ const results = [];
8417
+ for (const entry of entries) {
8418
+ if (entry.name.startsWith(".") && entry.name !== ".gitignore" && entry.name !== ".env.example") {
8419
+ if (IGNORE_DIRS.has(entry.name)) continue;
8420
+ }
8421
+ if (IGNORE_DIRS.has(entry.name)) continue;
8422
+ const full = path29.join(dir, entry.name);
8423
+ if (entry.isDirectory()) {
8424
+ results.push(...await this.walkFiles(full, maxDepth, depth + 1));
8425
+ } else if (entry.isFile()) {
8426
+ const ext = path29.extname(entry.name).toLowerCase();
8427
+ if (!IGNORE_EXTENSIONS.has(ext)) results.push(full);
8428
+ }
8429
+ }
8430
+ return results;
8431
+ }
8432
+ async readProjectName() {
8433
+ try {
8434
+ const raw = await fs26.readFile(path29.join(this.cwd, "package.json"), "utf-8");
8435
+ const pkg = JSON.parse(raw);
8436
+ if (pkg.name) return pkg.name;
8437
+ } catch {
8438
+ }
8439
+ return path29.basename(this.cwd);
8440
+ }
8441
+ };
8442
+ function inferRole(filePath) {
8443
+ const p = filePath.replace(/\\/g, "/");
8444
+ if (/\.(test|spec)\.(ts|tsx|js|jsx|mjs|py|go|rs)$/.test(p) || p.includes("__tests__")) return "test";
8445
+ if (p.includes("/docs/") || p.endsWith(".md")) return "docs";
8446
+ if (p.includes("/config/") || /\.(config|rc)\.(ts|js|json|yaml|yml)$/.test(p)) return "config";
8447
+ if (p.includes("/scripts/") || p.includes("/bin/")) return "script";
8448
+ if (p.includes("/migrations/") || p.includes("/migrate/")) return "migration";
8449
+ if (p.includes("/public/") || p.includes("/static/") || p.includes("/assets/")) return "asset";
8450
+ if (/\.(ts|tsx|js|jsx|mjs|py|go|rs|php|rb)$/.test(p)) return "source";
8451
+ if (/\.(json|yaml|yml|toml)$/.test(p)) return "config";
8452
+ return "other";
8453
+ }
8454
+ function groupFilesByRole(files) {
8455
+ const groups = {};
8456
+ for (const f of files) {
8457
+ (groups[f.role] ??= []).push(f);
8458
+ }
8459
+ return groups;
8460
+ }
8461
+
8462
+ // src/memory/memory-store.ts
8463
+ import fs27 from "fs/promises";
8464
+ import path30 from "path";
8465
+ import { randomUUID as randomUUID2 } from "crypto";
8466
+ var FILE_FOR = {
8467
+ decision: "decisions.jsonl",
8468
+ summary: "summaries.jsonl",
8469
+ gotcha: "gotchas.jsonl",
8470
+ pattern: "patterns.jsonl"
8471
+ };
8472
+ var MemoryStore = class {
8473
+ constructor(cwd) {
8474
+ this.cwd = cwd;
8475
+ this.memoryDir = path30.join(cwd, ".brainforge", "memory");
8476
+ }
8477
+ cwd;
8478
+ memoryDir;
8479
+ async append(entry) {
8480
+ const full = {
8481
+ id: entry.id ?? randomUUID2(),
8482
+ createdAt: entry.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
8483
+ ...entry
8484
+ };
8485
+ await fs27.mkdir(this.memoryDir, { recursive: true });
8486
+ const filePath = path30.join(this.memoryDir, FILE_FOR[full.type]);
8487
+ await fs27.appendFile(filePath, JSON.stringify(full) + "\n", "utf-8");
8488
+ await this.updateIndex(full);
8489
+ return full;
8490
+ }
8491
+ async readAll(type) {
8492
+ const types = type ? [type] : Object.keys(FILE_FOR);
8493
+ const results = [];
8494
+ for (const t of types) {
8495
+ results.push(...await this.readFile(t));
8496
+ }
8497
+ return results.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
8498
+ }
8499
+ async recent(type, limit = 10) {
8500
+ const all = await this.readAll(type);
8501
+ return all.slice(-limit).reverse();
8502
+ }
8503
+ async search(query) {
8504
+ const q = query.toLowerCase();
8505
+ const all = await this.readAll();
8506
+ return all.filter(
8507
+ (e) => e.title.toLowerCase().includes(q) || e.content.toLowerCase().includes(q) || e.tags.some((t) => t.toLowerCase().includes(q))
8508
+ );
8509
+ }
8510
+ async count() {
8511
+ const types = Object.keys(FILE_FOR);
8512
+ const counts = {};
8513
+ for (const t of types) {
8514
+ counts[t] = (await this.readFile(t)).length;
8515
+ }
8516
+ return counts;
8517
+ }
8518
+ async overwrite(type, entries) {
8519
+ const filePath = path30.join(this.memoryDir, FILE_FOR[type]);
8520
+ await fs27.mkdir(this.memoryDir, { recursive: true });
8521
+ const lines = entries.map((e) => JSON.stringify(e)).join("\n");
8522
+ await fs27.writeFile(filePath, lines ? lines + "\n" : "", "utf-8");
8523
+ }
8524
+ async readFile(type) {
8525
+ const filePath = path30.join(this.memoryDir, FILE_FOR[type]);
8526
+ try {
8527
+ const raw = await fs27.readFile(filePath, "utf-8");
8528
+ return raw.split("\n").filter((l) => l.trim()).map((l) => {
8529
+ try {
8530
+ return JSON.parse(l);
8531
+ } catch {
8532
+ return null;
8533
+ }
8534
+ }).filter((e) => e !== null);
8535
+ } catch {
8536
+ return [];
8537
+ }
8538
+ }
8539
+ async updateIndex(entry) {
8540
+ const indexPath = path30.join(this.memoryDir, "index.json");
8541
+ let index = {
8542
+ version: 1,
8543
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
8544
+ entries: []
8545
+ };
8546
+ try {
8547
+ const raw = await fs27.readFile(indexPath, "utf-8");
8548
+ index = JSON.parse(raw);
8549
+ } catch {
8550
+ }
8551
+ index.entries.push({ id: entry.id, type: entry.type, title: entry.title, createdAt: entry.createdAt });
8552
+ if (index.entries.length > 200) index.entries = index.entries.slice(-200);
8553
+ await fs27.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
8554
+ }
8555
+ };
8556
+
8557
+ // src/memory/decisions.ts
8558
+ var DecisionLogger = class {
8559
+ constructor(store) {
8560
+ this.store = store;
8561
+ }
8562
+ store;
8563
+ async log(decision) {
8564
+ const lines = [
8565
+ decision.description,
8566
+ "",
8567
+ `**Rationale:** ${decision.rationale}`
8568
+ ];
8569
+ if (decision.consequences) {
8570
+ lines.push(`**Consequences:** ${decision.consequences}`);
8571
+ }
8572
+ return this.store.append({
8573
+ type: "decision",
8574
+ title: decision.title,
8575
+ content: lines.join("\n"),
8576
+ tags: decision.tags ?? [],
8577
+ phaseId: decision.phaseId
8578
+ });
8579
+ }
8580
+ async list() {
8581
+ return this.store.readAll("decision");
8582
+ }
8583
+ async recent(limit = 5) {
8584
+ return this.store.recent("decision", limit);
8585
+ }
8586
+ };
8587
+
8588
+ // src/memory/gotchas.ts
8589
+ var GotchaLogger = class {
8590
+ constructor(store) {
8591
+ this.store = store;
8592
+ }
8593
+ store;
8594
+ async log(gotcha) {
8595
+ const content = [
8596
+ gotcha.description,
8597
+ "",
8598
+ `**Solution:** ${gotcha.solution}`
8599
+ ].join("\n");
8600
+ return this.store.append({
8601
+ type: "gotcha",
8602
+ title: gotcha.title,
8603
+ content,
8604
+ tags: gotcha.tags ?? [],
8605
+ phaseId: gotcha.phaseId
8606
+ });
8607
+ }
8608
+ async list() {
8609
+ return this.store.readAll("gotcha");
8610
+ }
8611
+ async recent(limit = 5) {
8612
+ return this.store.recent("gotcha", limit);
8613
+ }
8614
+ };
8615
+
8616
+ // src/memory/compression.ts
8617
+ async function compact(store, keepLast = 20) {
8618
+ const types = ["decision", "summary", "gotcha", "pattern"];
8619
+ const results = [];
8620
+ for (const type of types) {
8621
+ const all = await store.readAll(type);
8622
+ if (all.length <= keepLast) {
8623
+ results.push({ type, before: all.length, kept: all.length, compacted: 0 });
8624
+ continue;
8625
+ }
8626
+ const older = all.slice(0, all.length - keepLast);
8627
+ const recent = all.slice(all.length - keepLast);
8628
+ const compactedEntry = {
8629
+ id: `compacted-${type}-${Date.now()}`,
8630
+ type,
8631
+ title: `[Compacted] ${older.length} older ${type} entries`,
8632
+ content: summariseEntries(older),
8633
+ tags: ["compacted"],
8634
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
8635
+ };
8636
+ await store.overwrite(type, [compactedEntry, ...recent]);
8637
+ results.push({ type, before: all.length, kept: recent.length + 1, compacted: older.length });
8638
+ }
8639
+ return {
8640
+ ranAt: (/* @__PURE__ */ new Date()).toISOString(),
8641
+ results,
8642
+ totalBefore: results.reduce((n, r) => n + r.before, 0),
8643
+ totalAfter: results.reduce((n, r) => n + r.kept, 0)
8644
+ };
8645
+ }
8646
+ function summariseEntries(entries) {
8647
+ const lines = [`Compacted ${entries.length} entries on ${(/* @__PURE__ */ new Date()).toISOString()}:`, ""];
8648
+ for (const e of entries) {
8649
+ const date = e.createdAt.slice(0, 10);
8650
+ lines.push(`- [${date}] ${e.title}`);
8651
+ }
8652
+ return lines.join("\n");
8653
+ }
8654
+
8655
+ // src/memory/context-packets.ts
8656
+ import fs28 from "fs/promises";
8657
+ import path31 from "path";
8658
+ var ContextPacketBuilder = class {
8659
+ constructor(cwd) {
8660
+ this.cwd = cwd;
8661
+ this.packetDir = path31.join(cwd, ".brainforge", "memory", "context-packets");
8662
+ }
8663
+ cwd;
8664
+ packetDir;
8665
+ async build() {
8666
+ const manager = new StateManager(this.cwd);
8667
+ await manager.load();
8668
+ const state = manager.get();
8669
+ const config = await loadConfig(this.cwd);
8670
+ const store = new MemoryStore(this.cwd);
8671
+ const activePhase = state.phases.find((p) => p.id === state.activePhaseId);
8672
+ const completedPhases = state.phases.filter((p) => p.status === "completed").length;
8673
+ const pendingPhases = state.phases.filter((p) => p.status !== "completed").length;
8674
+ const recentDecisions = (await store.recent("decision", 3)).map((e) => ({
8675
+ title: e.title,
8676
+ content: e.content,
8677
+ createdAt: e.createdAt
8678
+ }));
8679
+ const knownGotchas = (await store.recent("gotcha", 3)).map((e) => ({
8680
+ title: e.title,
8681
+ content: e.content
8682
+ }));
8683
+ const recentSummaries = (await store.recent("summary", 2)).map((e) => ({
8684
+ title: e.title,
8685
+ content: e.content,
8686
+ createdAt: e.createdAt
8687
+ }));
8688
+ let currentPhase;
8689
+ if (activePhase) {
8690
+ const done = activePhase.tasks.filter((t) => t.status === "done").length;
8691
+ const inProg = activePhase.tasks.filter((t) => t.status === "in-progress").length;
8692
+ const next = activePhase.tasks.find((t) => t.status === "todo");
8693
+ currentPhase = {
8694
+ id: activePhase.id,
8695
+ name: activePhase.name,
8696
+ status: activePhase.status,
8697
+ tasksTotal: activePhase.tasks.length,
8698
+ tasksDone: done,
8699
+ tasksInProgress: inProg,
8700
+ nextTask: next?.title
8701
+ };
8702
+ }
8703
+ const nextActions = deriveNextActions(state, activePhase);
8704
+ const resumeCommands = deriveResumeCommands(state, activePhase);
8705
+ return {
8706
+ version: 1,
8707
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8708
+ project: {
8709
+ name: state.project.name,
8710
+ language: state.project.language,
8711
+ framework: state.project.framework,
8712
+ type: state.project.type
8713
+ },
8714
+ workflow: {
8715
+ mode: config.mode,
8716
+ runtime: config.runtime
8717
+ },
8718
+ phases: {
8719
+ total: state.phases.length,
8720
+ completed: completedPhases,
8721
+ pending: pendingPhases,
8722
+ activeId: state.activePhaseId,
8723
+ activeName: activePhase?.name
8724
+ },
8725
+ currentPhase,
8726
+ recentDecisions,
8727
+ knownGotchas,
8728
+ recentSummaries,
8729
+ nextActions,
8730
+ resumeCommands
8731
+ };
8732
+ }
8733
+ async save(packet) {
8734
+ await fs28.mkdir(this.packetDir, { recursive: true });
8735
+ const jsonPath = path31.join(this.packetDir, "latest.json");
8736
+ const mdPath = path31.join(this.packetDir, "latest.md");
8737
+ await fs28.writeFile(jsonPath, JSON.stringify(packet, null, 2), "utf-8");
8738
+ await fs28.writeFile(mdPath, this.toMarkdown(packet), "utf-8");
8739
+ const ts = packet.generatedAt.replace(/[:.]/g, "-").slice(0, 19);
8740
+ await fs28.writeFile(
8741
+ path31.join(this.packetDir, `packet-${ts}.json`),
8742
+ JSON.stringify(packet, null, 2),
8743
+ "utf-8"
8744
+ );
8745
+ return { jsonPath, mdPath };
8746
+ }
8747
+ async loadLatest() {
8748
+ try {
8749
+ const raw = await fs28.readFile(path31.join(this.packetDir, "latest.json"), "utf-8");
8750
+ return JSON.parse(raw);
8751
+ } catch {
8752
+ return null;
8753
+ }
8754
+ }
8755
+ toMarkdown(p) {
8756
+ const lines = [
8757
+ `# BrainForge AI \u2014 Resume Context`,
8758
+ `> Generated: ${p.generatedAt.replace("T", " ").slice(0, 16)} UTC`,
8759
+ `> Paste this into a fresh AI session to resume work immediately.`,
8760
+ "",
8761
+ "## Project",
8762
+ `**Name:** ${p.project.name}`,
8763
+ `**Language:** ${p.project.language}${p.project.framework ? " / " + p.project.framework : ""}`,
8764
+ `**Type:** ${p.project.type}`,
8765
+ `**Mode:** ${p.workflow.mode} | **Runtime:** ${p.workflow.runtime}`,
8766
+ "",
8767
+ "## Phase Progress",
8768
+ `Total phases: ${p.phases.total} | Completed: ${p.phases.completed} | Pending: ${p.phases.pending}`
8769
+ ];
8770
+ if (p.currentPhase) {
8771
+ const cp = p.currentPhase;
8772
+ const pct = cp.tasksTotal > 0 ? Math.round(cp.tasksDone / cp.tasksTotal * 100) : 0;
8773
+ const bar = progressBar(cp.tasksDone, cp.tasksTotal);
8774
+ lines.push(
8775
+ "",
8776
+ `### Active Phase: ${cp.name} (${cp.status})`,
8777
+ `[${bar}] ${pct}% \u2014 ${cp.tasksDone}/${cp.tasksTotal} tasks done`,
8778
+ cp.tasksInProgress > 0 ? `${cp.tasksInProgress} task(s) in progress` : "",
8779
+ cp.nextTask ? `**Next task:** ${cp.nextTask}` : "**All tasks complete \u2014 ready to verify.**"
8780
+ );
8781
+ }
8782
+ if (p.recentDecisions.length > 0) {
8783
+ lines.push("", "## Recent Decisions");
8784
+ for (const d of p.recentDecisions) {
8785
+ const date = d.createdAt.slice(0, 10);
8786
+ lines.push(`- **[${date}] ${d.title}**`);
8787
+ const firstLine = d.content.split("\n")[0]?.trim();
8788
+ if (firstLine) lines.push(` ${firstLine}`);
8789
+ }
8790
+ }
8791
+ if (p.knownGotchas.length > 0) {
8792
+ lines.push("", "## Known Gotchas");
8793
+ for (const g of p.knownGotchas) {
8794
+ lines.push(`- **${g.title}**`);
8795
+ const solution = g.content.split("**Solution:**")[1]?.trim().split("\n")[0];
8796
+ if (solution) lines.push(` Fix: ${solution}`);
8797
+ }
8798
+ }
8799
+ if (p.recentSummaries.length > 0) {
8800
+ lines.push("", "## Recent Phase Summaries");
8801
+ for (const s of p.recentSummaries) {
8802
+ const date = s.createdAt.slice(0, 10);
8803
+ lines.push(`- **[${date}] ${s.title}**`);
8804
+ }
8805
+ }
8806
+ lines.push("", "## What To Do Next");
8807
+ for (const action of p.nextActions) {
8808
+ lines.push(`- ${action}`);
8809
+ }
8810
+ lines.push("", "## Quick Commands");
8811
+ for (const cmd of p.resumeCommands) {
8812
+ lines.push(`\`${cmd}\``);
8813
+ }
8814
+ lines.push(
8815
+ "",
8816
+ "---",
8817
+ `*BrainForge AI \u2014 .brainforge/memory/context-packets/latest.md*`
8818
+ );
8819
+ return lines.filter((l) => l !== void 0).join("\n");
8820
+ }
8821
+ };
8822
+ function deriveNextActions(state, activePhase) {
8823
+ const actions = [];
8824
+ if (state.phases.length === 0) {
8825
+ actions.push("Run `brg plan` to create your first phase and tasks");
8826
+ return actions;
8827
+ }
8828
+ if (!activePhase) {
8829
+ const pending = state.phases.find((p) => p.status === "pending");
8830
+ if (pending) actions.push(`Start phase "${pending.name}" with \`brg phase switch ${pending.id}\``);
8831
+ return actions;
8832
+ }
8833
+ const todo = activePhase.tasks.filter((t) => t.status === "todo");
8834
+ const inProg = activePhase.tasks.filter((t) => t.status === "in-progress");
8835
+ const done = activePhase.tasks.filter((t) => t.status === "done");
8836
+ if (inProg.length > 0) {
8837
+ actions.push(`Continue ${inProg.length} in-progress task(s) with \`brg execute --prompt-only\``);
8838
+ } else if (todo.length > 0) {
8839
+ actions.push(`Execute ${todo.length} remaining task(s) with \`brg execute --phase ${activePhase.id} --prompt-only\``);
8840
+ } else if (done.length === activePhase.tasks.length && activePhase.tasks.length > 0) {
8841
+ actions.push(`All tasks done \u2014 run \`brg verify --phase ${activePhase.id}\` to verify`);
8842
+ }
8843
+ if (activePhase.status === "completed") {
8844
+ const next = state.phases.find((p) => p.status === "pending");
8845
+ if (next) actions.push(`Phase complete \u2014 next phase: "${next.name}"`);
8846
+ else actions.push("All phases complete \u2014 run `brg ship` to close the project");
8847
+ }
8848
+ return actions;
8849
+ }
8850
+ function deriveResumeCommands(state, activePhase) {
8851
+ const cmds = ["brg progress", "brg memory list"];
8852
+ if (activePhase) {
8853
+ cmds.push(`brg execute --phase ${activePhase.id} --prompt-only`);
8854
+ cmds.push(`brg verify --phase ${activePhase.id}`);
8855
+ } else {
8856
+ cmds.push("brg plan");
8857
+ }
8858
+ return cmds;
8859
+ }
8860
+ function progressBar(done, total, width = 16) {
8861
+ if (total === 0) return "\u2591".repeat(width);
8862
+ const filled = Math.round(done / total * width);
8863
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
8864
+ }
5144
8865
  export {
8866
+ AGENT_REGISTRY,
5145
8867
  AdapterManager,
8868
+ AutoEngine,
5146
8869
  BUILT_IN_SKILLS,
5147
8870
  BrainForgeServer,
8871
+ COMMANDS_BY_NAME,
8872
+ COMMAND_DEFINITIONS,
5148
8873
  ClaudeAdapter,
5149
8874
  CodebaseMapper,
8875
+ CodebaseScanner,
8876
+ ContextPacketBuilder,
8877
+ DEFAULT_CONFIG,
8878
+ DebugEngine,
8879
+ DecisionLogger,
5150
8880
  DeepSeekAdapter,
8881
+ ExecutionEngine,
5151
8882
  FileWatcher,
5152
8883
  GeminiAdapter,
8884
+ GotchaLogger,
8885
+ MemoryStore,
5153
8886
  MockDefense,
5154
8887
  OpenAIAdapter,
8888
+ ParallelOrchestrator,
8889
+ PhaseEngine,
5155
8890
  ProfessorScanner,
5156
8891
  PromptEngine,
8892
+ RUNTIME_REGISTRY,
8893
+ ShipEngine,
5157
8894
  SkillRegistry,
5158
8895
  StateManager,
5159
8896
  TaskPlanner,
8897
+ VerificationEngine,
8898
+ WavePlanner,
8899
+ WorkflowEngine,
8900
+ Workspace,
8901
+ analyzeDependencies,
8902
+ buildAgentPrompt,
8903
+ buildMergeReport,
5160
8904
  buildQuestionSet,
5161
- reduce
8905
+ buildWaveSummary,
8906
+ compact,
8907
+ detectAndMigrate,
8908
+ detectConflicts,
8909
+ detectConventions,
8910
+ detectRisks,
8911
+ detectRuntimes,
8912
+ detectStack,
8913
+ detectTests,
8914
+ getAgent,
8915
+ getRuntime,
8916
+ installRuntime,
8917
+ listAgents,
8918
+ listRuntimes,
8919
+ loadConfig,
8920
+ loadFileContents,
8921
+ mergeResults,
8922
+ reduce,
8923
+ saveConfig,
8924
+ validateConfig,
8925
+ validatePhase,
8926
+ validateProjectConfig,
8927
+ validateTask
5162
8928
  };