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>
This commit is contained in:
parent
d4df69890f
commit
3ea3ada808
137
.forum/src/core/meta_state_manager.ts
Normal file
137
.forum/src/core/meta_state_manager.ts
Normal 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();
|
||||
}
|
||||
20
.forum/src/core/transitions.json
Normal file
20
.forum/src/core/transitions.json
Normal 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
49
.forum/src/hooks/pre-commit
Executable 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
|
||||
277
.forum/src/install_hooks.ts
Normal file
277
.forum/src/install_hooks.ts
Normal file
@ -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();
|
||||
}
|
||||
2
deno.lock
generated
2
deno.lock
generated
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user