/** * Agent Forum v4 - Evaluator Agent * * Role: Govern pipeline integrity. * Inputs: transitions.json (BMC rules), YAML DAGs * Outputs: Pipeline progression (Allows or Blocks workflow transitions) */ import { parseArgs } from "https://deno.land/std@0.224.0/cli/parse_args.ts"; import { parse as parseYaml } from "npm:yaml"; import { globToRegExp } from "https://deno.land/std@0.224.0/path/mod.ts"; async function runCommand( cmd: string, args: string[], ): Promise<{ code: number; stdout: string; stderr: string }> { const command = new Deno.Command(cmd, { args, stdout: "piped", stderr: "piped", }); const { code, stdout, stderr } = await command.output(); const decoder = new TextDecoder(); return { code, stdout: decoder.decode(stdout).trim(), stderr: decoder.decode(stderr).trim(), }; } /** * Fetches content from the meta-state branch without checking it out. */ async function fetchFromMetaState(path: string): Promise { const check = await runCommand("git", ["show", `meta-state:${path}`]); if (check.code !== 0) { // Fallback for evaluator to use local file if meta-state missing during test if (path === "transitions.json") { const local = await runCommand("cat", [".forum/src/core/transitions.json"]); if (local.code === 0) return local.stdout; } return null; } return check.stdout; } /** * Fetches list of files in a directory on the meta-state branch. */ async function listMetaStateDir(path: string): Promise { const check = await runCommand("git", ["ls-tree", "-r", "--name-only", "meta-state", path]); if (check.code !== 0) { return []; } return check.stdout.split("\n").filter(Boolean); } /** * Checks if a specific Git Note exists for a namespace on HEAD. */ async function getGitNotePayload(namespace: string): Promise { const cmd = await runCommand("git", ["notes", "--ref", `refs/notes/${namespace}`, "show", "HEAD"]); if (cmd.code !== 0 || !cmd.stdout) return null; try { return JSON.parse(cmd.stdout); } catch (_e) { return null; } } async function writeGitNote( namespace: string, payload: Record, commitRef = "HEAD", ) { const jsonString = JSON.stringify(payload); const ref = `refs/notes/${namespace}`; const cmd = await runCommand("git", [ "notes", "--ref", ref, "add", "-f", "-m", jsonString, commitRef, ]); if (cmd.code !== 0) { console.error(`Failed to write Git Note to ${ref}:`, cmd.stderr); throw new Error("Git Note write failed."); } return true; } async function evaluatePipeline() { console.log("-> Evaluator Agent: Governing pipeline progression..."); // 1. Fetch transitions.json const transitionsStr = await fetchFromMetaState("transitions.json"); if (!transitionsStr) { console.warn( "⚠️ transitions.json not found on meta-state branch. Defaulting to strict fail-safe.", ); Deno.exit(1); } let transitions; try { transitions = JSON.parse(transitionsStr); } catch (_e) { console.error("❌ Failed to parse transitions.json."); Deno.exit(1); } console.log( " Loaded Bounded Model Checking rules:", Object.keys(transitions).join(", "), ); // 2. Fetch YAML DAGs to check task states console.log(" Checking Project DAG constraints..."); const taskFiles = await listMetaStateDir("tasks/"); const tasks = []; for (const file of taskFiles) { const content = await fetchFromMetaState(file); if (content) { try { const task = parseYaml(content); tasks.push(task); } catch (_e) { console.warn(` ⚠️ Failed to parse YAML for ${file}`); } } } console.log(` Found ${tasks.length} tasks in meta-state:tasks/`); // 3. Evaluate Bounded Model Checking rules console.log(" Evaluating role transitions and notes based on changed paths..."); let pipelineValid = true; let blockReason = ""; // Get changed files let diff = await runCommand("git", ["diff", "--cached", "--name-only"]); if (diff.stdout.trim() === "") { diff = await runCommand("git", ["diff", "HEAD", "--name-only"]); } const changedFiles = diff.stdout.split("\n").map(f => f.trim()).filter(f => f.length > 0); const scopes = transitions.scopes || {}; const requiredRules = new Set(); if (changedFiles.length > 0) { console.log(` Found ${changedFiles.length} changed file(s).`); for (const [scopeName, scopeDef] of Object.entries(scopes)) { const patterns: string[] = (scopeDef as any).patterns || []; const rules: string[] = (scopeDef as any).requires || []; const matched = changedFiles.some(file => { return patterns.some(pattern => { const regex = globToRegExp(pattern); return regex.test(file); }); }); if (matched) { console.log(` -> Matched scope: ${scopeName}`); for (const r of rules) { requiredRules.add(r); } } } } else { console.log(" No files changed. Falling back to default Coder requires for validation."); if (transitions["Coder"] && transitions["Coder"].requires) { for (const req of transitions["Coder"].requires) { requiredRules.add(req); } } } console.log(` -> Enforcing Rules: ${Array.from(requiredRules).join(", ")}`); // Verification based on union of rules for (const req of requiredRules) { if (req === "Gatekeeper_Approval") { const gatekeeperNote = await getGitNotePayload("gatekeeper_checklist"); if (!gatekeeperNote || (gatekeeperNote.status !== "Completed" && gatekeeperNote.status !== "Approved")) { pipelineValid = false; blockReason = "Gatekeeper checklist is missing or not marked as 'Approved' or 'Completed'."; console.warn(` ❌ BMC Rule Failed: ${blockReason}`); } else { console.log(` ✅ BMC Rule Passed: Gatekeeper_Approval satisfied.`); } } // Add other rule checks here if they exist // e.g. Adversary_Pass, Translator_Check } const evaluationResult = { agent: "Evaluator", timestamp: Date.now(), pipelineState: pipelineValid ? "Valid" : "Blocked", message: pipelineValid ? "All Bounded Model Checking rules satisfied. Transition permitted." : `Transition Blocked. Reason: ${blockReason}`, }; console.log( `-> Evaluator Agent: Pipeline is ${evaluationResult.pipelineState}.`, ); try { await writeGitNote("evaluator_state", evaluationResult); console.log( "-> Evaluation state written to Git Notes (refs/notes/evaluator_state).", ); } catch (e) { console.warn("⚠️ Failed to write evaluator state to Git Notes:", e); } if (!pipelineValid) { Deno.exit(1); } } async function main() { const args = parseArgs(Deno.args, { boolean: ["run"], }); if (args["run"]) { await evaluatePipeline(); } else { console.log("Evaluator Agent installed. Run with --run flag."); } } if (import.meta.main) { await main(); }