227 lines
6.9 KiB
TypeScript
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.`);
|
|
}
|