@agent-surface/cli 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import {
3
3
  extractCapabilities,
4
4
  findTsconfig,
5
5
  unresolved
6
- } from "./chunk-GXXKZTQB.js";
6
+ } from "./chunk-Q5WOLWEW.js";
7
7
  import {
8
8
  UsageError,
9
9
  isPlain,
@@ -138,4 +138,4 @@ async function ask(options, question) {
138
138
  export {
139
139
  runInit
140
140
  };
141
- //# sourceMappingURL=init-LYYVXFEQ.js.map
141
+ //# sourceMappingURL=init-LFQ5R3G7.js.map
@@ -1,18 +1,25 @@
1
1
  import {
2
+ coverageReport,
3
+ inventoryReport,
2
4
  renderCatalogPlain,
3
5
  renderCoveragePlain,
4
6
  renderFailuresPlain,
5
7
  renderNoVerdictPlain,
6
- renderSurfacePlain
7
- } from "./chunk-RXX63JSL.js";
8
+ renderSurfacePlain,
9
+ scenarioReport
10
+ } from "./chunk-TPWRSFK7.js";
8
11
  import {
9
12
  buildView
10
13
  } from "./chunk-DYDSJM7R.js";
11
14
  import {
15
+ authoredIds,
12
16
  joinCoverage,
13
17
  mountScenarios,
14
- readInventory
15
- } from "./chunk-GXXKZTQB.js";
18
+ readInventory,
19
+ scopeCapabilityIds,
20
+ scopeInventory,
21
+ staticConfigScope
22
+ } from "./chunk-Q5WOLWEW.js";
16
23
  import {
17
24
  isPlain,
18
25
  loadInk,
@@ -23,18 +30,6 @@ import {
23
30
 
24
31
  // src/commands/inspect.tsx
25
32
  import { jsx } from "react/jsx-runtime";
26
- function jsonForScenario(result, explain) {
27
- return {
28
- scenario: result.scenario,
29
- ...result.scope ? { scope: result.scope } : {},
30
- snapshot: result.snapshot,
31
- // Unconditional, and unconditionally present even when empty (`AS-CLI-006`):
32
- // a consumer that has to distinguish "no rejections" from "this CLI predates
33
- // the field" cannot rely on an absent key.
34
- rejections: result.rejections,
35
- ...explain ? { explanation: result.explanation } : {}
36
- };
37
- }
38
33
  async function runInspect(options) {
39
34
  const analysis = {
40
35
  configPath: options.configPath,
@@ -46,15 +41,21 @@ async function runInspect(options) {
46
41
  };
47
42
  const detail = options.detail === true || options.explain === true || options.schemas === true;
48
43
  const inventory = readInventory(analysis);
49
- if (inventory && !options.json) {
50
- write(renderCatalogPlain(inventory, { standalone: options.depth === "static" }));
44
+ const initialInventory = scopeInventory(inventory, staticConfigScope(analysis));
45
+ if (initialInventory && !options.json && options.depth === "static") {
46
+ write(renderCatalogPlain(initialInventory, { standalone: true }));
51
47
  }
52
48
  const ink = isPlain(options) ? null : await loadInk();
53
49
  const scenarios = [];
54
50
  let printed = 0;
55
51
  const runtime = await mountScenarios(analysis, async (result) => {
56
52
  if (options.json) {
57
- scenarios.push(jsonForScenario(result, options.explain === true));
53
+ scenarios.push(
54
+ scenarioReport(result, {
55
+ ...options.explain ? { attribution: true } : {},
56
+ ...options.schemas ? { schemas: true } : {}
57
+ })
58
+ );
58
59
  return;
59
60
  }
60
61
  const view = buildView(result, {
@@ -69,7 +70,17 @@ ${rendered}`);
69
70
  }
70
71
  printed += 1;
71
72
  });
73
+ const effectiveScope = options.scope ?? runtime?.scope ?? staticConfigScope(analysis);
74
+ const reportInventory = scopeInventory(inventory, effectiveScope);
75
+ const reportDomain = runtime?.domainManifestConfigured ? scopeCapabilityIds(runtime.domainCapabilities, effectiveScope) : void 0;
72
76
  const coverage = joinCoverage(inventory, runtime, analysis);
77
+ if (reportInventory && !options.json && options.depth === "full") {
78
+ write(
79
+ `${printed > 0 ? "\n" : ""}${renderCatalogPlain(reportInventory, {
80
+ ...coverage && runtime?.domainManifestConfigured ? { domainCapabilities: coverage.authored - authoredIds(reportInventory).size } : {}
81
+ })}`
82
+ );
83
+ }
73
84
  if (options.json) {
74
85
  write(
75
86
  JSON.stringify(
@@ -78,10 +89,10 @@ ${rendered}`);
78
89
  // the command was invoked. A half the depth did not compute is
79
90
  // `null`, which is a different statement from `[]` or `{}`.
80
91
  depth: options.depth,
81
- catalog: inventory ?? null,
92
+ catalog: inventoryReport(reportInventory, reportDomain),
82
93
  scenarios,
83
94
  failures: runtime?.failures ?? [],
84
- coverage: coverage ?? null
95
+ coverage: coverageReport(coverage)
85
96
  },
86
97
  null,
87
98
  2
@@ -105,4 +116,4 @@ ${renderNoVerdictPlain(runtime.failures)}`);
105
116
  export {
106
117
  runInspect
107
118
  };
108
- //# sourceMappingURL=inspect-3CBRKTDM.js.map
119
+ //# sourceMappingURL=inspect-ELUQ73MZ.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 }));\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":"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","names":[]}
@@ -1,16 +1,17 @@
1
1
  import {
2
2
  renderCoveragePlain,
3
- renderFailuresPlain
4
- } from "./chunk-RXX63JSL.js";
3
+ renderFailuresPlain,
4
+ scenarioBaseline
5
+ } from "./chunk-TPWRSFK7.js";
5
6
  import "./chunk-DYDSJM7R.js";
6
7
  import {
7
8
  baselinePath,
8
9
  joinCoverage,
9
10
  mountScenarios,
10
- normalize,
11
11
  readInventory,
12
- writeBaseline
13
- } from "./chunk-GXXKZTQB.js";
12
+ writeBaseline,
13
+ writeScenarioManifest
14
+ } from "./chunk-Q5WOLWEW.js";
14
15
  import {
15
16
  UsageError,
16
17
  write,
@@ -38,9 +39,10 @@ async function runSnapshot(options) {
38
39
  if (!runtime) throw new UsageError("snapshot needs a mount, and this depth performs none");
39
40
  for (const result of runtime.results) {
40
41
  const path = baselinePath(runtime.baselineDir, result.scenario);
41
- writeBaseline(path, normalize(result.snapshot));
42
+ writeBaseline(path, scenarioBaseline(result));
42
43
  write(`wrote ${relative(process.cwd(), path)}`);
43
44
  }
45
+ writeScenarioManifest(runtime.baselineDir, runtime.declaredScenarios);
44
46
  const coverage = joinCoverage(inventory, runtime, analysis);
45
47
  if (coverage) {
46
48
  write("");
@@ -56,4 +58,4 @@ ${renderFailuresPlain(runtime.failures)}`);
56
58
  export {
57
59
  runSnapshot
58
60
  };
59
- //# sourceMappingURL=snapshot-DJ22WCT4.js.map
61
+ //# sourceMappingURL=snapshot-3E7MVMVG.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/snapshot.ts"],"sourcesContent":["import { relative } from \"node:path\";\nimport {\n joinCoverage,\n mountScenarios,\n readInventory,\n UsageError,\n type AnalysisOptions,\n type Depth,\n} from \"../analysis.js\";\nimport { baselinePath, normalize, writeBaseline } from \"../baseline.js\";\nimport { renderCoveragePlain, renderFailuresPlain } from \"../render/plain.js\";\nimport { write, writeError } from \"../output.js\";\n\nexport interface SnapshotOptions {\n configPath: string;\n depth: Depth;\n scenario?: string;\n scope?: string[];\n tsconfig?: string;\n baselineDir?: string;\n}\n\n/**\n * Writes (or refreshes) the committed baseline `check` compares against.\n *\n * It prints the coverage verdict too. This is the command you run to *accept* a\n * change to the surface, which makes it the last moment before a reviewer sees\n * the diff — and accepting a projection while a capability sits behind a route\n * no scenario visits is exactly the state worth hearing about. It reports;\n * `check` is still the only thing that fails.\n */\nexport async function runSnapshot(options: SnapshotOptions): Promise<number> {\n if (options.depth === \"static\") {\n throw new UsageError(\n \"snapshot --depth static has nothing to write — a baseline is a projection, and \" +\n \"at this depth nothing is mounted.\",\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 const runtime = await mountScenarios(analysis);\n if (!runtime) throw new UsageError(\"snapshot needs a mount, and this depth performs none\");\n\n for (const result of runtime.results) {\n const path = baselinePath(runtime.baselineDir, result.scenario);\n writeBaseline(path, normalize(result.snapshot));\n write(`wrote ${relative(process.cwd(), path)}`);\n }\n\n const coverage = joinCoverage(inventory, runtime, analysis);\n if (coverage) {\n write(\"\");\n write(renderCoveragePlain(coverage));\n }\n\n if (runtime.failures.length > 0) {\n // A baseline written for some scenarios and not others is a baseline that\n // will fail `check` for a reason that has nothing to do with the surface.\n writeError(`\\n${renderFailuresPlain(runtime.failures)}`);\n return 2;\n }\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AA+BzB,eAAsB,YAAY,SAA2C;AAC3E,MAAI,QAAQ,UAAU,UAAU;AAC9B,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAEA,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;AAEA,QAAM,YAAY,cAAc,QAAQ;AACxC,QAAM,UAAU,MAAM,eAAe,QAAQ;AAC7C,MAAI,CAAC,QAAS,OAAM,IAAI,WAAW,sDAAsD;AAEzF,aAAW,UAAU,QAAQ,SAAS;AACpC,UAAM,OAAO,aAAa,QAAQ,aAAa,OAAO,QAAQ;AAC9D,kBAAc,MAAM,UAAU,OAAO,QAAQ,CAAC;AAC9C,UAAM,SAAS,SAAS,QAAQ,IAAI,GAAG,IAAI,CAAC,EAAE;AAAA,EAChD;AAEA,QAAM,WAAW,aAAa,WAAW,SAAS,QAAQ;AAC1D,MAAI,UAAU;AACZ,UAAM,EAAE;AACR,UAAM,oBAAoB,QAAQ,CAAC;AAAA,EACrC;AAEA,MAAI,QAAQ,SAAS,SAAS,GAAG;AAG/B,eAAW;AAAA,EAAK,oBAAoB,QAAQ,QAAQ,CAAC,EAAE;AACvD,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/commands/snapshot.ts"],"sourcesContent":["import { relative } from \"node:path\";\nimport {\n joinCoverage,\n mountScenarios,\n readInventory,\n UsageError,\n type AnalysisOptions,\n type Depth,\n} from \"../analysis.js\";\nimport { baselinePath, writeBaseline, writeScenarioManifest } from \"../baseline.js\";\nimport { scenarioBaseline } from \"../report.js\";\nimport { renderCoveragePlain, renderFailuresPlain } from \"../render/plain.js\";\nimport { write, writeError } from \"../output.js\";\n\nexport interface SnapshotOptions {\n configPath: string;\n depth: Depth;\n scenario?: string;\n scope?: string[];\n tsconfig?: string;\n baselineDir?: string;\n}\n\n/**\n * Writes (or refreshes) the committed baseline `check` compares against.\n *\n * It prints the coverage verdict too. This is the command you run to *accept* a\n * change to the surface, which makes it the last moment before a reviewer sees\n * the diff — and accepting a projection while a capability sits behind a route\n * no scenario visits is exactly the state worth hearing about. It reports;\n * `check` is still the only thing that fails.\n */\nexport async function runSnapshot(options: SnapshotOptions): Promise<number> {\n if (options.depth === \"static\") {\n throw new UsageError(\n \"snapshot --depth static has nothing to write — a baseline is a projection, and \" +\n \"at this depth nothing is mounted.\",\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 const runtime = await mountScenarios(analysis);\n if (!runtime) throw new UsageError(\"snapshot needs a mount, and this depth performs none\");\n\n for (const result of runtime.results) {\n const path = baselinePath(runtime.baselineDir, result.scenario);\n writeBaseline(path, scenarioBaseline(result));\n write(`wrote ${relative(process.cwd(), path)}`);\n }\n writeScenarioManifest(runtime.baselineDir, runtime.declaredScenarios);\n\n const coverage = joinCoverage(inventory, runtime, analysis);\n if (coverage) {\n write(\"\");\n write(renderCoveragePlain(coverage));\n }\n\n if (runtime.failures.length > 0) {\n // A baseline written for some scenarios and not others is a baseline that\n // will fail `check` for a reason that has nothing to do with the surface.\n writeError(`\\n${renderFailuresPlain(runtime.failures)}`);\n return 2;\n }\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AAgCzB,eAAsB,YAAY,SAA2C;AAC3E,MAAI,QAAQ,UAAU,UAAU;AAC9B,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAEA,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;AAEA,QAAM,YAAY,cAAc,QAAQ;AACxC,QAAM,UAAU,MAAM,eAAe,QAAQ;AAC7C,MAAI,CAAC,QAAS,OAAM,IAAI,WAAW,sDAAsD;AAEzF,aAAW,UAAU,QAAQ,SAAS;AACpC,UAAM,OAAO,aAAa,QAAQ,aAAa,OAAO,QAAQ;AAC9D,kBAAc,MAAM,iBAAiB,MAAM,CAAC;AAC5C,UAAM,SAAS,SAAS,QAAQ,IAAI,GAAG,IAAI,CAAC,EAAE;AAAA,EAChD;AACA,wBAAsB,QAAQ,aAAa,QAAQ,iBAAiB;AAEpE,QAAM,WAAW,aAAa,WAAW,SAAS,QAAQ;AAC1D,MAAI,UAAU;AACZ,UAAM,EAAE;AACR,UAAM,oBAAoB,QAAQ,CAAC;AAAA,EACrC;AAEA,MAAI,QAAQ,SAAS,SAAS,GAAG;AAG/B,eAAW;AAAA,EAAK,oBAAoB,QAAQ,QAAQ,CAAC,EAAE;AACvD,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
package/dist/vitest.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { RenderedAgentSurface } from '@agent-surface/testing/react';
2
2
  import { AgentConsumer } from '@agent-surface/core';
3
- import { M as MountResult, S as ScenarioProps, a as SurfaceConfig } from './config-DqK8YqyQ.js';
3
+ import { M as MountResult, S as ScenarioProps, a as SurfaceConfig } from './config-rHD2wEws.js';
4
4
  import 'react';
5
5
 
6
6
  declare const DEFAULT_CLI_CONSUMER: AgentConsumer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-surface/cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
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.11.0",
35
- "@agent-surface/react": "^0.10.1",
36
- "@agent-surface/testing": "^0.10.1"
34
+ "@agent-surface/core": "^0.12.0",
35
+ "@agent-surface/react": "^0.12.0",
36
+ "@agent-surface/testing": "^0.12.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@testing-library/react": ">=14",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["import { relative } from \"node:path\";\nimport {\n UsageError,\n joinCoverage,\n mountScenarios,\n readInventory,\n type AnalysisOptions,\n type Depth,\n} from \"../analysis.js\";\nimport { annotate, baselinePath, diff, normalize, readBaseline, type DiffEntry } from \"../baseline.js\";\nimport { coverageExitCode } from \"../coverage.js\";\nimport {\n renderCoveragePlain,\n renderDriftPlain,\n renderFailuresPlain,\n renderNoVerdictPlain,\n} from \"../render/plain.js\";\nimport { write, writeError } from \"../output.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\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 four:\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 *\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 = normalize(result.snapshot);\n const entries = annotate(diff(expected, actual), actual, expected);\n if (entries.length > 0) drifted.push({ scenario: result.scenario, entries });\n }\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 = drifted.length === 0 && !coverageFailed && !couldNotRun;\n\n if (options.json) {\n write(\n JSON.stringify(\n { ok, drifted, failures: runtime.failures, coverage: coverage ?? null },\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 (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":[]}