133 lines
4.3 KiB
JavaScript
133 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Guard 5: Tool Trace Guard
|
|
* Inventories tool call sites and checks for trace/log coverage.
|
|
*
|
|
* → Architecture Freeze v2 (PR-1): docs/architecture-freeze-v2.md
|
|
* → Future: PR-10 Trace Model enforcement
|
|
*
|
|
* SOFT GUARD — Trace model not yet implemented (PR-10).
|
|
* Currently inventories tool call entry points and reports gaps.
|
|
* No hard failure at this stage.
|
|
*/
|
|
|
|
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { execSync } from "node:child_process";
|
|
|
|
const OPENCLAW_HOME = process.env.OPENCLAW_HOME || "/opt/homebrew/lib/node_modules/openclaw";
|
|
const DIST_DIR = join(OPENCLAW_HOME, "dist");
|
|
|
|
function guardToolTrace() {
|
|
const issues = [];
|
|
const toolSites = [];
|
|
let totalToolCallSites = 0;
|
|
let sitesWithTrace = 0;
|
|
|
|
// Search for tool call patterns in dist
|
|
const searchTerms = [
|
|
"tool_call",
|
|
"toolCall",
|
|
"tool.call",
|
|
"tool_exec",
|
|
"toolExec",
|
|
"invokeTool",
|
|
"runTool",
|
|
];
|
|
|
|
try {
|
|
const grepResult = execSync(
|
|
`grep -rln -E "(tool_call|toolCall|tool\\.call|tool_exec|toolExec|executeTool|invokeTool|runTool)" "${DIST_DIR}" 2>/dev/null | grep -v ".d.ts$" | grep -v "node_modules" | grep ".js$" | head -30`,
|
|
{ timeout: 30000, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 }
|
|
).trim();
|
|
|
|
if (grepResult) {
|
|
const files = grepResult.split("\n").filter(Boolean);
|
|
totalToolCallSites = files.length;
|
|
|
|
for (const file of files) {
|
|
const relativePath = file.replace(DIST_DIR + "/", "");
|
|
|
|
// Check if this file has trace/log instrumentation
|
|
try {
|
|
const content = readFileSync(file, "utf8");
|
|
const hasTrace =
|
|
content.includes("trace") ||
|
|
content.includes("Trace") ||
|
|
content.includes("span") ||
|
|
content.includes("log") ||
|
|
content.includes("event") ||
|
|
content.includes("audit") ||
|
|
content.includes("diagnostics");
|
|
|
|
if (hasTrace) sitesWithTrace++;
|
|
|
|
toolSites.push({
|
|
file: relativePath,
|
|
hasTrace,
|
|
module: relativePath.split("/")[0] || "unknown",
|
|
});
|
|
} catch {
|
|
toolSites.push({
|
|
file: relativePath,
|
|
hasTrace: false,
|
|
module: relativePath.split("/")[0] || "unknown",
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (e.status !== 1) {
|
|
console.log(` Warning: grep failed: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
const sitesWithoutTrace = toolSites.filter(s => !s.hasTrace);
|
|
|
|
console.log("");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log(" Guard 5: Tool Trace Guard");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log("");
|
|
|
|
console.log(` Tool call entry points found: ${totalToolCallSites}`);
|
|
console.log(` Sites with trace/log: ${sitesWithTrace}`);
|
|
console.log(` Sites without trace: ${sitesWithoutTrace.length}`);
|
|
|
|
// Group by module
|
|
const moduleSummary = {};
|
|
for (const s of toolSites) {
|
|
if (!moduleSummary[s.module]) {
|
|
moduleSummary[s.module] = { total: 0, traced: 0 };
|
|
}
|
|
moduleSummary[s.module].total++;
|
|
if (s.hasTrace) moduleSummary[s.module].traced++;
|
|
}
|
|
|
|
if (Object.keys(moduleSummary).length > 0) {
|
|
console.log("\n Trace coverage by module:");
|
|
for (const [mod, stats] of Object.entries(moduleSummary)) {
|
|
const pct = stats.total > 0 ? Math.round(stats.traced / stats.total * 100) : 0;
|
|
const bar = "█".repeat(Math.round(pct / 10)) + "░".repeat(10 - Math.round(pct / 10));
|
|
console.log(` ${bar} ${mod}: ${stats.traced}/${stats.total} (${pct}%)`);
|
|
}
|
|
}
|
|
|
|
if (sitesWithoutTrace.length > 0 && sitesWithoutTrace.length <= 10) {
|
|
console.log("\n Sites without trace:");
|
|
for (const s of sitesWithoutTrace) {
|
|
console.log(` • ${s.file}`);
|
|
}
|
|
}
|
|
|
|
console.log("");
|
|
console.log(" ⚠ WARN: Trace model not yet enforced");
|
|
console.log(` Current coverage: ${sitesWithTrace}/${totalToolCallSites} sites have tracing`);
|
|
console.log(" After PR-10 (Trace Model), all tool calls will require trace spans.");
|
|
|
|
// No hard fail — soft guard
|
|
process.exit(0);
|
|
}
|
|
|
|
guardToolTrace();
|