@brainforge/core 3.1.11 → 3.1.12

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';
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;
@@ -755,7 +760,8 @@ interface VerifyResult {
755
760
  blocked: boolean;
756
761
  }
757
762
  declare class VerificationEngine {
758
- buildVerifyPrompt(phase: Phase, stack?: StackInfo): string;
763
+ detectAvailableCommands(cwd: string): Promise<string[]>;
764
+ buildVerifyPrompt(phase: Phase, stack?: StackInfo, detectedCommands?: string[]): string;
759
765
  buildVerifyMd(phase: Phase, bugs?: BugEntry[]): string;
760
766
  buildBugsMd(bugs: BugEntry[]): string;
761
767
  }
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",
332
+ "",
333
+ phaseBlocks.length ? phaseBlocks.join("\n\n") : "No phases yet.",
306
334
  "",
307
- phaseBlocks.length ? phaseBlocks.join("\n\n") : "No phases yet."
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,102 @@ 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
+ Bugs/Failures: {{failures}}
608
+ Original Plan: {{plan}}
609
+
610
+ Analyze:
611
+ - Root cause hypotheses
612
+ - Files to inspect
613
+ - Proposed correction steps
614
+ - Commands to re-run
615
+
616
+ Produce a FIX_PLAN.md.`,
617
+ ship_phase: `You are BrainForge AI. Generate a final ship report for phase "{{phase.name}}".
618
+
619
+ Completed Tasks: {{tasks}}
620
+ Verification Status: {{status}}
621
+
622
+ Summarize:
623
+ - Key features implemented
624
+ - Architecture decisions made
625
+ - Remaining technical debt
626
+ - Recommended next phase
627
+
628
+ Produce a SHIP.md.`,
629
+ professor_review: `You are a Professor AI. Review these advanced patterns found in a {{level}} student's {{language}} project.
630
+
631
+ Matches:
632
+ {{#each matches}}
633
+ - {{name}} in {{file}} (line {{line}}): "{{snippet}}"
634
+ {{/each}}
635
+
636
+ For each pattern:
637
+ 1. Explain it simply as if to a student.
638
+ 2. Provide a simpler alternative if applicable.
639
+ 3. Suggest 1-2 tricky questions a professor might ask about this.
640
+ 4. Provide the expected technical answer.
641
+
642
+ Identify if the overall code level feels like beginner, intermediate, or professional based on these patterns.`,
523
643
  plan: `You are BrainForge AI. Analyze the following task for project "{{project.name}}":
524
644
 
525
645
  Task: {{task.title}}
@@ -1216,7 +1336,7 @@ var ProfessorScanner = class {
1216
1336
  this.workingDir = workingDir;
1217
1337
  }
1218
1338
  workingDir;
1219
- async scan(language, studentLevel) {
1339
+ async scan(language, studentLevel, useAI = false) {
1220
1340
  const applicable = PATTERNS.filter(
1221
1341
  (p) => p.languages.includes(language) && p.flagForLevels.includes(studentLevel)
1222
1342
  );
@@ -1245,6 +1365,19 @@ var ProfessorScanner = class {
1245
1365
  pattern.regex.lastIndex = 0;
1246
1366
  }
1247
1367
  }
1368
+ let aiAnalysis;
1369
+ if (useAI && matches.length > 0) {
1370
+ try {
1371
+ const engine = new PromptEngine();
1372
+ aiAnalysis = engine.render("professor_review", {
1373
+ language,
1374
+ level: studentLevel,
1375
+ matches,
1376
+ count: matches.length
1377
+ });
1378
+ } catch {
1379
+ }
1380
+ }
1248
1381
  const errors = matches.filter((m) => m.severity === "error").length;
1249
1382
  const warnings = matches.filter((m) => m.severity === "warn").length;
1250
1383
  const report = {
@@ -1253,6 +1386,7 @@ var ProfessorScanner = class {
1253
1386
  studentLevel,
1254
1387
  totalFilesScanned: files.length,
1255
1388
  matches,
1389
+ aiAnalysis,
1256
1390
  summary: { errors, warnings, clean: matches.length === 0 }
1257
1391
  };
1258
1392
  await this.writeReport(report);
@@ -1294,7 +1428,10 @@ var ProfessorScanner = class {
1294
1428
  if (report.summary.clean) {
1295
1429
  lines.push("No advanced patterns detected. Your code matches your declared level.");
1296
1430
  } else {
1297
- lines.push("## Findings", "");
1431
+ if (report.aiAnalysis) {
1432
+ lines.push("## AI Analysis & Coaching", "", report.aiAnalysis, "");
1433
+ }
1434
+ lines.push("## Detailed Findings", "");
1298
1435
  for (const m of report.matches) {
1299
1436
  const icon = m.severity === "error" ? "[ERROR]" : "[WARN]";
1300
1437
  lines.push(
@@ -7223,9 +7360,45 @@ ${cmd}
7223
7360
  };
7224
7361
 
7225
7362
  // src/workflow/verification-engine.ts
7363
+ import fs23 from "fs/promises";
7364
+ import path25 from "path";
7226
7365
  var VerificationEngine = class {
7227
- buildVerifyPrompt(phase, stack) {
7228
- const tasksWithCriteria = phase.tasks.filter((t) => t.acceptanceCriteria?.length);
7366
+ async detectAvailableCommands(cwd) {
7367
+ const commands = [];
7368
+ try {
7369
+ const pkgPath = path25.join(cwd, "package.json");
7370
+ const raw = await fs23.readFile(pkgPath, "utf-8");
7371
+ const pkg = JSON.parse(raw);
7372
+ 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");
7377
+ } catch {
7378
+ }
7379
+ try {
7380
+ await fs23.access(path25.join(cwd, "pytest.ini"));
7381
+ commands.push("pytest");
7382
+ } catch {
7383
+ try {
7384
+ await fs23.access(path25.join(cwd, "conftest.py"));
7385
+ commands.push("pytest");
7386
+ } catch {
7387
+ }
7388
+ }
7389
+ try {
7390
+ await fs23.access(path25.join(cwd, "pom.xml"));
7391
+ commands.push("mvn test");
7392
+ } catch {
7393
+ }
7394
+ try {
7395
+ await fs23.access(path25.join(cwd, "build.gradle"));
7396
+ commands.push("gradle test");
7397
+ } catch {
7398
+ }
7399
+ return commands;
7400
+ }
7401
+ buildVerifyPrompt(phase, stack, detectedCommands = []) {
7229
7402
  const allCriteria = [];
7230
7403
  for (const t of phase.tasks) {
7231
7404
  for (const c of t.acceptanceCriteria ?? []) {
@@ -7252,34 +7425,23 @@ var VerificationEngine = class {
7252
7425
  }
7253
7426
  lines.push("");
7254
7427
  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)]) {
7428
+ const taskCmds = phase.tasks.flatMap((t) => t.verificationCommands ?? []);
7429
+ const uniqueCmds = [.../* @__PURE__ */ new Set([...detectedCommands, ...taskCmds])];
7430
+ if (uniqueCmds.length > 0) {
7431
+ for (const cmd of uniqueCmds) {
7258
7432
  lines.push(`\`\`\`bash
7259
7433
  ${cmd}
7260
7434
  \`\`\``);
7261
7435
  }
7262
7436
  } 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
- }
7437
+ lines.push("_(No verification commands detected \u2014 run your test suite manually.)_");
7267
7438
  }
7268
7439
  lines.push("");
7269
7440
  lines.push("## Instructions for your AI tool");
7270
7441
  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"');
7442
+ lines.push("2. For each checklist item, confirm it is truly done.");
7443
+ lines.push("3. List any bugs found with severity (critical | major | minor).");
7444
+ lines.push("4. Produce a VERIFY.md with the results.");
7283
7445
  lines.push("");
7284
7446
  lines.push("---");
7285
7447
  lines.push("*Generated by BrainForge AI \u2014 paste into your AI tool to verify the phase.*");
@@ -7724,12 +7886,12 @@ var AutoEngine = class {
7724
7886
  };
7725
7887
 
7726
7888
  // src/scanner/codebase-map.ts
7727
- import fs27 from "fs/promises";
7728
- import path30 from "path";
7889
+ import fs28 from "fs/promises";
7890
+ import path31 from "path";
7729
7891
 
7730
7892
  // src/scanner/stack-detector.ts
7731
- import fs23 from "fs/promises";
7732
- import path25 from "path";
7893
+ import fs24 from "fs/promises";
7894
+ import path26 from "path";
7733
7895
  var FRAMEWORK_MARKERS = {
7734
7896
  "next": ["next"],
7735
7897
  "nuxt": ["nuxt"],
@@ -7802,7 +7964,7 @@ async function detectStack(cwd) {
7802
7964
  let hasRuby = false;
7803
7965
  let runtime;
7804
7966
  try {
7805
- const raw = await fs23.readFile(path25.join(cwd, "package.json"), "utf-8");
7967
+ const raw = await fs24.readFile(path26.join(cwd, "package.json"), "utf-8");
7806
7968
  const pkg = JSON.parse(raw);
7807
7969
  Object.assign(allDeps, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
7808
7970
  if (pkg.engines?.["bun"]) runtime = "bun";
@@ -7921,7 +8083,7 @@ async function detectStack(cwd) {
7921
8083
  }
7922
8084
  async function fileExists4(cwd, ...parts) {
7923
8085
  try {
7924
- await fs23.access(path25.join(cwd, ...parts));
8086
+ await fs24.access(path26.join(cwd, ...parts));
7925
8087
  return true;
7926
8088
  } catch {
7927
8089
  return false;
@@ -7929,8 +8091,8 @@ async function fileExists4(cwd, ...parts) {
7929
8091
  }
7930
8092
 
7931
8093
  // src/scanner/dependency-analyzer.ts
7932
- import fs24 from "fs/promises";
7933
- import path26 from "path";
8094
+ import fs25 from "fs/promises";
8095
+ import path27 from "path";
7934
8096
  async function analyzeDependencies(cwd) {
7935
8097
  const handlers = [
7936
8098
  () => parseNodePackage(cwd),
@@ -7947,7 +8109,7 @@ async function analyzeDependencies(cwd) {
7947
8109
  }
7948
8110
  async function parseNodePackage(cwd) {
7949
8111
  try {
7950
- const raw = await fs24.readFile(path26.join(cwd, "package.json"), "utf-8");
8112
+ const raw = await fs25.readFile(path27.join(cwd, "package.json"), "utf-8");
7951
8113
  const pkg = JSON.parse(raw);
7952
8114
  const runtime = Object.entries(pkg.dependencies ?? {}).map(([name, version]) => ({
7953
8115
  name,
@@ -7972,7 +8134,7 @@ async function parseNodePackage(cwd) {
7972
8134
  }
7973
8135
  async function parsePythonRequirements(cwd) {
7974
8136
  try {
7975
- const raw = await fs24.readFile(path26.join(cwd, "pyproject.toml"), "utf-8");
8137
+ const raw = await fs25.readFile(path27.join(cwd, "pyproject.toml"), "utf-8");
7976
8138
  const deps = [];
7977
8139
  const depSection = raw.match(/\[project\][\s\S]*?dependencies\s*=\s*\[([\s\S]*?)\]/);
7978
8140
  if (depSection?.[1]) {
@@ -7990,7 +8152,7 @@ async function parsePythonRequirements(cwd) {
7990
8152
  } catch {
7991
8153
  }
7992
8154
  try {
7993
- const raw = await fs24.readFile(path26.join(cwd, "requirements.txt"), "utf-8");
8155
+ const raw = await fs25.readFile(path27.join(cwd, "requirements.txt"), "utf-8");
7994
8156
  const deps = raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && !l.startsWith("-")).map((l) => {
7995
8157
  const m = l.match(/^([a-zA-Z0-9_.-]+)([>=<!].+)?$/);
7996
8158
  return m ? { name: m[1], version: m[2] ?? "*", dev: false } : null;
@@ -8004,7 +8166,7 @@ async function parsePythonRequirements(cwd) {
8004
8166
  }
8005
8167
  async function parseGoMod(cwd) {
8006
8168
  try {
8007
- const raw = await fs24.readFile(path26.join(cwd, "go.mod"), "utf-8");
8169
+ const raw = await fs25.readFile(path27.join(cwd, "go.mod"), "utf-8");
8008
8170
  const deps = [];
8009
8171
  for (const line of raw.split("\n")) {
8010
8172
  const m = line.trim().match(/^([a-zA-Z0-9_./-]+)\s+(v[\d.]+.*)$/);
@@ -8017,7 +8179,7 @@ async function parseGoMod(cwd) {
8017
8179
  }
8018
8180
  async function parseCargoToml(cwd) {
8019
8181
  try {
8020
- const raw = await fs24.readFile(path26.join(cwd, "Cargo.toml"), "utf-8");
8182
+ const raw = await fs25.readFile(path27.join(cwd, "Cargo.toml"), "utf-8");
8021
8183
  const deps = [];
8022
8184
  let inDeps = false;
8023
8185
  let inDevDeps = false;
@@ -8048,7 +8210,7 @@ async function parseCargoToml(cwd) {
8048
8210
  }
8049
8211
  async function parseComposerJson(cwd) {
8050
8212
  try {
8051
- const raw = await fs24.readFile(path26.join(cwd, "composer.json"), "utf-8");
8213
+ const raw = await fs25.readFile(path27.join(cwd, "composer.json"), "utf-8");
8052
8214
  const pkg = JSON.parse(raw);
8053
8215
  const runtime = Object.entries(pkg.require ?? {}).filter(([name]) => name !== "php").map(([name, version]) => ({ name, version, dev: false }));
8054
8216
  const dev = Object.entries(pkg["require-dev"] ?? {}).map(([name, version]) => ({
@@ -8109,8 +8271,8 @@ function pickHighlights(deps) {
8109
8271
  }
8110
8272
 
8111
8273
  // src/scanner/convention-detector.ts
8112
- import fs25 from "fs/promises";
8113
- import path27 from "path";
8274
+ import fs26 from "fs/promises";
8275
+ import path28 from "path";
8114
8276
  async function detectConventions(cwd, files) {
8115
8277
  const srcFiles = files.filter((f) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
8116
8278
  const namingStyle = detectNamingStyle(srcFiles);
@@ -8135,7 +8297,7 @@ async function detectConventions(cwd, files) {
8135
8297
  };
8136
8298
  }
8137
8299
  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));
8300
+ const basenames = files.map((f) => path28.basename(f, path28.extname(f))).filter((b) => b.length > 2 && !/^index$|^main$|^app$/.test(b));
8139
8301
  const counts = { camel: 0, snake: 0, kebab: 0, pascal: 0 };
8140
8302
  for (const b of basenames) {
8141
8303
  if (/^[A-Z][a-zA-Z0-9]*$/.test(b)) counts.pascal++;
@@ -8170,7 +8332,7 @@ function detectFolderPattern(files) {
8170
8332
  }
8171
8333
  async function detectModuleStyle(cwd) {
8172
8334
  try {
8173
- const raw = await fs25.readFile(path27.join(cwd, "package.json"), "utf-8");
8335
+ const raw = await fs26.readFile(path28.join(cwd, "package.json"), "utf-8");
8174
8336
  const pkg = JSON.parse(raw);
8175
8337
  if (pkg.type === "module") return "esm";
8176
8338
  if (pkg.type === "commonjs") return "cjs";
@@ -8181,7 +8343,7 @@ async function detectModuleStyle(cwd) {
8181
8343
  }
8182
8344
  async function detectTsStrict(cwd) {
8183
8345
  try {
8184
- const raw = await fs25.readFile(path27.join(cwd, "tsconfig.json"), "utf-8");
8346
+ const raw = await fs26.readFile(path28.join(cwd, "tsconfig.json"), "utf-8");
8185
8347
  const stripped = raw.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
8186
8348
  const tsconfig = JSON.parse(stripped);
8187
8349
  return tsconfig.compilerOptions?.strict === true;
@@ -8210,7 +8372,7 @@ function detectEntryPoint(files) {
8210
8372
  }
8211
8373
  async function fileExists5(cwd, ...parts) {
8212
8374
  try {
8213
- await fs25.access(path27.join(cwd, ...parts));
8375
+ await fs26.access(path28.join(cwd, ...parts));
8214
8376
  return true;
8215
8377
  } catch {
8216
8378
  return false;
@@ -8218,8 +8380,8 @@ async function fileExists5(cwd, ...parts) {
8218
8380
  }
8219
8381
 
8220
8382
  // src/scanner/risk-detector.ts
8221
- import fs26 from "fs/promises";
8222
- import path28 from "path";
8383
+ import fs27 from "fs/promises";
8384
+ import path29 from "path";
8223
8385
  var LARGE_FILE_THRESHOLD = 500;
8224
8386
  var HUGE_FILE_THRESHOLD = 1e3;
8225
8387
  var SECRET_PATTERNS = [
@@ -8255,7 +8417,7 @@ async function detectRisks(cwd, files, fileContents) {
8255
8417
  }
8256
8418
  }
8257
8419
  for (const f of files) {
8258
- const basename = path28.basename(f);
8420
+ const basename = path29.basename(f);
8259
8421
  if (SECRET_PATTERNS.some((p) => p.test(basename)) && !basename.endsWith(".example")) {
8260
8422
  items.push({
8261
8423
  level: "high",
@@ -8292,7 +8454,7 @@ async function detectRisks(cwd, files, fileContents) {
8292
8454
  } else if (todoCount > 5) {
8293
8455
  items.push({ level: "low", category: "quality", message: `${todoCount} TODO/FIXME/HACK comments found` });
8294
8456
  }
8295
- if (!files.some((f) => path28.basename(f) === ".gitignore")) {
8457
+ if (!files.some((f) => path29.basename(f) === ".gitignore")) {
8296
8458
  items.push({ level: "medium", category: "hygiene", message: "No .gitignore found" });
8297
8459
  }
8298
8460
  const hasTestFiles = files.some(
@@ -8301,12 +8463,12 @@ async function detectRisks(cwd, files, fileContents) {
8301
8463
  if (!hasTestFiles) {
8302
8464
  items.push({ level: "high", category: "quality", message: "No test files detected" });
8303
8465
  }
8304
- if (!files.some((f) => /^readme\.md$/i.test(path28.basename(f)))) {
8466
+ if (!files.some((f) => /^readme\.md$/i.test(path29.basename(f)))) {
8305
8467
  items.push({ level: "low", category: "documentation", message: "No README.md found" });
8306
8468
  }
8307
- const hasPackageJson = files.some((f) => path28.basename(f) === "package.json");
8469
+ const hasPackageJson = files.some((f) => path29.basename(f) === "package.json");
8308
8470
  const hasLockFile = files.some(
8309
- (f) => ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb", "bun.lock"].includes(path28.basename(f))
8471
+ (f) => ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb", "bun.lock"].includes(path29.basename(f))
8310
8472
  );
8311
8473
  if (hasPackageJson && !hasLockFile) {
8312
8474
  items.push({ level: "medium", category: "hygiene", message: "Node project has no lock file committed" });
@@ -8337,8 +8499,8 @@ async function loadFileContents(cwd, files, maxLines = 1500) {
8337
8499
  await Promise.all(
8338
8500
  readable.map(async (rel) => {
8339
8501
  try {
8340
- const abs = path28.join(cwd, rel);
8341
- const raw = await fs26.readFile(abs, "utf-8");
8502
+ const abs = path29.join(cwd, rel);
8503
+ const raw = await fs27.readFile(abs, "utf-8");
8342
8504
  const lines = raw.split("\n");
8343
8505
  map.set(rel, lines.slice(0, maxLines).join("\n"));
8344
8506
  } catch {
@@ -8349,7 +8511,7 @@ async function loadFileContents(cwd, files, maxLines = 1500) {
8349
8511
  }
8350
8512
 
8351
8513
  // src/scanner/test-detector.ts
8352
- import path29 from "path";
8514
+ import path30 from "path";
8353
8515
  var TEST_FRAMEWORK_MARKERS = {
8354
8516
  vitest: ["vitest"],
8355
8517
  jest: ["jest", "@jest/core"],
@@ -8413,7 +8575,7 @@ function detectTests(files, deps, scripts) {
8413
8575
  );
8414
8576
  const coverageRatio = sourceFiles.length > 0 ? testFiles.length / sourceFiles.length : 0;
8415
8577
  const hasConfig = files.some(
8416
- (f) => TEST_CONFIG_FILES.some((c) => path29.basename(f) === c)
8578
+ (f) => TEST_CONFIG_FILES.some((c) => path30.basename(f) === c)
8417
8579
  );
8418
8580
  const coverageEnabled = "c8" in deps || "@vitest/coverage-v8" in deps || "@vitest/coverage-istanbul" in deps || Boolean(scripts?.["coverage"]) || Boolean(scripts?.["test:coverage"]);
8419
8581
  const testCommand = scripts?.["test"] ?? scripts?.["test:unit"];
@@ -8480,13 +8642,13 @@ var IGNORE_EXTENSIONS = /* @__PURE__ */ new Set([
8480
8642
  var CodebaseScanner = class {
8481
8643
  constructor(cwd) {
8482
8644
  this.cwd = cwd;
8483
- this.codebaseDir = path30.join(cwd, ".brainforge", "codebase");
8645
+ this.codebaseDir = path31.join(cwd, ".brainforge", "codebase");
8484
8646
  }
8485
8647
  cwd;
8486
8648
  codebaseDir;
8487
8649
  async scan(deep = false) {
8488
8650
  const files = await this.walkFiles(this.cwd, deep ? 8 : 5);
8489
- const relFiles = files.map((f) => path30.relative(this.cwd, f).replace(/\\/g, "/"));
8651
+ const relFiles = files.map((f) => path31.relative(this.cwd, f).replace(/\\/g, "/"));
8490
8652
  const fileContents = await loadFileContents(this.cwd, relFiles);
8491
8653
  const [stack, dependencies, conventions, risks] = await Promise.all([
8492
8654
  detectStack(this.cwd),
@@ -8496,7 +8658,7 @@ var CodebaseScanner = class {
8496
8658
  ]);
8497
8659
  let scripts;
8498
8660
  try {
8499
- const raw = await fs27.readFile(path30.join(this.cwd, "package.json"), "utf-8");
8661
+ const raw = await fs28.readFile(path31.join(this.cwd, "package.json"), "utf-8");
8500
8662
  scripts = JSON.parse(raw).scripts;
8501
8663
  } catch {
8502
8664
  }
@@ -8521,20 +8683,20 @@ var CodebaseScanner = class {
8521
8683
  };
8522
8684
  }
8523
8685
  async save(result) {
8524
- await fs27.mkdir(this.codebaseDir, { recursive: true });
8686
+ await fs28.mkdir(this.codebaseDir, { recursive: true });
8525
8687
  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")
8688
+ fs28.writeFile(path31.join(this.codebaseDir, "STACK.json"), JSON.stringify(result.stack, null, 2), "utf-8"),
8689
+ fs28.writeFile(path31.join(this.codebaseDir, "FILES.json"), JSON.stringify(result.files, null, 2), "utf-8"),
8690
+ fs28.writeFile(path31.join(this.codebaseDir, "MAP.md"), this.toMapMd(result), "utf-8"),
8691
+ fs28.writeFile(path31.join(this.codebaseDir, "CONVENTIONS.md"), this.toConventionsMd(result), "utf-8"),
8692
+ fs28.writeFile(path31.join(this.codebaseDir, "RISKS.md"), this.toRisksMd(result), "utf-8"),
8693
+ fs28.writeFile(path31.join(this.codebaseDir, "TESTS.md"), this.toTestsMd(result), "utf-8")
8532
8694
  ]);
8533
8695
  }
8534
8696
  async loadLatest() {
8535
8697
  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"));
8698
+ const stack = JSON.parse(await fs28.readFile(path31.join(this.codebaseDir, "STACK.json"), "utf-8"));
8699
+ const files = JSON.parse(await fs28.readFile(path31.join(this.codebaseDir, "FILES.json"), "utf-8"));
8538
8700
  return { stack, files };
8539
8701
  } catch {
8540
8702
  return null;
@@ -8646,7 +8808,7 @@ var CodebaseScanner = class {
8646
8808
  let size = 0;
8647
8809
  let lines = 0;
8648
8810
  try {
8649
- const stat = await fs27.stat(abs);
8811
+ const stat = await fs28.stat(abs);
8650
8812
  size = stat.size;
8651
8813
  const content = contents.get(rel);
8652
8814
  lines = content ? content.split("\n").length : 0;
@@ -8658,18 +8820,18 @@ var CodebaseScanner = class {
8658
8820
  }
8659
8821
  async walkFiles(dir, maxDepth, depth = 0) {
8660
8822
  if (depth > maxDepth) return [];
8661
- const entries = await fs27.readdir(dir, { withFileTypes: true });
8823
+ const entries = await fs28.readdir(dir, { withFileTypes: true });
8662
8824
  const results = [];
8663
8825
  for (const entry of entries) {
8664
8826
  if (entry.name.startsWith(".") && entry.name !== ".gitignore" && entry.name !== ".env.example") {
8665
8827
  if (IGNORE_DIRS.has(entry.name)) continue;
8666
8828
  }
8667
8829
  if (IGNORE_DIRS.has(entry.name)) continue;
8668
- const full = path30.join(dir, entry.name);
8830
+ const full = path31.join(dir, entry.name);
8669
8831
  if (entry.isDirectory()) {
8670
8832
  results.push(...await this.walkFiles(full, maxDepth, depth + 1));
8671
8833
  } else if (entry.isFile()) {
8672
- const ext = path30.extname(entry.name).toLowerCase();
8834
+ const ext = path31.extname(entry.name).toLowerCase();
8673
8835
  if (!IGNORE_EXTENSIONS.has(ext)) results.push(full);
8674
8836
  }
8675
8837
  }
@@ -8677,12 +8839,12 @@ var CodebaseScanner = class {
8677
8839
  }
8678
8840
  async readProjectName() {
8679
8841
  try {
8680
- const raw = await fs27.readFile(path30.join(this.cwd, "package.json"), "utf-8");
8842
+ const raw = await fs28.readFile(path31.join(this.cwd, "package.json"), "utf-8");
8681
8843
  const pkg = JSON.parse(raw);
8682
8844
  if (pkg.name) return pkg.name;
8683
8845
  } catch {
8684
8846
  }
8685
- return path30.basename(this.cwd);
8847
+ return path31.basename(this.cwd);
8686
8848
  }
8687
8849
  };
8688
8850
  function inferRole(filePath) {
@@ -8804,12 +8966,12 @@ function summariseEntries(entries) {
8804
8966
  }
8805
8967
 
8806
8968
  // src/memory/context-packets.ts
8807
- import fs28 from "fs/promises";
8808
- import path31 from "path";
8969
+ import fs29 from "fs/promises";
8970
+ import path32 from "path";
8809
8971
  var ContextPacketBuilder = class {
8810
8972
  constructor(cwd) {
8811
8973
  this.cwd = cwd;
8812
- this.packetDir = path31.join(cwd, ".brainforge", "memory", "context-packets");
8974
+ this.packetDir = path32.join(cwd, ".brainforge", "memory", "context-packets");
8813
8975
  }
8814
8976
  cwd;
8815
8977
  packetDir;
@@ -8882,14 +9044,14 @@ var ContextPacketBuilder = class {
8882
9044
  };
8883
9045
  }
8884
9046
  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");
9047
+ await fs29.mkdir(this.packetDir, { recursive: true });
9048
+ const jsonPath = path32.join(this.packetDir, "latest.json");
9049
+ const mdPath = path32.join(this.packetDir, "latest.md");
9050
+ await fs29.writeFile(jsonPath, JSON.stringify(packet, null, 2), "utf-8");
9051
+ await fs29.writeFile(mdPath, this.toMarkdown(packet), "utf-8");
8890
9052
  const ts = packet.generatedAt.replace(/[:.]/g, "-").slice(0, 19);
8891
- await fs28.writeFile(
8892
- path31.join(this.packetDir, `packet-${ts}.json`),
9053
+ await fs29.writeFile(
9054
+ path32.join(this.packetDir, `packet-${ts}.json`),
8893
9055
  JSON.stringify(packet, null, 2),
8894
9056
  "utf-8"
8895
9057
  );
@@ -8897,7 +9059,7 @@ var ContextPacketBuilder = class {
8897
9059
  }
8898
9060
  async loadLatest() {
8899
9061
  try {
8900
- const raw = await fs28.readFile(path31.join(this.packetDir, "latest.json"), "utf-8");
9062
+ const raw = await fs29.readFile(path32.join(this.packetDir, "latest.json"), "utf-8");
8901
9063
  return JSON.parse(raw);
8902
9064
  } catch {
8903
9065
  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.12",
4
4
  "description": "Core engine for BrainForge AI — state management, AST mapper, skills, and AI adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",