@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 +23 -7
- package/dist/index.js +354 -109
- 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' | '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
|
-
|
|
759
|
-
|
|
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) =>
|
|
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",
|
|
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}}
|
|
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
|
-
|
|
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
|
-
|
|
7228
|
-
const
|
|
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
|
|
7256
|
-
|
|
7257
|
-
|
|
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
|
|
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(
|
|
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"');
|
|
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
|
-
|
|
7291
|
-
|
|
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
|
-
`#
|
|
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
|
-
"##
|
|
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
|
|
7300
|
-
|
|
7301
|
-
|
|
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("", "
|
|
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
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
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
|
|
7728
|
-
import
|
|
7972
|
+
import fs28 from "fs/promises";
|
|
7973
|
+
import path31 from "path";
|
|
7729
7974
|
|
|
7730
7975
|
// src/scanner/stack-detector.ts
|
|
7731
|
-
import
|
|
7732
|
-
import
|
|
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
|
|
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
|
|
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
|
|
7933
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8113
|
-
import
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8222
|
-
import
|
|
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 =
|
|
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) =>
|
|
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(
|
|
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) =>
|
|
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(
|
|
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 =
|
|
8341
|
-
const raw = await
|
|
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
|
|
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) =>
|
|
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 =
|
|
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) =>
|
|
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
|
|
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
|
|
8769
|
+
await fs28.mkdir(this.codebaseDir, { recursive: true });
|
|
8525
8770
|
await Promise.all([
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
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
|
|
8537
|
-
const files = JSON.parse(await
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
8808
|
-
import
|
|
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 =
|
|
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
|
|
8886
|
-
const jsonPath =
|
|
8887
|
-
const mdPath =
|
|
8888
|
-
await
|
|
8889
|
-
await
|
|
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
|
|
8892
|
-
|
|
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
|
|
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;
|