227 lines
6.9 KiB
TypeScript

import { CommandExecutionError, runCommand } from "./core/sys_exec.ts";
import { globToRegExp } from "https://deno.land/std@0.224.0/path/mod.ts";
import { parseArgs } from "https://deno.land/std@0.224.0/cli/parse_args.ts";
import { analyzeRequirements } from "./agents/gatekeeper.ts";
import { runAdversaryAnalysis } from "./agents/adversary.ts";
import { updateDocumentation } from "./agents/translator.ts";
import { evaluatePipeline } from "./agents/evaluator.ts";
import { gatherHistoricalContext } from "./agents/historian.ts";
import { runArchitectureAnalysis } from "./agents/analyst.ts";
import {
getDiff,
getStagedDiff,
readBranchFile,
} from "./core/git_inspector.ts";
import { readNote } from "./core/git_storage.ts";
async function getTransitions() {
try {
const content = await readBranchFile("meta-state", "transitions.json");
if (content) return JSON.parse(content);
} catch {}
try {
return JSON.parse(
await Deno.readTextFile(".forum/src/core/transitions.json"),
);
} catch {
try {
return JSON.parse(await Deno.readTextFile("src/core/transitions.json"));
} catch (e) {
console.error("❌ No transitions.json:", e);
Deno.exit(1);
}
}
}
async function getChangedFiles(flags: { all?: boolean; push?: boolean }) {
if (flags.all) {
try {
const trackedCmd = await runCommand("git", [
"ls-files",
"src/",
"server/",
"docs/",
]);
if (trackedCmd.stdout) {
return trackedCmd.stdout.split("\n").map((f) => f.trim()).filter(
Boolean,
);
}
} catch {}
}
try {
const staged = await getStagedDiff();
if (staged.length > 0) return staged.map((d) => d.filepath);
const working = await getDiff(); // un-staged modifications in working tree
if (working.length > 0) return working.map((d) => d.filepath);
} catch {}
try {
const upstream = await getDiff("@{u}", "HEAD");
if (upstream.length > 0) return upstream.map((d) => d.filepath);
} catch {}
try {
const last = await getDiff("HEAD~1", "HEAD");
if (last.length > 0) return last.map((d) => d.filepath);
} catch {}
return [];
}
// Maps rule names to agent runners
const RULE_AGENT_RUNNERS: Record<string, any> = {
"Gatekeeper_Approval": () => analyzeRequirements(),
"Translator_Check": () => updateDocumentation(),
"Adversary_Pass": async (targets?: string[], fullMutation?: boolean) => {
const cliArgs = ["--run"];
if (targets && targets.length > 0) {
cliArgs.push("--targets", targets.join(","));
}
if (fullMutation) cliArgs.push("--full-mutation");
await runAdversaryAnalysis(cliArgs);
},
"Historian_Context": () => gatherHistoricalContext(),
"Analyst_Optimize": () => runArchitectureAnalysis(),
"Historian": () => gatherHistoricalContext(),
"Analyst": () => runArchitectureAnalysis(),
};
const runAgt = async (n: string, fn: any, tf?: string[], fm?: boolean) => {
console.log(`\n========================================\nRunning ${n}...`);
try {
await fn(tf, fm);
return { success: true, status: "Passed", noteRef: "-" };
} catch (e) {
console.error(`❌ ${n} error:`, e);
return { success: false, status: "Blocked", noteRef: "-" };
}
};
export async function main(cliArgs: string[] = Deno.args) {
console.log("=== Agent Forum Pipeline Runner ===");
const args = parseArgs(cliArgs, {
boolean: [
"all",
"full",
"push",
"adversary",
"mutation",
"full-mutation",
"full-adversary",
"force",
],
});
if (args.push && !args.force) {
try {
const state = await readNote<any>("evaluator_state", "HEAD");
if (state && state.pipelineState === "Valid") {
console.log(
"\n⚡ Commit HEAD has already been evaluated... Skipping duplicate compute.",
);
Deno.exit(0);
}
} catch {}
}
const transitions = await getTransitions();
const changedFiles = await getChangedFiles({
all: args.all || args.full,
push: args.push,
});
const { evaluateRequiredRules } = await import("./run_utils.ts");
if (changedFiles.length > 0) {
console.log(`\nChanged files (${changedFiles.length}):`);
changedFiles.forEach((f) => console.log(` - ${f}`));
} else {
console.log("\nNo files changed. Falling back to default Coder requires.");
}
const requiredRules = evaluateRequiredRules(
changedFiles,
transitions,
args.adversary || args.mutation || args["full-mutation"] ||
args["full-adversary"],
);
if (changedFiles.length > 0 && transitions.scopes) {
for (const [scope, def] of Object.entries(transitions.scopes)) {
const p = (def as any).patterns || [];
const m = changedFiles.filter((f) =>
p.some((pat: string) => globToRegExp(pat).test(f))
);
if (m.length > 0) {
console.log(`\nMatched scope: ${scope} (${m.length} files)`);
}
}
}
console.log(
`\nEnforcing Rules: ${Array.from(requiredRules.keys()).join(", ")}`,
);
const execSumm: { agent: string; status: string; noteRef: string }[] = [];
const fullMut = args["full-mutation"] || args["full-adversary"];
const rulePromises = new Map<string, Promise<void>>();
const execRule = async (rule: string): Promise<void> => {
if (rulePromises.has(rule)) return rulePromises.get(rule)!;
const p = (async () => {
const deps = transitions[rule.split("_")[0]]?.requires || [];
const waitDeps = deps.filter((d: string) => requiredRules.has(d));
if (waitDeps.length > 0) await Promise.all(waitDeps.map(execRule));
const run = RULE_AGENT_RUNNERS[rule];
if (run) {
const res = await runAgt(
rule,
run,
Array.from(requiredRules.get(rule)!),
fullMut,
);
execSumm.push({
agent: rule,
status: res.status,
noteRef: res.noteRef,
});
if (!res.success) throw new Error(`Pipeline halted: ${rule} failed.`);
} else console.warn(`⚠️ No runner for: ${rule}`);
})();
rulePromises.set(rule, p);
return p;
};
try {
await Promise.all(Array.from(requiredRules.keys()).map(execRule));
} catch (err) {
console.error("DAG failed:", err);
Deno.exit(1);
}
// Always run Evaluator at the end to check transitions.json state and DAGs
const evaluatorResult = await runAgt("Evaluator", evaluatePipeline);
execSumm.push({
agent: "Evaluator",
status: evaluatorResult.status,
noteRef: evaluatorResult.noteRef,
});
if (!evaluatorResult.success) {
console.error(`\n❌ Pipeline halted: Evaluator found invalid state.`);
Deno.exit(1);
}
console.log(`\n========================================`);
console.log(`📋 Execution Summary`);
console.log(`========================================`);
console.table(execSumm);
console.log(`========================================\n`);
console.log(`✅ Pipeline completed SUCCESSFULLY.`);
}