UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

209 lines (205 loc) • 8.94 kB
"use strict"; /** * check:semantics CLI core (9.53.0 — the build gate). * * Pattern: humble shell (the tool-lint `cli.ts` precedent) — * `bin/agentfootprint-check-semantics.mjs` is a 2-line wrapper; * ALL behavior (arg parsing, catalog coercion, report, exit code) * lives here so it is unit-testable without spawning a process. * Role: `src/semantics/`. Reads ONE JSON file of tools + sample * results, prints a report, returns the process exit code: * 0 — report.ok (and, under --strict, zero warnings) * 1 — findings failed the gate * 2 — usage / input error (bad flags, unreadable file, * unrecognized JSON shape, unknown resultClass) * * Consumers wire it beside their other gates (the `check:tools` convention): * * "check:semantics": "node scripts/dump-semantics-catalog.mjs && agentfootprint-check-semantics semantics-catalog.json" * * The catalog is sample results — what your MOCK tools return — because the * gate judges result shapes, and a build must never run tools that touch * live systems. A mock catalog is something this ecosystem already has * everywhere the check matters. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.runCheckSemanticsCli = exports.coerceSemanticsCatalog = void 0; const check_js_1 = require("./check.js"); const format_js_1 = require("./format.js"); const overclaim_js_1 = require("./overclaim.js"); const types_js_1 = require("./types.js"); const USAGE = `usage: agentfootprint-check-semantics <semantics-catalog.json> [options] <semantics-catalog.json> JSON file of tools + sample results. Accepted shapes: [{ name, resultClass?, results: [...] }] [{ name, resultClass?, result: ... }] (single sample) { tools: [...] } (either row shape) resultClass ∈ { ${types_js_1.RESULT_CLASSES.join(', ')} } — the class declared on defineTool({ resultClass }); omit for tools with no class rules (envelope rules still apply). refusals: [...] (optional) — what the tool throws or returns when it refuses a call, or { text, argument, value } naming what it looked up; judged by the overclaim rules. --strict warnings also fail the gate; the overclaim rules report as errors --rules=<set,...> opt-in rule sets: ${overclaim_js_1.SEMANTICS_RULE_SETS.join(', ')} — errors, judged over every sample --json print the full report as JSON instead of text exit codes: 0 ok · 1 findings failed the gate · 2 usage/input error`; /** * Normalize the accepted JSON shapes to the checker's catalog. Throws (with * a shape description) on unrecognized input — the CLI maps that to exit * code 2. `resultClass` values are passed through untouched; the checker is * the one owner of the closed-set refusal. */ function coerceSemanticsCatalog(json) { const list = Array.isArray(json) ? json : json !== null && typeof json === 'object' && Array.isArray(json.tools) ? json.tools : undefined; if (list === undefined) { throw new Error('expected a JSON array of { name, resultClass?, results } rows or { tools: [...] }'); } return list.map((raw, index) => { if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) { throw new Error(`tools[${index}] is not an object`); } const entry = raw; const name = entry.name; if (typeof name !== 'string' || name.length === 0) { throw new Error(`tools[${index}] has no string 'name'`); } const results = Array.isArray(entry.results) ? entry.results : 'result' in entry ? [entry.result] : undefined; if (results === undefined) { throw new Error(`tools[${index}] ('${name}') has neither 'results' (an array of sample results) nor ` + `'result' (one sample) — the gate judges sample results; a mock tool's return is ` + `the natural sample.`); } return { name, ...(entry.resultClass !== undefined ? { resultClass: entry.resultClass } : {}), results, ...(entry.projections !== undefined ? { projections: entry.projections } : {}), ...(entry.refusals !== undefined ? { refusals: entry.refusals } : {}), }; }); } exports.coerceSemanticsCatalog = coerceSemanticsCatalog; /** * Run the gate CLI. Returns the exit code (never calls `process.exit` — the * bin wrapper assigns it to `process.exitCode`). */ async function runCheckSemanticsCli(argv, io = { // eslint-disable-next-line no-console stdout: (line) => console.log(line), // eslint-disable-next-line no-console stderr: (line) => console.error(line), }) { let file; let strict = false; let json = false; let rules = []; for (const arg of argv) { if (arg === '--strict') strict = true; else if (arg === '--json') json = true; else if (arg.startsWith('--rules=')) { const asked = arg .slice('--rules='.length) .split(',') .filter((r) => r !== ''); const known = overclaim_js_1.SEMANTICS_RULE_SETS; const unknown = asked.find((r) => !known.includes(r)); if (asked.length === 0 || unknown !== undefined) { io.stderr(`unknown rule set '${unknown ?? ''}' — the opt-in rule sets are: ` + `${overclaim_js_1.SEMANTICS_RULE_SETS.join(', ')}\n\n${USAGE}`); return 2; } rules = asked; } else if (arg === '--help' || arg === '-h') { io.stderr(USAGE); return 2; } else if (arg.startsWith('-')) { io.stderr(`unknown flag '${arg}'\n\n${USAGE}`); return 2; } else if (file === undefined) file = arg; else { io.stderr(`unexpected extra argument '${arg}'\n\n${USAGE}`); return 2; } } if (file === undefined) { io.stderr(USAGE); return 2; } let report; try { // Lazy node:fs import (browser-compat — the tool-lint precedent): // `agentfootprint/observe` re-exports this module, and a top-level // node:fs import detonates a browser bundle at module-eval. The CLI // path is the only consumer that touches the filesystem. const { readFile } = await Promise.resolve().then(() => __importStar(require('node:fs/promises'))); const catalog = coerceSemanticsCatalog(JSON.parse(await readFile(file, 'utf8'))); const options = { ...(strict && { strict: true }), ...(rules.length > 0 && { rules }), }; report = (0, check_js_1.checkSemantics)(catalog, options); } catch (error) { io.stderr(`agentfootprint-check-semantics: ${file}: ${error.message}`); return 2; } if (json) io.stdout(JSON.stringify(report, null, 2)); else io.stdout((0, format_js_1.formatSemanticsReport)(report)); if (!report.ok) return 1; if (strict && report.findings.length > 0) return 1; return 0; } exports.runCheckSemanticsCli = runCheckSemanticsCli; //# sourceMappingURL=cli.js.map