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>
This commit is contained in:
Tyler Gillispie 2026-08-29 13:13:38 -07:00 committed by GitHub
parent 3ea3ada808
commit 8b91df1dca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -41,63 +41,78 @@ async function runCommand(
}
}
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...");
// Git
const gitCheck = await runCommand("git", ["--version"]);
if (gitCheck.code !== 0) {
console.error("❌ Git is not installed or not in PATH.");
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);
}
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.",
);
console.log(`[!] Warning: ${tool.name} not found. ${tool.warnMsg}`);
}
// 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.",
);
// 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(
"✅ 'meta-state' branch already exists. Preserving existing data.",
"[ 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...");
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", [
@ -111,7 +126,6 @@ async function initializeMetaStateBranch() {
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
@ -220,7 +234,7 @@ async function initializeMetaStateBranch() {
}
console.log(
"✅ 'meta-state' branch initialized successfully without modifying working tree.",
"[+] 'meta-state' branch freshly initialized successfully without modifying working tree.",
);
}
@ -229,7 +243,7 @@ async function installHooks() {
if (!existsSync(GIT_DIR)) {
console.error(
" .git directory not found. Must be run from the root of a git repository.",
"[!] Error: .git directory not found. Must be run from the root of a git repository.",
);
Deno.exit(1);
}
@ -238,13 +252,13 @@ async function installHooks() {
const preCommitTarget = join(HOOKS_DIR, "pre-commit");
if (!existsSync(preCommitSource)) {
console.error(` Hook template not found at ${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("Moving existing hook to pre-commit.backup...");
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"));
}
@ -255,7 +269,7 @@ async function installHooks() {
await runCommand("chmod", ["+x", preCommitTarget]);
}
console.log(" pre-commit hook installed successfully.");
console.log("[+] Freshly Initialized: pre-commit hook installed successfully.");
}
async function main() {