Compare commits

..

2 Commits

Author SHA1 Message Date
8b91df1dca
feat(agent-forum): Improve dependency checks and initialization reporting (#73)
Refactored the `.forum/src/install_hooks.ts` initialization script to utilize a robust configuration array (`REQUIRED_TOOLS`) for verifying system dependencies, correctly distinguishing between hard requirements (Git, Deno, NPM) and optional tools that log warnings (tree-sitter, semgrep, scip-typescript, madge, stryker).

Additionally improved the status reporting when initializing the `meta-state` orphan branch by using non-mutating `git ls-tree` to read and display the exact status (`[✓] Found`, `[+] Freshly Initialized`, `[~] Replaced`, `[!] Missing/Warning`) of all required branch components (`tasks/`, `ontologies/`, `vectors/`, `transitions.json`), ensuring no unintended new placeholder stubs are created in the process.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-29 13:13:38 -07:00
3ea3ada808
feat(agent-forum): Add scaffolding for final agent-forum integration (#72)
- Implemented `.forum/src/install_hooks.ts` to check dependencies, safely initialize the `meta-state` orphan branch via Git plumbing commands, and install the `pre-commit` hook.
- Added `.forum/src/hooks/pre-commit` bash template for Bounded Model Checking, SCIP generation, and security scanning.
- Created `.forum/src/core/meta_state_manager.ts` to manage BMC validations and Merkle diffing.
- Added `.forum/src/core/transitions.json` to define baseline state machine governance.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-29 12:42:09 -07:00
5 changed files with 499 additions and 0 deletions

View File

@ -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<string, unknown>,
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();
}

View File

@ -0,0 +1,20 @@
{
"Gatekeeper": {
"requires": []
},
"Coder": {
"requires": [
"Gatekeeper_Approval"
]
},
"Adversary": {
"requires": [
"Coder_Commit"
]
},
"Evaluator": {
"requires": [
"Adversary_Pass"
]
}
}

49
.forum/src/hooks/pre-commit Executable file
View File

@ -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

291
.forum/src/install_hooks.ts Normal file
View File

@ -0,0 +1,291 @@
/**
* 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),
};
}
}
const REQUIRED_TOOLS = [
{ name: "git", command: "git", args: ["--version"], required: true, failMsg: "Git is not installed or not in PATH." },
{ name: "Deno", command: "deno", args: ["--version"], required: true, failMsg: "Deno is not installed or not in PATH." },
{ name: "Node.js / npm", command: "npm", args: ["--version"], required: true, failMsg: "npm is not installed or not in PATH." },
{ name: "tree-sitter", command: "tree-sitter", args: ["--version"], required: false, warnMsg: "SCIP/AST generation may be limited." },
{ name: "semgrep", command: "semgrep", args: ["--version"], required: false, warnMsg: "Security analysis payloads may be skipped." },
{ name: "scip-typescript", command: "scip-typescript", args: ["--version"], required: false, warnMsg: "TypeScript SCIP indexing may be skipped." },
{ name: "madge", command: "madge", args: ["--version"], required: false, warnMsg: "Dependency graphing may be skipped." },
{ name: "stryker", command: "stryker", args: ["--version"], required: false, warnMsg: "Mutation testing may be skipped." }
];
async function checkDependencies() {
console.log("Checking dependencies...");
for (const tool of REQUIRED_TOOLS) {
const check = await runCommand(tool.command, tool.args);
if (check.code !== 0) {
if (tool.required) {
console.error(`[!] Missing: ${tool.name} - ${tool.failMsg}`);
Deno.exit(1);
} else {
console.log(`[!] Warning: ${tool.name} not found. ${tool.warnMsg}`);
}
} else {
// For tools like npm or tree-sitter that might output multiline or noisy versions,
// we take just the first line for a cleaner log.
const versionStr = check.stdout.split('\n')[0].substring(0, 30);
console.log(`[✓] Found: ${tool.name} (${versionStr})`);
}
}
}
async function initializeMetaStateBranch() {
console.log("\nInitializing 'meta-state' orphan branch...");
const dirs = ["tasks", "ontologies", "vectors"];
// Check if branch exists
const checkBranch = await runCommand("git", [
"show-ref",
"--verify",
"refs/heads/meta-state",
]);
if (checkBranch.code === 0) {
console.log(
"[ Already Exists ] 'meta-state' branch found. Checking existing structure...",
);
// Check which directories exist on the meta-state branch
const lsTree = await runCommand("git", ["ls-tree", "refs/heads/meta-state"]);
if (lsTree.code === 0) {
const existingItems = lsTree.stdout.split('\n').map(line => line.split('\t')[1]);
for (const d of dirs) {
if (existingItems.includes(d)) {
console.log(`[✓] Found directory: ${d}/`);
} else {
console.log(`[!] Missing directory in meta-state: ${d}/`);
}
}
if (existingItems.includes("transitions.json")) {
console.log(`[✓] Found file: transitions.json`);
} else {
console.log(`[!] Missing file in meta-state: transitions.json`);
}
}
console.log("[~] Preserving existing data. (If missing items are needed, you may need to add them manually to the meta-state branch)");
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
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 freshly initialized successfully without modifying working tree.",
);
}
async function installHooks() {
console.log("\nInstalling Git Hooks...");
if (!existsSync(GIT_DIR)) {
console.error(
"[!] 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(`[!] Error: Hook template not found at ${preCommitSource}`);
Deno.exit(1);
}
if (existsSync(preCommitTarget)) {
console.log("[~] An existing pre-commit hook was found.");
console.log("[~] Replaced: 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("[+] Freshly Initialized: 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();
}

2
deno.lock generated
View File

@ -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",