@brainforge/core 3.1.12 → 3.1.14

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
@@ -3,7 +3,7 @@ import { EventEmitter } from 'events';
3
3
  type ProjectType = 'student' | 'junior' | 'solo' | 'personal' | 'pro';
4
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' | 'draft' | 'discussed' | 'planned' | 'checked' | 'executing' | 'executed' | 'verified' | 'failed' | 'fixed' | 'shipped';
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';
@@ -152,10 +152,7 @@ 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
+ updatePhaseStatus(phaseId: string, newStatus: PhaseStatus, force?: boolean): Promise<void>;
159
156
  load(): Promise<BrainforgeState>;
160
157
  save(newState?: BrainforgeState): Promise<void>;
161
158
  init(project: ProjectConfig): Promise<BrainforgeState>;
@@ -750,6 +747,14 @@ interface BugEntry {
750
747
  taskId?: string;
751
748
  description?: string;
752
749
  }
750
+ interface CommandResult {
751
+ command: string;
752
+ status: 'passed' | 'failed' | 'timeout';
753
+ exitCode: number | null;
754
+ stdout: string;
755
+ stderr: string;
756
+ duration: number;
757
+ }
753
758
  interface VerifyResult {
754
759
  phaseId: string;
755
760
  phaseName: string;
@@ -758,11 +763,14 @@ interface VerifyResult {
758
763
  passed: number;
759
764
  bugs: BugEntry[];
760
765
  blocked: boolean;
766
+ commandResults?: CommandResult[];
761
767
  }
762
768
  declare class VerificationEngine {
769
+ private timeoutMs;
763
770
  detectAvailableCommands(cwd: string): Promise<string[]>;
771
+ runCommands(commands: string[], cwd: string): Promise<CommandResult[]>;
764
772
  buildVerifyPrompt(phase: Phase, stack?: StackInfo, detectedCommands?: string[]): string;
765
- buildVerifyMd(phase: Phase, bugs?: BugEntry[]): string;
773
+ buildVerifyMd(phase: Phase, commandResults?: CommandResult[], bugs?: BugEntry[]): string;
766
774
  buildBugsMd(bugs: BugEntry[]): string;
767
775
  }
768
776
 
package/dist/index.js CHANGED
@@ -166,7 +166,25 @@ 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
+ var VALID_TRANSITIONS = {
170
+ pending: ["active", "draft"],
171
+ draft: ["discussed", "active"],
172
+ discussed: ["planned", "active"],
173
+ active: ["discussed", "planned", "blocked", "executing"],
174
+ planned: ["executing", "checked", "active"],
175
+ checked: ["executing", "active"],
176
+ executing: ["executed", "failed", "blocked"],
177
+ executed: ["verified", "failed", "needs-manual-verification"],
178
+ "needs-manual-verification": ["verified", "failed"],
179
+ verified: ["shipped", "failed"],
180
+ failed: ["fixed", "active", "planned"],
181
+ fixed: ["verified", "executing"],
182
+ shipped: [],
183
+ // Terminal state
184
+ completed: [],
185
+ // Terminal state
186
+ blocked: ["active", "failed"]
187
+ };
170
188
  var StateManager = class {
171
189
  constructor(workingDir) {
172
190
  this.workingDir = workingDir;
@@ -175,20 +193,23 @@ var StateManager = class {
175
193
  workingDir;
176
194
  statePath;
177
195
  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");
196
+ // ... (getPhaseDir, ensurePhaseDir, writePhaseFile, readPhaseFile unchanged)
197
+ async updatePhaseStatus(phaseId, newStatus, force = false) {
198
+ const phase = this.get().phases.find((p) => p.id === phaseId);
199
+ if (!phase) throw new Error(`Phase "${phaseId}" not found.`);
200
+ if (!force) {
201
+ const allowed = VALID_TRANSITIONS[phase.status] || [];
202
+ if (!allowed.includes(newStatus) && phase.status !== newStatus) {
203
+ throw new Error(`Invalid transition: cannot move phase from "${phase.status}" to "${newStatus}".`);
204
+ }
205
+ }
206
+ phase.status = newStatus;
207
+ if (newStatus === "shipped" || newStatus === "completed") {
208
+ phase.completedAt = (/* @__PURE__ */ new Date()).toISOString();
209
+ }
210
+ await this.save();
191
211
  }
212
+ // ... (rest of the methods)
192
213
  async load() {
193
214
  const raw = await fs3.readFile(this.statePath, "utf-8");
194
215
  this.state = JSON.parse(raw);
@@ -604,16 +625,20 @@ Identify:
604
625
  Ensure every acceptance criterion is covered.`,
605
626
  fix_phase: `You are BrainForge AI. Generate a fix plan for phase "{{phase.name}}".
606
627
 
607
- Bugs/Failures: {{failures}}
608
- Original Plan: {{plan}}
628
+ # Verification Results
629
+ {{failures}}
630
+
631
+ # Original Plan Context
632
+ {{plan}}
609
633
 
610
- Analyze:
611
- - Root cause hypotheses
612
- - Files to inspect
613
- - Proposed correction steps
614
- - Commands to re-run
634
+ # Analysis Requirements
635
+ 1. Identify the failed commands and their error outputs.
636
+ 2. Formulate root cause hypotheses for each failure.
637
+ 3. List specific files to inspect and potential logical bugs.
638
+ 4. Propose step-by-step correction instructions for an AI agent.
639
+ 5. Identify the exact verification commands to re-run after fixes.
615
640
 
616
- Produce a FIX_PLAN.md.`,
641
+ Produce a FIX_PLAN.md that is honest: it guides the fix, it doesn't pretend to have fixed it automatically.`,
617
642
  ship_phase: `You are BrainForge AI. Generate a final ship report for phase "{{phase.name}}".
618
643
 
619
644
  Completed Tasks: {{tasks}}
@@ -7362,7 +7387,12 @@ ${cmd}
7362
7387
  // src/workflow/verification-engine.ts
7363
7388
  import fs23 from "fs/promises";
7364
7389
  import path25 from "path";
7390
+ import { exec as exec2 } from "child_process";
7391
+ import { promisify as promisify2 } from "util";
7392
+ var execAsync2 = promisify2(exec2);
7365
7393
  var VerificationEngine = class {
7394
+ timeoutMs = 5 * 60 * 1e3;
7395
+ // 5 minutes default
7366
7396
  async detectAvailableCommands(cwd) {
7367
7397
  const commands = [];
7368
7398
  try {
@@ -7370,10 +7400,15 @@ var VerificationEngine = class {
7370
7400
  const raw = await fs23.readFile(pkgPath, "utf-8");
7371
7401
  const pkg = JSON.parse(raw);
7372
7402
  const scripts = pkg.scripts || {};
7373
- if (scripts.build) commands.push("npm run build");
7374
- if (scripts.test) commands.push("npm test");
7375
- if (scripts.lint) commands.push("npm run lint");
7376
- if (scripts.typecheck) commands.push("npm run typecheck");
7403
+ const hasYarn = await fs23.access(path25.join(cwd, "yarn.lock")).then(() => true).catch(() => false);
7404
+ const hasPnpm = await fs23.access(path25.join(cwd, "pnpm-lock.yaml")).then(() => true).catch(() => false);
7405
+ const hasBun = await fs23.access(path25.join(cwd, "bun.lockb")).then(() => true).catch(() => false);
7406
+ const manager = hasBun ? "bun" : hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
7407
+ const run = manager === "npm" || manager === "bun" ? `${manager} run` : manager;
7408
+ if (scripts.build) commands.push(`${run} build`);
7409
+ if (scripts.test) commands.push(`${manager} test`);
7410
+ if (scripts.lint) commands.push(`${run} lint`);
7411
+ if (scripts.typecheck) commands.push(`${run} typecheck`);
7377
7412
  } catch {
7378
7413
  }
7379
7414
  try {
@@ -7393,11 +7428,49 @@ var VerificationEngine = class {
7393
7428
  }
7394
7429
  try {
7395
7430
  await fs23.access(path25.join(cwd, "build.gradle"));
7396
- commands.push("gradle test");
7431
+ commands.push("./gradlew test");
7432
+ } catch {
7433
+ }
7434
+ try {
7435
+ await fs23.access(path25.join(cwd, "go.mod"));
7436
+ commands.push("go test ./...");
7437
+ } catch {
7438
+ }
7439
+ try {
7440
+ await fs23.access(path25.join(cwd, "Cargo.toml"));
7441
+ commands.push("cargo test");
7397
7442
  } catch {
7398
7443
  }
7399
7444
  return commands;
7400
7445
  }
7446
+ async runCommands(commands, cwd) {
7447
+ const results = [];
7448
+ for (const command of commands) {
7449
+ const start = Date.now();
7450
+ try {
7451
+ const { stdout, stderr } = await execAsync2(command, { cwd, timeout: this.timeoutMs });
7452
+ results.push({
7453
+ command,
7454
+ status: "passed",
7455
+ exitCode: 0,
7456
+ stdout,
7457
+ stderr,
7458
+ duration: Date.now() - start
7459
+ });
7460
+ } catch (err) {
7461
+ const isTimeout = err.signal === "SIGTERM" || err.killed;
7462
+ results.push({
7463
+ command,
7464
+ status: isTimeout ? "timeout" : "failed",
7465
+ exitCode: err.code ?? null,
7466
+ stdout: err.stdout || "",
7467
+ stderr: err.stderr || err.message,
7468
+ duration: Date.now() - start
7469
+ });
7470
+ }
7471
+ }
7472
+ return results;
7473
+ }
7401
7474
  buildVerifyPrompt(phase, stack, detectedCommands = []) {
7402
7475
  const allCriteria = [];
7403
7476
  for (const t of phase.tasks) {
@@ -7447,23 +7520,54 @@ ${cmd}
7447
7520
  lines.push("*Generated by BrainForge AI \u2014 paste into your AI tool to verify the phase.*");
7448
7521
  return lines.join("\n");
7449
7522
  }
7450
- buildVerifyMd(phase, bugs = []) {
7523
+ buildVerifyMd(phase, commandResults = [], bugs = []) {
7451
7524
  const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
7452
- const criticalBugs = bugs.filter((b) => b.severity === "critical");
7453
- const blocked = criticalBugs.length > 0;
7525
+ let status = "passed";
7526
+ if (commandResults.length === 0) {
7527
+ status = "manual-required";
7528
+ } else if (commandResults.some((r) => r.status !== "passed") || bugs.some((b) => b.severity === "critical")) {
7529
+ status = "failed";
7530
+ }
7531
+ const passedCount = commandResults.filter((r) => r.status === "passed").length;
7532
+ const totalDuration = commandResults.reduce((acc, r) => acc + r.duration, 0);
7454
7533
  const lines = [
7455
- `# Verification Report \u2014 ${phase.name}`,
7456
- `> Generated: ${now} UTC`,
7457
- `> Status: ${blocked ? "\u{1F534} BLOCKED" : bugs.length > 0 ? "\u{1F7E1} ISSUES FOUND" : "\u{1F7E2} VERIFIED"}`,
7534
+ `# Verify Report: ${phase.name}`,
7458
7535
  "",
7459
- "## Tasks"
7536
+ "## Summary",
7537
+ "",
7538
+ `- **Status:** ${status.toUpperCase()}`,
7539
+ `- **Date:** ${now} UTC`,
7540
+ `- **Duration:** ${(totalDuration / 1e3).toFixed(2)}s`,
7541
+ `- **Commands run:** ${commandResults.length}`,
7542
+ `- **Commands passed:** ${passedCount}`,
7543
+ `- **Commands failed:** ${commandResults.length - passedCount}`,
7544
+ "",
7545
+ "## Commands",
7546
+ ""
7460
7547
  ];
7461
- for (const t of phase.tasks) {
7462
- const icon = t.status === "done" ? "\u2713" : "\u25CB";
7463
- lines.push(`- [${icon}] ${t.title}`);
7548
+ for (const r of commandResults) {
7549
+ const statusIcon = r.status === "passed" ? "\u{1F7E2} PASSED" : r.status === "timeout" ? "\u23F3 TIMEOUT" : "\u{1F534} FAILED";
7550
+ lines.push(
7551
+ `### \`${r.command}\``,
7552
+ "",
7553
+ `- **Status:** ${statusIcon}`,
7554
+ `- **Exit code:** ${r.exitCode ?? "N/A"}`,
7555
+ `- **Duration:** ${(r.duration / 1e3).toFixed(2)}s`,
7556
+ "",
7557
+ "#### stdout",
7558
+ "```txt",
7559
+ r.stdout || "(empty)",
7560
+ "```",
7561
+ "",
7562
+ "#### stderr",
7563
+ "```txt",
7564
+ r.stderr || "(empty)",
7565
+ "```",
7566
+ ""
7567
+ );
7464
7568
  }
7465
7569
  if (bugs.length > 0) {
7466
- lines.push("", "## Bugs Found");
7570
+ lines.push("## Findings", "");
7467
7571
  for (const bug of bugs) {
7468
7572
  lines.push(`### ${bug.id}: ${bug.title}`);
7469
7573
  lines.push(`**Severity:** ${bug.severity}`);
@@ -7472,13 +7576,17 @@ ${cmd}
7472
7576
  lines.push("");
7473
7577
  }
7474
7578
  }
7475
- if (!blocked) {
7476
- lines.push("", "## Next step");
7477
- lines.push("Run `brg ship` to close this phase and update project memory.");
7478
- } else {
7479
- lines.push("", "## Next step");
7480
- lines.push("Fix critical bugs then re-run `brg verify` before shipping.");
7481
- }
7579
+ lines.push(
7580
+ "## Diagnosis",
7581
+ "",
7582
+ 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.",
7583
+ "* Files likely involved: " + (phase.tasks.flatMap((t) => t.files || []).join(", ") || "N/A"),
7584
+ "",
7585
+ "## Final decision",
7586
+ "",
7587
+ `Phase status changed to: **${status === "passed" ? "verified" : status === "failed" ? "failed" : "needs-manual-verification"}**`,
7588
+ ""
7589
+ );
7482
7590
  return lines.join("\n");
7483
7591
  }
7484
7592
  buildBugsMd(bugs) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainforge/core",
3
- "version": "3.1.12",
3
+ "version": "3.1.14",
4
4
  "description": "Core engine for BrainForge AI — state management, AST mapper, skills, and AI adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",