diff --git a/.forum/src/core/meta_state_manager.ts b/.forum/src/core/meta_state_manager.ts new file mode 100644 index 0000000..78c2be1 --- /dev/null +++ b/.forum/src/core/meta_state_manager.ts @@ -0,0 +1,137 @@ +/** + * Agent Forum v4 - Meta-State Manager + * + * This script is executed during the pre-commit hook and by tools to interact with + * the embedded data structures: + * - Validating Bounded Model Checking rules (transitions.json). + * - Writing arbitrary metadata to Git Notes. + * - Generating Git Merkle DAG Diffs for O(1) context updates. + */ + +import { parseArgs } from "https://deno.land/std@0.224.0/cli/parse_args.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(), + }; +} + +/** + * Validates that the current commit meets the Bounded Model Checking rules. + * E.g., The Gatekeeper checklist must be completed before Coder changes are allowed. + */ +async function validateBoundedModelChecking() { + console.log("-> Validating Bounded Model Checking constraints..."); + + // We need to fetch transitions.json from the meta-state branch without checking it out + const fileCheck = await runCommand("git", [ + "show", + "meta-state:transitions.json", + ]); + + if (fileCheck.code !== 0) { + console.log( + "⚠️ Could not load transitions.json from meta-state branch. Skipping validation.", + ); + return; + } + + try { + const _transitions = JSON.parse(fileCheck.stdout); + // In a real implementation, this would cross-reference the current Git Notes + // (e.g., refs/notes/gatekeeper) to see if required prerequisite tasks were signed off. + + // Example pseudocode: + // const notes = await getGitNotes("gatekeeper"); + // if (transitions["Coder"].requires.includes("Gatekeeper_Approval") && !notes.includes("Approved")) { + // throw new Error("Gatekeeper approval missing in Git Notes."); + // } + + console.log("-> BMC validation passed (Placeholder)."); + } catch (e) { + console.error("❌ Invalid transitions.json payload:", e); + Deno.exit(1); + } +} + +/** + * Calculates the Git Merkle DAG Diff to provide agents with precise change context. + */ +async function generateMerkleDiff() { + console.log("-> Generating Merkle DAG context diff..."); + + // Compare working tree against HEAD + const diffCommand = await runCommand("git", ["diff-index", "HEAD"]); + + if (diffCommand.stdout === "") { + console.log(" No changes detected in working tree."); + return; + } + + // In a full implementation, we serialize this diff and possibly attach it via + // Git Notes or pass it immediately into the agent context pipeline. + // console.log(diffCommand.stdout); +} + +/** + * Helper to write a JSON payload to a specific Git Note namespace. + */ +export 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 handlePreCommit() { + await validateBoundedModelChecking(); + await generateMerkleDiff(); + + // E.g., attaching a simple telemetry note + // await writeGitNote("telemetry", { timestamp: Date.now(), agent: "System" }); +} + +async function main() { + const args = parseArgs(Deno.args, { + boolean: ["pre-commit"], + }); + + if (args["pre-commit"]) { + await handlePreCommit(); + } +} + +if (import.meta.main) { + await main(); +} diff --git a/.forum/src/core/transitions.json b/.forum/src/core/transitions.json new file mode 100644 index 0000000..b9d764a --- /dev/null +++ b/.forum/src/core/transitions.json @@ -0,0 +1,20 @@ +{ + "Gatekeeper": { + "requires": [] + }, + "Coder": { + "requires": [ + "Gatekeeper_Approval" + ] + }, + "Adversary": { + "requires": [ + "Coder_Commit" + ] + }, + "Evaluator": { + "requires": [ + "Adversary_Pass" + ] + } +} \ No newline at end of file diff --git a/.forum/src/hooks/pre-commit b/.forum/src/hooks/pre-commit new file mode 100755 index 0000000..568b1bd --- /dev/null +++ b/.forum/src/hooks/pre-commit @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# Agent Forum v4 - Pre-Commit Hook +# +# This hook executes Git-Native Agent Collaboration Ecosystem tasks before every commit: +# 1. Generates Semantic Code Intelligence Protocol (SCIP) & AST graphs. +# 2. Runs fast Static Analysis (Semgrep) if available. +# 3. Validates Bounded Model Checking rules via transitions.json. +# 4. Writes telemetry/reasoning payloads to Git Notes. + +echo "==================================================" +echo " Agent Forum v4 - Pre-Commit Validation " +echo "==================================================" + +# Exit on any failure +set -e + +REPO_ROOT=$(git rev-parse --show-toplevel) +META_MANAGER="$REPO_ROOT/.forum/src/core/meta_state_manager.ts" + +if [ -f "$META_MANAGER" ] && command -v deno &> /dev/null; then + echo "Running Agent Forum meta-state checks..." + + # Run the meta-state manager to extract structural diffs and enforce rules. + # Note: Using run -A for Deno as it requires fs and run permissions to inspect git + if ! deno run -A "$META_MANAGER" --pre-commit; then + echo "❌ Agent Forum Bounded Model Checking failed. Commit aborted." + exit 1 + fi +else + echo "⚠️ Agent Forum meta-state manager (Deno script) not found or Deno not installed. Skipping." +fi + +# Optional: SCIP / AST Extraction using tree-sitter +if command -v tree-sitter &> /dev/null; then + echo "-> Generating local AST structures..." + # Placeholder for actual tree-sitter invocation depending on languages used + # tree-sitter parse src/**/*.ts > .forum/temp_ast.json || true +fi + +# Optional: Fast Security Scan +if command -v semgrep &> /dev/null; then + echo "-> Running Semgrep baseline scan..." + # Using a fast baseline check to prevent adding obvious vulnerabilities + # semgrep scan --config=auto --error || true +fi + +echo "✅ Pre-commit validation passed." +exit 0 diff --git a/.forum/src/install_hooks.ts b/.forum/src/install_hooks.ts new file mode 100644 index 0000000..dd34eb5 --- /dev/null +++ b/.forum/src/install_hooks.ts @@ -0,0 +1,277 @@ +/** + * Agent Forum v4 - Installation and Setup Script + * + * This script initializes the Git-Native Agent Collaboration Ecosystem by: + * 1. Verifying system dependencies (Deno, Git, and optionally tree-sitter/semgrep). + * 2. Initializing the `meta-state` orphan branch with the required structure. + * 3. Installing the Git pre-commit hooks. + */ + +import { existsSync } from "https://deno.land/std@0.224.0/fs/exists.ts"; +import { join } from "https://deno.land/std@0.224.0/path/mod.ts"; + +const CWD = Deno.cwd(); +const GIT_DIR = join(CWD, ".git"); +const HOOKS_DIR = join(GIT_DIR, "hooks"); +const FORUM_HOOKS_DIR = join(CWD, ".forum", "src", "hooks"); + +async function runCommand( + cmd: string, + args: string[], +): Promise<{ code: number; stdout: string; stderr: string }> { + try { + 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(), + }; + } catch (e: any) { + return { + code: -1, + stdout: "", + stderr: e.message || String(e), + }; + } +} + +async function checkDependencies() { + console.log("Checking dependencies..."); + + // Git + const gitCheck = await runCommand("git", ["--version"]); + if (gitCheck.code !== 0) { + console.error("❌ Git is not installed or not in PATH."); + Deno.exit(1); + } + console.log(`✅ ${gitCheck.stdout}`); + + // Deno + const denoCheck = await runCommand("deno", ["--version"]); + if (denoCheck.code !== 0) { + console.error("❌ Deno is not installed or not in PATH."); + Deno.exit(1); + } + console.log(`✅ Deno installed`); + + // Optional: Tree-sitter + const treeSitterCheck = await runCommand("tree-sitter", ["--version"]); + if (treeSitterCheck.code === 0) { + console.log(`✅ tree-sitter found: ${treeSitterCheck.stdout}`); + } else { + console.log( + "⚠️ tree-sitter not found. SCIP/AST generation may be limited.", + ); + } + + // Optional: Semgrep + const semgrepCheck = await runCommand("semgrep", ["--version"]); + if (semgrepCheck.code === 0) { + console.log(`✅ semgrep found: ${semgrepCheck.stdout}`); + } else { + console.log( + "⚠️ semgrep not found. Security analysis payloads may be skipped.", + ); + } +} + +async function initializeMetaStateBranch() { + console.log("\nInitializing 'meta-state' orphan branch..."); + + // Check if branch exists + const checkBranch = await runCommand("git", [ + "show-ref", + "--verify", + "refs/heads/meta-state", + ]); + if (checkBranch.code === 0) { + console.log( + "✅ 'meta-state' branch already exists. Preserving existing data.", + ); + return; + } + + console.log("Creating 'meta-state' orphan branch..."); + + // Save current branch name (not strictly needed since we use plumbing, but kept for context if needed later) + const currentBranchRes = await runCommand("git", [ + "rev-parse", + "--abbrev-ref", + "HEAD", + ]); + const _currentBranch = currentBranchRes.stdout; + + // Create temporary directory for meta-state tree + const tempDir = await Deno.makeTempDir(); + + // Create structure + const dirs = ["tasks", "ontologies", "vectors"]; + for (const d of dirs) { + await Deno.mkdir(join(tempDir, d), { recursive: true }); + // Add a .gitkeep so git tracks the directory + await Deno.writeTextFile(join(tempDir, d, ".gitkeep"), ""); + } + + // Baseline transitions.json + const defaultTransitions = { + "Gatekeeper": { "requires": [] }, + "Coder": { "requires": ["Gatekeeper_Approval"] }, + "Adversary": { "requires": ["Coder_Commit"] }, + }; + await Deno.writeTextFile( + join(tempDir, "transitions.json"), + JSON.stringify(defaultTransitions, null, 2), + ); + + // Also copy from .forum/src/core/transitions.json if it exists + const coreTransitions = join( + CWD, + ".forum", + "src", + "core", + "transitions.json", + ); + if (existsSync(coreTransitions)) { + await Deno.copyFile(coreTransitions, join(tempDir, "transitions.json")); + } + + // We need to create a commit on the orphan branch using git plumbing commands + // to avoid changing the working directory files + + console.log("Running git plumbing commands to build meta-state tree..."); + + // 1. Create a temporary index file isolated from the main index + const env = { + ...Deno.env.toObject(), + GIT_INDEX_FILE: join(GIT_DIR, "index_meta_state"), + }; + + // Ensure index is clean + try { + await Deno.remove(env.GIT_INDEX_FILE); + } catch (_e) { + // Ignore if doesn't exist + } + + // 2. Add files to the temporary index using git hash-object and update-index + for (const d of dirs) { + const keepPath = join(tempDir, d, ".gitkeep"); + const hashCmd = await runCommand("git", ["hash-object", "-w", keepPath]); + const blobId = hashCmd.stdout; + await new Deno.Command("git", { + args: [ + "update-index", + "--add", + "--cacheinfo", + `100644,${blobId},${d}/.gitkeep`, + ], + env, + }).output(); + } + + const transPath = join(tempDir, "transitions.json"); + const transHashCmd = await runCommand("git", [ + "hash-object", + "-w", + transPath, + ]); + const transBlobId = transHashCmd.stdout; + await new Deno.Command("git", { + args: [ + "update-index", + "--add", + "--cacheinfo", + `100644,${transBlobId},transitions.json`, + ], + env, + }).output(); + + // 3. Write tree + const writeTreeCmd = await new Deno.Command("git", { + args: ["write-tree"], + env, + stdout: "piped", + }).output(); + const treeId = new TextDecoder().decode(writeTreeCmd.stdout).trim(); + + // 4. Commit tree as an orphan commit + const commitCmd = await runCommand("git", [ + "commit-tree", + treeId, + "-m", + "chore: initialize meta-state branch structure", + ]); + const commitId = commitCmd.stdout; + + // 5. Update ref to create the branch + await runCommand("git", ["update-ref", "refs/heads/meta-state", commitId]); + + // Clean up index file + try { + await Deno.remove(env.GIT_INDEX_FILE); + } catch (_e) { + // Ignore + } + + console.log( + "✅ 'meta-state' branch initialized successfully without modifying working tree.", + ); +} + +async function installHooks() { + console.log("\nInstalling Git Hooks..."); + + if (!existsSync(GIT_DIR)) { + console.error( + "❌ .git directory not found. Must be run from the root of a git repository.", + ); + Deno.exit(1); + } + + const preCommitSource = join(FORUM_HOOKS_DIR, "pre-commit"); + const preCommitTarget = join(HOOKS_DIR, "pre-commit"); + + if (!existsSync(preCommitSource)) { + console.error(`❌ Hook template not found at ${preCommitSource}`); + Deno.exit(1); + } + + if (existsSync(preCommitTarget)) { + console.log("⚠️ An existing pre-commit hook was found."); + console.log("Moving existing hook to pre-commit.backup..."); + await Deno.rename(preCommitTarget, join(HOOKS_DIR, "pre-commit.backup")); + } + + await Deno.copyFile(preCommitSource, preCommitTarget); + + // Make it executable (chmod +x) + if (Deno.build.os !== "windows") { + await runCommand("chmod", ["+x", preCommitTarget]); + } + + console.log("✅ pre-commit hook installed successfully."); +} + +async function main() { + console.log("=================================================="); + console.log(" Agent Forum v4 - Initialization Script "); + console.log("==================================================\n"); + + await checkDependencies(); + await installHooks(); + await initializeMetaStateBranch(); + + console.log("\n=================================================="); + console.log("🎉 Setup complete! The Agent Forum ecosystem is ready."); + console.log("=================================================="); +} + +if (import.meta.main) { + await main(); +} diff --git a/deno.lock b/deno.lock index fa5ce1c..5c0a1a1 100644 --- a/deno.lock +++ b/deno.lock @@ -661,10 +661,12 @@ "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", + "https://deno.land/std@0.224.0/cli/parse_args.ts": "5250832fb7c544d9111e8a41ad272c016f5a53f975ef84d5a9fe5fcb70566ece", "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", "https://deno.land/std@0.224.0/front_matter/_formats.ts": "9a8ac1524f93b3ae093bd66864a49fc0088037920c6d60863da136d10f92e04d", "https://deno.land/std@0.224.0/front_matter/create_extractor.ts": "642e6e55cd07864b7c8068f88d271290d5d0a13d979ad335e10a7f52046b1f80", "https://deno.land/std@0.224.0/front_matter/yaml.ts": "103b8338bec480c6b7a7e245cf6bda72682eb78ed2231c799a4526d52cb6888a", + "https://deno.land/std@0.224.0/fs/exists.ts": "3d38cb7dcbca3cf313be343a7b8af18a87bddb4b5ca1bd2314be12d06533b50f", "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e",