@agent-surface/cli 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -182
- package/dist/bin.js +181 -207
- package/dist/bin.js.map +1 -1
- package/dist/chunk-IA7FUP4R.js +125 -0
- package/dist/chunk-IA7FUP4R.js.map +1 -0
- package/dist/chunk-X6RGGBQ4.js +139 -0
- package/dist/chunk-X6RGGBQ4.js.map +1 -0
- package/dist/index.d.ts +31 -173
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/ink-E6DATMUQ.js +51 -0
- package/dist/ink-E6DATMUQ.js.map +1 -0
- package/package.json +4 -20
- package/dist/check-VWRYNYLN.js +0 -259
- package/dist/check-VWRYNYLN.js.map +0 -1
- package/dist/chunk-A2G4QLX5.js +0 -29
- package/dist/chunk-A2G4QLX5.js.map +0 -1
- package/dist/chunk-A5UCBF7D.js +0 -246
- package/dist/chunk-A5UCBF7D.js.map +0 -1
- package/dist/chunk-GYYWHZPM.js +0 -532
- package/dist/chunk-GYYWHZPM.js.map +0 -1
- package/dist/chunk-NFK3XWWH.js +0 -1475
- package/dist/chunk-NFK3XWWH.js.map +0 -1
- package/dist/chunk-Y2LSPEVK.js +0 -61
- package/dist/chunk-Y2LSPEVK.js.map +0 -1
- package/dist/collect.js +0 -52
- package/dist/collect.js.map +0 -1
- package/dist/config-rHD2wEws.d.ts +0 -60
- package/dist/init-BVZR6CRS.js +0 -164
- package/dist/init-BVZR6CRS.js.map +0 -1
- package/dist/ink-ZCQ26EY4.js +0 -345
- package/dist/ink-ZCQ26EY4.js.map +0 -1
- package/dist/inspect-3YFPCYQJ.js +0 -226
- package/dist/inspect-3YFPCYQJ.js.map +0 -1
- package/dist/snapshot-5QKLZKZI.js +0 -130
- package/dist/snapshot-5QKLZKZI.js.map +0 -1
- package/dist/vitest.d.ts +0 -30
- package/dist/vitest.js +0 -9
- package/dist/vitest.js.map +0 -1
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bin.ts","../src/dom.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\nimport { DEPTHS, isDepth } from \"./contract.js\";\nimport { installDom } from \"./dom.js\";\nimport { findConfig } from \"./load.js\";\nimport { writeError, write } from \"./output.js\";\nimport { renderReportPlain } from \"./render/plain.js\";\n\nconst USAGE = `agent-surface — the agent surface your app exposes\n\nUsage\n agent-surface init read the codebase, then scaffold a config\n agent-surface inspect [scenario] what an agent can reach, and what it cannot\n agent-surface snapshot [scenario] write/refresh the committed baseline\n agent-surface check [scenario] fail on drift, or on a capability no scenario reaches\n\nEvery command covers all scenarios in the config unless you name one.\n\nDepth\n --depth full read the source AND mount the scenarios, and report the gap (default)\n --depth static read the source only — no Vite, no jsdom, no mount, no scenarios needed\n --depth runtime mount only — skip the TypeScript program on a repo wide enough to feel it\n\nOptions\n --config <path> path to agent-surface.config.* (default: nearest, searching upward)\n --baseline-dir where baselines live (default: .agent-surface next to the config)\n --scope <prefix> restrict to a component-type prefix (repeatable)\n --detail full capability, origin, and diagnostic detail\n --explain name the policies behind every decision (implies --detail)\n --schemas include input/output JSON Schemas (implies --detail)\n --tsconfig <path> tsconfig the source read uses (default: nearest to the config)\n --allow-unresolved check: do not fail on a call site that could not be read\n --yes init: write without asking\n --json emit data instead of a rendered view\n --plain force plain text (implied when piped, or under CI / NO_COLOR)\n -h, --help show this\n -v, --version print the version\n\nExit codes are the contract: 0 clean, 1 a finding, 2 the command could not run.\n`;\n\nconst COMMANDS = [\"init\", \"inspect\", \"snapshot\", \"check\"];\n\n/** Commands that were cut, and where their answer went (0.11.0). */\nconst RETIRED: Record<string, string> = {\n capabilities: \"agent-surface inspect --depth static\",\n coverage: \"agent-surface inspect (or `check`, which now fails on the gap)\",\n};\n\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n let parsed;\n try {\n parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n config: { type: \"string\" },\n \"baseline-dir\": { type: \"string\" },\n scope: { type: \"string\", multiple: true },\n depth: { type: \"string\", default: \"full\" },\n detail: { type: \"boolean\", default: false },\n explain: { type: \"boolean\", default: false },\n schemas: { type: \"boolean\", default: false },\n tsconfig: { type: \"string\" },\n \"allow-unresolved\": { type: \"boolean\", default: false },\n yes: { type: \"boolean\", default: false },\n json: { type: \"boolean\", default: false },\n plain: { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n version: { type: \"boolean\", short: \"v\", default: false },\n },\n });\n } catch (error) {\n writeError(error instanceof Error ? error.message : String(error));\n writeError(USAGE);\n return 2;\n }\n\n const { values, positionals } = parsed;\n if (values.help) {\n write(USAGE);\n return 0;\n }\n if (values.version) {\n write(await readVersion());\n return 0;\n }\n\n const [command, scenario] = positionals;\n if (!command) {\n write(USAGE);\n return 2;\n }\n if (!COMMANDS.includes(command)) {\n const moved = RETIRED[command];\n writeError(\n moved\n ? `\"${command}\" was removed in 0.11 — its answer is now \\`${moved}\\`. ` +\n \"The static catalog and the live projection are one command, so the gap between \" +\n \"them is reported rather than left for whoever remembers to look.\"\n : `unknown command \"${command}\"`,\n );\n if (!moved) writeError(USAGE);\n return 2;\n }\n\n if (!isDepth(values.depth)) {\n writeError(`--depth must be one of ${DEPTHS.join(\", \")} — got \"${values.depth}\"`);\n return 2;\n }\n const depth = values.depth;\n\n try {\n if (command === \"init\") {\n // Nothing to find and nothing to mount: `init` is the command that runs\n // before a config exists.\n const { runInit } = await import(\"./commands/init.js\");\n return await runInit({\n cwd: process.cwd(),\n ...(values.tsconfig ? { tsconfig: values.tsconfig } : {}),\n ...(values.yes ? { yes: true } : {}),\n ...(values.plain ? { plain: true } : {}),\n });\n }\n\n const configPath = values.config ?? findConfig();\n if (!configPath) {\n writeError(\n \"no agent-surface.config.* found (searched upward from the working directory).\\n\" +\n \"Run `agent-surface init`, or see https://agent-surface-docs.vercel.app/20-cli\",\n );\n return 2;\n }\n\n // A presentation surface needs a DOM to mount into, and react-dom reads\n // these globals at import time — so this must happen before any app module\n // loads. It stays installed for the life of the process, on purpose\n // (see dom.ts).\n //\n // `--depth static` is the exception, and deliberately so: it reads the\n // TypeScript program and mounts nothing, so it must not pay for — or be\n // able to be affected by — a DOM it never renders into.\n if (depth !== \"static\") installDom();\n\n const shared = {\n configPath,\n depth,\n ...(scenario ? { scenario } : {}),\n ...(values.scope ? { scope: values.scope } : {}),\n ...(values.tsconfig ? { tsconfig: values.tsconfig } : {}),\n ...(values.json ? { json: true } : {}),\n ...(values.plain ? { plain: true } : {}),\n ...(values[\"baseline-dir\"] ? { baselineDir: values[\"baseline-dir\"] } : {}),\n };\n\n if (command === \"inspect\") {\n const { runInspect } = await import(\"./commands/inspect.js\");\n return await runInspect({\n ...shared,\n ...(values.detail ? { detail: true } : {}),\n ...(values.explain ? { explain: true } : {}),\n ...(values.schemas ? { schemas: true } : {}),\n });\n }\n if (command === \"snapshot\") {\n const { runSnapshot } = await import(\"./commands/snapshot.js\");\n return await runSnapshot(shared);\n }\n const { runCheck } = await import(\"./commands/check.js\");\n return await runCheck({\n ...shared,\n ...(values.detail ? { detail: true } : {}),\n ...(values[\"allow-unresolved\"] ? { allowUnresolved: true } : {}),\n });\n } catch (error) {\n writeError(error instanceof Error ? error.message : String(error));\n if (error instanceof Error && error.stack && process.env[\"AGENT_SURFACE_DEBUG\"]) {\n writeError(error.stack);\n }\n // `2` — the command could not run, as opposed to running and finding\n // something. CI has to tell those apart: a gate that exits 1 both when the\n // surface changed and when the tool never loaded the app is a gate whose\n // green is the only signal worth anything, and whose red says nothing.\n return 2;\n }\n}\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFileSync } = await import(\"node:fs\");\n const path = fileURLToPath(new URL(\"../package.json\", import.meta.url));\n return (JSON.parse(readFileSync(path, \"utf8\")) as { version: string }).version;\n } catch {\n return \"unknown\";\n }\n}\n\n/**\n * Self-execute only as a binary; importing this module (tests) must not run it.\n * `argv[1]` is compared through `realpathSync` because package managers install\n * the bin as a symlink — comparing the raw path silently never matches, and the\n * CLI exits 0 having done nothing.\n */\nfunction invokedAsBinary(): boolean {\n const entry = process.argv[1];\n if (!entry) return false;\n try {\n return fileURLToPath(import.meta.url) === realpathSync(entry);\n } catch {\n return false;\n }\n}\n\n/**\n * How long a finished command is allowed to keep running before it is treated\n * as wedged. Costs a hung run one extra second; costs a healthy run nothing,\n * because a healthy run has already exited by then.\n */\nconst GRACE_MS = 1000;\n\n/**\n * What this process's own stdin/stdout/stderr are called, depending on where\n * they were pointed: a terminal, a `|`, or a `>`. None of the three holds the\n * event loop open — a clean run exits naturally through all of them — so when\n * something *else* has wedged the command they are still in the handle table,\n * and naming them would send the reader after the one thing that is not the\n * cause. Their own leak would be the CLI's bug to fix, not the app's to hear\n * about.\n */\nconst OWN_STDIO = new Set([\"TTYWrap\", \"PipeWrap\", \"FileWrap\"]);\n\n/** Resource types still holding the loop once the command is provably wedged. */\nfunction heldHandles(): string[] {\n const active = process.getActiveResourcesInfo?.() ?? [];\n return active.filter((resource) => !OWN_STDIO.has(resource));\n}\n\n/**\n * `process.exit()` discards whatever is still buffered on a pipe, so a\n * redirected run could lose its last lines — and redirected runs are the ones\n * that matter (`--json`, CI logs). Drain both streams first, but never wait\n * indefinitely: a reader that has stopped consuming must not turn a forced exit\n * back into the hang it exists to prevent.\n */\nasync function flushOutput(): Promise<void> {\n const drained = Promise.all(\n [process.stdout, process.stderr].map(\n (stream) =>\n new Promise<void>((resolve) => {\n if (stream.writableLength === 0) resolve();\n else stream.write(\"\", () => resolve());\n }),\n ),\n );\n const deadline = new Promise<void>((resolve) => {\n setTimeout(resolve, 2000).unref();\n });\n await Promise.race([drained, deadline]);\n}\n\n/**\n * Ends the process, and says why it had to be ended when that is the case.\n *\n * The mount is an arbitrary React tree, not code written for a one-shot\n * process: a polling interval, a websocket, an animation loop or a data layer's\n * cache timer all keep Node's event loop alive long after the surface has been\n * rendered. Setting `process.exitCode` alone means such a command prints its\n * full, correct output and then appears to hang — with a successful exit code\n * already set, and nothing on screen to explain the wait (`AS-CLI-005`).\n *\n * The detector is the timer itself, not a reading of the handle table. An\n * unref'd timer does not hold the loop open, so a command with nothing left to\n * do exits naturally on `process.exitCode` and this never fires. Its firing is\n * therefore the diagnosis — this run *was* about to hang — and whatever it then\n * finds in the handle table is genuinely the cause. Reading the table eagerly\n * instead would blame the app for the CLI's own teardown: vite's dev server is\n * still closing its socket at the moment the last scenario is rendered, so\n * every healthy run would accuse its own app of leaking a `TCPServerWrap`.\n *\n * Naming the handles is the same move the package already makes for\n * capabilities: the invisible thing becomes inspectable. Exiting anyway is what\n * makes the tool usable unattended.\n */\nfunction exitWhenWedged(code: number): void {\n process.exitCode = code;\n setTimeout(() => {\n const held = heldHandles();\n if (held.length > 0) {\n const kinds = [...new Set(held)].sort().join(\", \");\n // Rendered plain, and through the report's own grid: this is a block of\n // the report, so it has to line up with one — but it is written during a\n // forced teardown, after the presenter has drawn its last frame and\n // unmounted, and opening a new live frame there is how a command that was\n // about to be killed hangs instead.\n writeError(\n `\\n${renderReportPlain([\n {\n title: \"PROCESS CLEANUP WARN\",\n rows: [\n { label: \"Open handles\", text: `${held.length} (${kinds})` },\n { label: \"Impact\", text: \"report complete; exit code unchanged; process exit forced\" },\n {\n label: \"Likely cause\",\n text: \"polling, websocket, or a cache timer created during mount\",\n },\n { label: \"Exit\", text: String(code) },\n ],\n },\n ])}`,\n );\n }\n void flushOutput().then(() => process.exit(code));\n }, GRACE_MS).unref();\n}\n\nif (invokedAsBinary()) {\n main().then(\n (code) => exitWhenWedged(code),\n (error: unknown) => {\n writeError(error instanceof Error ? error.message : String(error));\n exitWhenWedged(1);\n },\n );\n}\n","import { JSDOM } from \"jsdom\";\n\nconst DOM_EVENT_GLOBALS = [\"Event\", \"CustomEvent\"] as const;\ntype EventRealm = Record<(typeof DOM_EVENT_GLOBALS)[number], unknown>;\n\n/** DOM nodes reject events constructed by Node's separate Web API realm. */\nfunction alignEventRealm(globals: Record<string, unknown>, window: EventRealm): void {\n for (const key of DOM_EVENT_GLOBALS) {\n if (typeof window[key] !== \"function\") continue;\n Object.defineProperty(globals, key, {\n value: window[key],\n writable: true,\n configurable: true,\n });\n }\n}\n\n/**\n * A presentation surface only exists once components mount, and mounting needs\n * a DOM. Vitest gets one from its `jsdom` environment; a plain Node process has\n * to install one itself — *before* anything imports `react-dom`, which reads\n * these globals at module scope.\n *\n * Process-wide on purpose, and permanent: the app tree runs inside the\n * vite-node graph, which shares this realm's globals, and there is deliberately\n * no way to take the DOM back down — see the note on teardown below. Returning\n * nothing is the honest signature; an installer that handed back a disposer\n * doing nothing would read, at every call site, as cleanup that happens.\n */\nexport function installDom(url = \"http://localhost/\"): void {\n const globals = globalThis as Record<string, unknown>;\n if (typeof globals[\"document\"] !== \"undefined\") {\n const installedWindow = globals[\"window\"];\n if (installedWindow && typeof installedWindow === \"object\") {\n alignEventRealm(globals, installedWindow as EventRealm);\n }\n return;\n }\n\n const dom = new JSDOM(\"<!doctype html><html><body></body></html>\", {\n url,\n pretendToBeVisual: true,\n });\n const { window } = dom;\n\n // Everything jsdom's window defines that this realm does not already have.\n // Skipping existing keys matters: Node's own `fetch`, `URL` and timers are\n // more capable than jsdom's shims, and clobbering them breaks app code.\n for (const key of Object.getOwnPropertyNames(window)) {\n if (key.startsWith(\"_\")) continue;\n if (key in globals) continue;\n const descriptor = Object.getOwnPropertyDescriptor(window, key);\n if (!descriptor) continue;\n Object.defineProperty(globals, key, descriptor);\n }\n\n for (const key of [\"window\", \"document\", \"navigator\"] as const) {\n if (!(key in globals)) {\n Object.defineProperty(globals, key, { value: window[key], configurable: true });\n }\n }\n\n // Node 19+ already defines Event and CustomEvent, so the copy loop above\n // deliberately skips them. They are not interchangeable with jsdom's:\n // dispatching Node's event at a jsdom node throws a foreign-realm TypeError.\n // Radix focus scopes do exactly that while Dialog/Popover mount (#43).\n alignEventRealm(globals, window as unknown as EventRealm);\n}\n\n/**\n * There is deliberately no teardown, and the DOM is deliberately process-wide.\n *\n * `react-dom` captures `window`/`document` when it is first imported. Removing\n * the globals — or worse, calling `window.close()` — leaves that captured\n * reference pointing at a dead realm, so the *next* mount in the same process\n * fails in a way that looks nothing like its cause. A CLI invocation ends by\n * exiting (see `exitWhenWedged` in `bin.ts`), so there is nothing to reclaim; only\n * in-process callers (the test suite) run more than one command, and those are\n * exactly the ones this protects.\n */\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts","../src/commands.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\nimport { runCheck, runInspect, runSnapshot, type CommandOptions } from \"./commands.js\";\nimport type { OutputFormat } from \"./report.js\";\n\nconst USAGE = `agent-surface — compiled production capability contract\n\nUsage\n agent-surface inspect\n agent-surface snapshot\n agent-surface check --base origin/main --format github\n\nOptions\n --root <path> application root (default: cwd)\n --config <path> Vite config, relative to root\n --snapshot <path> committed contract (default: .agent-surface/contract.json)\n --target <name> production build target; repeatable\n --base <git-ref> render committed PR drift against Git base\n --policy <mode> all | widening | narrowing | neutral | none (default: all)\n --format <format> human | json | github | markdown (default: human)\n --json alias for --format json\n --plain disable Ink terminal rendering\n -h, --help\n -v, --version\n\nExit: 0 clean/viewed, 1 deterministic drift/policy finding, 2 completeness failure.\n`;\n\nconst COMMANDS = new Set([\"inspect\", \"snapshot\", \"check\"]);\nconst FORMATS = new Set<OutputFormat>([\"human\", \"json\", \"github\", \"markdown\"]);\nconst POLICIES = new Set([\"all\", \"widening\", \"narrowing\", \"neutral\", \"none\"]);\n\nfunction write(text: string, error = false): void {\n (error ? process.stderr : process.stdout).write(text.endsWith(\"\\n\") ? text : `${text}\\n`);\n}\n\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n let parsed;\n try {\n parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n root: { type: \"string\", default: process.cwd() },\n config: { type: \"string\" },\n snapshot: { type: \"string\" },\n target: { type: \"string\", multiple: true },\n base: { type: \"string\" },\n policy: { type: \"string\", default: \"all\" },\n format: { type: \"string\", default: \"human\" },\n json: { type: \"boolean\", default: false },\n plain: { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n version: { type: \"boolean\", short: \"v\", default: false },\n },\n });\n } catch (error) {\n write(error instanceof Error ? error.message : String(error), true);\n write(USAGE, true);\n return 2;\n }\n const { values, positionals } = parsed;\n if (values.help) {\n write(USAGE);\n return 0;\n }\n if (values.version) {\n write(await readVersion());\n return 0;\n }\n const [command, ...extra] = positionals;\n if (!command || !COMMANDS.has(command) || extra.length > 0) {\n write(command === \"init\" ? \"init removed: configure the Vite compiler plugin\" : `invalid command ${command ?? \"\"}`, true);\n write(USAGE, true);\n return 2;\n }\n const format = (values.json ? \"json\" : values.format) as OutputFormat;\n if (!FORMATS.has(format)) {\n write(`--format must be human, json, github, or markdown`, true);\n return 2;\n }\n if (!POLICIES.has(values.policy)) {\n write(`--policy must be all, widening, narrowing, neutral, or none`, true);\n return 2;\n }\n const options: CommandOptions = {\n root: values.root,\n ...(values.config ? { configFile: values.config } : {}),\n ...(values.snapshot ? { snapshot: values.snapshot } : {}),\n targets: values.target ?? [],\n ...(values.base ? { base: values.base } : {}),\n format,\n policy: values.policy as CommandOptions[\"policy\"],\n ...(values.plain ? { plain: true } : {}),\n };\n try {\n if (command === \"inspect\") return await runInspect(options);\n if (command === \"snapshot\") return await runSnapshot(options);\n return await runCheck(options);\n } catch (error) {\n write(error instanceof Error ? error.message : String(error), true);\n if (error instanceof Error && error.stack && process.env[\"AGENT_SURFACE_DEBUG\"]) {\n write(error.stack, true);\n }\n return 2;\n }\n}\n\nasync function readVersion(): Promise<string> {\n const { readFileSync } = await import(\"node:fs\");\n const path = fileURLToPath(new URL(\"../package.json\", import.meta.url));\n return (JSON.parse(readFileSync(path, \"utf8\")) as { version: string }).version;\n}\n\nfunction invokedAsBinary(): boolean {\n const entry = process.argv[1];\n if (!entry) return false;\n try {\n return fileURLToPath(import.meta.url) === realpathSync(entry);\n } catch {\n return false;\n }\n}\n\nif (invokedAsBinary()) {\n void main().then((code) => {\n process.exitCode = code;\n });\n}\n","import { resolve } from \"node:path\";\nimport type { CapabilityContractEntry, CapabilityContractManifest } from \"@agent-surface/core\";\nimport {\n compileCapabilityContract,\n computeManifestHash,\n} from \"@agent-surface/compiler\";\nimport { diffContracts, type ChangeClassification } from \"./diff.js\";\nimport { DEFAULT_SNAPSHOT, readBaseManifest, readManifest, writeManifest } from \"./files.js\";\nimport type { ContractReport, OutputFormat } from \"./report.js\";\nimport { renderReport } from \"./report.js\";\n\nexport interface CommandOptions {\n root: string;\n configFile?: string;\n snapshot?: string;\n targets: string[];\n base?: string;\n format: OutputFormat;\n policy: \"all\" | ChangeClassification | \"none\";\n plain?: boolean;\n}\n\nfunction mergeManifests(manifests: CapabilityContractManifest[]): CapabilityContractManifest {\n const entries = new Map<string, CapabilityContractEntry>();\n for (const manifest of manifests) {\n for (const entry of manifest.capabilities) {\n const key = `${entry.declarationId}\\0${entry.capabilityId}`;\n const existing = entries.get(key);\n if (!existing) entries.set(key, { ...entry, targets: [...entry.targets] });\n else {\n if (existing.contractHash !== entry.contractHash) {\n throw new Error(`contract ${entry.declarationId} / ${entry.capabilityId} differs across targets`);\n }\n existing.targets = [...new Set([...existing.targets, ...entry.targets])].sort();\n }\n }\n }\n const payload = {\n formatVersion: 3 as const,\n compilerVersion: manifests[0]?.compilerVersion ?? \"unknown\",\n targets: [...new Set(manifests.flatMap((manifest) => manifest.targets))].sort(),\n capabilities: [...entries.values()].sort((a, b) =>\n `${a.declarationId}\\0${a.capabilityId}`.localeCompare(`${b.declarationId}\\0${b.capabilityId}`),\n ),\n externalContracts: manifests\n .flatMap((manifest) => manifest.externalContracts)\n .filter((entry, index, all) => all.findIndex((candidate) => candidate.digest === entry.digest) === index)\n .sort((a, b) => a.source.localeCompare(b.source)),\n completeness: { status: \"proven\" as const },\n };\n return { ...payload, hash: computeManifestHash(payload) };\n}\n\nasync function compile(options: CommandOptions): Promise<CapabilityContractManifest> {\n const targets = options.targets.length > 0 ? options.targets : [\"web-production\"];\n const manifests = await Promise.all(\n targets.map((target) =>\n compileCapabilityContract({\n root: options.root,\n ...(options.configFile ? { configFile: options.configFile } : {}),\n target,\n }),\n ),\n );\n return mergeManifests(manifests);\n}\n\nfunction shouldFail(\n policy: CommandOptions[\"policy\"],\n changes: ReturnType<typeof diffContracts>,\n): boolean {\n if (policy === \"none\") return false;\n if (policy === \"all\") return changes.length > 0;\n return changes.some((change) => change.classification === policy);\n}\n\nasync function present(report: ContractReport, options: CommandOptions): Promise<void> {\n const interactive =\n options.format === \"human\" &&\n !options.plain &&\n process.stdout.isTTY === true &&\n !process.env[\"CI\"] &&\n !process.env[\"NO_COLOR\"];\n if (interactive) {\n const { renderInk } = await import(\"./ink.js\");\n await renderInk(report);\n return;\n }\n process.stdout.write(renderReport(report, options.format));\n}\n\nexport async function runInspect(options: CommandOptions): Promise<number> {\n const manifest = await compile(options);\n const snapshotPath = resolve(options.root, options.snapshot ?? DEFAULT_SNAPSHOT);\n const snapshot = readManifest(snapshotPath);\n const integrityChanges = diffContracts(snapshot, manifest);\n const report: ContractReport = {\n command: \"inspect\",\n status: \"view\",\n manifest,\n snapshotPath,\n integrity: {\n status: !snapshot ? \"missing\" : snapshot.hash === manifest.hash ? \"current\" : \"stale\",\n changes: integrityChanges,\n },\n ...(options.base\n ? {\n pullRequest: {\n base: options.base,\n changes: diffContracts(readBaseManifest(options.base, snapshotPath, options.root), manifest),\n },\n }\n : {}),\n };\n await present(report, options);\n return 0;\n}\n\nexport async function runSnapshot(options: CommandOptions): Promise<number> {\n const manifest = await compile(options);\n const snapshotPath = resolve(options.root, options.snapshot ?? DEFAULT_SNAPSHOT);\n writeManifest(snapshotPath, manifest);\n await present({ command: \"snapshot\", status: \"written\", manifest, snapshotPath }, options);\n return 0;\n}\n\nexport async function runCheck(options: CommandOptions): Promise<number> {\n const manifest = await compile(options);\n const snapshotPath = resolve(options.root, options.snapshot ?? DEFAULT_SNAPSHOT);\n const snapshot = readManifest(snapshotPath);\n const integrityChanges = diffContracts(snapshot, manifest);\n const integrityCurrent = snapshot?.hash === manifest.hash;\n const pr = options.base\n ? {\n base: options.base,\n changes: diffContracts(readBaseManifest(options.base, snapshotPath, options.root), snapshot ?? manifest),\n }\n : undefined;\n const failed = !integrityCurrent || (pr ? shouldFail(options.policy, pr.changes) : false);\n const report: ContractReport = {\n command: \"check\",\n status: failed ? \"fail\" : \"pass\",\n manifest,\n snapshotPath,\n integrity: {\n status: !snapshot ? \"missing\" : integrityCurrent ? \"current\" : \"stale\",\n changes: integrityChanges,\n },\n ...(pr ? { pullRequest: pr } : {}),\n };\n await present(report, options);\n return failed ? 1 : 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/diff.ts
|
|
2
|
+
var RISK = {
|
|
3
|
+
read: 0,
|
|
4
|
+
"local-state": 1,
|
|
5
|
+
navigation: 2,
|
|
6
|
+
"server-query": 3,
|
|
7
|
+
"server-mutation": 4,
|
|
8
|
+
"external-side-effect": 5,
|
|
9
|
+
destructive: 6
|
|
10
|
+
};
|
|
11
|
+
var CONFIRMATION = { never: 0, optional: 1, required: 2 };
|
|
12
|
+
function key(entry) {
|
|
13
|
+
return `${entry.declarationId}\0${entry.capabilityId}`;
|
|
14
|
+
}
|
|
15
|
+
function equal(a, b) {
|
|
16
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
17
|
+
}
|
|
18
|
+
function stringSet(value) {
|
|
19
|
+
return new Set(Array.isArray(value) ? value.map((item) => JSON.stringify(item)) : []);
|
|
20
|
+
}
|
|
21
|
+
function setDirection(before, after) {
|
|
22
|
+
const a = stringSet(before);
|
|
23
|
+
const b = stringSet(after);
|
|
24
|
+
const added = [...b].some((item) => !a.has(item));
|
|
25
|
+
const removed = [...a].some((item) => !b.has(item));
|
|
26
|
+
if (added && removed) return "mixed";
|
|
27
|
+
if (added) return "added";
|
|
28
|
+
if (removed) return "removed";
|
|
29
|
+
return "same";
|
|
30
|
+
}
|
|
31
|
+
function classify(field, before, after) {
|
|
32
|
+
if (field === "targets") {
|
|
33
|
+
const direction = setDirection(before, after);
|
|
34
|
+
return direction === "added" ? "widening" : direction === "removed" ? "narrowing" : "neutral";
|
|
35
|
+
}
|
|
36
|
+
if (field === "policies") {
|
|
37
|
+
const direction = setDirection(before, after);
|
|
38
|
+
return direction === "removed" || direction === "mixed" ? "widening" : direction === "added" ? "narrowing" : "neutral";
|
|
39
|
+
}
|
|
40
|
+
if (field === "confirmation") {
|
|
41
|
+
const a = CONFIRMATION[String(before ?? "never")] ?? 0;
|
|
42
|
+
const b = CONFIRMATION[String(after ?? "never")] ?? 0;
|
|
43
|
+
return b < a ? "widening" : b > a ? "narrowing" : "neutral";
|
|
44
|
+
}
|
|
45
|
+
if (field === "effect") {
|
|
46
|
+
const a = RISK[String(before)] ?? 0;
|
|
47
|
+
const b = RISK[String(after)] ?? 0;
|
|
48
|
+
return b < a ? "widening" : b > a ? "narrowing" : "neutral";
|
|
49
|
+
}
|
|
50
|
+
return "neutral";
|
|
51
|
+
}
|
|
52
|
+
function diffContracts(before, after) {
|
|
53
|
+
if (!before) {
|
|
54
|
+
return after.capabilities.map((entry) => ({
|
|
55
|
+
declarationId: entry.declarationId,
|
|
56
|
+
capabilityId: entry.capabilityId,
|
|
57
|
+
kind: "added",
|
|
58
|
+
field: "capability",
|
|
59
|
+
classification: "widening",
|
|
60
|
+
after: entry
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
const left = new Map(before.capabilities.map((entry) => [key(entry), entry]));
|
|
64
|
+
const right = new Map(after.capabilities.map((entry) => [key(entry), entry]));
|
|
65
|
+
const changes = [];
|
|
66
|
+
for (const entryKey of [.../* @__PURE__ */ new Set([...left.keys(), ...right.keys()])].sort()) {
|
|
67
|
+
const a = left.get(entryKey);
|
|
68
|
+
const b = right.get(entryKey);
|
|
69
|
+
if (!a && b) {
|
|
70
|
+
changes.push({
|
|
71
|
+
declarationId: b.declarationId,
|
|
72
|
+
capabilityId: b.capabilityId,
|
|
73
|
+
kind: "added",
|
|
74
|
+
field: "capability",
|
|
75
|
+
classification: "widening",
|
|
76
|
+
after: b
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (a && !b) {
|
|
81
|
+
changes.push({
|
|
82
|
+
declarationId: a.declarationId,
|
|
83
|
+
capabilityId: a.capabilityId,
|
|
84
|
+
kind: "removed",
|
|
85
|
+
field: "capability",
|
|
86
|
+
classification: "narrowing",
|
|
87
|
+
before: a
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!a || !b) continue;
|
|
92
|
+
const fields = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
93
|
+
fields.delete("declarationId");
|
|
94
|
+
fields.delete("capabilityId");
|
|
95
|
+
fields.delete("contractHash");
|
|
96
|
+
for (const field of [...fields].sort()) {
|
|
97
|
+
const beforeValue = a[field];
|
|
98
|
+
const afterValue = b[field];
|
|
99
|
+
if (equal(beforeValue, afterValue)) continue;
|
|
100
|
+
changes.push({
|
|
101
|
+
declarationId: b.declarationId,
|
|
102
|
+
capabilityId: b.capabilityId,
|
|
103
|
+
kind: "changed",
|
|
104
|
+
field,
|
|
105
|
+
classification: classify(field, beforeValue, afterValue),
|
|
106
|
+
before: beforeValue,
|
|
107
|
+
after: afterValue
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return changes;
|
|
112
|
+
}
|
|
113
|
+
function changeCounts(changes) {
|
|
114
|
+
return {
|
|
115
|
+
widening: changes.filter((change) => change.classification === "widening").length,
|
|
116
|
+
narrowing: changes.filter((change) => change.classification === "narrowing").length,
|
|
117
|
+
neutral: changes.filter((change) => change.classification === "neutral").length
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
diffContracts,
|
|
123
|
+
changeCounts
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=chunk-IA7FUP4R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/diff.ts"],"sourcesContent":["import type { CapabilityContractEntry, CapabilityContractManifest } from \"@agent-surface/core\";\n\nexport type ChangeClassification = \"widening\" | \"narrowing\" | \"neutral\";\nexport type ChangeKind = \"added\" | \"removed\" | \"changed\";\n\nexport interface ContractChange {\n declarationId: string;\n capabilityId: string;\n kind: ChangeKind;\n field: string;\n classification: ChangeClassification;\n before?: unknown;\n after?: unknown;\n}\n\nconst RISK: Record<string, number> = {\n read: 0,\n \"local-state\": 1,\n navigation: 2,\n \"server-query\": 3,\n \"server-mutation\": 4,\n \"external-side-effect\": 5,\n destructive: 6,\n};\n\nconst CONFIRMATION: Record<string, number> = { never: 0, optional: 1, required: 2 };\n\nfunction key(entry: CapabilityContractEntry): string {\n return `${entry.declarationId}\\0${entry.capabilityId}`;\n}\n\nfunction equal(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\nfunction stringSet(value: unknown): Set<string> {\n return new Set(Array.isArray(value) ? value.map((item) => JSON.stringify(item)) : []);\n}\n\nfunction setDirection(before: unknown, after: unknown): \"added\" | \"removed\" | \"mixed\" | \"same\" {\n const a = stringSet(before);\n const b = stringSet(after);\n const added = [...b].some((item) => !a.has(item));\n const removed = [...a].some((item) => !b.has(item));\n if (added && removed) return \"mixed\";\n if (added) return \"added\";\n if (removed) return \"removed\";\n return \"same\";\n}\n\nfunction classify(field: string, before: unknown, after: unknown): ChangeClassification {\n if (field === \"targets\") {\n const direction = setDirection(before, after);\n return direction === \"added\" ? \"widening\" : direction === \"removed\" ? \"narrowing\" : \"neutral\";\n }\n if (field === \"policies\") {\n const direction = setDirection(before, after);\n return direction === \"removed\" || direction === \"mixed\"\n ? \"widening\"\n : direction === \"added\"\n ? \"narrowing\"\n : \"neutral\";\n }\n if (field === \"confirmation\") {\n const a = CONFIRMATION[String(before ?? \"never\")] ?? 0;\n const b = CONFIRMATION[String(after ?? \"never\")] ?? 0;\n return b < a ? \"widening\" : b > a ? \"narrowing\" : \"neutral\";\n }\n if (field === \"effect\") {\n const a = RISK[String(before)] ?? 0;\n const b = RISK[String(after)] ?? 0;\n // Lowering declared risk weakens review/approval posture.\n return b < a ? \"widening\" : b > a ? \"narrowing\" : \"neutral\";\n }\n return \"neutral\";\n}\n\nexport function diffContracts(\n before: CapabilityContractManifest | undefined,\n after: CapabilityContractManifest,\n): ContractChange[] {\n if (!before) {\n return after.capabilities.map((entry) => ({\n declarationId: entry.declarationId,\n capabilityId: entry.capabilityId,\n kind: \"added\",\n field: \"capability\",\n classification: \"widening\",\n after: entry,\n }));\n }\n const left = new Map(before.capabilities.map((entry) => [key(entry), entry]));\n const right = new Map(after.capabilities.map((entry) => [key(entry), entry]));\n const changes: ContractChange[] = [];\n for (const entryKey of [...new Set([...left.keys(), ...right.keys()])].sort()) {\n const a = left.get(entryKey);\n const b = right.get(entryKey);\n if (!a && b) {\n changes.push({\n declarationId: b.declarationId,\n capabilityId: b.capabilityId,\n kind: \"added\",\n field: \"capability\",\n classification: \"widening\",\n after: b,\n });\n continue;\n }\n if (a && !b) {\n changes.push({\n declarationId: a.declarationId,\n capabilityId: a.capabilityId,\n kind: \"removed\",\n field: \"capability\",\n classification: \"narrowing\",\n before: a,\n });\n continue;\n }\n if (!a || !b) continue;\n const fields = new Set([...Object.keys(a), ...Object.keys(b)]);\n fields.delete(\"declarationId\");\n fields.delete(\"capabilityId\");\n fields.delete(\"contractHash\");\n for (const field of [...fields].sort()) {\n const beforeValue = (a as unknown as Record<string, unknown>)[field];\n const afterValue = (b as unknown as Record<string, unknown>)[field];\n if (equal(beforeValue, afterValue)) continue;\n changes.push({\n declarationId: b.declarationId,\n capabilityId: b.capabilityId,\n kind: \"changed\",\n field,\n classification: classify(field, beforeValue, afterValue),\n before: beforeValue,\n after: afterValue,\n });\n }\n }\n return changes;\n}\n\nexport function changeCounts(changes: readonly ContractChange[]): Record<ChangeClassification, number> {\n return {\n widening: changes.filter((change) => change.classification === \"widening\").length,\n narrowing: changes.filter((change) => change.classification === \"narrowing\").length,\n neutral: changes.filter((change) => change.classification === \"neutral\").length,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
changeCounts
|
|
3
|
+
} from "./chunk-IA7FUP4R.js";
|
|
4
|
+
|
|
5
|
+
// src/files.ts
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
7
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
8
|
+
import { spawnSync } from "child_process";
|
|
9
|
+
import { canonicalManifestJson, verifyManifest } from "@agent-surface/compiler";
|
|
10
|
+
var DEFAULT_SNAPSHOT = ".agent-surface/contract.json";
|
|
11
|
+
function readManifest(path) {
|
|
12
|
+
if (!existsSync(path)) return void 0;
|
|
13
|
+
try {
|
|
14
|
+
const manifest = JSON.parse(readFileSync(path, "utf8"));
|
|
15
|
+
verifyManifest(manifest);
|
|
16
|
+
return manifest;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error(`could not read contract ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function writeManifest(path, manifest) {
|
|
22
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
23
|
+
writeFileSync(path, canonicalManifestJson(manifest), "utf8");
|
|
24
|
+
}
|
|
25
|
+
function git(args, cwd) {
|
|
26
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
27
|
+
if (result.status !== 0) {
|
|
28
|
+
throw new Error(result.stderr.trim() || `git ${args.join(" ")} failed`);
|
|
29
|
+
}
|
|
30
|
+
return result.stdout.trim();
|
|
31
|
+
}
|
|
32
|
+
function readBaseManifest(base, snapshotPath, cwd) {
|
|
33
|
+
const root = git(["rev-parse", "--show-toplevel"], cwd);
|
|
34
|
+
const absolute = isAbsolute(snapshotPath) ? snapshotPath : resolve(cwd, snapshotPath);
|
|
35
|
+
const path = relative(root, absolute);
|
|
36
|
+
if (path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path)) {
|
|
37
|
+
throw new Error(`snapshot ${snapshotPath} is outside the Git worktree`);
|
|
38
|
+
}
|
|
39
|
+
const text = git(["show", `${base}:${path.split(sep).join("/")}`], root);
|
|
40
|
+
try {
|
|
41
|
+
const manifest = JSON.parse(text);
|
|
42
|
+
verifyManifest(manifest);
|
|
43
|
+
return manifest;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(`could not read ${snapshotPath} at ${base}: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/report.ts
|
|
50
|
+
import { canonicalJson } from "@agent-surface/compiler";
|
|
51
|
+
function value(value2) {
|
|
52
|
+
if (value2 === void 0) return "\u2014";
|
|
53
|
+
const text = typeof value2 === "string" ? value2 : JSON.stringify(value2);
|
|
54
|
+
return text.length > 100 ? `${text.slice(0, 97)}\u2026` : text;
|
|
55
|
+
}
|
|
56
|
+
function changeLine(change) {
|
|
57
|
+
const mark = change.kind === "added" ? "+" : change.kind === "removed" ? "-" : "~";
|
|
58
|
+
return `${mark} ${change.classification.padEnd(9)} ${change.capabilityId} \xB7 ${change.declarationId} \xB7 ${change.field}${change.kind === "changed" ? `: ${value(change.before)} \u2192 ${value(change.after)}` : ""}`;
|
|
59
|
+
}
|
|
60
|
+
function section(title, changes) {
|
|
61
|
+
const counts = changeCounts(changes);
|
|
62
|
+
return [
|
|
63
|
+
`${title} (${changes.length}) \xB7 widening ${counts.widening} \xB7 narrowing ${counts.narrowing} \xB7 neutral ${counts.neutral}`,
|
|
64
|
+
...changes.length > 0 ? changes.map(changeLine) : ["no changes"]
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
function humanReport(report) {
|
|
68
|
+
const lines = [
|
|
69
|
+
`AGENT SURFACE ${report.command.toUpperCase()} \xB7 ${report.status.toUpperCase()}`,
|
|
70
|
+
`Contract ${report.manifest.hash}`,
|
|
71
|
+
`Completeness ${report.manifest.completeness.status}`,
|
|
72
|
+
`Targets ${report.manifest.targets.join(", ") || "\u2014"}`,
|
|
73
|
+
`Capabilities ${report.manifest.capabilities.length}`,
|
|
74
|
+
`Snapshot ${report.snapshotPath}`
|
|
75
|
+
];
|
|
76
|
+
if (report.integrity) {
|
|
77
|
+
lines.push(`Integrity ${report.integrity.status}`);
|
|
78
|
+
lines.push("", ...section("SOURCE \u2194 SNAPSHOT", report.integrity.changes));
|
|
79
|
+
}
|
|
80
|
+
if (report.pullRequest) {
|
|
81
|
+
lines.push("", ...section(`PR DRIFT vs ${report.pullRequest.base}`, report.pullRequest.changes));
|
|
82
|
+
}
|
|
83
|
+
if (report.command === "inspect") {
|
|
84
|
+
lines.push("", "REPOSITORY CONTRACT");
|
|
85
|
+
for (const entry of report.manifest.capabilities) {
|
|
86
|
+
lines.push(
|
|
87
|
+
`${entry.capabilityId} \xB7 ${entry.kind} \xB7 ${entry.effect} \xB7 ${entry.declarationId} \xB7 ${entry.targets.join(",")}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return `${lines.join("\n")}
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
function markdownChanges(changes) {
|
|
95
|
+
if (changes.length === 0) return "No changes.\n";
|
|
96
|
+
return [
|
|
97
|
+
"| Class | Change | Capability | Declaration | Field |",
|
|
98
|
+
"|---|---|---|---|---|",
|
|
99
|
+
...changes.map(
|
|
100
|
+
(change) => `| ${change.classification} | ${change.kind} | \`${change.capabilityId}\` | \`${change.declarationId}\` | \`${change.field}\` |`
|
|
101
|
+
)
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
function markdownReport(report, github = false) {
|
|
105
|
+
const lines = [
|
|
106
|
+
`## Agent Surface ${report.command} \u2014 ${report.status}`,
|
|
107
|
+
"",
|
|
108
|
+
`- Contract: \`${report.manifest.hash}\``,
|
|
109
|
+
`- Completeness: **${report.manifest.completeness.status}**`,
|
|
110
|
+
`- Capabilities: **${report.manifest.capabilities.length}**`,
|
|
111
|
+
`- Targets: ${report.manifest.targets.map((target) => `\`${target}\``).join(", ") || "\u2014"}`
|
|
112
|
+
];
|
|
113
|
+
if (report.integrity) {
|
|
114
|
+
lines.push("", `### Source \u2194 snapshot \u2014 ${report.integrity.status}`, "", markdownChanges(report.integrity.changes));
|
|
115
|
+
}
|
|
116
|
+
if (report.pullRequest) {
|
|
117
|
+
lines.push("", `### PR drift vs \`${report.pullRequest.base}\``, "", markdownChanges(report.pullRequest.changes));
|
|
118
|
+
}
|
|
119
|
+
if (github && report.status === "fail") {
|
|
120
|
+
lines.unshift(`::error title=Agent Surface contract drift::${report.integrity?.changes.length ?? 0} integrity change(s)`);
|
|
121
|
+
}
|
|
122
|
+
return `${lines.join("\n")}
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
function renderReport(report, format) {
|
|
126
|
+
if (format === "json") return canonicalJson(report, true);
|
|
127
|
+
if (format === "markdown") return markdownReport(report);
|
|
128
|
+
if (format === "github") return markdownReport(report, true);
|
|
129
|
+
return humanReport(report);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
DEFAULT_SNAPSHOT,
|
|
134
|
+
readManifest,
|
|
135
|
+
writeManifest,
|
|
136
|
+
readBaseManifest,
|
|
137
|
+
renderReport
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=chunk-X6RGGBQ4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/files.ts","../src/report.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport type { CapabilityContractManifest } from \"@agent-surface/core\";\nimport { canonicalManifestJson, verifyManifest } from \"@agent-surface/compiler\";\n\nexport const DEFAULT_SNAPSHOT = \".agent-surface/contract.json\";\n\nexport function readManifest(path: string): CapabilityContractManifest | undefined {\n if (!existsSync(path)) return undefined;\n try {\n const manifest = JSON.parse(readFileSync(path, \"utf8\")) as CapabilityContractManifest;\n verifyManifest(manifest);\n return manifest;\n } catch (error) {\n throw new Error(`could not read contract ${path}: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\nexport function writeManifest(path: string, manifest: CapabilityContractManifest): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, canonicalManifestJson(manifest), \"utf8\");\n}\n\nfunction git(args: string[], cwd: string): string {\n const result = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\n if (result.status !== 0) {\n throw new Error(result.stderr.trim() || `git ${args.join(\" \")} failed`);\n }\n return result.stdout.trim();\n}\n\nexport function readBaseManifest(\n base: string,\n snapshotPath: string,\n cwd: string,\n): CapabilityContractManifest {\n const root = git([\"rev-parse\", \"--show-toplevel\"], cwd);\n const absolute = isAbsolute(snapshotPath) ? snapshotPath : resolve(cwd, snapshotPath);\n const path = relative(root, absolute);\n if (path === \"..\" || path.startsWith(`..${sep}`) || isAbsolute(path)) {\n throw new Error(`snapshot ${snapshotPath} is outside the Git worktree`);\n }\n const text = git([\"show\", `${base}:${path.split(sep).join(\"/\")}`], root);\n try {\n const manifest = JSON.parse(text) as CapabilityContractManifest;\n verifyManifest(manifest);\n return manifest;\n } catch (error) {\n throw new Error(`could not read ${snapshotPath} at ${base}: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n","import type { CapabilityContractManifest } from \"@agent-surface/core\";\nimport { canonicalJson } from \"@agent-surface/compiler\";\nimport type { ContractChange } from \"./diff.js\";\nimport { changeCounts } from \"./diff.js\";\n\nexport type OutputFormat = \"human\" | \"json\" | \"github\" | \"markdown\";\n\nexport interface ContractReport {\n command: \"inspect\" | \"check\" | \"snapshot\";\n status: \"pass\" | \"fail\" | \"written\" | \"view\";\n manifest: CapabilityContractManifest;\n snapshotPath: string;\n integrity?: {\n status: \"current\" | \"missing\" | \"stale\";\n changes: ContractChange[];\n };\n pullRequest?: {\n base: string;\n changes: ContractChange[];\n };\n}\n\nfunction value(value: unknown): string {\n if (value === undefined) return \"—\";\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\n return text.length > 100 ? `${text.slice(0, 97)}…` : text;\n}\n\nfunction changeLine(change: ContractChange): string {\n const mark = change.kind === \"added\" ? \"+\" : change.kind === \"removed\" ? \"-\" : \"~\";\n return `${mark} ${change.classification.padEnd(9)} ${change.capabilityId} · ${change.declarationId} · ${change.field}${\n change.kind === \"changed\" ? `: ${value(change.before)} → ${value(change.after)}` : \"\"\n }`;\n}\n\nfunction section(title: string, changes: readonly ContractChange[]): string[] {\n const counts = changeCounts(changes);\n return [\n `${title} (${changes.length}) · widening ${counts.widening} · narrowing ${counts.narrowing} · neutral ${counts.neutral}`,\n ...(changes.length > 0 ? changes.map(changeLine) : [\"no changes\"]),\n ];\n}\n\nexport function humanReport(report: ContractReport): string {\n const lines = [\n `AGENT SURFACE ${report.command.toUpperCase()} · ${report.status.toUpperCase()}`,\n `Contract ${report.manifest.hash}`,\n `Completeness ${report.manifest.completeness.status}`,\n `Targets ${report.manifest.targets.join(\", \") || \"—\"}`,\n `Capabilities ${report.manifest.capabilities.length}`,\n `Snapshot ${report.snapshotPath}`,\n ];\n if (report.integrity) {\n lines.push(`Integrity ${report.integrity.status}`);\n lines.push(\"\", ...section(\"SOURCE ↔ SNAPSHOT\", report.integrity.changes));\n }\n if (report.pullRequest) {\n lines.push(\"\", ...section(`PR DRIFT vs ${report.pullRequest.base}`, report.pullRequest.changes));\n }\n if (report.command === \"inspect\") {\n lines.push(\"\", \"REPOSITORY CONTRACT\");\n for (const entry of report.manifest.capabilities) {\n lines.push(\n `${entry.capabilityId} · ${entry.kind} · ${entry.effect} · ${entry.declarationId} · ${entry.targets.join(\",\")}`,\n );\n }\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction markdownChanges(changes: readonly ContractChange[]): string {\n if (changes.length === 0) return \"No changes.\\n\";\n return [\n \"| Class | Change | Capability | Declaration | Field |\",\n \"|---|---|---|---|---|\",\n ...changes.map(\n (change) =>\n `| ${change.classification} | ${change.kind} | \\`${change.capabilityId}\\` | \\`${change.declarationId}\\` | \\`${change.field}\\` |`,\n ),\n ].join(\"\\n\");\n}\n\nexport function markdownReport(report: ContractReport, github = false): string {\n const lines = [\n `## Agent Surface ${report.command} — ${report.status}`,\n \"\",\n `- Contract: \\`${report.manifest.hash}\\``,\n `- Completeness: **${report.manifest.completeness.status}**`,\n `- Capabilities: **${report.manifest.capabilities.length}**`,\n `- Targets: ${report.manifest.targets.map((target) => `\\`${target}\\``).join(\", \") || \"—\"}`,\n ];\n if (report.integrity) {\n lines.push(\"\", `### Source ↔ snapshot — ${report.integrity.status}`, \"\", markdownChanges(report.integrity.changes));\n }\n if (report.pullRequest) {\n lines.push(\"\", `### PR drift vs \\`${report.pullRequest.base}\\``, \"\", markdownChanges(report.pullRequest.changes));\n }\n if (github && report.status === \"fail\") {\n lines.unshift(`::error title=Agent Surface contract drift::${report.integrity?.changes.length ?? 0} integrity change(s)`);\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function renderReport(report: ContractReport, format: OutputFormat): string {\n if (format === \"json\") return canonicalJson(report, true);\n if (format === \"markdown\") return markdownReport(report);\n if (format === \"github\") return markdownReport(report, true);\n return humanReport(report);\n}\n"],"mappings":"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","names":["value"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,180 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
import { SurfaceExplanation } from '@agent-surface/core/explain';
|
|
3
|
-
import { AgentSurfaceSnapshot } from '@agent-surface/core';
|
|
4
|
-
import 'react';
|
|
1
|
+
import { CapabilityContractManifest } from '@agent-surface/core';
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Two things force it:
|
|
11
|
-
*
|
|
12
|
-
* 1. **One React.** The app's component tree resolves React through the app's
|
|
13
|
-
* own Vite config. If the mount ran in the CLI's Node graph instead, a
|
|
14
|
-
* second React copy would render it and every hook would throw.
|
|
15
|
-
*
|
|
16
|
-
* 2. **One `@agent-surface/core`.** `explainSurface()` reaches the registry
|
|
17
|
-
* through a plain `Symbol` seam, and a symbol is only equal to itself within
|
|
18
|
-
* one module instance. Load core twice and the seam silently misses. So the
|
|
19
|
-
* explanation is computed *here*, beside the registry that owns it.
|
|
20
|
-
*
|
|
21
|
-
* Everything crosses back as plain JSON. Nothing live — no registry, no React
|
|
22
|
-
* element, no policy function — escapes into the CLI process.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* A registration the registry refused while the scenario mounted (`AS-CLI-006`).
|
|
27
|
-
*
|
|
28
|
-
* Rejection is the one failure that is invisible everywhere else. The handle is
|
|
29
|
-
* dead, so the capability never reaches the snapshot; the registration never
|
|
30
|
-
* became active, so `explainSurface()` does not iterate it either. The only
|
|
31
|
-
* diagnostic core emits goes through `devError`, which prints nothing unless
|
|
32
|
-
* the app was built with `environment: "development"` — and the config shape
|
|
33
|
-
* this CLI documents builds it with `"test"`.
|
|
34
|
-
*/
|
|
35
|
-
interface RegistrationRejection {
|
|
36
|
-
componentType: string;
|
|
37
|
-
instanceId: string;
|
|
38
|
-
reason: "duplicate" | "guard";
|
|
39
|
-
}
|
|
40
|
-
interface CollectResult {
|
|
41
|
-
scenario: string;
|
|
42
|
-
snapshot: AgentSurfaceSnapshot;
|
|
43
|
-
explanation: SurfaceExplanation;
|
|
44
|
-
/** Refused during this mount. Empty on a healthy one. */
|
|
45
|
-
rejections: RegistrationRejection[];
|
|
46
|
-
/** The scope the two projections above were computed under, when one was set. */
|
|
47
|
-
scope?: string[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface AuthoredCapability {
|
|
51
|
-
/** Canonical id, instance-independent: `view:devices.table.sort`. */
|
|
3
|
+
type ChangeClassification = "widening" | "narrowing" | "neutral";
|
|
4
|
+
type ChangeKind = "added" | "removed" | "changed";
|
|
5
|
+
interface ContractChange {
|
|
6
|
+
declarationId: string;
|
|
52
7
|
capabilityId: string;
|
|
53
|
-
kind:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
site: string;
|
|
59
|
-
};
|
|
60
|
-
/** Literals recovered from the call site; absent when not statically known. */
|
|
61
|
-
description?: string;
|
|
62
|
-
effect?: string;
|
|
63
|
-
/**
|
|
64
|
-
* How much of this call site the extractor understood.
|
|
65
|
-
*
|
|
66
|
-
* `static` — identity and metadata both recovered from literals.
|
|
67
|
-
* `partial` — identity resolved, some metadata or runtime presence dynamic.
|
|
68
|
-
* The common case: a spread `instanceId`, a conditional capability, or a
|
|
69
|
-
* description built from a template.
|
|
70
|
-
* `unresolved` — identity NOT resolved. Reported, never dropped.
|
|
71
|
-
*/
|
|
72
|
-
resolution: "static" | "partial" | "unresolved";
|
|
73
|
-
/** Present on `partial`/`unresolved`: what defeated the extractor. */
|
|
74
|
-
note?: string;
|
|
75
|
-
/**
|
|
76
|
-
* Present on `unresolved`: *which* construct defeated the extractor, as a
|
|
77
|
-
* stable code rather than prose.
|
|
78
|
-
*
|
|
79
|
-
* `note` is written for a human and gets reworded — the spread note changed
|
|
80
|
-
* in the same release that introduced it. Anything keyed on that prose would
|
|
81
|
-
* silently invalidate itself on an edit no one thought was behavioural, which
|
|
82
|
-
* is exactly what `unresolved-allow.json` must not do. This is the key.
|
|
83
|
-
*/
|
|
84
|
-
reason?: UnreadReason;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Why a registration could not be fully read. Stable identifiers: adding one is
|
|
88
|
-
* fine, renaming one invalidates committed allowlists and is a breaking change.
|
|
89
|
-
*/
|
|
90
|
-
type UnreadReason = "dynamic-type" | "dynamic-config" | "dynamic-group" | "dynamic-callee" | "spread-members" | "computed-name" | "granular-hook";
|
|
91
|
-
interface CapabilityInventory {
|
|
92
|
-
capabilities: AuthoredCapability[];
|
|
93
|
-
/** Absolute path to the tsconfig whose file list was analyzed. */
|
|
94
|
-
tsconfig: string;
|
|
95
|
-
/** Directory the analysis was rooted at — the surface config's own. */
|
|
96
|
-
root: string;
|
|
97
|
-
/** Files the program actually walked — the inventory's blast radius. */
|
|
98
|
-
filesAnalyzed: number;
|
|
99
|
-
/**
|
|
100
|
-
* Agent-surface implementation files excluded outside `root`. First-party
|
|
101
|
-
* workspace sources are analyzed; the implementation behind the authored
|
|
102
|
-
* hooks is not a second app registration.
|
|
103
|
-
*/
|
|
104
|
-
filesOutsideRoot: number;
|
|
105
|
-
/**
|
|
106
|
-
* The `domain:` plane is deliberately *not* analyzed here. Those capabilities
|
|
107
|
-
* come from the oRPC router, which is already a static export (OQ-1), and
|
|
108
|
-
* reporting zero of them would read as "there are none" rather than "nobody
|
|
109
|
-
* looked".
|
|
110
|
-
*/
|
|
111
|
-
domain: "not-analyzed";
|
|
8
|
+
kind: ChangeKind;
|
|
9
|
+
field: string;
|
|
10
|
+
classification: ChangeClassification;
|
|
11
|
+
before?: unknown;
|
|
12
|
+
after?: unknown;
|
|
112
13
|
}
|
|
14
|
+
declare function diffContracts(before: CapabilityContractManifest | undefined, after: CapabilityContractManifest): ContractChange[];
|
|
15
|
+
declare function changeCounts(changes: readonly ContractChange[]): Record<ChangeClassification, number>;
|
|
113
16
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
17
|
+
declare const DEFAULT_SNAPSHOT = ".agent-surface/contract.json";
|
|
18
|
+
declare function readManifest(path: string): CapabilityContractManifest | undefined;
|
|
19
|
+
declare function writeManifest(path: string, manifest: CapabilityContractManifest): void;
|
|
20
|
+
|
|
21
|
+
type OutputFormat = "human" | "json" | "github" | "markdown";
|
|
22
|
+
interface ContractReport {
|
|
23
|
+
command: "inspect" | "check" | "snapshot";
|
|
24
|
+
status: "pass" | "fail" | "written" | "view";
|
|
25
|
+
manifest: CapabilityContractManifest;
|
|
26
|
+
snapshotPath: string;
|
|
27
|
+
integrity?: {
|
|
28
|
+
status: "current" | "missing" | "stale";
|
|
29
|
+
changes: ContractChange[];
|
|
30
|
+
};
|
|
31
|
+
pullRequest?: {
|
|
32
|
+
base: string;
|
|
33
|
+
changes: ContractChange[];
|
|
127
34
|
};
|
|
128
35
|
}
|
|
129
|
-
|
|
130
|
-
/** Distinct capability ids the inventory resolved, within any active scope. */
|
|
131
|
-
authored: number;
|
|
132
|
-
/** How many of them at least one scenario surfaced. */
|
|
133
|
-
reached: number;
|
|
134
|
-
scenarios: string[];
|
|
135
|
-
/**
|
|
136
|
-
* The scope every number here was computed under (`AS-CLI-007`). A scope
|
|
137
|
-
* filters the catalog *and* the mount, so `10 authored` without it on screen
|
|
138
|
-
* reads as a claim about the whole codebase when it is a claim about one
|
|
139
|
-
* prefix of it.
|
|
140
|
-
*/
|
|
141
|
-
scope?: string[];
|
|
142
|
-
/**
|
|
143
|
-
* Allowlist entries outside the active scope, which a scoped run cannot
|
|
144
|
-
* judge: not unreached (nothing looked), not stale (nothing reached them).
|
|
145
|
-
* Counted rather than silently dropped, so a scoped run never reads as a
|
|
146
|
-
* verdict on the whole allowlist.
|
|
147
|
-
*/
|
|
148
|
-
allowlistOutOfScope: number;
|
|
149
|
-
/** Authored, surfaced by no scenario, and not allowlisted — the finding. */
|
|
150
|
-
unreached: UnreachedCapability[];
|
|
151
|
-
/**
|
|
152
|
-
* Present at runtime with no static origin: a dynamic registration, or a gap
|
|
153
|
-
* in the extractor. `view:` only — see `domainReached`.
|
|
154
|
-
*/
|
|
155
|
-
undeclared: string[];
|
|
156
|
-
/**
|
|
157
|
-
* `domain:` capabilities a scenario surfaced. Held apart from `undeclared`
|
|
158
|
-
* because the inventory never claimed to analyze that plane: filing them as
|
|
159
|
-
* "no static origin" would report the design's own stated boundary as a
|
|
160
|
-
* defect, which is the misleading check this whole command rejects.
|
|
161
|
-
*/
|
|
162
|
-
domainReached: string[];
|
|
163
|
-
/** Runtime domain entries absent from an explicitly configured manifest. */
|
|
164
|
-
unmanifestedDomain: string[];
|
|
165
|
-
domainAuthoritative: boolean;
|
|
166
|
-
/** Carried forward from the inventory, minus anything allowlisted. */
|
|
167
|
-
unresolved: AuthoredCapability[];
|
|
168
|
-
/** Unreached, but listed in the allowlist. */
|
|
169
|
-
allowed: string[];
|
|
170
|
-
/** Listed in the allowlist and reached anyway — the list has rotted. */
|
|
171
|
-
staleAllowlist: string[];
|
|
172
|
-
allowlistPath: string;
|
|
173
|
-
/** Unread, but listed in `unresolved-allow.json`. Keys, not entries. */
|
|
174
|
-
allowedUnread: string[];
|
|
175
|
-
/** Listed there and no longer unread — that list has rotted too. */
|
|
176
|
-
staleUnreadAllowlist: string[];
|
|
177
|
-
unreadAllowlistPath: string;
|
|
178
|
-
}
|
|
36
|
+
declare function renderReport(report: ContractReport, format: OutputFormat): string;
|
|
179
37
|
|
|
180
|
-
export type
|
|
38
|
+
export { type ChangeClassification, type ChangeKind, type ContractChange, type ContractReport, DEFAULT_SNAPSHOT, type OutputFormat, changeCounts, diffContracts, readManifest, renderReport, writeManifest };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_SNAPSHOT,
|
|
3
|
+
readManifest,
|
|
4
|
+
renderReport,
|
|
5
|
+
writeManifest
|
|
6
|
+
} from "./chunk-X6RGGBQ4.js";
|
|
7
|
+
import {
|
|
8
|
+
changeCounts,
|
|
9
|
+
diffContracts
|
|
10
|
+
} from "./chunk-IA7FUP4R.js";
|
|
5
11
|
export {
|
|
6
|
-
|
|
12
|
+
DEFAULT_SNAPSHOT,
|
|
13
|
+
changeCounts,
|
|
14
|
+
diffContracts,
|
|
15
|
+
readManifest,
|
|
16
|
+
renderReport,
|
|
17
|
+
writeManifest
|
|
7
18
|
};
|
|
8
19
|
//# sourceMappingURL=index.js.map
|