@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 +12 -6
- package/dist/index.js +253 -91
- package/package.json +1 -1
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
"##
|
|
331
|
+
"## Phase Summary",
|
|
332
|
+
"",
|
|
333
|
+
phaseBlocks.length ? phaseBlocks.join("\n\n") : "No phases yet.",
|
|
306
334
|
"",
|
|
307
|
-
|
|
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}}
|
|
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
|
-
|
|
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
|
-
|
|
7228
|
-
const
|
|
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
|
|
7256
|
-
|
|
7257
|
-
|
|
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
|
|
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(
|
|
7272
|
-
lines.push("3. List any bugs found
|
|
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
|
|
7728
|
-
import
|
|
7889
|
+
import fs28 from "fs/promises";
|
|
7890
|
+
import path31 from "path";
|
|
7729
7891
|
|
|
7730
7892
|
// src/scanner/stack-detector.ts
|
|
7731
|
-
import
|
|
7732
|
-
import
|
|
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
|
|
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
|
|
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
|
|
7933
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8113
|
-
import
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8222
|
-
import
|
|
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 =
|
|
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) =>
|
|
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(
|
|
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) =>
|
|
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(
|
|
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 =
|
|
8341
|
-
const raw = await
|
|
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
|
|
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) =>
|
|
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 =
|
|
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) =>
|
|
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
|
|
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
|
|
8686
|
+
await fs28.mkdir(this.codebaseDir, { recursive: true });
|
|
8525
8687
|
await Promise.all([
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
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
|
|
8537
|
-
const files = JSON.parse(await
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
8808
|
-
import
|
|
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 =
|
|
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
|
|
8886
|
-
const jsonPath =
|
|
8887
|
-
const mdPath =
|
|
8888
|
-
await
|
|
8889
|
-
await
|
|
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
|
|
8892
|
-
|
|
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
|
|
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;
|