@brainforge/core 3.1.11 → 3.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { EventEmitter } from 'events';
2
2
 
3
3
  type ProjectType = 'student' | 'junior' | 'solo' | 'personal' | 'pro';
4
- type StudentLevel = 'beginner' | 'intermediate' | 'advanced';
4
+ type StudentLevel = 'beginner' | 'intermediate' | 'advanced' | 'professional';
5
5
  type Language = 'typescript' | 'javascript' | 'python' | 'java' | 'go' | 'php' | 'rust' | 'csharp' | 'ruby';
6
- type PhaseStatus = 'pending' | 'active' | 'completed' | 'blocked';
6
+ type PhaseStatus = 'pending' | 'active' | 'completed' | 'blocked' | 'draft' | 'discussed' | 'planned' | 'checked' | 'executing' | 'executed' | 'verified' | 'failed' | 'fixed' | 'shipped' | 'needs-manual-verification';
7
7
  type TaskStatus = 'todo' | 'in-progress' | 'done' | 'skipped';
8
8
  type TaskPriority = 'low' | 'medium' | 'high' | 'critical';
9
9
  type TaskType = 'backend' | 'frontend' | 'database' | 'devops' | 'security' | 'test' | 'docs' | 'general';
@@ -130,7 +130,7 @@ interface BrainForgeConfig {
130
130
  };
131
131
  student: {
132
132
  enabled: boolean;
133
- level: 'beginner' | 'intermediate' | 'advanced';
133
+ level: 'beginner' | 'intermediate' | 'advanced' | 'professional';
134
134
  professorMode: boolean;
135
135
  };
136
136
  safety: {
@@ -152,8 +152,12 @@ declare class StateManager {
152
152
  private statePath;
153
153
  private state;
154
154
  constructor(workingDir: string);
155
+ getPhaseDir(phaseId: string): string;
156
+ ensurePhaseDir(phaseId: string): Promise<void>;
157
+ writePhaseFile(phaseId: string, filename: string, content: string): Promise<void>;
158
+ readPhaseFile(phaseId: string, filename: string): Promise<string>;
155
159
  load(): Promise<BrainforgeState>;
156
- save(): Promise<void>;
160
+ save(newState?: BrainforgeState): Promise<void>;
157
161
  init(project: ProjectConfig): Promise<BrainforgeState>;
158
162
  loadWorkspaceConfig(): Promise<BrainForgeConfig>;
159
163
  saveWorkspaceConfig(config: BrainForgeConfig): Promise<void>;
@@ -352,6 +356,7 @@ interface ScanReport {
352
356
  studentLevel: StudentLevel;
353
357
  totalFilesScanned: number;
354
358
  matches: PatternMatch[];
359
+ aiAnalysis?: string;
355
360
  summary: {
356
361
  errors: number;
357
362
  warnings: number;
@@ -361,7 +366,7 @@ interface ScanReport {
361
366
  declare class ProfessorScanner {
362
367
  private readonly workingDir;
363
368
  constructor(workingDir: string);
364
- scan(language: Language, studentLevel: StudentLevel): Promise<ScanReport>;
369
+ scan(language: Language, studentLevel: StudentLevel, useAI?: boolean): Promise<ScanReport>;
365
370
  private findFiles;
366
371
  private walk;
367
372
  private writeReport;
@@ -745,6 +750,14 @@ interface BugEntry {
745
750
  taskId?: string;
746
751
  description?: string;
747
752
  }
753
+ interface CommandResult {
754
+ command: string;
755
+ status: 'passed' | 'failed';
756
+ exitCode: number;
757
+ stdout: string;
758
+ stderr: string;
759
+ duration: number;
760
+ }
748
761
  interface VerifyResult {
749
762
  phaseId: string;
750
763
  phaseName: string;
@@ -753,10 +766,13 @@ interface VerifyResult {
753
766
  passed: number;
754
767
  bugs: BugEntry[];
755
768
  blocked: boolean;
769
+ commandResults?: CommandResult[];
756
770
  }
757
771
  declare class VerificationEngine {
758
- buildVerifyPrompt(phase: Phase, stack?: StackInfo): string;
759
- buildVerifyMd(phase: Phase, bugs?: BugEntry[]): string;
772
+ detectAvailableCommands(cwd: string): Promise<string[]>;
773
+ runCommands(commands: string[], cwd: string): Promise<CommandResult[]>;
774
+ buildVerifyPrompt(phase: Phase, stack?: StackInfo, detectedCommands?: string[]): string;
775
+ buildVerifyMd(phase: Phase, commandResults?: CommandResult[], bugs?: BugEntry[]): string;
760
776
  buildBugsMd(bugs: BugEntry[]): string;
761
777
  }
762
778
 
package/dist/index.js CHANGED
@@ -166,6 +166,7 @@ async function fileExists(p) {
166
166
  // src/state/manager.ts
167
167
  var STATE_DIR = ".brainforge";
168
168
  var STATE_FILE = "core.json";
169
+ var PHASES_DIR = "phases";
169
170
  var StateManager = class {
170
171
  constructor(workingDir) {
171
172
  this.workingDir = workingDir;
@@ -174,12 +175,27 @@ var StateManager = class {
174
175
  workingDir;
175
176
  statePath;
176
177
  state = null;
178
+ getPhaseDir(phaseId) {
179
+ return path3.join(this.workingDir, STATE_DIR, PHASES_DIR, phaseId);
180
+ }
181
+ async ensurePhaseDir(phaseId) {
182
+ const dir = this.getPhaseDir(phaseId);
183
+ await fs3.mkdir(dir, { recursive: true });
184
+ }
185
+ async writePhaseFile(phaseId, filename, content) {
186
+ await this.ensurePhaseDir(phaseId);
187
+ await fs3.writeFile(path3.join(this.getPhaseDir(phaseId), filename), content, "utf-8");
188
+ }
189
+ async readPhaseFile(phaseId, filename) {
190
+ return fs3.readFile(path3.join(this.getPhaseDir(phaseId), filename), "utf-8");
191
+ }
177
192
  async load() {
178
193
  const raw = await fs3.readFile(this.statePath, "utf-8");
179
194
  this.state = JSON.parse(raw);
180
195
  return this.state;
181
196
  }
182
- async save() {
197
+ async save(newState) {
198
+ if (newState) this.state = newState;
183
199
  if (!this.state) throw new Error("No state loaded \u2014 call init() or load() first.");
184
200
  this.state.metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
185
201
  await fs3.mkdir(path3.dirname(this.statePath), { recursive: true });
@@ -267,7 +283,10 @@ var StateManager = class {
267
283
  }
268
284
  async writeProjectMd() {
269
285
  const s = this.state;
270
- const phaseLines = s.phases.length ? s.phases.map((p) => `- [${p.status === "completed" ? "x" : " "}] **${p.name}** \u2014 ${p.description}`) : ["- No phases yet. Run `brainforge plan` to create one."];
286
+ const phaseLines = s.phases.length ? s.phases.map((p) => {
287
+ const active = p.id === s.activePhaseId ? " \u25C0 ACTIVE" : "";
288
+ return `- [${p.status === "shipped" || p.status === "completed" ? "x" : " "}] **${p.name}** \`${p.status}\`${active} \u2014 ${p.description}`;
289
+ }) : ["- No phases yet. Run `brg phase create` or `brg plan` to add one."];
271
290
  const content = [
272
291
  `# ${s.project.name}`,
273
292
  "",
@@ -279,6 +298,11 @@ var StateManager = class {
279
298
  "",
280
299
  ...phaseLines,
281
300
  "",
301
+ "## Quick Commands",
302
+ "- `brg next` \u2014 See what to do next",
303
+ "- `brg phase list` \u2014 List all phases",
304
+ "- `brg serve --open` \u2014 Open the live dashboard",
305
+ "",
282
306
  `---`,
283
307
  `*Generated by BrainForge AI v3 \u2014 ${s.metadata.updatedAt}*`
284
308
  ].join("\n");
@@ -291,20 +315,25 @@ var StateManager = class {
291
315
  const doneTasks = s.phases.reduce((n, p) => n + p.tasks.filter((t) => t.status === "done").length, 0);
292
316
  const phaseBlocks = s.phases.map((p) => {
293
317
  const done = p.tasks.filter((t) => t.status === "done").length;
294
- return `### ${p.name} \`${p.status}\`
318
+ const statusIcon = p.status === "shipped" ? "\u{1F7E2}" : p.status === "failed" ? "\u{1F534}" : "\u{1F7E1}";
319
+ return `### ${statusIcon} ${p.name} \`${p.status}\`
295
320
  ${p.description}
296
- Tasks: ${done}/${p.tasks.length}`;
321
+ Tasks: ${done}/${p.tasks.length}
322
+ Artifacts: \`.brainforge/phases/${p.id}/\``;
297
323
  });
298
324
  const content = [
299
325
  "# Project State",
300
326
  "",
301
327
  `**Last Updated:** ${s.metadata.updatedAt}`,
302
- `**Active Phase:** ${active ? active.name : "None"}`,
328
+ `**Active Phase:** ${active ? `"${active.name}" (${active.status})` : "None"}`,
303
329
  `**Progress:** ${doneTasks}/${totalTasks} tasks complete`,
304
330
  "",
305
- "## Phases",
331
+ "## Phase Summary",
306
332
  "",
307
- phaseBlocks.length ? phaseBlocks.join("\n\n") : "No phases yet."
333
+ phaseBlocks.length ? phaseBlocks.join("\n\n") : "No phases yet.",
334
+ "",
335
+ "---",
336
+ `*BrainForge AI Internal State*`
308
337
  ].join("\n");
309
338
  await fs3.writeFile(path3.join(this.workingDir, "STATE.md"), content, "utf-8");
310
339
  }
@@ -515,11 +544,106 @@ var BUILTIN_TEMPLATES = {
515
544
 
516
545
  Project: {{project.description}}
517
546
  Language: {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}
518
- {{#if project.studentLevel}}Student Level: {{project.studentLevel}}{{/if}}
547
+ {{#if project.studentLevel}}Level: {{project.studentLevel}}{{/if}}
519
548
 
520
549
  The developer wants to discuss: {{topic}}
521
550
 
522
551
  Provide structured, actionable guidance appropriate for their level. Be concise but thorough.`,
552
+ discuss_phase: `You are BrainForge AI. Help a {{project.type}} developer discuss phase "{{phase.name}}" for project "{{project.name}}".
553
+
554
+ Phase Goal: {{phase.description}}
555
+ Project Level: {{project.studentLevel}}
556
+
557
+ Help the user clarify:
558
+ - Technical constraints
559
+ - Expected functionality
560
+ - Affected files/modules
561
+ - Risks and acceptance criteria
562
+ - Expected code quality ({{project.studentLevel}})
563
+
564
+ Ensure the discussion covers everything needed for a solid implementation plan.`,
565
+ plan_phase: `You are BrainForge AI. Generate an implementation plan for phase "{{phase.name}}" of "{{project.name}}".
566
+
567
+ Phase Goal: {{phase.description}}
568
+ Discussion Context: {{discussion}}
569
+ Level: {{project.studentLevel}}
570
+
571
+ Generate:
572
+ 1. A detailed PLAN.md overview
573
+ 2. A TASKS.json with small, atomic, verifiable tasks
574
+ 3. Files to create/modify
575
+ 4. Risk assessment per task
576
+
577
+ The tasks MUST respect the {{project.studentLevel}} level:
578
+ - beginner: simple code, few abstractions, pedagogic comments.
579
+ - intermediate: clean architecture, services/controllers, basic validation.
580
+ - professional: robust architecture, strict validation, full error handling, security.`,
581
+ check_plan: `You are BrainForge AI. Review the implementation plan for phase "{{phase.name}}".
582
+
583
+ Plan: {{plan}}
584
+ Level: {{project.studentLevel}}
585
+
586
+ Check for:
587
+ - Tasks too large or ambiguous
588
+ - Missing acceptance criteria
589
+ - Inconsistencies with the current codebase architecture
590
+ - If the level ({{project.studentLevel}}) is respected
591
+ - Missing test/verification commands
592
+
593
+ Produce a PLAN_REVIEW.md report.`,
594
+ verify_phase: `You are BrainForge AI. Generate a verification checklist for phase "{{phase.name}}".
595
+
596
+ Tasks: {{tasks}}
597
+ Bugs Found: {{bugs}}
598
+
599
+ Identify:
600
+ - Automated tests to run (build, test, lint)
601
+ - Manual verification steps
602
+ - Edge cases to check
603
+
604
+ Ensure every acceptance criterion is covered.`,
605
+ fix_phase: `You are BrainForge AI. Generate a fix plan for phase "{{phase.name}}".
606
+
607
+ # Verification Results
608
+ {{failures}}
609
+
610
+ # Original Plan Context
611
+ {{plan}}
612
+
613
+ # Analysis Requirements
614
+ 1. Identify the failed commands and their error outputs.
615
+ 2. Formulate root cause hypotheses for each failure.
616
+ 3. List specific files to inspect and potential logical bugs.
617
+ 4. Propose step-by-step correction instructions for an AI agent.
618
+ 5. Identify the exact verification commands to re-run after fixes.
619
+
620
+ Produce a FIX_PLAN.md that is honest: it guides the fix, it doesn't pretend to have fixed it automatically.`,
621
+ ship_phase: `You are BrainForge AI. Generate a final ship report for phase "{{phase.name}}".
622
+
623
+ Completed Tasks: {{tasks}}
624
+ Verification Status: {{status}}
625
+
626
+ Summarize:
627
+ - Key features implemented
628
+ - Architecture decisions made
629
+ - Remaining technical debt
630
+ - Recommended next phase
631
+
632
+ Produce a SHIP.md.`,
633
+ professor_review: `You are a Professor AI. Review these advanced patterns found in a {{level}} student's {{language}} project.
634
+
635
+ Matches:
636
+ {{#each matches}}
637
+ - {{name}} in {{file}} (line {{line}}): "{{snippet}}"
638
+ {{/each}}
639
+
640
+ For each pattern:
641
+ 1. Explain it simply as if to a student.
642
+ 2. Provide a simpler alternative if applicable.
643
+ 3. Suggest 1-2 tricky questions a professor might ask about this.
644
+ 4. Provide the expected technical answer.
645
+
646
+ Identify if the overall code level feels like beginner, intermediate, or professional based on these patterns.`,
523
647
  plan: `You are BrainForge AI. Analyze the following task for project "{{project.name}}":
524
648
 
525
649
  Task: {{task.title}}
@@ -1216,7 +1340,7 @@ var ProfessorScanner = class {
1216
1340
  this.workingDir = workingDir;
1217
1341
  }
1218
1342
  workingDir;
1219
- async scan(language, studentLevel) {
1343
+ async scan(language, studentLevel, useAI = false) {
1220
1344
  const applicable = PATTERNS.filter(
1221
1345
  (p) => p.languages.includes(language) && p.flagForLevels.includes(studentLevel)
1222
1346
  );
@@ -1245,6 +1369,19 @@ var ProfessorScanner = class {
1245
1369
  pattern.regex.lastIndex = 0;
1246
1370
  }
1247
1371
  }
1372
+ let aiAnalysis;
1373
+ if (useAI && matches.length > 0) {
1374
+ try {
1375
+ const engine = new PromptEngine();
1376
+ aiAnalysis = engine.render("professor_review", {
1377
+ language,
1378
+ level: studentLevel,
1379
+ matches,
1380
+ count: matches.length
1381
+ });
1382
+ } catch {
1383
+ }
1384
+ }
1248
1385
  const errors = matches.filter((m) => m.severity === "error").length;
1249
1386
  const warnings = matches.filter((m) => m.severity === "warn").length;
1250
1387
  const report = {
@@ -1253,6 +1390,7 @@ var ProfessorScanner = class {
1253
1390
  studentLevel,
1254
1391
  totalFilesScanned: files.length,
1255
1392
  matches,
1393
+ aiAnalysis,
1256
1394
  summary: { errors, warnings, clean: matches.length === 0 }
1257
1395
  };
1258
1396
  await this.writeReport(report);
@@ -1294,7 +1432,10 @@ var ProfessorScanner = class {
1294
1432
  if (report.summary.clean) {
1295
1433
  lines.push("No advanced patterns detected. Your code matches your declared level.");
1296
1434
  } else {
1297
- lines.push("## Findings", "");
1435
+ if (report.aiAnalysis) {
1436
+ lines.push("## AI Analysis & Coaching", "", report.aiAnalysis, "");
1437
+ }
1438
+ lines.push("## Detailed Findings", "");
1298
1439
  for (const m of report.matches) {
1299
1440
  const icon = m.severity === "error" ? "[ERROR]" : "[WARN]";
1300
1441
  lines.push(
@@ -7223,9 +7364,90 @@ ${cmd}
7223
7364
  };
7224
7365
 
7225
7366
  // src/workflow/verification-engine.ts
7367
+ import fs23 from "fs/promises";
7368
+ import path25 from "path";
7369
+ import { exec as exec2 } from "child_process";
7370
+ import { promisify as promisify2 } from "util";
7371
+ var execAsync2 = promisify2(exec2);
7226
7372
  var VerificationEngine = class {
7227
- buildVerifyPrompt(phase, stack) {
7228
- const tasksWithCriteria = phase.tasks.filter((t) => t.acceptanceCriteria?.length);
7373
+ async detectAvailableCommands(cwd) {
7374
+ const commands = [];
7375
+ try {
7376
+ const pkgPath = path25.join(cwd, "package.json");
7377
+ const raw = await fs23.readFile(pkgPath, "utf-8");
7378
+ const pkg = JSON.parse(raw);
7379
+ const scripts = pkg.scripts || {};
7380
+ const hasYarn = await fs23.access(path25.join(cwd, "yarn.lock")).then(() => true).catch(() => false);
7381
+ const hasPnpm = await fs23.access(path25.join(cwd, "pnpm-lock.yaml")).then(() => true).catch(() => false);
7382
+ const hasBun = await fs23.access(path25.join(cwd, "bun.lockb")).then(() => true).catch(() => false);
7383
+ const manager = hasBun ? "bun" : hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
7384
+ const run = manager === "npm" || manager === "bun" ? `${manager} run` : manager;
7385
+ if (scripts.build) commands.push(`${run} build`);
7386
+ if (scripts.test) commands.push(`${manager} test`);
7387
+ if (scripts.lint) commands.push(`${run} lint`);
7388
+ if (scripts.typecheck) commands.push(`${run} typecheck`);
7389
+ } catch {
7390
+ }
7391
+ try {
7392
+ await fs23.access(path25.join(cwd, "pytest.ini"));
7393
+ commands.push("pytest");
7394
+ } catch {
7395
+ try {
7396
+ await fs23.access(path25.join(cwd, "conftest.py"));
7397
+ commands.push("pytest");
7398
+ } catch {
7399
+ }
7400
+ }
7401
+ try {
7402
+ await fs23.access(path25.join(cwd, "pom.xml"));
7403
+ commands.push("mvn test");
7404
+ } catch {
7405
+ }
7406
+ try {
7407
+ await fs23.access(path25.join(cwd, "build.gradle"));
7408
+ commands.push("./gradlew test");
7409
+ } catch {
7410
+ }
7411
+ try {
7412
+ await fs23.access(path25.join(cwd, "go.mod"));
7413
+ commands.push("go test ./...");
7414
+ } catch {
7415
+ }
7416
+ try {
7417
+ await fs23.access(path25.join(cwd, "Cargo.toml"));
7418
+ commands.push("cargo test");
7419
+ } catch {
7420
+ }
7421
+ return commands;
7422
+ }
7423
+ async runCommands(commands, cwd) {
7424
+ const results = [];
7425
+ for (const command of commands) {
7426
+ const start = Date.now();
7427
+ try {
7428
+ const { stdout, stderr } = await execAsync2(command, { cwd });
7429
+ results.push({
7430
+ command,
7431
+ status: "passed",
7432
+ exitCode: 0,
7433
+ stdout,
7434
+ stderr,
7435
+ duration: Date.now() - start
7436
+ });
7437
+ } catch (err) {
7438
+ results.push({
7439
+ command,
7440
+ status: "failed",
7441
+ exitCode: err.code || 1,
7442
+ stdout: err.stdout || "",
7443
+ stderr: err.stderr || err.message,
7444
+ duration: Date.now() - start
7445
+ });
7446
+ }
7447
+ }
7448
+ return results;
7449
+ }
7450
+ buildVerifyPrompt(phase, stack, detectedCommands = []) {
7229
7451
  const allCriteria = [];
7230
7452
  for (const t of phase.tasks) {
7231
7453
  for (const c of t.acceptanceCriteria ?? []) {
@@ -7252,56 +7474,75 @@ var VerificationEngine = class {
7252
7474
  }
7253
7475
  lines.push("");
7254
7476
  lines.push("## Verification commands to run");
7255
- const cmds = phase.tasks.flatMap((t) => t.verificationCommands ?? []);
7256
- if (cmds.length > 0) {
7257
- for (const cmd of [...new Set(cmds)]) {
7477
+ const taskCmds = phase.tasks.flatMap((t) => t.verificationCommands ?? []);
7478
+ const uniqueCmds = [.../* @__PURE__ */ new Set([...detectedCommands, ...taskCmds])];
7479
+ if (uniqueCmds.length > 0) {
7480
+ for (const cmd of uniqueCmds) {
7258
7481
  lines.push(`\`\`\`bash
7259
7482
  ${cmd}
7260
7483
  \`\`\``);
7261
7484
  }
7262
7485
  } else {
7263
- lines.push("_(No verification commands defined \u2014 run your test suite manually.)_");
7264
- if (stack?.runtime === "node" || stack?.runtime === "bun") {
7265
- lines.push("```bash\nnpm test\n```");
7266
- }
7486
+ lines.push("_(No verification commands detected \u2014 run your test suite manually.)_");
7267
7487
  }
7268
7488
  lines.push("");
7269
7489
  lines.push("## Instructions for your AI tool");
7270
7490
  lines.push("1. Run all verification commands. Report pass/fail for each.");
7271
- lines.push('2. For each checklist item, confirm it is truly done \u2014 not just "the code exists".');
7272
- lines.push("3. List any bugs found using the format below:");
7273
- lines.push("");
7274
- lines.push("```");
7275
- lines.push("BUG: [title]");
7276
- lines.push("Severity: critical | major | minor");
7277
- lines.push("Task: [task name]");
7278
- lines.push("Description: [what is wrong]");
7279
- lines.push("```");
7280
- lines.push("");
7281
- lines.push("4. If there are **critical** bugs, do NOT mark the phase as verified.");
7282
- lines.push('5. If all criteria pass, confirm with: "PHASE VERIFIED \u2713"');
7491
+ lines.push("2. For each checklist item, confirm it is truly done.");
7492
+ lines.push("3. List any bugs found with severity (critical | major | minor).");
7493
+ lines.push("4. Produce a VERIFY.md with the results.");
7283
7494
  lines.push("");
7284
7495
  lines.push("---");
7285
7496
  lines.push("*Generated by BrainForge AI \u2014 paste into your AI tool to verify the phase.*");
7286
7497
  return lines.join("\n");
7287
7498
  }
7288
- buildVerifyMd(phase, bugs = []) {
7499
+ buildVerifyMd(phase, commandResults = [], bugs = []) {
7289
7500
  const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
7290
- const criticalBugs = bugs.filter((b) => b.severity === "critical");
7291
- const blocked = criticalBugs.length > 0;
7501
+ let status = "passed";
7502
+ if (commandResults.length === 0) {
7503
+ status = "manual-required";
7504
+ } else if (commandResults.some((r) => r.status === "failed") || bugs.some((b) => b.severity === "critical")) {
7505
+ status = "failed";
7506
+ }
7507
+ const passedCount = commandResults.filter((r) => r.status === "passed").length;
7508
+ const totalDuration = commandResults.reduce((acc, r) => acc + r.duration, 0);
7292
7509
  const lines = [
7293
- `# Verification Report \u2014 ${phase.name}`,
7294
- `> Generated: ${now} UTC`,
7295
- `> Status: ${blocked ? "\u{1F534} BLOCKED" : bugs.length > 0 ? "\u{1F7E1} ISSUES FOUND" : "\u{1F7E2} VERIFIED"}`,
7510
+ `# Verify Report: ${phase.name}`,
7296
7511
  "",
7297
- "## Tasks"
7512
+ "## Summary",
7513
+ "",
7514
+ `- **Status:** ${status.toUpperCase()}`,
7515
+ `- **Date:** ${now} UTC`,
7516
+ `- **Duration:** ${(totalDuration / 1e3).toFixed(2)}s`,
7517
+ `- **Commands run:** ${commandResults.length}`,
7518
+ `- **Commands passed:** ${passedCount}`,
7519
+ `- **Commands failed:** ${commandResults.length - passedCount}`,
7520
+ "",
7521
+ "## Commands",
7522
+ ""
7298
7523
  ];
7299
- for (const t of phase.tasks) {
7300
- const icon = t.status === "done" ? "\u2713" : "\u25CB";
7301
- lines.push(`- [${icon}] ${t.title}`);
7524
+ for (const r of commandResults) {
7525
+ lines.push(
7526
+ `### \`${r.command}\``,
7527
+ "",
7528
+ `- **Status:** ${r.status === "passed" ? "\u{1F7E2} PASSED" : "\u{1F534} FAILED"}`,
7529
+ `- **Exit code:** ${r.exitCode}`,
7530
+ `- **Duration:** ${(r.duration / 1e3).toFixed(2)}s`,
7531
+ "",
7532
+ "#### stdout",
7533
+ "```txt",
7534
+ r.stdout || "(empty)",
7535
+ "```",
7536
+ "",
7537
+ "#### stderr",
7538
+ "```txt",
7539
+ r.stderr || "(empty)",
7540
+ "```",
7541
+ ""
7542
+ );
7302
7543
  }
7303
7544
  if (bugs.length > 0) {
7304
- lines.push("", "## Bugs Found");
7545
+ lines.push("## Findings", "");
7305
7546
  for (const bug of bugs) {
7306
7547
  lines.push(`### ${bug.id}: ${bug.title}`);
7307
7548
  lines.push(`**Severity:** ${bug.severity}`);
@@ -7310,13 +7551,17 @@ ${cmd}
7310
7551
  lines.push("");
7311
7552
  }
7312
7553
  }
7313
- if (!blocked) {
7314
- lines.push("", "## Next step");
7315
- lines.push("Run `brg ship` to close this phase and update project memory.");
7316
- } else {
7317
- lines.push("", "## Next step");
7318
- lines.push("Fix critical bugs then re-run `brg verify` before shipping.");
7319
- }
7554
+ lines.push(
7555
+ "## Diagnosis",
7556
+ "",
7557
+ status === "passed" ? "* All automated checks passed. Ready to ship." : status === "manual-required" ? "* No automated commands detected. Manual verification is required." : "* Automated checks failed. Review stderr above for root causes.",
7558
+ "* Files likely involved: " + (phase.tasks.flatMap((t) => t.files || []).join(", ") || "N/A"),
7559
+ "",
7560
+ "## Final decision",
7561
+ "",
7562
+ `Phase status changed to: **${status === "passed" ? "verified" : status === "failed" ? "failed" : "needs-manual-verification"}**`,
7563
+ ""
7564
+ );
7320
7565
  return lines.join("\n");
7321
7566
  }
7322
7567
  buildBugsMd(bugs) {
@@ -7724,12 +7969,12 @@ var AutoEngine = class {
7724
7969
  };
7725
7970
 
7726
7971
  // src/scanner/codebase-map.ts
7727
- import fs27 from "fs/promises";
7728
- import path30 from "path";
7972
+ import fs28 from "fs/promises";
7973
+ import path31 from "path";
7729
7974
 
7730
7975
  // src/scanner/stack-detector.ts
7731
- import fs23 from "fs/promises";
7732
- import path25 from "path";
7976
+ import fs24 from "fs/promises";
7977
+ import path26 from "path";
7733
7978
  var FRAMEWORK_MARKERS = {
7734
7979
  "next": ["next"],
7735
7980
  "nuxt": ["nuxt"],
@@ -7802,7 +8047,7 @@ async function detectStack(cwd) {
7802
8047
  let hasRuby = false;
7803
8048
  let runtime;
7804
8049
  try {
7805
- const raw = await fs23.readFile(path25.join(cwd, "package.json"), "utf-8");
8050
+ const raw = await fs24.readFile(path26.join(cwd, "package.json"), "utf-8");
7806
8051
  const pkg = JSON.parse(raw);
7807
8052
  Object.assign(allDeps, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
7808
8053
  if (pkg.engines?.["bun"]) runtime = "bun";
@@ -7921,7 +8166,7 @@ async function detectStack(cwd) {
7921
8166
  }
7922
8167
  async function fileExists4(cwd, ...parts) {
7923
8168
  try {
7924
- await fs23.access(path25.join(cwd, ...parts));
8169
+ await fs24.access(path26.join(cwd, ...parts));
7925
8170
  return true;
7926
8171
  } catch {
7927
8172
  return false;
@@ -7929,8 +8174,8 @@ async function fileExists4(cwd, ...parts) {
7929
8174
  }
7930
8175
 
7931
8176
  // src/scanner/dependency-analyzer.ts
7932
- import fs24 from "fs/promises";
7933
- import path26 from "path";
8177
+ import fs25 from "fs/promises";
8178
+ import path27 from "path";
7934
8179
  async function analyzeDependencies(cwd) {
7935
8180
  const handlers = [
7936
8181
  () => parseNodePackage(cwd),
@@ -7947,7 +8192,7 @@ async function analyzeDependencies(cwd) {
7947
8192
  }
7948
8193
  async function parseNodePackage(cwd) {
7949
8194
  try {
7950
- const raw = await fs24.readFile(path26.join(cwd, "package.json"), "utf-8");
8195
+ const raw = await fs25.readFile(path27.join(cwd, "package.json"), "utf-8");
7951
8196
  const pkg = JSON.parse(raw);
7952
8197
  const runtime = Object.entries(pkg.dependencies ?? {}).map(([name, version]) => ({
7953
8198
  name,
@@ -7972,7 +8217,7 @@ async function parseNodePackage(cwd) {
7972
8217
  }
7973
8218
  async function parsePythonRequirements(cwd) {
7974
8219
  try {
7975
- const raw = await fs24.readFile(path26.join(cwd, "pyproject.toml"), "utf-8");
8220
+ const raw = await fs25.readFile(path27.join(cwd, "pyproject.toml"), "utf-8");
7976
8221
  const deps = [];
7977
8222
  const depSection = raw.match(/\[project\][\s\S]*?dependencies\s*=\s*\[([\s\S]*?)\]/);
7978
8223
  if (depSection?.[1]) {
@@ -7990,7 +8235,7 @@ async function parsePythonRequirements(cwd) {
7990
8235
  } catch {
7991
8236
  }
7992
8237
  try {
7993
- const raw = await fs24.readFile(path26.join(cwd, "requirements.txt"), "utf-8");
8238
+ const raw = await fs25.readFile(path27.join(cwd, "requirements.txt"), "utf-8");
7994
8239
  const deps = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && !l.startsWith("-")).map((l) => {
7995
8240
  const m = l.match(/^([a-zA-Z0-9_.-]+)([>=<!].+)?$/);
7996
8241
  return m ? { name: m[1], version: m[2] ?? "*", dev: false } : null;
@@ -8004,7 +8249,7 @@ async function parsePythonRequirements(cwd) {
8004
8249
  }
8005
8250
  async function parseGoMod(cwd) {
8006
8251
  try {
8007
- const raw = await fs24.readFile(path26.join(cwd, "go.mod"), "utf-8");
8252
+ const raw = await fs25.readFile(path27.join(cwd, "go.mod"), "utf-8");
8008
8253
  const deps = [];
8009
8254
  for (const line of raw.split("\n")) {
8010
8255
  const m = line.trim().match(/^([a-zA-Z0-9_./-]+)\s+(v[\d.]+.*)$/);
@@ -8017,7 +8262,7 @@ async function parseGoMod(cwd) {
8017
8262
  }
8018
8263
  async function parseCargoToml(cwd) {
8019
8264
  try {
8020
- const raw = await fs24.readFile(path26.join(cwd, "Cargo.toml"), "utf-8");
8265
+ const raw = await fs25.readFile(path27.join(cwd, "Cargo.toml"), "utf-8");
8021
8266
  const deps = [];
8022
8267
  let inDeps = false;
8023
8268
  let inDevDeps = false;
@@ -8048,7 +8293,7 @@ async function parseCargoToml(cwd) {
8048
8293
  }
8049
8294
  async function parseComposerJson(cwd) {
8050
8295
  try {
8051
- const raw = await fs24.readFile(path26.join(cwd, "composer.json"), "utf-8");
8296
+ const raw = await fs25.readFile(path27.join(cwd, "composer.json"), "utf-8");
8052
8297
  const pkg = JSON.parse(raw);
8053
8298
  const runtime = Object.entries(pkg.require ?? {}).filter(([name]) => name !== "php").map(([name, version]) => ({ name, version, dev: false }));
8054
8299
  const dev = Object.entries(pkg["require-dev"] ?? {}).map(([name, version]) => ({
@@ -8109,8 +8354,8 @@ function pickHighlights(deps) {
8109
8354
  }
8110
8355
 
8111
8356
  // src/scanner/convention-detector.ts
8112
- import fs25 from "fs/promises";
8113
- import path27 from "path";
8357
+ import fs26 from "fs/promises";
8358
+ import path28 from "path";
8114
8359
  async function detectConventions(cwd, files) {
8115
8360
  const srcFiles = files.filter((f) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
8116
8361
  const namingStyle = detectNamingStyle(srcFiles);
@@ -8135,7 +8380,7 @@ async function detectConventions(cwd, files) {
8135
8380
  };
8136
8381
  }
8137
8382
  function detectNamingStyle(files) {
8138
- const basenames = files.map((f) => path27.basename(f, path27.extname(f))).filter((b) => b.length > 2 && !/^index$|^main$|^app$/.test(b));
8383
+ const basenames = files.map((f) => path28.basename(f, path28.extname(f))).filter((b) => b.length > 2 && !/^index$|^main$|^app$/.test(b));
8139
8384
  const counts = { camel: 0, snake: 0, kebab: 0, pascal: 0 };
8140
8385
  for (const b of basenames) {
8141
8386
  if (/^[A-Z][a-zA-Z0-9]*$/.test(b)) counts.pascal++;
@@ -8170,7 +8415,7 @@ function detectFolderPattern(files) {
8170
8415
  }
8171
8416
  async function detectModuleStyle(cwd) {
8172
8417
  try {
8173
- const raw = await fs25.readFile(path27.join(cwd, "package.json"), "utf-8");
8418
+ const raw = await fs26.readFile(path28.join(cwd, "package.json"), "utf-8");
8174
8419
  const pkg = JSON.parse(raw);
8175
8420
  if (pkg.type === "module") return "esm";
8176
8421
  if (pkg.type === "commonjs") return "cjs";
@@ -8181,7 +8426,7 @@ async function detectModuleStyle(cwd) {
8181
8426
  }
8182
8427
  async function detectTsStrict(cwd) {
8183
8428
  try {
8184
- const raw = await fs25.readFile(path27.join(cwd, "tsconfig.json"), "utf-8");
8429
+ const raw = await fs26.readFile(path28.join(cwd, "tsconfig.json"), "utf-8");
8185
8430
  const stripped = raw.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
8186
8431
  const tsconfig = JSON.parse(stripped);
8187
8432
  return tsconfig.compilerOptions?.strict === true;
@@ -8210,7 +8455,7 @@ function detectEntryPoint(files) {
8210
8455
  }
8211
8456
  async function fileExists5(cwd, ...parts) {
8212
8457
  try {
8213
- await fs25.access(path27.join(cwd, ...parts));
8458
+ await fs26.access(path28.join(cwd, ...parts));
8214
8459
  return true;
8215
8460
  } catch {
8216
8461
  return false;
@@ -8218,8 +8463,8 @@ async function fileExists5(cwd, ...parts) {
8218
8463
  }
8219
8464
 
8220
8465
  // src/scanner/risk-detector.ts
8221
- import fs26 from "fs/promises";
8222
- import path28 from "path";
8466
+ import fs27 from "fs/promises";
8467
+ import path29 from "path";
8223
8468
  var LARGE_FILE_THRESHOLD = 500;
8224
8469
  var HUGE_FILE_THRESHOLD = 1e3;
8225
8470
  var SECRET_PATTERNS = [
@@ -8255,7 +8500,7 @@ async function detectRisks(cwd, files, fileContents) {
8255
8500
  }
8256
8501
  }
8257
8502
  for (const f of files) {
8258
- const basename = path28.basename(f);
8503
+ const basename = path29.basename(f);
8259
8504
  if (SECRET_PATTERNS.some((p) => p.test(basename)) && !basename.endsWith(".example")) {
8260
8505
  items.push({
8261
8506
  level: "high",
@@ -8292,7 +8537,7 @@ async function detectRisks(cwd, files, fileContents) {
8292
8537
  } else if (todoCount > 5) {
8293
8538
  items.push({ level: "low", category: "quality", message: `${todoCount} TODO/FIXME/HACK comments found` });
8294
8539
  }
8295
- if (!files.some((f) => path28.basename(f) === ".gitignore")) {
8540
+ if (!files.some((f) => path29.basename(f) === ".gitignore")) {
8296
8541
  items.push({ level: "medium", category: "hygiene", message: "No .gitignore found" });
8297
8542
  }
8298
8543
  const hasTestFiles = files.some(
@@ -8301,12 +8546,12 @@ async function detectRisks(cwd, files, fileContents) {
8301
8546
  if (!hasTestFiles) {
8302
8547
  items.push({ level: "high", category: "quality", message: "No test files detected" });
8303
8548
  }
8304
- if (!files.some((f) => /^readme\.md$/i.test(path28.basename(f)))) {
8549
+ if (!files.some((f) => /^readme\.md$/i.test(path29.basename(f)))) {
8305
8550
  items.push({ level: "low", category: "documentation", message: "No README.md found" });
8306
8551
  }
8307
- const hasPackageJson = files.some((f) => path28.basename(f) === "package.json");
8552
+ const hasPackageJson = files.some((f) => path29.basename(f) === "package.json");
8308
8553
  const hasLockFile = files.some(
8309
- (f) => ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb", "bun.lock"].includes(path28.basename(f))
8554
+ (f) => ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb", "bun.lock"].includes(path29.basename(f))
8310
8555
  );
8311
8556
  if (hasPackageJson && !hasLockFile) {
8312
8557
  items.push({ level: "medium", category: "hygiene", message: "Node project has no lock file committed" });
@@ -8337,8 +8582,8 @@ async function loadFileContents(cwd, files, maxLines = 1500) {
8337
8582
  await Promise.all(
8338
8583
  readable.map(async (rel) => {
8339
8584
  try {
8340
- const abs = path28.join(cwd, rel);
8341
- const raw = await fs26.readFile(abs, "utf-8");
8585
+ const abs = path29.join(cwd, rel);
8586
+ const raw = await fs27.readFile(abs, "utf-8");
8342
8587
  const lines = raw.split("\n");
8343
8588
  map.set(rel, lines.slice(0, maxLines).join("\n"));
8344
8589
  } catch {
@@ -8349,7 +8594,7 @@ async function loadFileContents(cwd, files, maxLines = 1500) {
8349
8594
  }
8350
8595
 
8351
8596
  // src/scanner/test-detector.ts
8352
- import path29 from "path";
8597
+ import path30 from "path";
8353
8598
  var TEST_FRAMEWORK_MARKERS = {
8354
8599
  vitest: ["vitest"],
8355
8600
  jest: ["jest", "@jest/core"],
@@ -8413,7 +8658,7 @@ function detectTests(files, deps, scripts) {
8413
8658
  );
8414
8659
  const coverageRatio = sourceFiles.length > 0 ? testFiles.length / sourceFiles.length : 0;
8415
8660
  const hasConfig = files.some(
8416
- (f) => TEST_CONFIG_FILES.some((c) => path29.basename(f) === c)
8661
+ (f) => TEST_CONFIG_FILES.some((c) => path30.basename(f) === c)
8417
8662
  );
8418
8663
  const coverageEnabled = "c8" in deps || "@vitest/coverage-v8" in deps || "@vitest/coverage-istanbul" in deps || Boolean(scripts?.["coverage"]) || Boolean(scripts?.["test:coverage"]);
8419
8664
  const testCommand = scripts?.["test"] ?? scripts?.["test:unit"];
@@ -8480,13 +8725,13 @@ var IGNORE_EXTENSIONS = /* @__PURE__ */ new Set([
8480
8725
  var CodebaseScanner = class {
8481
8726
  constructor(cwd) {
8482
8727
  this.cwd = cwd;
8483
- this.codebaseDir = path30.join(cwd, ".brainforge", "codebase");
8728
+ this.codebaseDir = path31.join(cwd, ".brainforge", "codebase");
8484
8729
  }
8485
8730
  cwd;
8486
8731
  codebaseDir;
8487
8732
  async scan(deep = false) {
8488
8733
  const files = await this.walkFiles(this.cwd, deep ? 8 : 5);
8489
- const relFiles = files.map((f) => path30.relative(this.cwd, f).replace(/\\/g, "/"));
8734
+ const relFiles = files.map((f) => path31.relative(this.cwd, f).replace(/\\/g, "/"));
8490
8735
  const fileContents = await loadFileContents(this.cwd, relFiles);
8491
8736
  const [stack, dependencies, conventions, risks] = await Promise.all([
8492
8737
  detectStack(this.cwd),
@@ -8496,7 +8741,7 @@ var CodebaseScanner = class {
8496
8741
  ]);
8497
8742
  let scripts;
8498
8743
  try {
8499
- const raw = await fs27.readFile(path30.join(this.cwd, "package.json"), "utf-8");
8744
+ const raw = await fs28.readFile(path31.join(this.cwd, "package.json"), "utf-8");
8500
8745
  scripts = JSON.parse(raw).scripts;
8501
8746
  } catch {
8502
8747
  }
@@ -8521,20 +8766,20 @@ var CodebaseScanner = class {
8521
8766
  };
8522
8767
  }
8523
8768
  async save(result) {
8524
- await fs27.mkdir(this.codebaseDir, { recursive: true });
8769
+ await fs28.mkdir(this.codebaseDir, { recursive: true });
8525
8770
  await Promise.all([
8526
- fs27.writeFile(path30.join(this.codebaseDir, "STACK.json"), JSON.stringify(result.stack, null, 2), "utf-8"),
8527
- fs27.writeFile(path30.join(this.codebaseDir, "FILES.json"), JSON.stringify(result.files, null, 2), "utf-8"),
8528
- fs27.writeFile(path30.join(this.codebaseDir, "MAP.md"), this.toMapMd(result), "utf-8"),
8529
- fs27.writeFile(path30.join(this.codebaseDir, "CONVENTIONS.md"), this.toConventionsMd(result), "utf-8"),
8530
- fs27.writeFile(path30.join(this.codebaseDir, "RISKS.md"), this.toRisksMd(result), "utf-8"),
8531
- fs27.writeFile(path30.join(this.codebaseDir, "TESTS.md"), this.toTestsMd(result), "utf-8")
8771
+ fs28.writeFile(path31.join(this.codebaseDir, "STACK.json"), JSON.stringify(result.stack, null, 2), "utf-8"),
8772
+ fs28.writeFile(path31.join(this.codebaseDir, "FILES.json"), JSON.stringify(result.files, null, 2), "utf-8"),
8773
+ fs28.writeFile(path31.join(this.codebaseDir, "MAP.md"), this.toMapMd(result), "utf-8"),
8774
+ fs28.writeFile(path31.join(this.codebaseDir, "CONVENTIONS.md"), this.toConventionsMd(result), "utf-8"),
8775
+ fs28.writeFile(path31.join(this.codebaseDir, "RISKS.md"), this.toRisksMd(result), "utf-8"),
8776
+ fs28.writeFile(path31.join(this.codebaseDir, "TESTS.md"), this.toTestsMd(result), "utf-8")
8532
8777
  ]);
8533
8778
  }
8534
8779
  async loadLatest() {
8535
8780
  try {
8536
- const stack = JSON.parse(await fs27.readFile(path30.join(this.codebaseDir, "STACK.json"), "utf-8"));
8537
- const files = JSON.parse(await fs27.readFile(path30.join(this.codebaseDir, "FILES.json"), "utf-8"));
8781
+ const stack = JSON.parse(await fs28.readFile(path31.join(this.codebaseDir, "STACK.json"), "utf-8"));
8782
+ const files = JSON.parse(await fs28.readFile(path31.join(this.codebaseDir, "FILES.json"), "utf-8"));
8538
8783
  return { stack, files };
8539
8784
  } catch {
8540
8785
  return null;
@@ -8646,7 +8891,7 @@ var CodebaseScanner = class {
8646
8891
  let size = 0;
8647
8892
  let lines = 0;
8648
8893
  try {
8649
- const stat = await fs27.stat(abs);
8894
+ const stat = await fs28.stat(abs);
8650
8895
  size = stat.size;
8651
8896
  const content = contents.get(rel);
8652
8897
  lines = content ? content.split("\n").length : 0;
@@ -8658,18 +8903,18 @@ var CodebaseScanner = class {
8658
8903
  }
8659
8904
  async walkFiles(dir, maxDepth, depth = 0) {
8660
8905
  if (depth > maxDepth) return [];
8661
- const entries = await fs27.readdir(dir, { withFileTypes: true });
8906
+ const entries = await fs28.readdir(dir, { withFileTypes: true });
8662
8907
  const results = [];
8663
8908
  for (const entry of entries) {
8664
8909
  if (entry.name.startsWith(".") && entry.name !== ".gitignore" && entry.name !== ".env.example") {
8665
8910
  if (IGNORE_DIRS.has(entry.name)) continue;
8666
8911
  }
8667
8912
  if (IGNORE_DIRS.has(entry.name)) continue;
8668
- const full = path30.join(dir, entry.name);
8913
+ const full = path31.join(dir, entry.name);
8669
8914
  if (entry.isDirectory()) {
8670
8915
  results.push(...await this.walkFiles(full, maxDepth, depth + 1));
8671
8916
  } else if (entry.isFile()) {
8672
- const ext = path30.extname(entry.name).toLowerCase();
8917
+ const ext = path31.extname(entry.name).toLowerCase();
8673
8918
  if (!IGNORE_EXTENSIONS.has(ext)) results.push(full);
8674
8919
  }
8675
8920
  }
@@ -8677,12 +8922,12 @@ var CodebaseScanner = class {
8677
8922
  }
8678
8923
  async readProjectName() {
8679
8924
  try {
8680
- const raw = await fs27.readFile(path30.join(this.cwd, "package.json"), "utf-8");
8925
+ const raw = await fs28.readFile(path31.join(this.cwd, "package.json"), "utf-8");
8681
8926
  const pkg = JSON.parse(raw);
8682
8927
  if (pkg.name) return pkg.name;
8683
8928
  } catch {
8684
8929
  }
8685
- return path30.basename(this.cwd);
8930
+ return path31.basename(this.cwd);
8686
8931
  }
8687
8932
  };
8688
8933
  function inferRole(filePath) {
@@ -8804,12 +9049,12 @@ function summariseEntries(entries) {
8804
9049
  }
8805
9050
 
8806
9051
  // src/memory/context-packets.ts
8807
- import fs28 from "fs/promises";
8808
- import path31 from "path";
9052
+ import fs29 from "fs/promises";
9053
+ import path32 from "path";
8809
9054
  var ContextPacketBuilder = class {
8810
9055
  constructor(cwd) {
8811
9056
  this.cwd = cwd;
8812
- this.packetDir = path31.join(cwd, ".brainforge", "memory", "context-packets");
9057
+ this.packetDir = path32.join(cwd, ".brainforge", "memory", "context-packets");
8813
9058
  }
8814
9059
  cwd;
8815
9060
  packetDir;
@@ -8882,14 +9127,14 @@ var ContextPacketBuilder = class {
8882
9127
  };
8883
9128
  }
8884
9129
  async save(packet) {
8885
- await fs28.mkdir(this.packetDir, { recursive: true });
8886
- const jsonPath = path31.join(this.packetDir, "latest.json");
8887
- const mdPath = path31.join(this.packetDir, "latest.md");
8888
- await fs28.writeFile(jsonPath, JSON.stringify(packet, null, 2), "utf-8");
8889
- await fs28.writeFile(mdPath, this.toMarkdown(packet), "utf-8");
9130
+ await fs29.mkdir(this.packetDir, { recursive: true });
9131
+ const jsonPath = path32.join(this.packetDir, "latest.json");
9132
+ const mdPath = path32.join(this.packetDir, "latest.md");
9133
+ await fs29.writeFile(jsonPath, JSON.stringify(packet, null, 2), "utf-8");
9134
+ await fs29.writeFile(mdPath, this.toMarkdown(packet), "utf-8");
8890
9135
  const ts = packet.generatedAt.replace(/[:.]/g, "-").slice(0, 19);
8891
- await fs28.writeFile(
8892
- path31.join(this.packetDir, `packet-${ts}.json`),
9136
+ await fs29.writeFile(
9137
+ path32.join(this.packetDir, `packet-${ts}.json`),
8893
9138
  JSON.stringify(packet, null, 2),
8894
9139
  "utf-8"
8895
9140
  );
@@ -8897,7 +9142,7 @@ var ContextPacketBuilder = class {
8897
9142
  }
8898
9143
  async loadLatest() {
8899
9144
  try {
8900
- const raw = await fs28.readFile(path31.join(this.packetDir, "latest.json"), "utf-8");
9145
+ const raw = await fs29.readFile(path32.join(this.packetDir, "latest.json"), "utf-8");
8901
9146
  return JSON.parse(raw);
8902
9147
  } catch {
8903
9148
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainforge/core",
3
- "version": "3.1.11",
3
+ "version": "3.1.13",
4
4
  "description": "Core engine for BrainForge AI — state management, AST mapper, skills, and AI adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",