#!/usr/bin/env node /** * Guard 3: Tool Path Guard * Records current tool call entry points. Soft warning — no hard fail yet. * * → Architecture Freeze v2 (PR-1): docs/architecture-freeze-v2.md * → Adapter §3.2: Tool Adapters * * ToolCore not yet implemented — this guard inventories the current state * and will FAIL only if a NEW unknown tool path appears. * * Known tool paths: * - native tool (dist/commands/*) * - MCP tool (dist/mcp/*) * - plugin tool (dist/extensions plugin.json contracts.tools) * - codex tool (dist/extensions/codex-supervisor/*) */ import { readdirSync, readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; const OPENCLAW_HOME = process.env.OPENCLAW_HOME || "/opt/homebrew/lib/node_modules/openclaw"; const DIST_DIR = join(OPENCLAW_HOME, "dist"); const EXTENSIONS_DIR = join(DIST_DIR, "extensions"); function guardToolPaths() { const issues = []; const inventory = { native: [], mcp: [], plugin: [], codex: [], unknown: [] }; const knownToolPaths = new Set([ "native", "mcp", "plugin", "codex" ]); // 1. Native tools — look for tool registrations in commands const commandsDir = join(DIST_DIR, "commands"); if (existsSync(commandsDir)) { try { const entries = readdirSync(commandsDir, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory()) { inventory.native.push(`commands/${entry.name}`); } } } catch (e) { issues.push({ level: "WARN", msg: `Could not scan commands dir: ${e.message}` }); } } // 2. MCP tools const mcpDir = join(DIST_DIR, "mcp"); if (existsSync(mcpDir)) { try { const files = readdirSync(mcpDir).filter(f => f.endsWith(".js")); inventory.mcp.push(...files.map(f => `mcp/${f}`)); } catch (e) { issues.push({ level: "WARN", msg: `Could not scan mcp dir: ${e.message}` }); } } // 3. Plugin tools (from openclaw.plugin.json contracts) if (existsSync(EXTENSIONS_DIR)) { try { const extDirs = readdirSync(EXTENSIONS_DIR, { withFileTypes: true }) .filter(d => d.isDirectory()); for (const extDir of extDirs) { const pluginJsonPath = join(EXTENSIONS_DIR, extDir.name, "openclaw.plugin.json"); if (!existsSync(pluginJsonPath)) continue; try { const plugin = JSON.parse(readFileSync(pluginJsonPath, "utf8")); const tools = plugin.contracts?.tools || []; if (tools.length > 0) { // Categorize if (extDir.name.includes("codex") || extDir.name.includes("supervisor")) { inventory.codex.push(`${extDir.name}: [${tools.join(", ")}]`); } else { inventory.plugin.push(`${extDir.name}: [${tools.join(", ")}]`); } } } catch {} } } catch (e) { issues.push({ level: "WARN", msg: `Could not scan extensions: ${e.message}` }); } } // 4. Check for unknown tool paths in key files const keyFiles = readdirSync(DIST_DIR).filter(f => f.endsWith(".js") && (f.includes("tool") || f.includes("command") || f.includes("mcp")) ); for (const f of keyFiles) { const name = f.replace(/\.js$/, "").replace(/-[A-Za-z0-9]{8}$/, ""); // Categorize based on naming if (name.includes("mcp") || name.includes("codex-mcp")) { if (!inventory.mcp.some(t => t.includes(name.substring(0, 10)))) { inventory.mcp.push(`dist/${f}`); } } else if (name.includes("tool")) { if (!inventory.native.some(t => t.includes(name.substring(0, 10)))) { inventory.native.push(`dist/${f}`); } } } // Report console.log(""); console.log("═══════════════════════════════════════"); console.log(" Guard 3: Tool Path Guard"); console.log("═══════════════════════════════════════"); console.log(""); console.log(" Tool Path Inventory:"); console.log(` Native tools: ${inventory.native.length} entries`); console.log(` MCP tools: ${inventory.mcp.length} entries`); console.log(` Plugin tools: ${inventory.plugin.length} plugins`); console.log(` Codex tools: ${inventory.codex.length} plugins`); if (inventory.plugin.length > 0) { console.log("\n Plugin tool contracts:"); for (const p of inventory.plugin) { console.log(` • ${p}`); } } if (inventory.codex.length > 0) { console.log("\n Codex tool contracts:"); for (const p of inventory.codex) { console.log(` • ${p}`); } } // This guard is WARN-only for now (ToolCore not yet implemented) console.log(""); console.log(" ⚠ WARN: ToolCore not yet enforced"); console.log(" This guard currently inventories tool paths."); console.log(" After PR-8/PR-9 (ToolCore), it will enforce all tools go through ToolExecutor."); // No hard fail — informational only at this stage process.exit(0); } guardToolPaths();