@agent-surface/cli 0.12.0 → 0.12.1

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 CHANGED
@@ -68,7 +68,7 @@ devices.disable procedure destructive disabled confirmation:requi
68
68
 
69
69
  Every count names what it is relative to — the scenario always, the scope when one is active. A surface is a projection of one mounted context, never "the app".
70
70
 
71
- `--detail` swaps the table for one paragraph per capability; `--explain` and `--schemas` imply it.
71
+ `--detail` shows full capability, origin, and diagnostic detail; `--explain` and `--schemas` imply it.
72
72
 
73
73
  ### Depth
74
74
 
package/dist/bin.js CHANGED
@@ -56,7 +56,7 @@ Options
56
56
  --config <path> path to agent-surface.config.* (default: nearest, searching upward)
57
57
  --baseline-dir where baselines live (default: .agent-surface next to the config)
58
58
  --scope <prefix> restrict to a component-type prefix (repeatable)
59
- --detail one paragraph per capability instead of the table
59
+ --detail full capability, origin, and diagnostic detail
60
60
  --explain name the policies behind every decision (implies --detail)
61
61
  --schemas include input/output JSON Schemas (implies --detail)
62
62
  --tsconfig <path> tsconfig the source read uses (default: nearest to the config)
@@ -158,7 +158,7 @@ async function main(argv = process.argv.slice(2)) {
158
158
  ...values["baseline-dir"] ? { baselineDir: values["baseline-dir"] } : {}
159
159
  };
160
160
  if (command === "inspect") {
161
- const { runInspect } = await import("./inspect-ELUQ73MZ.js");
161
+ const { runInspect } = await import("./inspect-XMDZBSK2.js");
162
162
  return await runInspect({
163
163
  ...shared,
164
164
  ...values.detail ? { detail: true } : {},
@@ -167,12 +167,13 @@ async function main(argv = process.argv.slice(2)) {
167
167
  });
168
168
  }
169
169
  if (command === "snapshot") {
170
- const { runSnapshot } = await import("./snapshot-3E7MVMVG.js");
170
+ const { runSnapshot } = await import("./snapshot-FLXE5UC6.js");
171
171
  return await runSnapshot(shared);
172
172
  }
173
- const { runCheck } = await import("./check-CS4Z3ZK3.js");
173
+ const { runCheck } = await import("./check-POE5VRSE.js");
174
174
  return await runCheck({
175
175
  ...shared,
176
+ ...values.detail ? { detail: true } : {},
176
177
  ...values["allow-unresolved"] ? { allowUnresolved: true } : {}
177
178
  });
178
179
  } catch (error) {
@@ -228,7 +229,14 @@ function exitWhenWedged(code) {
228
229
  if (held.length > 0) {
229
230
  const kinds = [...new Set(held)].sort().join(", ");
230
231
  writeError(
231
- `agent-surface: the output above is complete, but ${held.length} handle(s) are still open (${kinds}) \u2014 something started during the mount is still running, so this command would have waited instead of exiting. Common causes: a polling interval, a websocket, or a data layer whose cache timer outlives the render. Exiting ${code}.`
232
+ [
233
+ "",
234
+ "PROCESS CLEANUP WARN",
235
+ `Open handles ${held.length} (${kinds})`,
236
+ "Impact report complete; exit code unchanged; process exit forced",
237
+ "Likely cause polling, websocket, or a cache timer created during mount",
238
+ `Exit ${code}`
239
+ ].join("\n")
232
240
  );
233
241
  }
234
242
  void flushOutput().then(() => process.exit(code));
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\";\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 one paragraph per capability instead of the table\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[\"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 writeError(\n `agent-surface: the output above is complete, but ${held.length} handle(s) are still ` +\n `open (${kinds}) — something started during the mount is still running, so this ` +\n `command would have waited instead of exiting. Common causes: a polling interval, a ` +\n `websocket, or a data layer whose cache timer outlives the render. Exiting ${code}.`,\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\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\") return;\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\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/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\";\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 writeError(\n [\n \"\",\n \"PROCESS CLEANUP WARN\",\n `Open handles ${held.length} (${kinds})`,\n \"Impact report complete; exit code unchanged; process exit forced\",\n \"Likely cause polling, websocket, or a cache timer created during mount\",\n `Exit ${code}`,\n ].join(\"\\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\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\") return;\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\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,11 +1,12 @@
1
1
  import {
2
2
  coverageReport,
3
+ renderCheckOverviewPlain,
3
4
  renderCoveragePlain,
4
5
  renderDriftPlain,
5
6
  renderFailuresPlain,
6
7
  renderNoVerdictPlain,
7
8
  scenarioBaseline
8
- } from "./chunk-TPWRSFK7.js";
9
+ } from "./chunk-J2NN3J5N.js";
9
10
  import "./chunk-DYDSJM7R.js";
10
11
  import {
11
12
  ALLOWLIST_FILE,
@@ -90,10 +91,30 @@ async function runCheck(options) {
90
91
  );
91
92
  return couldNotRun ? 2 : ok ? 0 : 1;
92
93
  }
94
+ const overview = renderCheckOverviewPlain({
95
+ status: couldNotRun ? "ERROR" : ok ? "PASS" : "FAIL",
96
+ ...coverage ? { coverage } : {},
97
+ unresolvedAllowed: options.allowUnresolved === true,
98
+ baselineCurrent: Math.max(0, runtime.results.length - drifted.length),
99
+ baselineTotal: runtime.scenarios.length,
100
+ scenarioManifestOk: !scenarioManifestMismatch && staleBaselineFiles.length === 0,
101
+ rejected: rejected.reduce((sum, entry) => sum + entry.rejections.length, 0),
102
+ mountFailures: runtime.failures.length,
103
+ scenarios: runtime.scenarios
104
+ });
105
+ if (ok) write(overview);
106
+ else writeError(overview);
93
107
  if (coverage) {
94
- const rendered = renderCoveragePlain(coverage);
95
- if (coverageFailed) writeError(rendered);
96
- else write(rendered);
108
+ const rendered = renderCoveragePlain(coverage, {
109
+ compact: true,
110
+ ...options.detail ? { detail: true } : {}
111
+ });
112
+ if (rendered) {
113
+ if (coverageFailed) writeError(`
114
+ ${rendered}`);
115
+ else write(`
116
+ ${rendered}`);
117
+ }
97
118
  }
98
119
  const missing = drifted.filter((entry) => entry.missingBaseline);
99
120
  const changed = drifted.filter((entry) => !entry.missingBaseline);
@@ -152,13 +173,6 @@ ${renderNoVerdictPlain(runtime.failures)}`);
152
173
  return 2;
153
174
  }
154
175
  if (ok) {
155
- if (coverage) write("");
156
- write(`surface matches the baseline in ${runtime.scenarios.join(", ")}`);
157
- if (!coverage) {
158
- write(
159
- "that is a statement about these scenarios only \u2014 re-run at --depth full to find capabilities no scenario mounts"
160
- );
161
- }
162
176
  return 0;
163
177
  }
164
178
  return 1;
@@ -166,4 +180,4 @@ ${renderNoVerdictPlain(runtime.failures)}`);
166
180
  export {
167
181
  runCheck
168
182
  };
169
- //# sourceMappingURL=check-CS4Z3ZK3.js.map
183
+ //# sourceMappingURL=check-POE5VRSE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["import { existsSync, readdirSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport {\n UsageError,\n joinCoverage,\n mountScenarios,\n readInventory,\n type AnalysisOptions,\n type Depth,\n} from \"../analysis.js\";\nimport {\n annotate,\n baselinePath,\n diff,\n readBaseline,\n readScenarioManifest,\n SCENARIO_MANIFEST_FILE,\n type DiffEntry,\n} from \"../baseline.js\";\nimport { ALLOWLIST_FILE, coverageExitCode, UNREAD_ALLOWLIST_FILE } from \"../coverage.js\";\nimport {\n renderCoveragePlain,\n renderCheckOverviewPlain,\n renderDriftPlain,\n renderFailuresPlain,\n renderNoVerdictPlain,\n} from \"../render/plain.js\";\nimport { write, writeError } from \"../output.js\";\nimport { coverageReport, scenarioBaseline } from \"../report.js\";\n\nexport interface CheckOptions {\n configPath: string;\n depth: Depth;\n scenario?: string;\n scope?: string[];\n tsconfig?: string;\n baselineDir?: string;\n allowUnresolved?: boolean;\n json?: boolean;\n plain?: boolean;\n detail?: boolean;\n}\n\ninterface ScenarioDrift {\n scenario: string;\n missingBaseline?: boolean;\n entries: DiffEntry[];\n}\n\ninterface RejectedScenario {\n scenario: string;\n rejections: Array<{ componentType: string; instanceId: string; reason: string }>;\n}\n\n/**\n * The gate. The only command in this package that fails on a finding, which is\n * why every finding has to reach it.\n *\n * It used to fail on exactly one class — the projection drifting from its\n * baseline — and print a line telling you that capabilities no scenario mounts\n * were a different command's question. A gate that names the check it is not\n * performing is a gate with a hole in it, and in CI the hole was silent: a\n * whole unreached route sat behind a green tick.\n *\n * So it now fails on every incomplete or changed report:\n *\n * - **drift** — the surface changed against its committed baseline;\n * - **a missing baseline** — nothing to compare, which is not the same as a match;\n * - **an unreached capability** — authored, and no scenario mounts it;\n * - **an unread call site** — the catalog is incomplete, so the third check\n * above is computed over a denominator that is only a floor;\n * - **a rejected registration** — authored surface was refused;\n * - **scenario drift** — config, manifest and baseline files disagree.\n *\n * `.agent-surface/coverage-allow.json` ratchets the third; `--allow-unresolved`\n * accepts the fourth. Both are deliberate, committed decisions rather than\n * flags that quietly widen the gate.\n *\n * Output is always plain, with no rendering framework in its path at all: this\n * is a report that gets pasted into a pull request and read out of a CI log,\n * and neither of those is a terminal.\n */\nexport async function runCheck(options: CheckOptions): Promise<number> {\n if (options.depth === \"static\") {\n throw new UsageError(\n \"check --depth static has nothing to compare — a baseline is a projection, and \" +\n \"at this depth nothing is mounted. Use --depth runtime for drift alone, or full for both.\",\n );\n }\n\n const analysis: AnalysisOptions = {\n configPath: options.configPath,\n depth: options.depth,\n ...(options.scenario ? { scenario: options.scenario } : {}),\n ...(options.scope ? { scope: options.scope } : {}),\n ...(options.tsconfig ? { tsconfig: options.tsconfig } : {}),\n ...(options.baselineDir ? { baselineDir: options.baselineDir } : {}),\n };\n\n const inventory = readInventory(analysis);\n // No streaming here, unlike `inspect`. A report is read top-down and has to\n // lead with its findings, which means every finding has to exist first.\n const runtime = await mountScenarios(analysis);\n if (!runtime) throw new UsageError(\"check needs a mount, and this depth performs none\");\n\n const drifted: ScenarioDrift[] = [];\n for (const result of runtime.results) {\n const path = baselinePath(runtime.baselineDir, result.scenario);\n const expected = readBaseline(path);\n if (expected === undefined) {\n drifted.push({ scenario: result.scenario, missingBaseline: true, entries: [] });\n continue;\n }\n const actual = scenarioBaseline(result);\n const entries = annotate(diff(expected, actual), actual, expected);\n if (entries.length > 0) drifted.push({ scenario: result.scenario, entries });\n }\n\n const committedScenarios = readScenarioManifest(runtime.baselineDir);\n const declared = [...runtime.declaredScenarios].sort();\n const scenarioManifestMismatch =\n committedScenarios === undefined ||\n JSON.stringify(committedScenarios) !== JSON.stringify(declared);\n const reserved = new Set([SCENARIO_MANIFEST_FILE, ALLOWLIST_FILE, UNREAD_ALLOWLIST_FILE]);\n const staleBaselineFiles = (existsSync(runtime.baselineDir)\n ? readdirSync(runtime.baselineDir, { withFileTypes: true })\n : [])\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".json\") && !reserved.has(entry.name))\n .map((entry) => entry.name.slice(0, -5))\n .filter((scenario) => !runtime.declaredScenarios.includes(scenario))\n .sort();\n\n const rejected: RejectedScenario[] = runtime.results\n .filter((result) => result.rejections.length > 0)\n .map((result) => ({ scenario: result.scenario, rejections: result.rejections }));\n\n const coverage = joinCoverage(inventory, runtime, analysis);\n const coverageFailed =\n coverage !== undefined &&\n coverageExitCode(coverage, { allowUnresolved: options.allowUnresolved === true }) !== 0;\n const couldNotRun = runtime.failures.length > 0;\n const ok =\n drifted.length === 0 &&\n !coverageFailed &&\n !couldNotRun &&\n rejected.length === 0 &&\n !scenarioManifestMismatch &&\n staleBaselineFiles.length === 0;\n\n if (options.json) {\n write(\n JSON.stringify(\n {\n ok,\n drifted,\n failures: runtime.failures,\n rejected,\n scenarioManifest: {\n expected: declared,\n committed: committedScenarios ?? null,\n staleBaselines: staleBaselineFiles,\n },\n coverage: coverageReport(coverage),\n },\n null,\n 2,\n ),\n );\n return couldNotRun ? 2 : ok ? 0 : 1;\n }\n\n const overview = renderCheckOverviewPlain({\n status: couldNotRun ? \"ERROR\" : ok ? \"PASS\" : \"FAIL\",\n ...(coverage ? { coverage } : {}),\n unresolvedAllowed: options.allowUnresolved === true,\n baselineCurrent: Math.max(0, runtime.results.length - drifted.length),\n baselineTotal: runtime.scenarios.length,\n scenarioManifestOk: !scenarioManifestMismatch && staleBaselineFiles.length === 0,\n rejected: rejected.reduce((sum, entry) => sum + entry.rejections.length, 0),\n mountFailures: runtime.failures.length,\n scenarios: runtime.scenarios,\n });\n if (ok) write(overview);\n else writeError(overview);\n\n // The gap leads, because it is the finding this command could not previously\n // make at all. Drift follows, because it is the one it always could.\n if (coverage) {\n const rendered = renderCoveragePlain(coverage, {\n compact: true,\n ...(options.detail ? { detail: true } : {}),\n });\n if (rendered) {\n if (coverageFailed) writeError(`\\n${rendered}`);\n else write(`\\n${rendered}`);\n }\n }\n\n // \"No baseline\" is not drift, and filing it under a heading that says the\n // surface changed would be a claim about a comparison that never happened.\n const missing = drifted.filter((entry) => entry.missingBaseline);\n const changed = drifted.filter((entry) => !entry.missingBaseline);\n\n if (rejected.length > 0) {\n writeError(\n [\n `${coverage ? \"\\n\" : \"\"}REJECTED REGISTRATIONS — the runtime refused authored surface (${rejected.reduce((sum, entry) => sum + entry.rejections.length, 0)})`,\n ...rejected.flatMap((entry) =>\n entry.rejections.map(\n (rejection) =>\n ` ${entry.scenario}: ${rejection.componentType}@${rejection.instanceId} (${rejection.reason})`,\n ),\n ),\n ].join(\"\\n\"),\n );\n }\n\n if (scenarioManifestMismatch || staleBaselineFiles.length > 0) {\n const committed = committedScenarios?.join(\", \") ?? \"missing\";\n writeError(\n [\n `${coverage || rejected.length > 0 ? \"\\n\" : \"\"}SCENARIO DRIFT — committed baselines do not match config`,\n ` config: ${declared.join(\", \")}`,\n ` manifest: ${committed}`,\n ...(staleBaselineFiles.length > 0\n ? [` stale baselines: ${staleBaselineFiles.join(\", \")}`]\n : []),\n \" run `agent-surface snapshot` and commit the manifest\",\n ].join(\"\\n\"),\n );\n }\n\n if (missing.length > 0) {\n writeError(\n [\n `${coverage ? \"\\n\" : \"\"}NO BASELINE — nothing to compare against, which is not the same as a match (${missing.length})`,\n ...missing.map(\n (entry) =>\n ` ${entry.scenario}: ${relative(\n process.cwd(),\n baselinePath(runtime.baselineDir, entry.scenario),\n )} does not exist — run \\`agent-surface snapshot\\` and commit it`,\n ),\n ].join(\"\\n\"),\n );\n }\n\n if (changed.length > 0) {\n writeError(\n [\n `${coverage || missing.length > 0 ? \"\\n\" : \"\"}DRIFT — the surface changed against its baseline (${changed.length})`,\n ...changed.map((entry) => renderDriftPlain(entry.scenario, entry.entries)),\n \"\",\n `surface drift in ${changed.length} scenario${\n changed.length === 1 ? \"\" : \"s\"\n } — review the change, then \\`agent-surface snapshot\\` to accept it`,\n ].join(\"\\n\"),\n );\n }\n\n if (couldNotRun) {\n writeError(`\\n${renderFailuresPlain(runtime.failures)}`);\n if (inventory) writeError(`\\n${renderNoVerdictPlain(runtime.failures)}`);\n return 2;\n }\n\n if (ok) {\n return 0;\n }\n return 1;\n}\n"],"mappings":"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","names":[]}
@@ -15,6 +15,22 @@ import { relative } from "path";
15
15
  var MARK = { expose: "+", disable: "~", hide: "-" };
16
16
  var STATE = { expose: "callable", disable: "disabled", hide: "hidden" };
17
17
  var NONE = "\u2014";
18
+ var REPORT_WIDTH = 100;
19
+ function wrapText(text, width) {
20
+ const words = text.split(/\s+/).filter(Boolean);
21
+ const lines = [];
22
+ let line = "";
23
+ for (const word of words) {
24
+ if (line && line.length + 1 + word.length > width) {
25
+ lines.push(line);
26
+ line = word;
27
+ } else {
28
+ line = line ? `${line} ${word}` : word;
29
+ }
30
+ }
31
+ if (line) lines.push(line);
32
+ return lines.length > 0 ? lines : [""];
33
+ }
18
34
  function renderTable(headers, rows) {
19
35
  const widths = headers.map(
20
36
  (header, column) => Math.max(header.length, ...rows.map((row) => (row.cells[column] ?? "").length))
@@ -23,7 +39,11 @@ function renderTable(headers, rows) {
23
39
  const lines = [line(headers)];
24
40
  for (const row of rows) {
25
41
  lines.push(line(row.cells));
26
- if (row.note) lines.push(` \u2937 ${row.note}`);
42
+ if (row.note) {
43
+ for (const [index, note] of wrapText(row.note, REPORT_WIDTH - 6).entries()) {
44
+ lines.push(` ${index === 0 ? "\u2937 " : " "}${note}`);
45
+ }
46
+ }
27
47
  }
28
48
  return lines;
29
49
  }
@@ -133,40 +153,111 @@ function renderSurfacePlain(view, options = {}) {
133
153
  );
134
154
  return lines.join("\n");
135
155
  }
156
+ function componentOf(capabilityId) {
157
+ const path = capabilityId.replace(/^(view|domain):/, "");
158
+ const dot = path.lastIndexOf(".");
159
+ return dot === -1 ? path : path.slice(0, dot);
160
+ }
136
161
  function renderCatalogPlain(inventory, options = {}) {
137
162
  const lines = [];
138
163
  const resolved = inventory.capabilities.filter((c) => c.resolution !== "unresolved");
164
+ const unreadEntries = unresolved(inventory);
165
+ const dynamicMetadata = resolved.filter((c) => c.resolution === "partial").length;
139
166
  const ids = authoredIds(inventory);
140
167
  const authored = ids.size + (options.domainCapabilities ?? 0);
168
+ lines.push("STATIC CATALOG");
141
169
  lines.push(
142
- `${authored} authored (upper bound) \xB7 ${resolved.length} call site${resolved.length === 1 ? "" : "s"} across ${inventory.filesAnalyzed} file${inventory.filesAnalyzed === 1 ? "" : "s"}` + (options.domainCapabilities === void 0 ? " \xB7 domain not analyzed without a loaded oRPC manifest" : ` \xB7 ${options.domainCapabilities} domain manifest capabilit${options.domainCapabilities === 1 ? "y" : "ies"}`)
170
+ `STATUS ${unreadEntries.length > 0 ? "INCOMPLETE" : "COMPLETE"}${unreadEntries.length > 0 ? ` \u2014 ${unreadEntries.length} unread capability identit${unreadEntries.length === 1 ? "y" : "ies"}` : " \u2014 every capability identity resolved"}`
171
+ );
172
+ lines.push(
173
+ `Capabilities ${authored} authored (upper bound) \xB7 ${resolved.length} resolved call site${resolved.length === 1 ? "" : "s"}`
174
+ );
175
+ lines.push(
176
+ `Program ${inventory.filesAnalyzed} file${inventory.filesAnalyzed === 1 ? "" : "s"} analyzed` + (inventory.filesOutsideRoot > 0 ? ` \xB7 ${inventory.filesOutsideRoot} agent-surface implementation file${inventory.filesOutsideRoot === 1 ? "" : "s"} excluded` : "")
177
+ );
178
+ lines.push(
179
+ `Metadata ${dynamicMetadata} call site${dynamicMetadata === 1 ? "" : "s"} partially read` + (dynamicMetadata > 0 ? " \xB7 identity remains resolved" : "")
180
+ );
181
+ lines.push(
182
+ options.domainCapabilities === void 0 ? "Domain not analyzed at static depth; full depth reads the oRPC manifest" : `Domain ${options.domainCapabilities} manifest capabilit${options.domainCapabilities === 1 ? "y" : "ies"}`
143
183
  );
144
- if (inventory.filesOutsideRoot > 0) {
145
- lines.push(
146
- `${inventory.filesOutsideRoot} program file${inventory.filesOutsideRoot === 1 ? "" : "s"} from agent-surface implementation packages were excluded`
147
- );
148
- }
149
184
  if (!options.standalone) return lines.join("\n");
150
- const byId = [...resolved].sort((a, b) => a.capabilityId.localeCompare(b.capabilityId));
151
- if (byId.length > 0) {
185
+ const components = /* @__PURE__ */ new Map();
186
+ for (const capability of resolved) {
187
+ const component = componentOf(capability.capabilityId);
188
+ const current = components.get(component) ?? { ids: /* @__PURE__ */ new Set(), sites: 0, partial: 0 };
189
+ current.ids.add(capability.capabilityId);
190
+ current.sites += 1;
191
+ if (capability.resolution === "partial") current.partial += 1;
192
+ components.set(component, current);
193
+ }
194
+ if (components.size > 0) {
152
195
  lines.push("");
196
+ lines.push(`COMPONENTS (${components.size})`);
153
197
  lines.push(
154
198
  ...renderTable(
155
- ["CAPABILITY", "KIND", "ORIGIN", "READ"],
156
- byId.map((capability) => ({
199
+ ["COMPONENT", "CAPABILITIES", "CALL SITES", "DYNAMIC META"],
200
+ [...components.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([component, data]) => ({
157
201
  cells: [
158
- capability.capabilityId,
159
- capability.kind,
160
- `${capability.origin.file}:${capability.origin.line}`,
161
- capability.resolution
202
+ `view:${component}`,
203
+ String(data.ids.size),
204
+ String(data.sites),
205
+ data.partial > 0 ? String(data.partial) : NONE
162
206
  ],
163
- ...capability.note ? { note: capability.note } : {}
207
+ note: [...data.ids].sort().join(" \xB7 ")
164
208
  }))
165
209
  )
166
210
  );
167
211
  }
168
- const unread = renderUnread(unresolved(inventory));
169
- if (unread.length > 0) lines.push("", ...unread);
212
+ if (unreadEntries.length > 0) {
213
+ const groups = /* @__PURE__ */ new Map();
214
+ for (const entry of unreadEntries) {
215
+ const key = `${entry.origin.file}\0${entry.reason ?? "unknown"}`;
216
+ groups.set(key, (groups.get(key) ?? 0) + 1);
217
+ }
218
+ lines.push("");
219
+ lines.push(`UNREAD SITES (${unreadEntries.length})`);
220
+ lines.push("Counts above are a floor until these sites are resolved or explicitly accepted.");
221
+ lines.push(
222
+ ...renderTable(
223
+ ["FILE", "REASON", "SITES"],
224
+ [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, count]) => {
225
+ const [file, reason] = key.split("\0");
226
+ return { cells: [file ?? "?", reason ?? "unknown", String(count)] };
227
+ })
228
+ )
229
+ );
230
+ lines.push("", "ALLOWLIST KEYS");
231
+ for (const entry of unreadEntries) lines.push(` allowlist key: ${unreadKey(entry)}`);
232
+ }
233
+ if (options.detail) {
234
+ const byId = [...resolved].sort((a, b) => a.capabilityId.localeCompare(b.capabilityId));
235
+ if (byId.length > 0) {
236
+ lines.push("");
237
+ lines.push(`CAPABILITY DETAILS (${byId.length} call sites)`);
238
+ lines.push(
239
+ ...renderTable(
240
+ ["CAPABILITY", "KIND", "ORIGIN", "READ"],
241
+ byId.map((capability) => ({
242
+ cells: [
243
+ capability.capabilityId,
244
+ capability.kind,
245
+ `${capability.origin.file}:${capability.origin.line}`,
246
+ capability.resolution
247
+ ],
248
+ ...capability.note ? { note: capability.note } : {}
249
+ }))
250
+ )
251
+ );
252
+ }
253
+ const unread = renderUnread(unreadEntries);
254
+ if (unread.length > 0) lines.push("", ...unread);
255
+ } else if (unreadEntries.length > 0 || resolved.length > 0) {
256
+ lines.push("");
257
+ lines.push(
258
+ "Details: re-run with --detail for origins, metadata diagnostics, and allowlist keys."
259
+ );
260
+ }
170
261
  return lines.join("\n");
171
262
  }
172
263
  function renderUnread(entries) {
@@ -186,7 +277,7 @@ function renderUnread(entries) {
186
277
  }
187
278
  return lines;
188
279
  }
189
- function renderCoveragePlain(report) {
280
+ function renderCoveragePlain(report, options = {}) {
190
281
  const lines = [];
191
282
  if (report.unreached.length > 0) {
192
283
  lines.push(
@@ -203,14 +294,20 @@ function renderCoveragePlain(report) {
203
294
  lines.push("");
204
295
  }
205
296
  if (report.undeclared.length > 0) {
206
- lines.push(
207
- section(
208
- "UNDECLARED",
209
- "present at runtime with no static origin \u2014 a dynamic registration, or a gap here",
210
- report.undeclared.length
211
- )
212
- );
213
- for (const id of report.undeclared) lines.push(` ${id}`);
297
+ if (!options.compact || options.detail) {
298
+ lines.push(
299
+ section(
300
+ "UNDECLARED",
301
+ "present at runtime with no static origin \u2014 a dynamic registration, or a gap here",
302
+ report.undeclared.length
303
+ )
304
+ );
305
+ for (const id of report.undeclared) lines.push(` ${id}`);
306
+ } else {
307
+ lines.push(
308
+ `NOTICE \u2014 ${report.undeclared.length} runtime capabilit${report.undeclared.length === 1 ? "y has" : "ies have"} no static origin; re-run check with --detail to list them.`
309
+ );
310
+ }
214
311
  lines.push("");
215
312
  }
216
313
  if (report.unmanifestedDomain.length > 0) {
@@ -249,7 +346,70 @@ function renderCoveragePlain(report) {
249
346
  if (report.unresolved.length > 0) {
250
347
  lines.push(...renderUnread(report.unresolved), "");
251
348
  }
252
- lines.push(...renderCoverageSummary(report));
349
+ if (!options.compact) lines.push(...renderCoverageSummary(report));
350
+ return lines.join("\n");
351
+ }
352
+ function overviewRow(label, status, text) {
353
+ return `${label.padEnd(12)}${status.padEnd(7)}${text}`;
354
+ }
355
+ function wrappedList(items) {
356
+ const prefix = " ";
357
+ return wrapText(items.join(", "), REPORT_WIDTH - prefix.length).map((line) => `${prefix}${line}`);
358
+ }
359
+ function renderCheckOverviewPlain(input) {
360
+ const lines = [`SURFACE CHECK ${input.status}`, ""];
361
+ const coverage = input.coverage;
362
+ if (coverage) {
363
+ const coverageStatus = coverage.unreached.length > 0 || coverage.staleAllowlist.length > 0 ? "FAIL" : coverage.allowed.length > 0 ? "WARN" : "PASS";
364
+ lines.push(
365
+ overviewRow(
366
+ "Coverage",
367
+ coverageStatus,
368
+ `${coverage.reached}/${coverage.authored} authored capabilities reached` + (coverage.unreached.length > 0 ? ` \xB7 ${coverage.unreached.length} unreached` : "") + (coverage.allowed.length > 0 ? ` \xB7 ${coverage.allowed.length} unreached allowlisted` : "") + (coverage.staleAllowlist.length > 0 ? ` \xB7 ${coverage.staleAllowlist.length} stale allowlist entr${coverage.staleAllowlist.length === 1 ? "y" : "ies"}` : "")
369
+ )
370
+ );
371
+ const unread = coverage.unresolved.length;
372
+ const accepted = coverage.allowedUnread.length;
373
+ const catalogStatus = coverage.staleUnreadAllowlist.length > 0 || unread > 0 && !input.unresolvedAllowed ? "FAIL" : unread > 0 || accepted > 0 ? "WARN" : "PASS";
374
+ lines.push(
375
+ overviewRow(
376
+ "Catalog",
377
+ catalogStatus,
378
+ coverage.staleUnreadAllowlist.length > 0 ? `${coverage.staleUnreadAllowlist.length} stale unread allowlist entr${coverage.staleUnreadAllowlist.length === 1 ? "y" : "ies"}` : unread > 0 ? `${unread} unread static site${unread === 1 ? "" : "s"}${input.unresolvedAllowed ? " accepted by --allow-unresolved" : ""}` : accepted > 0 ? `${accepted} unread static site${accepted === 1 ? "" : "s"} allowlisted` : "all static sites resolved"
379
+ )
380
+ );
381
+ lines.push(
382
+ overviewRow(
383
+ "Domain",
384
+ coverage.unmanifestedDomain.length > 0 ? "FAIL" : coverage.domainAuthoritative ? "PASS" : "WARN",
385
+ coverage.unmanifestedDomain.length > 0 ? `${coverage.unmanifestedDomain.length} mounted capabilit${coverage.unmanifestedDomain.length === 1 ? "y" : "ies"} absent from manifest` : coverage.domainAuthoritative ? `${coverage.domainReached.length} manifest capabilit${coverage.domainReached.length === 1 ? "y" : "ies"} reached` : "authoritative manifest not configured"
386
+ )
387
+ );
388
+ } else {
389
+ lines.push(
390
+ overviewRow(
391
+ "Coverage",
392
+ input.status === "ERROR" ? "ERROR" : "WARN",
393
+ input.status === "ERROR" ? "no verdict; runtime analysis incomplete" : "not evaluated \u2014 statement about these scenarios only; re-run with --depth full"
394
+ )
395
+ );
396
+ }
397
+ const baselineOk = input.baselineCurrent === input.baselineTotal && input.scenarioManifestOk;
398
+ lines.push(
399
+ overviewRow(
400
+ "Baselines",
401
+ baselineOk ? "PASS" : "FAIL",
402
+ `${input.baselineCurrent}/${input.baselineTotal} scenario baselines current` + (input.scenarioManifestOk ? "" : " \xB7 scenario manifest differs")
403
+ )
404
+ );
405
+ lines.push(
406
+ overviewRow(
407
+ "Runtime",
408
+ input.mountFailures > 0 ? "ERROR" : input.rejected > 0 ? "FAIL" : "PASS",
409
+ input.mountFailures > 0 ? `${input.mountFailures} scenario${input.mountFailures === 1 ? "" : "s"} did not mount` : input.rejected > 0 ? `${input.rejected} registration${input.rejected === 1 ? "" : "s"} rejected` : `${input.scenarios.length} scenario${input.scenarios.length === 1 ? "" : "s"} mounted`
410
+ )
411
+ );
412
+ lines.push("", `SCENARIOS (${input.scenarios.length})`, ...wrappedList(input.scenarios));
253
413
  return lines.join("\n");
254
414
  }
255
415
  function renderCoverageSummary(report) {
@@ -369,6 +529,7 @@ export {
369
529
  renderSurfacePlain,
370
530
  renderCatalogPlain,
371
531
  renderCoveragePlain,
532
+ renderCheckOverviewPlain,
372
533
  renderNoVerdictPlain,
373
534
  renderFailuresPlain,
374
535
  renderDriftPlain,
@@ -377,4 +538,4 @@ export {
377
538
  inventoryReport,
378
539
  coverageReport
379
540
  };
380
- //# sourceMappingURL=chunk-TPWRSFK7.js.map
541
+ //# sourceMappingURL=chunk-J2NN3J5N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/render/plain.ts","../src/report.ts"],"sourcesContent":["import { relative } from \"node:path\";\nimport type { CapabilityRow, SurfaceView } from \"./model.js\";\nimport { flatRows } from \"./model.js\";\nimport type { DiffEntry } from \"../baseline.js\";\nimport { formatValue } from \"../baseline.js\";\nimport type { ScenarioFailure } from \"../analysis.js\";\nimport {\n authoredIds,\n unresolved,\n type AuthoredCapability,\n type CapabilityInventory,\n} from \"../extract.js\";\nimport { unreadKey, type CoverageReport } from \"../coverage.js\";\n\n/**\n * The no-colour, no-cursor rendering used when stdout is piped or when\n * `--plain`, `CI` or `NO_COLOR` is set. Same view model as the Ink UI, so the\n * two cannot disagree about what the surface contains.\n */\n\nconst MARK = { expose: \"+\", disable: \"~\", hide: \"-\" } as const;\nconst STATE = { expose: \"callable\", disable: \"disabled\", hide: \"hidden\" } as const;\nconst NONE = \"—\";\nconst REPORT_WIDTH = 100;\n\n/** Deterministic wrapping: readable in logs, independent of terminal width. */\nfunction wrapText(text: string, width: number): string[] {\n const words = text.split(/\\s+/).filter(Boolean);\n const lines: string[] = [];\n let line = \"\";\n for (const word of words) {\n if (line && line.length + 1 + word.length > width) {\n lines.push(line);\n line = word;\n } else {\n line = line ? `${line} ${word}` : word;\n }\n }\n if (line) lines.push(line);\n return lines.length > 0 ? lines : [\"\"];\n}\n\n/**\n * Column widths come from the *content*, never from `process.stdout.columns`.\n *\n * `AS-CLI-003` requires plain output to be byte-stable across runs, and a table\n * laid out against the terminal it happened to run in is stable only until two\n * people diff the same CI log from different windows. Same rows in, same bytes\n * out, everywhere.\n *\n * The last column is not padded, so no line ever carries trailing whitespace —\n * which some diff tools render and others strip, i.e. another way for identical\n * output to look different.\n */\nfunction renderTable(headers: string[], rows: Array<{ cells: string[]; note?: string }>): string[] {\n const widths = headers.map((header, column) =>\n Math.max(header.length, ...rows.map((row) => (row.cells[column] ?? \"\").length)),\n );\n const line = (cells: string[]): string =>\n cells\n .map((cell, column) => (column === headers.length - 1 ? cell : cell.padEnd(widths[column]!)))\n .join(\" \")\n .trimEnd();\n\n const lines = [line(headers)];\n for (const row of rows) {\n lines.push(line(row.cells));\n // The unavailability reason is prose of unbounded length. A column for it\n // would set the table's width by its longest sentence; a continuation line\n // keeps the grid aligned and puts the reason directly under its capability.\n if (row.note) {\n for (const [index, note] of wrapText(row.note, REPORT_WIDTH - 6).entries()) {\n lines.push(` ${index === 0 ? \"⤷ \" : \" \"}${note}`);\n }\n }\n }\n return lines;\n}\n\n/** `UNREACHED — authored, and no scenario mounts it (1)` */\nfunction section(title: string, gloss: string, count: number): string {\n return `${title} — ${gloss} (${count})`;\n}\n\nfunction renderDetailRow(row: CapabilityRow, lines: string[]): void {\n const tags = row.tags.length > 0 ? ` [${row.tags.join(\", \")}]` : \"\";\n lines.push(` ${MARK[row.outcome]} ${row.name}${tags}`);\n lines.push(` ${row.description}`);\n if (row.reason) lines.push(` reason: ${row.reason}`);\n\n if (row.policies) {\n if (row.policies.length === 0) {\n lines.push(\" policies: none\");\n } else {\n for (const policy of row.policies) {\n const vote = policy.discovery\n ? policy.discovery.decision === \"disable\"\n ? `disable — ${policy.discovery.reason}`\n : policy.discovery.decision\n : \"no discovery hook\";\n const phases = policy.phases.length > 0 ? policy.phases.join(\"/\") : NONE;\n const flags = [\n policy.threw ? \"THREW\" : \"\",\n policy.confirmationEscalation ? \"escalates-confirmation\" : \"\",\n ]\n .filter(Boolean)\n .join(\", \");\n lines.push(\n ` policy ${policy.name} (${policy.scope}, ${phases}): ${vote}${\n flags ? ` [${flags}]` : \"\"\n }`,\n );\n }\n }\n if (row.availability && !row.availability.available) {\n lines.push(\n ` availability: unavailable${\n row.availability.reason ? ` — ${row.availability.reason}` : \"\"\n }`,\n );\n }\n }\n\n if (row.schemas) {\n if (row.schemas.input !== undefined) {\n lines.push(` input: ${JSON.stringify(row.schemas.input)}`);\n }\n if (row.schemas.output !== undefined) {\n lines.push(` output: ${JSON.stringify(row.schemas.output)}`);\n }\n }\n}\n\n/**\n * The counts line, and everything it is relative to (`AS-CLI-007`).\n *\n * `hidden` is printed unconditionally. It is computed on every run — the\n * explanation is always collected — and suppressing it outside `--explain`\n * meant a surface with a policy-hidden half rendered as a complete one. The\n * *attribution* still needs `--explain`; the count and the rows do not.\n */\nexport function renderCountsPlain(view: SurfaceView): string {\n return (\n `${view.counts.callable} callable, ${view.counts.disabled} visible-disabled, ` +\n `${view.counts.hidden} hidden` +\n (view.rejections.length > 0\n ? `, ${view.rejections.length} registration${view.rejections.length === 1 ? \"\" : \"s\"} rejected`\n : \"\")\n );\n}\n\nfunction renderHeader(view: SurfaceView, lines: string[]): void {\n lines.push(\n `scenario ${view.scenario}${view.route ? ` route ${view.route}` : \"\"}${\n view.scope && view.scope.length > 0 ? ` scope ${view.scope.join(\" \")}` : \"\"\n }`,\n );\n lines.push(renderCountsPlain(view));\n}\n\nfunction renderRejections(view: SurfaceView, lines: string[]): void {\n if (view.rejections.length === 0) return;\n lines.push(\"\");\n lines.push(\n section(\"REJECTED\", \"the registry refused these during the mount\", view.rejections.length),\n );\n for (const rejection of view.rejections) {\n const why =\n rejection.reason === \"duplicate\"\n ? \"duplicate — an earlier registration holds this key\"\n : \"guard — onRegister rejected this registration\";\n lines.push(` ! ${rejection.componentType} (${rejection.instanceId}) ${why}`);\n }\n}\n\nfunction renderEmpty(view: SurfaceView, lines: string[]): void {\n lines.push(\"\");\n // \"Nothing is registered\" is only true when nothing was hidden. Saying it\n // over a surface a policy emptied sends the reader to the wrong file.\n if (view.counts.hidden > 0) {\n lines.push(\n `Nothing is callable here — all ${view.counts.hidden} registered capabilities were hidden by policy.`,\n );\n if (!view.explained) lines.push(\"Re-run with --explain to see which policy hid them.\");\n } else {\n lines.push(\"Nothing is registered for this scenario — the agent has no surface here.\");\n if (!view.explained) lines.push(\"Re-run with --explain to see whether a policy hid it.\");\n }\n}\n\nexport interface SurfaceRenderOptions {\n /** The grouped, one-capability-per-paragraph view. Implied by --explain/--schemas. */\n detail?: boolean;\n}\n\n/**\n * One capability per line, aligned. The default, because the question `inspect`\n * is usually asked is *what is on this surface* — which is a scanning question,\n * and prose does not scan.\n *\n * Policy chains and JSON Schemas are multi-line by nature and cannot live in a\n * cell, so `--explain` and `--schemas` fall back to the detail view rather than\n * producing a table with most of the answer missing.\n */\nexport function renderSurfacePlain(\n view: SurfaceView,\n options: SurfaceRenderOptions = {},\n): string {\n const lines: string[] = [];\n renderHeader(view, lines);\n renderRejections(view, lines);\n\n const rows = flatRows(view);\n if (rows.length === 0) {\n renderEmpty(view, lines);\n return lines.join(\"\\n\");\n }\n\n if (options.detail) {\n for (const group of view.groups.filter((group) => group.rows.length > 0)) {\n lines.push(\"\");\n lines.push(`${group.heading} (${group.rows.length})`);\n for (const row of group.rows) renderDetailRow(row, lines);\n }\n return lines.join(\"\\n\");\n }\n\n lines.push(\"\");\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"KIND\", \"EFFECT\", \"STATE\", \"FLAGS\"],\n rows.map((row) => ({\n cells: [\n row.path,\n row.kind,\n row.effect ?? NONE,\n STATE[row.outcome],\n row.flags.length > 0 ? row.flags.join(\" · \") : NONE,\n ],\n ...(row.reason ? { note: row.reason } : {}),\n })),\n ),\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * The static catalog (`AS-COVER-001…003`). The summary says \"upper bound\" in so\n * many words: a tsconfig's include globs are wider than what a bundle reaches,\n * so a capability in a component no route renders any more is in here. That is\n * dead code — a different finding, not a false positive — and the reader has to\n * be told which number they are holding.\n */\nexport interface CatalogRenderOptions {\n /**\n * This catalog *is* the command's output, rather than its preamble.\n *\n * True at `--depth static`: there are no scenario tables and no verdict, so\n * the listing and the unread call sites have nowhere else to appear.\n *\n * False at `--depth full`, where the scenario tables below name every\n * capability a scenario reached, the `UNREACHED` section names the ones it did\n * not, and the verdict carries the unread call sites — so printing any of it\n * here is the same information a second time, above the answer instead of in\n * it. Only the summary line survives.\n */\n standalone?: boolean;\n /** Authoritative domain manifest entries, when runtime config was loaded. */\n domainCapabilities?: number;\n /** Show origins, notes and per-site allowlist keys. */\n detail?: boolean;\n}\n\nfunction componentOf(capabilityId: string): string {\n const path = capabilityId.replace(/^(view|domain):/, \"\");\n const dot = path.lastIndexOf(\".\");\n return dot === -1 ? path : path.slice(0, dot);\n}\n\nexport function renderCatalogPlain(\n inventory: CapabilityInventory,\n options: CatalogRenderOptions = {},\n): string {\n const lines: string[] = [];\n const resolved = inventory.capabilities.filter((c) => c.resolution !== \"unresolved\");\n const unreadEntries = unresolved(inventory);\n const dynamicMetadata = resolved.filter((c) => c.resolution === \"partial\").length;\n const ids = authoredIds(inventory);\n const authored = ids.size + (options.domainCapabilities ?? 0);\n\n lines.push(\"STATIC CATALOG\");\n lines.push(\n `STATUS ${unreadEntries.length > 0 ? \"INCOMPLETE\" : \"COMPLETE\"}${\n unreadEntries.length > 0\n ? ` — ${unreadEntries.length} unread capability identit${unreadEntries.length === 1 ? \"y\" : \"ies\"}`\n : \" — every capability identity resolved\"\n }`,\n );\n lines.push(\n `Capabilities ${authored} authored (upper bound) · ${resolved.length} resolved call site${\n resolved.length === 1 ? \"\" : \"s\"\n }`,\n );\n lines.push(\n `Program ${inventory.filesAnalyzed} file${inventory.filesAnalyzed === 1 ? \"\" : \"s\"} analyzed` +\n (inventory.filesOutsideRoot > 0\n ? ` · ${inventory.filesOutsideRoot} agent-surface implementation file${\n inventory.filesOutsideRoot === 1 ? \"\" : \"s\"\n } excluded`\n : \"\"),\n );\n lines.push(\n `Metadata ${dynamicMetadata} call site${dynamicMetadata === 1 ? \"\" : \"s\"} partially read` +\n (dynamicMetadata > 0 ? \" · identity remains resolved\" : \"\"),\n );\n lines.push(\n options.domainCapabilities === undefined\n ? \"Domain not analyzed at static depth; full depth reads the oRPC manifest\"\n : `Domain ${options.domainCapabilities} manifest capabilit${\n options.domainCapabilities === 1 ? \"y\" : \"ies\"\n }`,\n );\n\n if (!options.standalone) return lines.join(\"\\n\");\n\n const components = new Map<\n string,\n { ids: Set<string>; sites: number; partial: number }\n >();\n for (const capability of resolved) {\n const component = componentOf(capability.capabilityId);\n const current = components.get(component) ?? { ids: new Set<string>(), sites: 0, partial: 0 };\n current.ids.add(capability.capabilityId);\n current.sites += 1;\n if (capability.resolution === \"partial\") current.partial += 1;\n components.set(component, current);\n }\n\n if (components.size > 0) {\n lines.push(\"\");\n lines.push(`COMPONENTS (${components.size})`);\n lines.push(\n ...renderTable(\n [\"COMPONENT\", \"CAPABILITIES\", \"CALL SITES\", \"DYNAMIC META\"],\n [...components.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([component, data]) => ({\n cells: [\n `view:${component}`,\n String(data.ids.size),\n String(data.sites),\n data.partial > 0 ? String(data.partial) : NONE,\n ],\n note: [...data.ids].sort().join(\" · \"),\n })),\n ),\n );\n }\n\n if (unreadEntries.length > 0) {\n const groups = new Map<string, number>();\n for (const entry of unreadEntries) {\n const key = `${entry.origin.file}\\0${entry.reason ?? \"unknown\"}`;\n groups.set(key, (groups.get(key) ?? 0) + 1);\n }\n lines.push(\"\");\n lines.push(`UNREAD SITES (${unreadEntries.length})`);\n lines.push(\"Counts above are a floor until these sites are resolved or explicitly accepted.\");\n lines.push(\n ...renderTable(\n [\"FILE\", \"REASON\", \"SITES\"],\n [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, count]) => {\n const [file, reason] = key.split(\"\\0\");\n return { cells: [file ?? \"?\", reason ?? \"unknown\", String(count)] };\n }),\n ),\n );\n lines.push(\"\", \"ALLOWLIST KEYS\");\n for (const entry of unreadEntries) lines.push(` allowlist key: ${unreadKey(entry)}`);\n }\n\n if (options.detail) {\n const byId = [...resolved].sort((a, b) => a.capabilityId.localeCompare(b.capabilityId));\n if (byId.length > 0) {\n lines.push(\"\");\n lines.push(`CAPABILITY DETAILS (${byId.length} call sites)`);\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"KIND\", \"ORIGIN\", \"READ\"],\n byId.map((capability) => ({\n cells: [\n capability.capabilityId,\n capability.kind,\n `${capability.origin.file}:${capability.origin.line}`,\n capability.resolution,\n ],\n ...(capability.note ? { note: capability.note } : {}),\n })),\n ),\n );\n }\n const unread = renderUnread(unreadEntries);\n if (unread.length > 0) lines.push(\"\", ...unread);\n } else if (unreadEntries.length > 0 || resolved.length > 0) {\n lines.push(\"\");\n lines.push(\n \"Details: re-run with --detail for origins, metadata diagnostics, and allowlist keys.\",\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Call sites the extractor could not read. Reported with file and line, never\n * dropped: an inventory that silently omitted what it failed to parse would\n * understate the denominator, and every number built on it would claim a\n * completeness it never had.\n */\nfunction renderUnread(entries: AuthoredCapability[]): string[] {\n if (entries.length === 0) return [];\n const lines: string[] = [];\n lines.push(\n section(\n \"UNREAD CALL SITES\",\n \"the catalog is incomplete, so every count above is a floor\",\n entries.length,\n ),\n );\n for (const capability of entries) {\n lines.push(` ? ${capability.origin.file}:${capability.origin.line}`);\n lines.push(` ${capability.note ?? \"the extractor could not read this call site\"}`);\n // The allowlist key, spelled out. It is `file#reason#site`, and the site is\n // a hash — not the line the reader is looking at — so leaving them to infer\n // it guarantees a wrong guess and an entry that never matches.\n lines.push(` allowlist key: ${unreadKey(capability)}`);\n }\n return lines;\n}\n\n/**\n * The verdict: authored minus reached (`AS-COVER-004…005`).\n *\n * This is the finding the command surface used to hide behind a fifth command,\n * so it is the last thing printed and the thing a reader stops on.\n */\nexport interface CoverageRenderOptions {\n /** Check already prints an executive summary; render findings only. */\n compact?: boolean;\n /** Include non-gating inventories such as undeclared runtime ids. */\n detail?: boolean;\n}\n\nexport function renderCoveragePlain(\n report: CoverageReport,\n options: CoverageRenderOptions = {},\n): string {\n const lines: string[] = [];\n\n if (report.unreached.length > 0) {\n lines.push(\n section(\"UNREACHED\", \"authored, and no scenario mounts it\", report.unreached.length),\n );\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"ORIGIN\"],\n report.unreached.map((entry) => ({\n cells: [entry.capabilityId, `${entry.origin.file}:${entry.origin.line}`],\n })),\n ),\n );\n lines.push(\"\");\n }\n\n if (report.undeclared.length > 0) {\n if (!options.compact || options.detail) {\n lines.push(\n section(\n \"UNDECLARED\",\n \"present at runtime with no static origin — a dynamic registration, or a gap here\",\n report.undeclared.length,\n ),\n );\n for (const id of report.undeclared) lines.push(` ${id}`);\n } else {\n lines.push(\n `NOTICE — ${report.undeclared.length} runtime capabilit${\n report.undeclared.length === 1 ? \"y has\" : \"ies have\"\n } no static origin; re-run check with --detail to list them.`,\n );\n }\n lines.push(\"\");\n }\n\n if (report.unmanifestedDomain.length > 0) {\n lines.push(\n section(\n \"UNMANIFESTED DOMAIN\",\n \"mounted, but absent from the authoritative oRPC manifest\",\n report.unmanifestedDomain.length,\n ),\n );\n for (const id of report.unmanifestedDomain) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.staleAllowlist.length > 0) {\n lines.push(\n section(\n \"STALE ALLOWLIST\",\n \"a scenario reaches these now, so delete them before the list rots\",\n report.staleAllowlist.length,\n ),\n );\n for (const id of report.staleAllowlist) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.staleUnreadAllowlist.length > 0) {\n lines.push(\n section(\n \"STALE UNREAD ALLOWLIST\",\n \"the extractor reads these now, so delete them before the list rots\",\n report.staleUnreadAllowlist.length,\n ),\n );\n for (const key of report.staleUnreadAllowlist) lines.push(` ${key}`);\n lines.push(\"\");\n }\n\n if (report.unresolved.length > 0) {\n lines.push(...renderUnread(report.unresolved), \"\");\n }\n\n if (!options.compact) lines.push(...renderCoverageSummary(report));\n return lines.join(\"\\n\");\n}\n\nexport interface CheckOverview {\n status: \"PASS\" | \"FAIL\" | \"ERROR\";\n coverage?: CoverageReport;\n unresolvedAllowed: boolean;\n baselineCurrent: number;\n baselineTotal: number;\n scenarioManifestOk: boolean;\n rejected: number;\n mountFailures: number;\n scenarios: string[];\n}\n\nfunction overviewRow(label: string, status: \"PASS\" | \"WARN\" | \"FAIL\" | \"ERROR\", text: string): string {\n return `${label.padEnd(12)}${status.padEnd(7)}${text}`;\n}\n\nfunction wrappedList(items: string[]): string[] {\n const prefix = \" \";\n return wrapText(items.join(\", \"), REPORT_WIDTH - prefix.length).map((line) => `${prefix}${line}`);\n}\n\n/** First-screen answer for `check`: verdict and health dimensions before detail. */\nexport function renderCheckOverviewPlain(input: CheckOverview): string {\n const lines = [`SURFACE CHECK ${input.status}`, \"\"];\n const coverage = input.coverage;\n if (coverage) {\n const coverageStatus =\n coverage.unreached.length > 0 || coverage.staleAllowlist.length > 0\n ? \"FAIL\"\n : coverage.allowed.length > 0\n ? \"WARN\"\n : \"PASS\";\n lines.push(\n overviewRow(\n \"Coverage\",\n coverageStatus,\n `${coverage.reached}/${coverage.authored} authored capabilities reached` +\n (coverage.unreached.length > 0 ? ` · ${coverage.unreached.length} unreached` : \"\") +\n (coverage.allowed.length > 0\n ? ` · ${coverage.allowed.length} unreached allowlisted`\n : \"\") +\n (coverage.staleAllowlist.length > 0\n ? ` · ${coverage.staleAllowlist.length} stale allowlist entr${\n coverage.staleAllowlist.length === 1 ? \"y\" : \"ies\"\n }`\n : \"\"),\n ),\n );\n const unread = coverage.unresolved.length;\n const accepted = coverage.allowedUnread.length;\n const catalogStatus =\n coverage.staleUnreadAllowlist.length > 0 || (unread > 0 && !input.unresolvedAllowed)\n ? \"FAIL\"\n : unread > 0 || accepted > 0\n ? \"WARN\"\n : \"PASS\";\n lines.push(\n overviewRow(\n \"Catalog\",\n catalogStatus,\n coverage.staleUnreadAllowlist.length > 0\n ? `${coverage.staleUnreadAllowlist.length} stale unread allowlist entr${\n coverage.staleUnreadAllowlist.length === 1 ? \"y\" : \"ies\"\n }`\n : unread > 0\n ? `${unread} unread static site${unread === 1 ? \"\" : \"s\"}${\n input.unresolvedAllowed ? \" accepted by --allow-unresolved\" : \"\"\n }`\n : accepted > 0\n ? `${accepted} unread static site${accepted === 1 ? \"\" : \"s\"} allowlisted`\n : \"all static sites resolved\",\n ),\n );\n lines.push(\n overviewRow(\n \"Domain\",\n coverage.unmanifestedDomain.length > 0 ? \"FAIL\" : coverage.domainAuthoritative ? \"PASS\" : \"WARN\",\n coverage.unmanifestedDomain.length > 0\n ? `${coverage.unmanifestedDomain.length} mounted capabilit${\n coverage.unmanifestedDomain.length === 1 ? \"y\" : \"ies\"\n } absent from manifest`\n : coverage.domainAuthoritative\n ? `${coverage.domainReached.length} manifest capabilit${\n coverage.domainReached.length === 1 ? \"y\" : \"ies\"\n } reached`\n : \"authoritative manifest not configured\",\n ),\n );\n } else {\n lines.push(\n overviewRow(\n \"Coverage\",\n input.status === \"ERROR\" ? \"ERROR\" : \"WARN\",\n input.status === \"ERROR\"\n ? \"no verdict; runtime analysis incomplete\"\n : \"not evaluated — statement about these scenarios only; re-run with --depth full\",\n ),\n );\n }\n\n const baselineOk = input.baselineCurrent === input.baselineTotal && input.scenarioManifestOk;\n lines.push(\n overviewRow(\n \"Baselines\",\n baselineOk ? \"PASS\" : \"FAIL\",\n `${input.baselineCurrent}/${input.baselineTotal} scenario baselines current` +\n (input.scenarioManifestOk ? \"\" : \" · scenario manifest differs\"),\n ),\n );\n lines.push(\n overviewRow(\n \"Runtime\",\n input.mountFailures > 0 ? \"ERROR\" : input.rejected > 0 ? \"FAIL\" : \"PASS\",\n input.mountFailures > 0\n ? `${input.mountFailures} scenario${input.mountFailures === 1 ? \"\" : \"s\"} did not mount`\n : input.rejected > 0\n ? `${input.rejected} registration${input.rejected === 1 ? \"\" : \"s\"} rejected`\n : `${input.scenarios.length} scenario${input.scenarios.length === 1 ? \"\" : \"s\"} mounted`,\n ),\n );\n lines.push(\"\", `SCENARIOS (${input.scenarios.length})`, ...wrappedList(input.scenarios));\n return lines.join(\"\\n\");\n}\n\n/** The one line a reader who stops at the bottom takes away. */\nfunction renderCoverageSummary(report: CoverageReport): string[] {\n const qualifiers = [\n `${report.scenarios.length} scenario${report.scenarios.length === 1 ? \"\" : \"s\"} (${report.scenarios.join(\n \", \",\n )})`,\n ];\n // Every count is relative to the scope, so the scope is printed with them\n // (`AS-CLI-007`) — `10 authored` under a scope is a claim about one prefix of\n // the codebase, not about the codebase.\n if (report.scope && report.scope.length > 0) qualifiers.push(`scope ${report.scope.join(\" \")}`);\n\n const lines = [\n `${report.authored} authored · ${report.reached} reached · ${report.unreached.length} unreached` +\n ` · ${qualifiers.join(\" · \")}`,\n ];\n\n if (report.domainReached.length > 0) {\n lines.push(\n `${report.domainReached.length} domain capabilit${\n report.domainReached.length === 1 ? \"y\" : \"ies\"\n } reached${\n report.domainAuthoritative\n ? \" against the authoritative oRPC manifest\"\n : \" and held apart — configure the authoritative oRPC manifest to cover that plane\"\n }`,\n );\n }\n if (report.allowed.length > 0) {\n lines.push(\n `${report.allowed.length} unreached capabilit${\n report.allowed.length === 1 ? \"y is\" : \"ies are\"\n } allowlisted in ${relative(process.cwd(), report.allowlistPath)}`,\n );\n }\n if (report.allowedUnread.length > 0) {\n lines.push(\n `${report.allowedUnread.length} unread call site${\n report.allowedUnread.length === 1 ? \" is\" : \"s are\"\n } allowlisted in ${relative(process.cwd(), report.unreadAllowlistPath)}`,\n );\n }\n if (report.allowlistOutOfScope > 0) {\n lines.push(\n `${report.allowlistOutOfScope} allowlist entr${\n report.allowlistOutOfScope === 1 ? \"y\" : \"ies\"\n } outside this scope were not judged either way`,\n );\n }\n\n // Each bucket gets its own remedy. \"Add a scenario, or delete the component\"\n // is the right advice for an unreached capability and useless advice for a\n // call site the extractor could not read.\n if (\n report.unreached.length === 0 &&\n report.unresolved.length === 0 &&\n report.staleAllowlist.length === 0 &&\n report.staleUnreadAllowlist.length === 0 &&\n report.unmanifestedDomain.length === 0\n ) {\n lines.push(\n report.allowed.length > 0\n ? \"no new surface coverage gaps — the allowlist still holds the known ones\"\n : \"every authored capability is reached by a scenario\",\n );\n }\n return lines;\n}\n\n/**\n * Why there is no coverage verdict. Never silence: a reader who asked for the\n * complete answer and got a partial one has to be told which part is missing,\n * or the partial one reads as the complete one.\n */\nexport function renderNoVerdictPlain(failures: ScenarioFailure[]): string {\n return [\n section(\"NO COVERAGE VERDICT\", \"a scenario did not mount, so nothing reached anything\", failures.length),\n ...failures.flatMap((failure) => [` ${failure.scenario}`, ` ${failure.message}`]),\n \"\",\n \"Every capability those scenarios would have surfaced would be reported unreached,\",\n \"so no verdict is printed at all. Fix the mount, or name a scenario that works.\",\n ].join(\"\\n\");\n}\n\nexport function renderFailuresPlain(failures: ScenarioFailure[]): string {\n return [\n section(\"DID NOT MOUNT\", \"these scenarios threw, and were skipped\", failures.length),\n ...failures.flatMap((failure) => [` ${failure.scenario}`, ` ${failure.message}`]),\n ].join(\"\\n\");\n}\n\nexport function renderDriftPlain(scenario: string, entries: DiffEntry[]): string {\n const lines = [` ${scenario}: ${entries.length} change${entries.length === 1 ? \"\" : \"s\"}`];\n for (const entry of entries) {\n const where = entry.subject ? `${entry.subject} (${entry.path})` : entry.path;\n if (entry.kind === \"added\") lines.push(` + ${where} ${formatValue(entry.after)}`);\n else if (entry.kind === \"removed\") lines.push(` - ${where} ${formatValue(entry.before)}`);\n else {\n lines.push(` ~ ${where}`);\n lines.push(` before: ${formatValue(entry.before)}`);\n lines.push(` after: ${formatValue(entry.after)}`);\n }\n }\n return lines.join(\"\\n\");\n}\n","import { basename, relative } from \"node:path\";\nimport type { CollectResult } from \"./collect.js\";\nimport type { CoverageReport } from \"./coverage.js\";\nimport type { CapabilityInventory } from \"./extract.js\";\nimport { normalize } from \"./baseline.js\";\nimport { buildView, flatRows, type CapabilityRow } from \"./render/model.js\";\n\n/** Stable, complete per-scenario document shared by JSON, baselines and check. */\nexport interface ScenarioReport {\n scenario: string;\n scope?: string[];\n snapshot: unknown;\n capabilities: CapabilityRow[];\n rejections: CollectResult[\"rejections\"];\n explanation?: { capabilities: CapabilityRow[] };\n}\n\nexport function scenarioReport(\n result: CollectResult,\n options: { attribution?: boolean; schemas?: boolean } = {},\n): ScenarioReport {\n const view = buildView(result, {\n ...(options.attribution ? { explain: true } : {}),\n ...(options.schemas ? { schemas: true } : {}),\n });\n const capabilities = flatRows(view);\n return {\n scenario: result.scenario,\n ...(result.scope ? { scope: result.scope } : {}),\n snapshot: normalize(result.snapshot),\n // Includes expose, disable and hide. Rows never contain runtime ids.\n capabilities,\n rejections: [...result.rejections].sort(\n (a, b) =>\n a.componentType.localeCompare(b.componentType) ||\n a.instanceId.localeCompare(b.instanceId) ||\n a.reason.localeCompare(b.reason),\n ),\n ...(options.attribution ? { explanation: { capabilities } } : {}),\n };\n}\n\n/** Baseline payload: same semantic document, without invocation-only labels. */\nexport function scenarioBaseline(result: CollectResult): Record<string, unknown> {\n const report = scenarioReport(result);\n return {\n ...(report.snapshot as Record<string, unknown>),\n capabilities: report.capabilities,\n rejections: report.rejections,\n };\n}\n\n/** Machine output must not contain checkout-specific absolute paths. */\nexport function inventoryReport(\n inventory: CapabilityInventory | undefined,\n domainCapabilities?: string[],\n): unknown {\n if (!inventory) return null;\n return {\n ...inventory,\n root: \".\",\n tsconfig: relative(inventory.root, inventory.tsconfig) || \"tsconfig.json\",\n ...(domainCapabilities\n ? { domain: { source: \"manifest\", capabilities: [...domainCapabilities].sort() } }\n : {}),\n };\n}\n\nexport function coverageReport(report: CoverageReport | undefined): unknown {\n if (!report) return null;\n return {\n ...report,\n allowlistPath: basename(report.allowlistPath),\n unreadAllowlistPath: basename(report.unreadAllowlistPath),\n };\n}\n"],"mappings":"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","names":["group","relative","relative"]}
@@ -7,7 +7,7 @@ import {
7
7
  renderNoVerdictPlain,
8
8
  renderSurfacePlain,
9
9
  scenarioReport
10
- } from "./chunk-TPWRSFK7.js";
10
+ } from "./chunk-J2NN3J5N.js";
11
11
  import {
12
12
  buildView
13
13
  } from "./chunk-DYDSJM7R.js";
@@ -43,7 +43,7 @@ async function runInspect(options) {
43
43
  const inventory = readInventory(analysis);
44
44
  const initialInventory = scopeInventory(inventory, staticConfigScope(analysis));
45
45
  if (initialInventory && !options.json && options.depth === "static") {
46
- write(renderCatalogPlain(initialInventory, { standalone: true }));
46
+ write(renderCatalogPlain(initialInventory, { standalone: true, detail }));
47
47
  }
48
48
  const ink = isPlain(options) ? null : await loadInk();
49
49
  const scenarios = [];
@@ -116,4 +116,4 @@ ${renderNoVerdictPlain(runtime.failures)}`);
116
116
  export {
117
117
  runInspect
118
118
  };
119
- //# sourceMappingURL=inspect-ELUQ73MZ.js.map
119
+ //# sourceMappingURL=inspect-XMDZBSK2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/inspect.tsx"],"sourcesContent":["import {\n joinCoverage,\n mountScenarios,\n readInventory,\n scopeCapabilityIds,\n scopeInventory,\n staticConfigScope,\n type AnalysisOptions,\n type Depth,\n} from \"../analysis.js\";\nimport { buildView } from \"../render/model.js\";\nimport { authoredIds } from \"../extract.js\";\nimport {\n renderCatalogPlain,\n renderCoveragePlain,\n renderFailuresPlain,\n renderNoVerdictPlain,\n renderSurfacePlain,\n} from \"../render/plain.js\";\nimport { isPlain, loadInk, paint, write, writeError } from \"../output.js\";\nimport {\n coverageReport,\n inventoryReport,\n scenarioReport,\n type ScenarioReport,\n} from \"../report.js\";\n\nexport interface InspectOptions {\n configPath: string;\n depth: Depth;\n scenario?: string;\n scope?: string[];\n tsconfig?: string;\n baselineDir?: string;\n detail?: boolean;\n explain?: boolean;\n schemas?: boolean;\n json?: boolean;\n plain?: boolean;\n}\n\n/**\n * The whole surface: what this codebase authors, what a mount surfaces, and the\n * difference between them.\n *\n * It answers findings, it does not gate on them — exit `0` whatever it reports,\n * because `check` is the gate and a viewer that sometimes fails is a viewer\n * nobody puts in a pipeline. The exception is `2`, which is not a finding: the\n * command could not run at all.\n *\n * Each scenario prints as it finishes, so a config with ten of them is not ten\n * mounts of blank terminal. The normalized catalog summary and verdict follow\n * once effective config scope and the domain manifest are known.\n */\nexport async function runInspect(options: InspectOptions): Promise<number> {\n const analysis: AnalysisOptions = {\n configPath: options.configPath,\n depth: options.depth,\n ...(options.scenario ? { scenario: options.scenario } : {}),\n ...(options.scope ? { scope: options.scope } : {}),\n ...(options.tsconfig ? { tsconfig: options.tsconfig } : {}),\n ...(options.baselineDir ? { baselineDir: options.baselineDir } : {}),\n };\n\n // Policy chains and JSON Schemas are multi-line by nature, so asking for\n // either is asking for the view that can hold them.\n const detail = options.detail === true || options.explain === true || options.schemas === true;\n\n const inventory = readInventory(analysis);\n const initialInventory = scopeInventory(inventory, staticConfigScope(analysis));\n if (initialInventory && !options.json && options.depth === \"static\") {\n // At `--depth static` this listing is the output. At `--depth full` the\n // scenario tables below carry the same capabilities and the verdict names\n // the ones they miss, so only the summary line prints here.\n write(renderCatalogPlain(initialInventory, { standalone: true, detail }));\n }\n\n // `null` when Ink cannot run here (React 18 host), which is a fallback to\n // plain text rather than a failed command — see loadInk().\n const ink = isPlain(options) ? null : await loadInk();\n const scenarios: ScenarioReport[] = [];\n let printed = 0;\n\n const runtime = await mountScenarios(analysis, async (result) => {\n if (options.json) {\n scenarios.push(\n scenarioReport(result, {\n ...(options.explain ? { attribution: true } : {}),\n ...(options.schemas ? { schemas: true } : {}),\n }),\n );\n return;\n }\n const view = buildView(result, {\n ...(options.explain ? { explain: true } : {}),\n ...(options.schemas ? { schemas: true } : {}),\n });\n if (ink) await paint(<ink.Surface view={view} detail={detail} />);\n else {\n const rendered = renderSurfacePlain(view, { detail });\n write(printed === 0 && !inventory ? rendered : `\\n${rendered}`);\n }\n printed += 1;\n });\n\n const effectiveScope = options.scope ?? runtime?.scope ?? staticConfigScope(analysis);\n const reportInventory = scopeInventory(inventory, effectiveScope);\n const reportDomain = runtime?.domainManifestConfigured\n ? scopeCapabilityIds(runtime.domainCapabilities, effectiveScope)\n : undefined;\n const coverage = joinCoverage(inventory, runtime, analysis);\n if (reportInventory && !options.json && options.depth === \"full\") {\n write(\n `${printed > 0 ? \"\\n\" : \"\"}${renderCatalogPlain(reportInventory, {\n ...(coverage && runtime?.domainManifestConfigured\n ? { domainCapabilities: coverage.authored - authoredIds(reportInventory).size }\n : {}),\n })}`,\n );\n }\n\n if (options.json) {\n write(\n JSON.stringify(\n {\n // One shape whatever the depth, so a consumer never branches on how\n // the command was invoked. A half the depth did not compute is\n // `null`, which is a different statement from `[]` or `{}`.\n depth: options.depth,\n catalog: inventoryReport(reportInventory, reportDomain),\n scenarios,\n failures: runtime?.failures ?? [],\n coverage: coverageReport(coverage),\n },\n null,\n 2,\n ),\n );\n return runtime && runtime.failures.length > 0 ? 2 : 0;\n }\n\n if (runtime && runtime.failures.length > 0) {\n writeError(`\\n${renderFailuresPlain(runtime.failures)}`);\n }\n if (coverage) {\n write(\"\");\n write(renderCoveragePlain(coverage));\n } else if (inventory && runtime && runtime.failures.length > 0) {\n writeError(`\\n${renderNoVerdictPlain(runtime.failures)}`);\n }\n\n // A scenario that would not mount is not a finding about the surface, it is\n // the command failing to observe one. `2` — the same code a usage error gets,\n // because CI has to tell both apart from \"the surface changed\".\n return runtime && runtime.failures.length > 0 ? 2 : 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiGyB;AA3CzB,eAAsB,WAAW,SAA0C;AACzE,QAAM,WAA4B;AAAA,IAChC,YAAY,QAAQ;AAAA,IACpB,OAAO,QAAQ;AAAA,IACf,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzD,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAChD,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzD,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;AAAA,EACpE;AAIA,QAAM,SAAS,QAAQ,WAAW,QAAQ,QAAQ,YAAY,QAAQ,QAAQ,YAAY;AAE1F,QAAM,YAAY,cAAc,QAAQ;AACxC,QAAM,mBAAmB,eAAe,WAAW,kBAAkB,QAAQ,CAAC;AAC9E,MAAI,oBAAoB,CAAC,QAAQ,QAAQ,QAAQ,UAAU,UAAU;AAInE,UAAM,mBAAmB,kBAAkB,EAAE,YAAY,MAAM,OAAO,CAAC,CAAC;AAAA,EAC1E;AAIA,QAAM,MAAM,QAAQ,OAAO,IAAI,OAAO,MAAM,QAAQ;AACpD,QAAM,YAA8B,CAAC;AACrC,MAAI,UAAU;AAEd,QAAM,UAAU,MAAM,eAAe,UAAU,OAAO,WAAW;AAC/D,QAAI,QAAQ,MAAM;AAChB,gBAAU;AAAA,QACR,eAAe,QAAQ;AAAA,UACrB,GAAI,QAAQ,UAAU,EAAE,aAAa,KAAK,IAAI,CAAC;AAAA,UAC/C,GAAI,QAAQ,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,QAC7C,CAAC;AAAA,MACH;AACA;AAAA,IACF;AACA,UAAM,OAAO,UAAU,QAAQ;AAAA,MAC7B,GAAI,QAAQ,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,MAC3C,GAAI,QAAQ,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,IAC7C,CAAC;AACD,QAAI,IAAK,OAAM,MAAM,oBAAC,IAAI,SAAJ,EAAY,MAAY,QAAgB,CAAE;AAAA,SAC3D;AACH,YAAM,WAAW,mBAAmB,MAAM,EAAE,OAAO,CAAC;AACpD,YAAM,YAAY,KAAK,CAAC,YAAY,WAAW;AAAA,EAAK,QAAQ,EAAE;AAAA,IAChE;AACA,eAAW;AAAA,EACb,CAAC;AAED,QAAM,iBAAiB,QAAQ,SAAS,SAAS,SAAS,kBAAkB,QAAQ;AACpF,QAAM,kBAAkB,eAAe,WAAW,cAAc;AAChE,QAAM,eAAe,SAAS,2BAC1B,mBAAmB,QAAQ,oBAAoB,cAAc,IAC7D;AACJ,QAAM,WAAW,aAAa,WAAW,SAAS,QAAQ;AAC1D,MAAI,mBAAmB,CAAC,QAAQ,QAAQ,QAAQ,UAAU,QAAQ;AAChE;AAAA,MACE,GAAG,UAAU,IAAI,OAAO,EAAE,GAAG,mBAAmB,iBAAiB;AAAA,QAC/D,GAAI,YAAY,SAAS,2BACrB,EAAE,oBAAoB,SAAS,WAAW,YAAY,eAAe,EAAE,KAAK,IAC5E,CAAC;AAAA,MACP,CAAC,CAAC;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,QAAQ,MAAM;AAChB;AAAA,MACE,KAAK;AAAA,QACH;AAAA;AAAA;AAAA;AAAA,UAIE,OAAO,QAAQ;AAAA,UACf,SAAS,gBAAgB,iBAAiB,YAAY;AAAA,UACtD;AAAA,UACA,UAAU,SAAS,YAAY,CAAC;AAAA,UAChC,UAAU,eAAe,QAAQ;AAAA,QACnC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO,WAAW,QAAQ,SAAS,SAAS,IAAI,IAAI;AAAA,EACtD;AAEA,MAAI,WAAW,QAAQ,SAAS,SAAS,GAAG;AAC1C,eAAW;AAAA,EAAK,oBAAoB,QAAQ,QAAQ,CAAC,EAAE;AAAA,EACzD;AACA,MAAI,UAAU;AACZ,UAAM,EAAE;AACR,UAAM,oBAAoB,QAAQ,CAAC;AAAA,EACrC,WAAW,aAAa,WAAW,QAAQ,SAAS,SAAS,GAAG;AAC9D,eAAW;AAAA,EAAK,qBAAqB,QAAQ,QAAQ,CAAC,EAAE;AAAA,EAC1D;AAKA,SAAO,WAAW,QAAQ,SAAS,SAAS,IAAI,IAAI;AACtD;","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  renderCoveragePlain,
3
3
  renderFailuresPlain,
4
4
  scenarioBaseline
5
- } from "./chunk-TPWRSFK7.js";
5
+ } from "./chunk-J2NN3J5N.js";
6
6
  import "./chunk-DYDSJM7R.js";
7
7
  import {
8
8
  baselinePath,
@@ -58,4 +58,4 @@ ${renderFailuresPlain(runtime.failures)}`);
58
58
  export {
59
59
  runSnapshot
60
60
  };
61
- //# sourceMappingURL=snapshot-3E7MVMVG.js.map
61
+ //# sourceMappingURL=snapshot-FLXE5UC6.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-surface/cli",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Inspect and check the agent surface your app exposes — in the terminal and in CI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,9 +31,9 @@
31
31
  "typescript": "^5.9.2",
32
32
  "vite": "^7.1.3",
33
33
  "vite-node": "^3.2.4",
34
- "@agent-surface/core": "^0.12.0",
35
- "@agent-surface/react": "^0.12.0",
36
- "@agent-surface/testing": "^0.12.0"
34
+ "@agent-surface/core": "^0.12.1",
35
+ "@agent-surface/react": "^0.12.1",
36
+ "@agent-surface/testing": "^0.12.1"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@testing-library/react": ">=14",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["import { existsSync, readdirSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport {\n UsageError,\n joinCoverage,\n mountScenarios,\n readInventory,\n type AnalysisOptions,\n type Depth,\n} from \"../analysis.js\";\nimport {\n annotate,\n baselinePath,\n diff,\n readBaseline,\n readScenarioManifest,\n SCENARIO_MANIFEST_FILE,\n type DiffEntry,\n} from \"../baseline.js\";\nimport { ALLOWLIST_FILE, coverageExitCode, UNREAD_ALLOWLIST_FILE } from \"../coverage.js\";\nimport {\n renderCoveragePlain,\n renderDriftPlain,\n renderFailuresPlain,\n renderNoVerdictPlain,\n} from \"../render/plain.js\";\nimport { write, writeError } from \"../output.js\";\nimport { coverageReport, scenarioBaseline } from \"../report.js\";\n\nexport interface CheckOptions {\n configPath: string;\n depth: Depth;\n scenario?: string;\n scope?: string[];\n tsconfig?: string;\n baselineDir?: string;\n allowUnresolved?: boolean;\n json?: boolean;\n plain?: boolean;\n}\n\ninterface ScenarioDrift {\n scenario: string;\n missingBaseline?: boolean;\n entries: DiffEntry[];\n}\n\ninterface RejectedScenario {\n scenario: string;\n rejections: Array<{ componentType: string; instanceId: string; reason: string }>;\n}\n\n/**\n * The gate. The only command in this package that fails on a finding, which is\n * why every finding has to reach it.\n *\n * It used to fail on exactly one class — the projection drifting from its\n * baseline — and print a line telling you that capabilities no scenario mounts\n * were a different command's question. A gate that names the check it is not\n * performing is a gate with a hole in it, and in CI the hole was silent: a\n * whole unreached route sat behind a green tick.\n *\n * So it now fails on every incomplete or changed report:\n *\n * - **drift** — the surface changed against its committed baseline;\n * - **a missing baseline** — nothing to compare, which is not the same as a match;\n * - **an unreached capability** — authored, and no scenario mounts it;\n * - **an unread call site** — the catalog is incomplete, so the third check\n * above is computed over a denominator that is only a floor;\n * - **a rejected registration** — authored surface was refused;\n * - **scenario drift** — config, manifest and baseline files disagree.\n *\n * `.agent-surface/coverage-allow.json` ratchets the third; `--allow-unresolved`\n * accepts the fourth. Both are deliberate, committed decisions rather than\n * flags that quietly widen the gate.\n *\n * Output is always plain, with no rendering framework in its path at all: this\n * is a report that gets pasted into a pull request and read out of a CI log,\n * and neither of those is a terminal.\n */\nexport async function runCheck(options: CheckOptions): Promise<number> {\n if (options.depth === \"static\") {\n throw new UsageError(\n \"check --depth static has nothing to compare — a baseline is a projection, and \" +\n \"at this depth nothing is mounted. Use --depth runtime for drift alone, or full for both.\",\n );\n }\n\n const analysis: AnalysisOptions = {\n configPath: options.configPath,\n depth: options.depth,\n ...(options.scenario ? { scenario: options.scenario } : {}),\n ...(options.scope ? { scope: options.scope } : {}),\n ...(options.tsconfig ? { tsconfig: options.tsconfig } : {}),\n ...(options.baselineDir ? { baselineDir: options.baselineDir } : {}),\n };\n\n const inventory = readInventory(analysis);\n // No streaming here, unlike `inspect`. A report is read top-down and has to\n // lead with its findings, which means every finding has to exist first.\n const runtime = await mountScenarios(analysis);\n if (!runtime) throw new UsageError(\"check needs a mount, and this depth performs none\");\n\n const drifted: ScenarioDrift[] = [];\n for (const result of runtime.results) {\n const path = baselinePath(runtime.baselineDir, result.scenario);\n const expected = readBaseline(path);\n if (expected === undefined) {\n drifted.push({ scenario: result.scenario, missingBaseline: true, entries: [] });\n continue;\n }\n const actual = scenarioBaseline(result);\n const entries = annotate(diff(expected, actual), actual, expected);\n if (entries.length > 0) drifted.push({ scenario: result.scenario, entries });\n }\n\n const committedScenarios = readScenarioManifest(runtime.baselineDir);\n const declared = [...runtime.declaredScenarios].sort();\n const scenarioManifestMismatch =\n committedScenarios === undefined ||\n JSON.stringify(committedScenarios) !== JSON.stringify(declared);\n const reserved = new Set([SCENARIO_MANIFEST_FILE, ALLOWLIST_FILE, UNREAD_ALLOWLIST_FILE]);\n const staleBaselineFiles = (existsSync(runtime.baselineDir)\n ? readdirSync(runtime.baselineDir, { withFileTypes: true })\n : [])\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".json\") && !reserved.has(entry.name))\n .map((entry) => entry.name.slice(0, -5))\n .filter((scenario) => !runtime.declaredScenarios.includes(scenario))\n .sort();\n\n const rejected: RejectedScenario[] = runtime.results\n .filter((result) => result.rejections.length > 0)\n .map((result) => ({ scenario: result.scenario, rejections: result.rejections }));\n\n const coverage = joinCoverage(inventory, runtime, analysis);\n const coverageFailed =\n coverage !== undefined &&\n coverageExitCode(coverage, { allowUnresolved: options.allowUnresolved === true }) !== 0;\n const couldNotRun = runtime.failures.length > 0;\n const ok =\n drifted.length === 0 &&\n !coverageFailed &&\n !couldNotRun &&\n rejected.length === 0 &&\n !scenarioManifestMismatch &&\n staleBaselineFiles.length === 0;\n\n if (options.json) {\n write(\n JSON.stringify(\n {\n ok,\n drifted,\n failures: runtime.failures,\n rejected,\n scenarioManifest: {\n expected: declared,\n committed: committedScenarios ?? null,\n staleBaselines: staleBaselineFiles,\n },\n coverage: coverageReport(coverage),\n },\n null,\n 2,\n ),\n );\n return couldNotRun ? 2 : ok ? 0 : 1;\n }\n\n // The gap leads, because it is the finding this command could not previously\n // make at all. Drift follows, because it is the one it always could.\n if (coverage) {\n const rendered = renderCoveragePlain(coverage);\n if (coverageFailed) writeError(rendered);\n else write(rendered);\n }\n\n // \"No baseline\" is not drift, and filing it under a heading that says the\n // surface changed would be a claim about a comparison that never happened.\n const missing = drifted.filter((entry) => entry.missingBaseline);\n const changed = drifted.filter((entry) => !entry.missingBaseline);\n\n if (rejected.length > 0) {\n writeError(\n [\n `${coverage ? \"\\n\" : \"\"}REJECTED REGISTRATIONS — the runtime refused authored surface (${rejected.reduce((sum, entry) => sum + entry.rejections.length, 0)})`,\n ...rejected.flatMap((entry) =>\n entry.rejections.map(\n (rejection) =>\n ` ${entry.scenario}: ${rejection.componentType}@${rejection.instanceId} (${rejection.reason})`,\n ),\n ),\n ].join(\"\\n\"),\n );\n }\n\n if (scenarioManifestMismatch || staleBaselineFiles.length > 0) {\n const committed = committedScenarios?.join(\", \") ?? \"missing\";\n writeError(\n [\n `${coverage || rejected.length > 0 ? \"\\n\" : \"\"}SCENARIO DRIFT — committed baselines do not match config`,\n ` config: ${declared.join(\", \")}`,\n ` manifest: ${committed}`,\n ...(staleBaselineFiles.length > 0\n ? [` stale baselines: ${staleBaselineFiles.join(\", \")}`]\n : []),\n \" run `agent-surface snapshot` and commit the manifest\",\n ].join(\"\\n\"),\n );\n }\n\n if (missing.length > 0) {\n writeError(\n [\n `${coverage ? \"\\n\" : \"\"}NO BASELINE — nothing to compare against, which is not the same as a match (${missing.length})`,\n ...missing.map(\n (entry) =>\n ` ${entry.scenario}: ${relative(\n process.cwd(),\n baselinePath(runtime.baselineDir, entry.scenario),\n )} does not exist — run \\`agent-surface snapshot\\` and commit it`,\n ),\n ].join(\"\\n\"),\n );\n }\n\n if (changed.length > 0) {\n writeError(\n [\n `${coverage || missing.length > 0 ? \"\\n\" : \"\"}DRIFT — the surface changed against its baseline (${changed.length})`,\n ...changed.map((entry) => renderDriftPlain(entry.scenario, entry.entries)),\n \"\",\n `surface drift in ${changed.length} scenario${\n changed.length === 1 ? \"\" : \"s\"\n } — review the change, then \\`agent-surface snapshot\\` to accept it`,\n ].join(\"\\n\"),\n );\n }\n\n if (couldNotRun) {\n writeError(`\\n${renderFailuresPlain(runtime.failures)}`);\n if (inventory) writeError(`\\n${renderNoVerdictPlain(runtime.failures)}`);\n return 2;\n }\n\n if (ok) {\n if (coverage) write(\"\");\n // Naming them is the point (`AS-CLI-007`). This used to have to add that it\n // was a statement about these scenarios only, and point at another command\n // for the rest; at `--depth full` there is no rest, so the caveat is gone\n // along with the command it pointed at.\n write(`surface matches the baseline in ${runtime.scenarios.join(\", \")}`);\n if (!coverage) {\n write(\n \"that is a statement about these scenarios only — re-run at --depth full to find \" +\n \"capabilities no scenario mounts\",\n );\n }\n return 0;\n }\n return 1;\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/render/plain.ts","../src/report.ts"],"sourcesContent":["import { relative } from \"node:path\";\nimport type { CapabilityRow, SurfaceView } from \"./model.js\";\nimport { flatRows } from \"./model.js\";\nimport type { DiffEntry } from \"../baseline.js\";\nimport { formatValue } from \"../baseline.js\";\nimport type { ScenarioFailure } from \"../analysis.js\";\nimport {\n authoredIds,\n unresolved,\n type AuthoredCapability,\n type CapabilityInventory,\n} from \"../extract.js\";\nimport { unreadKey, type CoverageReport } from \"../coverage.js\";\n\n/**\n * The no-colour, no-cursor rendering used when stdout is piped or when\n * `--plain`, `CI` or `NO_COLOR` is set. Same view model as the Ink UI, so the\n * two cannot disagree about what the surface contains.\n */\n\nconst MARK = { expose: \"+\", disable: \"~\", hide: \"-\" } as const;\nconst STATE = { expose: \"callable\", disable: \"disabled\", hide: \"hidden\" } as const;\nconst NONE = \"—\";\n\n/**\n * Column widths come from the *content*, never from `process.stdout.columns`.\n *\n * `AS-CLI-003` requires plain output to be byte-stable across runs, and a table\n * laid out against the terminal it happened to run in is stable only until two\n * people diff the same CI log from different windows. Same rows in, same bytes\n * out, everywhere.\n *\n * The last column is not padded, so no line ever carries trailing whitespace —\n * which some diff tools render and others strip, i.e. another way for identical\n * output to look different.\n */\nfunction renderTable(headers: string[], rows: Array<{ cells: string[]; note?: string }>): string[] {\n const widths = headers.map((header, column) =>\n Math.max(header.length, ...rows.map((row) => (row.cells[column] ?? \"\").length)),\n );\n const line = (cells: string[]): string =>\n cells\n .map((cell, column) => (column === headers.length - 1 ? cell : cell.padEnd(widths[column]!)))\n .join(\" \")\n .trimEnd();\n\n const lines = [line(headers)];\n for (const row of rows) {\n lines.push(line(row.cells));\n // The unavailability reason is prose of unbounded length. A column for it\n // would set the table's width by its longest sentence; a continuation line\n // keeps the grid aligned and puts the reason directly under its capability.\n if (row.note) lines.push(` ⤷ ${row.note}`);\n }\n return lines;\n}\n\n/** `UNREACHED — authored, and no scenario mounts it (1)` */\nfunction section(title: string, gloss: string, count: number): string {\n return `${title} — ${gloss} (${count})`;\n}\n\nfunction renderDetailRow(row: CapabilityRow, lines: string[]): void {\n const tags = row.tags.length > 0 ? ` [${row.tags.join(\", \")}]` : \"\";\n lines.push(` ${MARK[row.outcome]} ${row.name}${tags}`);\n lines.push(` ${row.description}`);\n if (row.reason) lines.push(` reason: ${row.reason}`);\n\n if (row.policies) {\n if (row.policies.length === 0) {\n lines.push(\" policies: none\");\n } else {\n for (const policy of row.policies) {\n const vote = policy.discovery\n ? policy.discovery.decision === \"disable\"\n ? `disable — ${policy.discovery.reason}`\n : policy.discovery.decision\n : \"no discovery hook\";\n const phases = policy.phases.length > 0 ? policy.phases.join(\"/\") : NONE;\n const flags = [\n policy.threw ? \"THREW\" : \"\",\n policy.confirmationEscalation ? \"escalates-confirmation\" : \"\",\n ]\n .filter(Boolean)\n .join(\", \");\n lines.push(\n ` policy ${policy.name} (${policy.scope}, ${phases}): ${vote}${\n flags ? ` [${flags}]` : \"\"\n }`,\n );\n }\n }\n if (row.availability && !row.availability.available) {\n lines.push(\n ` availability: unavailable${\n row.availability.reason ? ` — ${row.availability.reason}` : \"\"\n }`,\n );\n }\n }\n\n if (row.schemas) {\n if (row.schemas.input !== undefined) {\n lines.push(` input: ${JSON.stringify(row.schemas.input)}`);\n }\n if (row.schemas.output !== undefined) {\n lines.push(` output: ${JSON.stringify(row.schemas.output)}`);\n }\n }\n}\n\n/**\n * The counts line, and everything it is relative to (`AS-CLI-007`).\n *\n * `hidden` is printed unconditionally. It is computed on every run — the\n * explanation is always collected — and suppressing it outside `--explain`\n * meant a surface with a policy-hidden half rendered as a complete one. The\n * *attribution* still needs `--explain`; the count and the rows do not.\n */\nexport function renderCountsPlain(view: SurfaceView): string {\n return (\n `${view.counts.callable} callable, ${view.counts.disabled} visible-disabled, ` +\n `${view.counts.hidden} hidden` +\n (view.rejections.length > 0\n ? `, ${view.rejections.length} registration${view.rejections.length === 1 ? \"\" : \"s\"} rejected`\n : \"\")\n );\n}\n\nfunction renderHeader(view: SurfaceView, lines: string[]): void {\n lines.push(\n `scenario ${view.scenario}${view.route ? ` route ${view.route}` : \"\"}${\n view.scope && view.scope.length > 0 ? ` scope ${view.scope.join(\" \")}` : \"\"\n }`,\n );\n lines.push(renderCountsPlain(view));\n}\n\nfunction renderRejections(view: SurfaceView, lines: string[]): void {\n if (view.rejections.length === 0) return;\n lines.push(\"\");\n lines.push(\n section(\"REJECTED\", \"the registry refused these during the mount\", view.rejections.length),\n );\n for (const rejection of view.rejections) {\n const why =\n rejection.reason === \"duplicate\"\n ? \"duplicate — an earlier registration holds this key\"\n : \"guard — onRegister rejected this registration\";\n lines.push(` ! ${rejection.componentType} (${rejection.instanceId}) ${why}`);\n }\n}\n\nfunction renderEmpty(view: SurfaceView, lines: string[]): void {\n lines.push(\"\");\n // \"Nothing is registered\" is only true when nothing was hidden. Saying it\n // over a surface a policy emptied sends the reader to the wrong file.\n if (view.counts.hidden > 0) {\n lines.push(\n `Nothing is callable here — all ${view.counts.hidden} registered capabilities were hidden by policy.`,\n );\n if (!view.explained) lines.push(\"Re-run with --explain to see which policy hid them.\");\n } else {\n lines.push(\"Nothing is registered for this scenario — the agent has no surface here.\");\n if (!view.explained) lines.push(\"Re-run with --explain to see whether a policy hid it.\");\n }\n}\n\nexport interface SurfaceRenderOptions {\n /** The grouped, one-capability-per-paragraph view. Implied by --explain/--schemas. */\n detail?: boolean;\n}\n\n/**\n * One capability per line, aligned. The default, because the question `inspect`\n * is usually asked is *what is on this surface* — which is a scanning question,\n * and prose does not scan.\n *\n * Policy chains and JSON Schemas are multi-line by nature and cannot live in a\n * cell, so `--explain` and `--schemas` fall back to the detail view rather than\n * producing a table with most of the answer missing.\n */\nexport function renderSurfacePlain(\n view: SurfaceView,\n options: SurfaceRenderOptions = {},\n): string {\n const lines: string[] = [];\n renderHeader(view, lines);\n renderRejections(view, lines);\n\n const rows = flatRows(view);\n if (rows.length === 0) {\n renderEmpty(view, lines);\n return lines.join(\"\\n\");\n }\n\n if (options.detail) {\n for (const group of view.groups.filter((group) => group.rows.length > 0)) {\n lines.push(\"\");\n lines.push(`${group.heading} (${group.rows.length})`);\n for (const row of group.rows) renderDetailRow(row, lines);\n }\n return lines.join(\"\\n\");\n }\n\n lines.push(\"\");\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"KIND\", \"EFFECT\", \"STATE\", \"FLAGS\"],\n rows.map((row) => ({\n cells: [\n row.path,\n row.kind,\n row.effect ?? NONE,\n STATE[row.outcome],\n row.flags.length > 0 ? row.flags.join(\" · \") : NONE,\n ],\n ...(row.reason ? { note: row.reason } : {}),\n })),\n ),\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * The static catalog (`AS-COVER-001…003`). The summary says \"upper bound\" in so\n * many words: a tsconfig's include globs are wider than what a bundle reaches,\n * so a capability in a component no route renders any more is in here. That is\n * dead code — a different finding, not a false positive — and the reader has to\n * be told which number they are holding.\n */\nexport interface CatalogRenderOptions {\n /**\n * This catalog *is* the command's output, rather than its preamble.\n *\n * True at `--depth static`: there are no scenario tables and no verdict, so\n * the listing and the unread call sites have nowhere else to appear.\n *\n * False at `--depth full`, where the scenario tables below name every\n * capability a scenario reached, the `UNREACHED` section names the ones it did\n * not, and the verdict carries the unread call sites — so printing any of it\n * here is the same information a second time, above the answer instead of in\n * it. Only the summary line survives.\n */\n standalone?: boolean;\n /** Authoritative domain manifest entries, when runtime config was loaded. */\n domainCapabilities?: number;\n}\n\nexport function renderCatalogPlain(\n inventory: CapabilityInventory,\n options: CatalogRenderOptions = {},\n): string {\n const lines: string[] = [];\n const resolved = inventory.capabilities.filter((c) => c.resolution !== \"unresolved\");\n const ids = authoredIds(inventory);\n const authored = ids.size + (options.domainCapabilities ?? 0);\n\n lines.push(\n `${authored} authored (upper bound) · ${resolved.length} call site${\n resolved.length === 1 ? \"\" : \"s\"\n } across ${inventory.filesAnalyzed} file${inventory.filesAnalyzed === 1 ? \"\" : \"s\"}` +\n (options.domainCapabilities === undefined\n ? \" · domain not analyzed without a loaded oRPC manifest\"\n : ` · ${options.domainCapabilities} domain manifest capabilit${\n options.domainCapabilities === 1 ? \"y\" : \"ies\"\n }`),\n );\n if (inventory.filesOutsideRoot > 0) {\n // Relative, not absolute: plain output is byte-stable across runs\n // (`AS-CLI-003`), and an absolute path makes it machine-specific the moment\n // two people diff a CI log.\n lines.push(\n `${inventory.filesOutsideRoot} program file${\n inventory.filesOutsideRoot === 1 ? \"\" : \"s\"\n } from agent-surface implementation packages were excluded`,\n );\n }\n\n if (!options.standalone) return lines.join(\"\\n\");\n\n const byId = [...resolved].sort((a, b) => a.capabilityId.localeCompare(b.capabilityId));\n if (byId.length > 0) {\n lines.push(\"\");\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"KIND\", \"ORIGIN\", \"READ\"],\n byId.map((capability) => ({\n cells: [\n capability.capabilityId,\n capability.kind,\n `${capability.origin.file}:${capability.origin.line}`,\n capability.resolution,\n ],\n ...(capability.note ? { note: capability.note } : {}),\n })),\n ),\n );\n }\n\n const unread = renderUnread(unresolved(inventory));\n if (unread.length > 0) lines.push(\"\", ...unread);\n return lines.join(\"\\n\");\n}\n\n/**\n * Call sites the extractor could not read. Reported with file and line, never\n * dropped: an inventory that silently omitted what it failed to parse would\n * understate the denominator, and every number built on it would claim a\n * completeness it never had.\n */\nfunction renderUnread(entries: AuthoredCapability[]): string[] {\n if (entries.length === 0) return [];\n const lines: string[] = [];\n lines.push(\n section(\n \"UNREAD CALL SITES\",\n \"the catalog is incomplete, so every count above is a floor\",\n entries.length,\n ),\n );\n for (const capability of entries) {\n lines.push(` ? ${capability.origin.file}:${capability.origin.line}`);\n lines.push(` ${capability.note ?? \"the extractor could not read this call site\"}`);\n // The allowlist key, spelled out. It is `file#reason#site`, and the site is\n // a hash — not the line the reader is looking at — so leaving them to infer\n // it guarantees a wrong guess and an entry that never matches.\n lines.push(` allowlist key: ${unreadKey(capability)}`);\n }\n return lines;\n}\n\n/**\n * The verdict: authored minus reached (`AS-COVER-004…005`).\n *\n * This is the finding the command surface used to hide behind a fifth command,\n * so it is the last thing printed and the thing a reader stops on.\n */\nexport function renderCoveragePlain(report: CoverageReport): string {\n const lines: string[] = [];\n\n if (report.unreached.length > 0) {\n lines.push(\n section(\"UNREACHED\", \"authored, and no scenario mounts it\", report.unreached.length),\n );\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"ORIGIN\"],\n report.unreached.map((entry) => ({\n cells: [entry.capabilityId, `${entry.origin.file}:${entry.origin.line}`],\n })),\n ),\n );\n lines.push(\"\");\n }\n\n if (report.undeclared.length > 0) {\n lines.push(\n section(\n \"UNDECLARED\",\n \"present at runtime with no static origin — a dynamic registration, or a gap here\",\n report.undeclared.length,\n ),\n );\n for (const id of report.undeclared) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.unmanifestedDomain.length > 0) {\n lines.push(\n section(\n \"UNMANIFESTED DOMAIN\",\n \"mounted, but absent from the authoritative oRPC manifest\",\n report.unmanifestedDomain.length,\n ),\n );\n for (const id of report.unmanifestedDomain) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.staleAllowlist.length > 0) {\n lines.push(\n section(\n \"STALE ALLOWLIST\",\n \"a scenario reaches these now, so delete them before the list rots\",\n report.staleAllowlist.length,\n ),\n );\n for (const id of report.staleAllowlist) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.staleUnreadAllowlist.length > 0) {\n lines.push(\n section(\n \"STALE UNREAD ALLOWLIST\",\n \"the extractor reads these now, so delete them before the list rots\",\n report.staleUnreadAllowlist.length,\n ),\n );\n for (const key of report.staleUnreadAllowlist) lines.push(` ${key}`);\n lines.push(\"\");\n }\n\n if (report.unresolved.length > 0) {\n lines.push(...renderUnread(report.unresolved), \"\");\n }\n\n lines.push(...renderCoverageSummary(report));\n return lines.join(\"\\n\");\n}\n\n/** The one line a reader who stops at the bottom takes away. */\nfunction renderCoverageSummary(report: CoverageReport): string[] {\n const qualifiers = [\n `${report.scenarios.length} scenario${report.scenarios.length === 1 ? \"\" : \"s\"} (${report.scenarios.join(\n \", \",\n )})`,\n ];\n // Every count is relative to the scope, so the scope is printed with them\n // (`AS-CLI-007`) — `10 authored` under a scope is a claim about one prefix of\n // the codebase, not about the codebase.\n if (report.scope && report.scope.length > 0) qualifiers.push(`scope ${report.scope.join(\" \")}`);\n\n const lines = [\n `${report.authored} authored · ${report.reached} reached · ${report.unreached.length} unreached` +\n ` · ${qualifiers.join(\" · \")}`,\n ];\n\n if (report.domainReached.length > 0) {\n lines.push(\n `${report.domainReached.length} domain capabilit${\n report.domainReached.length === 1 ? \"y\" : \"ies\"\n } reached${\n report.domainAuthoritative\n ? \" against the authoritative oRPC manifest\"\n : \" and held apart — configure the authoritative oRPC manifest to cover that plane\"\n }`,\n );\n }\n if (report.allowed.length > 0) {\n lines.push(\n `${report.allowed.length} unreached capabilit${\n report.allowed.length === 1 ? \"y is\" : \"ies are\"\n } allowlisted in ${relative(process.cwd(), report.allowlistPath)}`,\n );\n }\n if (report.allowedUnread.length > 0) {\n lines.push(\n `${report.allowedUnread.length} unread call site${\n report.allowedUnread.length === 1 ? \" is\" : \"s are\"\n } allowlisted in ${relative(process.cwd(), report.unreadAllowlistPath)}`,\n );\n }\n if (report.allowlistOutOfScope > 0) {\n lines.push(\n `${report.allowlistOutOfScope} allowlist entr${\n report.allowlistOutOfScope === 1 ? \"y\" : \"ies\"\n } outside this scope were not judged either way`,\n );\n }\n\n // Each bucket gets its own remedy. \"Add a scenario, or delete the component\"\n // is the right advice for an unreached capability and useless advice for a\n // call site the extractor could not read.\n if (\n report.unreached.length === 0 &&\n report.unresolved.length === 0 &&\n report.staleAllowlist.length === 0 &&\n report.staleUnreadAllowlist.length === 0 &&\n report.unmanifestedDomain.length === 0\n ) {\n lines.push(\n report.allowed.length > 0\n ? \"no new surface coverage gaps — the allowlist still holds the known ones\"\n : \"every authored capability is reached by a scenario\",\n );\n }\n return lines;\n}\n\n/**\n * Why there is no coverage verdict. Never silence: a reader who asked for the\n * complete answer and got a partial one has to be told which part is missing,\n * or the partial one reads as the complete one.\n */\nexport function renderNoVerdictPlain(failures: ScenarioFailure[]): string {\n return [\n section(\"NO COVERAGE VERDICT\", \"a scenario did not mount, so nothing reached anything\", failures.length),\n ...failures.flatMap((failure) => [` ${failure.scenario}`, ` ${failure.message}`]),\n \"\",\n \"Every capability those scenarios would have surfaced would be reported unreached,\",\n \"so no verdict is printed at all. Fix the mount, or name a scenario that works.\",\n ].join(\"\\n\");\n}\n\nexport function renderFailuresPlain(failures: ScenarioFailure[]): string {\n return [\n section(\"DID NOT MOUNT\", \"these scenarios threw, and were skipped\", failures.length),\n ...failures.flatMap((failure) => [` ${failure.scenario}`, ` ${failure.message}`]),\n ].join(\"\\n\");\n}\n\nexport function renderDriftPlain(scenario: string, entries: DiffEntry[]): string {\n const lines = [` ${scenario}: ${entries.length} change${entries.length === 1 ? \"\" : \"s\"}`];\n for (const entry of entries) {\n const where = entry.subject ? `${entry.subject} (${entry.path})` : entry.path;\n if (entry.kind === \"added\") lines.push(` + ${where} ${formatValue(entry.after)}`);\n else if (entry.kind === \"removed\") lines.push(` - ${where} ${formatValue(entry.before)}`);\n else {\n lines.push(` ~ ${where}`);\n lines.push(` before: ${formatValue(entry.before)}`);\n lines.push(` after: ${formatValue(entry.after)}`);\n }\n }\n return lines.join(\"\\n\");\n}\n","import { basename, relative } from \"node:path\";\nimport type { CollectResult } from \"./collect.js\";\nimport type { CoverageReport } from \"./coverage.js\";\nimport type { CapabilityInventory } from \"./extract.js\";\nimport { normalize } from \"./baseline.js\";\nimport { buildView, flatRows, type CapabilityRow } from \"./render/model.js\";\n\n/** Stable, complete per-scenario document shared by JSON, baselines and check. */\nexport interface ScenarioReport {\n scenario: string;\n scope?: string[];\n snapshot: unknown;\n capabilities: CapabilityRow[];\n rejections: CollectResult[\"rejections\"];\n explanation?: { capabilities: CapabilityRow[] };\n}\n\nexport function scenarioReport(\n result: CollectResult,\n options: { attribution?: boolean; schemas?: boolean } = {},\n): ScenarioReport {\n const view = buildView(result, {\n ...(options.attribution ? { explain: true } : {}),\n ...(options.schemas ? { schemas: true } : {}),\n });\n const capabilities = flatRows(view);\n return {\n scenario: result.scenario,\n ...(result.scope ? { scope: result.scope } : {}),\n snapshot: normalize(result.snapshot),\n // Includes expose, disable and hide. Rows never contain runtime ids.\n capabilities,\n rejections: [...result.rejections].sort(\n (a, b) =>\n a.componentType.localeCompare(b.componentType) ||\n a.instanceId.localeCompare(b.instanceId) ||\n a.reason.localeCompare(b.reason),\n ),\n ...(options.attribution ? { explanation: { capabilities } } : {}),\n };\n}\n\n/** Baseline payload: same semantic document, without invocation-only labels. */\nexport function scenarioBaseline(result: CollectResult): Record<string, unknown> {\n const report = scenarioReport(result);\n return {\n ...(report.snapshot as Record<string, unknown>),\n capabilities: report.capabilities,\n rejections: report.rejections,\n };\n}\n\n/** Machine output must not contain checkout-specific absolute paths. */\nexport function inventoryReport(\n inventory: CapabilityInventory | undefined,\n domainCapabilities?: string[],\n): unknown {\n if (!inventory) return null;\n return {\n ...inventory,\n root: \".\",\n tsconfig: relative(inventory.root, inventory.tsconfig) || \"tsconfig.json\",\n ...(domainCapabilities\n ? { domain: { source: \"manifest\", capabilities: [...domainCapabilities].sort() } }\n : {}),\n };\n}\n\nexport function coverageReport(report: CoverageReport | undefined): unknown {\n if (!report) return null;\n return {\n ...report,\n allowlistPath: basename(report.allowlistPath),\n unreadAllowlistPath: basename(report.unreadAllowlistPath),\n };\n}\n"],"mappings":"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","names":["group","relative","relative"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/inspect.tsx"],"sourcesContent":["import {\n joinCoverage,\n mountScenarios,\n readInventory,\n scopeCapabilityIds,\n scopeInventory,\n staticConfigScope,\n type AnalysisOptions,\n type Depth,\n} from \"../analysis.js\";\nimport { buildView } from \"../render/model.js\";\nimport { authoredIds } from \"../extract.js\";\nimport {\n renderCatalogPlain,\n renderCoveragePlain,\n renderFailuresPlain,\n renderNoVerdictPlain,\n renderSurfacePlain,\n} from \"../render/plain.js\";\nimport { isPlain, loadInk, paint, write, writeError } from \"../output.js\";\nimport {\n coverageReport,\n inventoryReport,\n scenarioReport,\n type ScenarioReport,\n} from \"../report.js\";\n\nexport interface InspectOptions {\n configPath: string;\n depth: Depth;\n scenario?: string;\n scope?: string[];\n tsconfig?: string;\n baselineDir?: string;\n detail?: boolean;\n explain?: boolean;\n schemas?: boolean;\n json?: boolean;\n plain?: boolean;\n}\n\n/**\n * The whole surface: what this codebase authors, what a mount surfaces, and the\n * difference between them.\n *\n * It answers findings, it does not gate on them — exit `0` whatever it reports,\n * because `check` is the gate and a viewer that sometimes fails is a viewer\n * nobody puts in a pipeline. The exception is `2`, which is not a finding: the\n * command could not run at all.\n *\n * Each scenario prints as it finishes, so a config with ten of them is not ten\n * mounts of blank terminal. The normalized catalog summary and verdict follow\n * once effective config scope and the domain manifest are known.\n */\nexport async function runInspect(options: InspectOptions): Promise<number> {\n const analysis: AnalysisOptions = {\n configPath: options.configPath,\n depth: options.depth,\n ...(options.scenario ? { scenario: options.scenario } : {}),\n ...(options.scope ? { scope: options.scope } : {}),\n ...(options.tsconfig ? { tsconfig: options.tsconfig } : {}),\n ...(options.baselineDir ? { baselineDir: options.baselineDir } : {}),\n };\n\n // Policy chains and JSON Schemas are multi-line by nature, so asking for\n // either is asking for the view that can hold them.\n const detail = options.detail === true || options.explain === true || options.schemas === true;\n\n const inventory = readInventory(analysis);\n const initialInventory = scopeInventory(inventory, staticConfigScope(analysis));\n if (initialInventory && !options.json && options.depth === \"static\") {\n // At `--depth static` this listing is the output. At `--depth full` the\n // scenario tables below carry the same capabilities and the verdict names\n // the ones they miss, so only the summary line prints here.\n write(renderCatalogPlain(initialInventory, { standalone: true }));\n }\n\n // `null` when Ink cannot run here (React 18 host), which is a fallback to\n // plain text rather than a failed command — see loadInk().\n const ink = isPlain(options) ? null : await loadInk();\n const scenarios: ScenarioReport[] = [];\n let printed = 0;\n\n const runtime = await mountScenarios(analysis, async (result) => {\n if (options.json) {\n scenarios.push(\n scenarioReport(result, {\n ...(options.explain ? { attribution: true } : {}),\n ...(options.schemas ? { schemas: true } : {}),\n }),\n );\n return;\n }\n const view = buildView(result, {\n ...(options.explain ? { explain: true } : {}),\n ...(options.schemas ? { schemas: true } : {}),\n });\n if (ink) await paint(<ink.Surface view={view} detail={detail} />);\n else {\n const rendered = renderSurfacePlain(view, { detail });\n write(printed === 0 && !inventory ? rendered : `\\n${rendered}`);\n }\n printed += 1;\n });\n\n const effectiveScope = options.scope ?? runtime?.scope ?? staticConfigScope(analysis);\n const reportInventory = scopeInventory(inventory, effectiveScope);\n const reportDomain = runtime?.domainManifestConfigured\n ? scopeCapabilityIds(runtime.domainCapabilities, effectiveScope)\n : undefined;\n const coverage = joinCoverage(inventory, runtime, analysis);\n if (reportInventory && !options.json && options.depth === \"full\") {\n write(\n `${printed > 0 ? \"\\n\" : \"\"}${renderCatalogPlain(reportInventory, {\n ...(coverage && runtime?.domainManifestConfigured\n ? { domainCapabilities: coverage.authored - authoredIds(reportInventory).size }\n : {}),\n })}`,\n );\n }\n\n if (options.json) {\n write(\n JSON.stringify(\n {\n // One shape whatever the depth, so a consumer never branches on how\n // the command was invoked. A half the depth did not compute is\n // `null`, which is a different statement from `[]` or `{}`.\n depth: options.depth,\n catalog: inventoryReport(reportInventory, reportDomain),\n scenarios,\n failures: runtime?.failures ?? [],\n coverage: coverageReport(coverage),\n },\n null,\n 2,\n ),\n );\n return runtime && runtime.failures.length > 0 ? 2 : 0;\n }\n\n if (runtime && runtime.failures.length > 0) {\n writeError(`\\n${renderFailuresPlain(runtime.failures)}`);\n }\n if (coverage) {\n write(\"\");\n write(renderCoveragePlain(coverage));\n } else if (inventory && runtime && runtime.failures.length > 0) {\n writeError(`\\n${renderNoVerdictPlain(runtime.failures)}`);\n }\n\n // A scenario that would not mount is not a finding about the surface, it is\n // the command failing to observe one. `2` — the same code a usage error gets,\n // because CI has to tell both apart from \"the surface changed\".\n return runtime && runtime.failures.length > 0 ? 2 : 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiGyB;AA3CzB,eAAsB,WAAW,SAA0C;AACzE,QAAM,WAA4B;AAAA,IAChC,YAAY,QAAQ;AAAA,IACpB,OAAO,QAAQ;AAAA,IACf,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzD,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAChD,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzD,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;AAAA,EACpE;AAIA,QAAM,SAAS,QAAQ,WAAW,QAAQ,QAAQ,YAAY,QAAQ,QAAQ,YAAY;AAE1F,QAAM,YAAY,cAAc,QAAQ;AACxC,QAAM,mBAAmB,eAAe,WAAW,kBAAkB,QAAQ,CAAC;AAC9E,MAAI,oBAAoB,CAAC,QAAQ,QAAQ,QAAQ,UAAU,UAAU;AAInE,UAAM,mBAAmB,kBAAkB,EAAE,YAAY,KAAK,CAAC,CAAC;AAAA,EAClE;AAIA,QAAM,MAAM,QAAQ,OAAO,IAAI,OAAO,MAAM,QAAQ;AACpD,QAAM,YAA8B,CAAC;AACrC,MAAI,UAAU;AAEd,QAAM,UAAU,MAAM,eAAe,UAAU,OAAO,WAAW;AAC/D,QAAI,QAAQ,MAAM;AAChB,gBAAU;AAAA,QACR,eAAe,QAAQ;AAAA,UACrB,GAAI,QAAQ,UAAU,EAAE,aAAa,KAAK,IAAI,CAAC;AAAA,UAC/C,GAAI,QAAQ,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,QAC7C,CAAC;AAAA,MACH;AACA;AAAA,IACF;AACA,UAAM,OAAO,UAAU,QAAQ;AAAA,MAC7B,GAAI,QAAQ,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,MAC3C,GAAI,QAAQ,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,IAC7C,CAAC;AACD,QAAI,IAAK,OAAM,MAAM,oBAAC,IAAI,SAAJ,EAAY,MAAY,QAAgB,CAAE;AAAA,SAC3D;AACH,YAAM,WAAW,mBAAmB,MAAM,EAAE,OAAO,CAAC;AACpD,YAAM,YAAY,KAAK,CAAC,YAAY,WAAW;AAAA,EAAK,QAAQ,EAAE;AAAA,IAChE;AACA,eAAW;AAAA,EACb,CAAC;AAED,QAAM,iBAAiB,QAAQ,SAAS,SAAS,SAAS,kBAAkB,QAAQ;AACpF,QAAM,kBAAkB,eAAe,WAAW,cAAc;AAChE,QAAM,eAAe,SAAS,2BAC1B,mBAAmB,QAAQ,oBAAoB,cAAc,IAC7D;AACJ,QAAM,WAAW,aAAa,WAAW,SAAS,QAAQ;AAC1D,MAAI,mBAAmB,CAAC,QAAQ,QAAQ,QAAQ,UAAU,QAAQ;AAChE;AAAA,MACE,GAAG,UAAU,IAAI,OAAO,EAAE,GAAG,mBAAmB,iBAAiB;AAAA,QAC/D,GAAI,YAAY,SAAS,2BACrB,EAAE,oBAAoB,SAAS,WAAW,YAAY,eAAe,EAAE,KAAK,IAC5E,CAAC;AAAA,MACP,CAAC,CAAC;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,QAAQ,MAAM;AAChB;AAAA,MACE,KAAK;AAAA,QACH;AAAA;AAAA;AAAA;AAAA,UAIE,OAAO,QAAQ;AAAA,UACf,SAAS,gBAAgB,iBAAiB,YAAY;AAAA,UACtD;AAAA,UACA,UAAU,SAAS,YAAY,CAAC;AAAA,UAChC,UAAU,eAAe,QAAQ;AAAA,QACnC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO,WAAW,QAAQ,SAAS,SAAS,IAAI,IAAI;AAAA,EACtD;AAEA,MAAI,WAAW,QAAQ,SAAS,SAAS,GAAG;AAC1C,eAAW;AAAA,EAAK,oBAAoB,QAAQ,QAAQ,CAAC,EAAE;AAAA,EACzD;AACA,MAAI,UAAU;AACZ,UAAM,EAAE;AACR,UAAM,oBAAoB,QAAQ,CAAC;AAAA,EACrC,WAAW,aAAa,WAAW,QAAQ,SAAS,SAAS,GAAG;AAC9D,eAAW;AAAA,EAAK,qBAAqB,QAAQ,QAAQ,CAAC,EAAE;AAAA,EAC1D;AAKA,SAAO,WAAW,QAAQ,SAAS,SAAS,IAAI,IAAI;AACtD;","names":[]}