diff --git a/src/hooks/hookEngine.ts b/src/hooks/hookEngine.ts new file mode 100644 index 0000000000..ceacb6ea75 --- /dev/null +++ b/src/hooks/hookEngine.ts @@ -0,0 +1,18 @@ +import { preHook } from "./preHook" +import { postHook } from "./postHook" + +export async function runWithHooks(command: string, args: any) { + // Pre-Hook intercept + const preResult = await preHook(command, args) + if (!preResult.allowed) { + throw new Error(`Blocked by PreHook: ${preResult.reason}`) + } + + // Execute actual command + const result = await executeCommand(command, args) + + // Post-Hook intercept + await postHook(command, args, result) + + return result +} diff --git a/src/hooks/postHook.ts b/src/hooks/postHook.ts new file mode 100644 index 0000000000..2894d85dca --- /dev/null +++ b/src/hooks/postHook.ts @@ -0,0 +1,25 @@ +import fs from "fs" +import crypto from "crypto" + +export async function postHook(command: string, args: any, result: any) { + // Compute SHA256 of the content + const content = fs.readFileSync(args.file, "utf8") + const hash = crypto.createHash("sha256").update(content).digest("hex") + + // Append trace + const trace = { + id: crypto.randomUUID(), + timestamp: new Date().toISOString(), + vcs: { revision_id: "git_sha_placeholder" }, + files: [ + { + relative_path: args.file, + conversations: [], + ranges: [{ start_line: 0, end_line: content.split("\n").length, content_hash: hash }], + related: [{ type: "specification", value: args.intent_id }], + }, + ], + } + + fs.appendFileSync(".orchestration/agent_trace.jsonl", JSON.stringify(trace) + "\n") +} diff --git a/src/hooks/preHook.ts b/src/hooks/preHook.ts new file mode 100644 index 0000000000..f638210b65 --- /dev/null +++ b/src/hooks/preHook.ts @@ -0,0 +1,19 @@ +import fs from "fs" +import yaml from "js-yaml" +export async function preHook(command: string, args: any) { + // Load active intents + const intents = yaml.load(fs.readFileSync(".orchestration/active_intents.yaml", "utf8")) + + // Ensure the agent selects an intent + if (!args.intent_id || !intents.active_intents.find((i: any) => i.id === args.intent_id)) { + return { allowed: false, reason: "You must cite a valid active Intent ID" } + } + + // Enforce scope + const intent = intents.active_intents.find((i: any) => i.id === args.intent_id) + if (!intent.owned_scope.some((pattern: string) => args.file?.startsWith(pattern.replace("/**", "")))) { + return { allowed: false, reason: `Scope Violation: ${args.intent_id} cannot edit ${args.file}` } + } + + return { allowed: true } +}