@agent-surface/cli 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import type { ReactElement } from \"react\";\nimport type { AgentConsumer, AgentSurfaceRegistry } from \"@agent-surface/core\";\n\n/** Structural subset of the authoritative orpc-agent manifest the CLI consumes. */\nexport interface SurfaceDomainManifest {\n tools: Record<string, { description: string }>;\n}\n\n/** What `mount()` hands back: the app's own registry and its rendered tree. */\nexport interface MountResult<TApp = unknown> {\n registry: AgentSurfaceRegistry;\n ui: ReactElement;\n /**\n * Anything else your tests need back — the app wiring, a backend double, a\n * router handle. The CLI ignores it entirely; it exists so the same scenario\n * can drive `agent-surface inspect` and a Vitest suite without the suite\n * having to rebuild the app a second way.\n */\n app?: TApp;\n}\n\n/**\n * Scenario properties are whatever your `mount()` needs — a user, a route, a\n * feature flag. The CLI never interprets them; it just hands them back, plus\n * `scenario` (the key it was listed under).\n */\nexport type ScenarioProps = Record<string, unknown>;\n\nexport interface SurfaceConfig<TScenario extends ScenarioProps = ScenarioProps, TApp = unknown> {\n /**\n * Build the app the way the app builds itself. This should point at your\n * existing composition root, not restate it — whatever `main.tsx` calls.\n */\n mount(\n props: TScenario & { scenario: string },\n ): MountResult<TApp> | Promise<MountResult<TApp>>;\n\n /**\n * Optional extra settling after mount effects flush, for anything the first\n * render kicks off asynchronously (an initial fetch, a router resolve).\n * The CLI already flushes React effects and pending microtasks for you.\n */\n settle?: (mounted: MountResult<TApp>) => void | Promise<void>;\n\n /** Named surfaces to inspect and check. At least one is required. */\n scenarios: Record<string, TScenario>;\n\n /** Consumer identity snapshots are computed for. Default `{id:\"cli\",kind:\"test\"}`. */\n consumer?: AgentConsumer;\n\n /** Component-type prefixes to restrict to, same meaning as `SnapshotContext.scope`. */\n scope?: string[];\n\n /** Authoritative domain denominator. Full analysis joins every manifest tool. */\n manifest?: SurfaceDomainManifest;\n\n /** Where `snapshot`/`check` keep baselines. Default `.agent-surface`, relative to the config. */\n baselineDir?: string;\n}\n\n/**\n * Identity function that exists purely for type inference — the same shape as\n * Vite's `defineConfig`. Your scenario props stay strongly typed inside\n * `mount()` without you annotating them.\n */\nexport function defineSurface<TScenario extends ScenarioProps, TApp = unknown>(\n config: SurfaceConfig<TScenario, TApp>,\n): SurfaceConfig<TScenario, TApp> {\n return config;\n}\n"],"mappings":";AAiEO,SAAS,cACd,QACgC;AAChC,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,51 @@
1
+ import {
2
+ changeCounts
3
+ } from "./chunk-IA7FUP4R.js";
4
+
5
+ // src/ink.tsx
6
+ import { Box, Text, render } from "ink";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ function App({ report }) {
9
+ const ok = report.status === "pass" || report.status === "written" || report.status === "view";
10
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
11
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: ok ? "green" : "red", children: [
12
+ "AGENT SURFACE ",
13
+ report.command.toUpperCase(),
14
+ " \xB7 ",
15
+ report.status.toUpperCase()
16
+ ] }),
17
+ /* @__PURE__ */ jsxs(Text, { children: [
18
+ "Contract ",
19
+ report.manifest.hash
20
+ ] }),
21
+ /* @__PURE__ */ jsxs(Text, { children: [
22
+ "Completeness ",
23
+ report.manifest.completeness.status
24
+ ] }),
25
+ /* @__PURE__ */ jsxs(Text, { children: [
26
+ "Capabilities ",
27
+ report.manifest.capabilities.length
28
+ ] }),
29
+ report.integrity ? /* @__PURE__ */ jsxs(Text, { children: [
30
+ "Integrity ",
31
+ report.integrity.status
32
+ ] }) : null,
33
+ report.pullRequest ? /* @__PURE__ */ jsxs(Text, { children: [
34
+ "PR drift ",
35
+ report.pullRequest.changes.length,
36
+ " vs ",
37
+ report.pullRequest.base,
38
+ " (",
39
+ changeCounts(report.pullRequest.changes).widening,
40
+ " widening)"
41
+ ] }) : null
42
+ ] });
43
+ }
44
+ async function renderInk(report) {
45
+ const instance = render(/* @__PURE__ */ jsx(App, { report }));
46
+ await instance.waitUntilExit();
47
+ }
48
+ export {
49
+ renderInk
50
+ };
51
+ //# sourceMappingURL=ink-E6DATMUQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ink.tsx"],"sourcesContent":["import { Box, Text, render } from \"ink\";\nimport type { ContractReport } from \"./report.js\";\nimport { changeCounts } from \"./diff.js\";\n\nfunction App({ report }: { report: ContractReport }) {\n const ok = report.status === \"pass\" || report.status === \"written\" || report.status === \"view\";\n return (\n <Box flexDirection=\"column\">\n <Text bold color={ok ? \"green\" : \"red\"}>\n AGENT SURFACE {report.command.toUpperCase()} · {report.status.toUpperCase()}\n </Text>\n <Text>Contract {report.manifest.hash}</Text>\n <Text>Completeness {report.manifest.completeness.status}</Text>\n <Text>Capabilities {report.manifest.capabilities.length}</Text>\n {report.integrity ? <Text>Integrity {report.integrity.status}</Text> : null}\n {report.pullRequest ? (\n <Text>\n PR drift {report.pullRequest.changes.length} vs {report.pullRequest.base} ({\n changeCounts(report.pullRequest.changes).widening\n } widening)\n </Text>\n ) : null}\n </Box>\n );\n}\n\nexport async function renderInk(report: ContractReport): Promise<void> {\n const instance = render(<App report={report} />);\n await instance.waitUntilExit();\n}\n"],"mappings":";;;;;AAAA,SAAS,KAAK,MAAM,cAAc;AAQ5B,SAmBoB,KAnBpB;AAJN,SAAS,IAAI,EAAE,OAAO,GAA+B;AACnD,QAAM,KAAK,OAAO,WAAW,UAAU,OAAO,WAAW,aAAa,OAAO,WAAW;AACxF,SACE,qBAAC,OAAI,eAAc,UACjB;AAAA,yBAAC,QAAK,MAAI,MAAC,OAAO,KAAK,UAAU,OAAO;AAAA;AAAA,MACvB,OAAO,QAAQ,YAAY;AAAA,MAAE;AAAA,MAAI,OAAO,OAAO,YAAY;AAAA,OAC5E;AAAA,IACA,qBAAC,QAAK;AAAA;AAAA,MAAe,OAAO,SAAS;AAAA,OAAK;AAAA,IAC1C,qBAAC,QAAK;AAAA;AAAA,MAAe,OAAO,SAAS,aAAa;AAAA,OAAO;AAAA,IACzD,qBAAC,QAAK;AAAA;AAAA,MAAe,OAAO,SAAS,aAAa;AAAA,OAAO;AAAA,IACxD,OAAO,YAAY,qBAAC,QAAK;AAAA;AAAA,MAAe,OAAO,UAAU;AAAA,OAAO,IAAU;AAAA,IAC1E,OAAO,cACN,qBAAC,QAAK;AAAA;AAAA,MACW,OAAO,YAAY,QAAQ;AAAA,MAAO;AAAA,MAAK,OAAO,YAAY;AAAA,MAAK;AAAA,MAC5E,aAAa,OAAO,YAAY,OAAO,EAAE;AAAA,MAC1C;AAAA,OACH,IACE;AAAA,KACN;AAEJ;AAEA,eAAsB,UAAU,QAAuC;AACrE,QAAM,WAAW,OAAO,oBAAC,OAAI,QAAgB,CAAE;AAC/C,QAAM,SAAS,cAAc;AAC/B;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-surface/cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.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",
@@ -13,10 +13,6 @@
13
13
  ".": {
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js"
16
- },
17
- "./vitest": {
18
- "types": "./dist/vitest.d.ts",
19
- "import": "./dist/vitest.js"
20
16
  }
21
17
  },
22
18
  "files": [
@@ -25,26 +21,14 @@
25
21
  ],
26
22
  "dependencies": {
27
23
  "ink": "^6.3.1",
28
- "ink-spinner": "^5.0.0",
29
- "jsdom": "^26.1.0",
30
24
  "react": "^19.1.1",
31
- "typescript": "^5.9.2",
32
25
  "vite": "^7.1.3",
33
- "vite-node": "^3.2.4",
34
- "@agent-surface/core": "^0.15.0",
35
- "@agent-surface/react": "^0.15.0",
36
- "@agent-surface/testing": "^0.15.0"
37
- },
38
- "peerDependencies": {
39
- "@testing-library/react": ">=14",
40
- "react-dom": ">=18.2"
26
+ "@agent-surface/compiler": "^0.16.0",
27
+ "@agent-surface/core": "^0.16.0"
41
28
  },
42
29
  "devDependencies": {
43
- "@testing-library/react": "^16.3.0",
44
- "@types/jsdom": "^21.1.7",
45
30
  "@types/node": "^20.19.0",
46
- "@types/react": "^19.1.9",
47
- "react-dom": "^19.1.1"
31
+ "@types/react": "^19.1.9"
48
32
  },
49
33
  "author": "Paolo Barbato",
50
34
  "engines": {
@@ -1,259 +0,0 @@
1
- import {
2
- coverageReport,
3
- scenarioBaseline
4
- } from "./chunk-Y2LSPEVK.js";
5
- import {
6
- createPresenter,
7
- joinCoverage,
8
- mountScenarios,
9
- readInventory
10
- } from "./chunk-A5UCBF7D.js";
11
- import {
12
- SCENARIO_MANIFEST_FILE,
13
- UsageError,
14
- annotate,
15
- baselinePath,
16
- diff,
17
- readBaseline,
18
- readScenarioManifest,
19
- renderDriftPlain,
20
- write
21
- } from "./chunk-GYYWHZPM.js";
22
- import {
23
- ALLOWLIST_FILE,
24
- READING_SOURCE,
25
- UNREAD_ALLOWLIST_FILE,
26
- checkOverviewParts,
27
- coverageExitCode,
28
- coverageSections,
29
- displayPath,
30
- failureSection,
31
- mountingLabel,
32
- noVerdictSection,
33
- scenarioStats
34
- } from "./chunk-NFK3XWWH.js";
35
-
36
- // src/commands/check.ts
37
- import { existsSync, readdirSync } from "fs";
38
- async function runCheck(options) {
39
- if (options.depth === "static") {
40
- throw new UsageError(
41
- "check --depth static has nothing to compare \u2014 a baseline is a projection, and at this depth nothing is mounted. Use --depth runtime for drift alone, or full for both."
42
- );
43
- }
44
- const analysis = {
45
- configPath: options.configPath,
46
- depth: options.depth,
47
- ...options.scenario ? { scenario: options.scenario } : {},
48
- ...options.scope ? { scope: options.scope } : {},
49
- ...options.tsconfig ? { tsconfig: options.tsconfig } : {},
50
- ...options.baselineDir ? { baselineDir: options.baselineDir } : {}
51
- };
52
- const present = await createPresenter(options);
53
- if (!options.json) await present.wait(READING_SOURCE);
54
- const inventory = readInventory(analysis);
55
- let scenarios = [];
56
- let mountedCount = 0;
57
- const runtime = await mountScenarios(analysis, {
58
- onPlan: async (plan) => {
59
- scenarios = plan.scenarios;
60
- if (scenarios.length > 0) await present.wait(mountingLabel(scenarios, 0));
61
- },
62
- onEach: async () => {
63
- mountedCount += 1;
64
- if (mountedCount < scenarios.length) {
65
- await present.wait(mountingLabel(scenarios, mountedCount));
66
- }
67
- }
68
- });
69
- present.settle();
70
- if (!runtime) throw new UsageError("check needs a mount, and this depth performs none");
71
- const drifted = [];
72
- for (const result of runtime.results) {
73
- const path = baselinePath(runtime.baselineDir, result.scenario);
74
- const expected = readBaseline(path);
75
- if (expected === void 0) {
76
- drifted.push({ scenario: result.scenario, missingBaseline: true, entries: [] });
77
- continue;
78
- }
79
- const actual = scenarioBaseline(result);
80
- const entries = annotate(diff(expected, actual), actual, expected);
81
- if (entries.length > 0) drifted.push({ scenario: result.scenario, entries });
82
- }
83
- const committedScenarios = readScenarioManifest(runtime.baselineDir);
84
- const declared = [...runtime.declaredScenarios].sort();
85
- const scenarioManifestMismatch = committedScenarios === void 0 || JSON.stringify(committedScenarios) !== JSON.stringify(declared);
86
- const reserved = /* @__PURE__ */ new Set([SCENARIO_MANIFEST_FILE, ALLOWLIST_FILE, UNREAD_ALLOWLIST_FILE]);
87
- const staleBaselineFiles = (existsSync(runtime.baselineDir) ? readdirSync(runtime.baselineDir, { withFileTypes: true }) : []).filter((entry) => entry.isFile() && entry.name.endsWith(".json") && !reserved.has(entry.name)).map((entry) => entry.name.slice(0, -5)).filter((scenario) => !runtime.declaredScenarios.includes(scenario)).sort();
88
- const rejected = runtime.results.filter((result) => result.rejections.length > 0).map((result) => ({ scenario: result.scenario, rejections: result.rejections }));
89
- const coverage = joinCoverage(inventory, runtime, analysis);
90
- const coverageFailed = coverage !== void 0 && coverageExitCode(coverage, { allowUnresolved: options.allowUnresolved === true }) !== 0;
91
- const couldNotRun = runtime.failures.length > 0;
92
- const ok = drifted.length === 0 && !coverageFailed && !couldNotRun && rejected.length === 0 && !scenarioManifestMismatch && staleBaselineFiles.length === 0;
93
- if (options.json) {
94
- write(
95
- JSON.stringify(
96
- {
97
- ok,
98
- drifted,
99
- failures: runtime.failures,
100
- rejected,
101
- scenarioManifest: {
102
- expected: declared,
103
- committed: committedScenarios ?? null,
104
- staleBaselines: staleBaselineFiles
105
- },
106
- coverage: coverageReport(coverage)
107
- },
108
- null,
109
- 2
110
- )
111
- );
112
- return couldNotRun ? 2 : ok ? 0 : 1;
113
- }
114
- const missing = drifted.filter((entry) => entry.missingBaseline);
115
- const changed = drifted.filter((entry) => !entry.missingBaseline);
116
- const baselineOf = (scenario) => {
117
- const entry = drifted.find((candidate) => candidate.scenario === scenario);
118
- if (!entry) return "current";
119
- if (entry.missingBaseline) return "missing";
120
- return `drift (${entry.entries.length})`;
121
- };
122
- const stats = runtime.scenarios.map((scenario) => {
123
- const result = runtime.results.find((candidate) => candidate.scenario === scenario);
124
- if (result) return { ...scenarioStats(result), baseline: baselineOf(scenario) };
125
- const failure = runtime.failures.find((entry) => entry.scenario === scenario);
126
- return {
127
- scenario,
128
- callable: 0,
129
- disabled: 0,
130
- hidden: 0,
131
- rejected: 0,
132
- failed: true,
133
- ...failure?.message ? { failure: failure.message } : {}
134
- };
135
- });
136
- const stream = ok ? void 0 : "err";
137
- const parts = checkOverviewParts({
138
- status: couldNotRun ? "ERROR" : ok ? "PASS" : "FAIL",
139
- ...coverage ? { coverage } : {},
140
- unresolvedAllowed: options.allowUnresolved === true,
141
- baselineCurrent: Math.max(0, runtime.results.length - drifted.length),
142
- baselineTotal: runtime.scenarios.length,
143
- scenarioManifestOk: !scenarioManifestMismatch && staleBaselineFiles.length === 0,
144
- rejected: rejected.reduce((sum, entry) => sum + entry.rejections.length, 0),
145
- mountFailures: runtime.failures.length,
146
- context: {
147
- configPath: options.configPath,
148
- depth: options.depth,
149
- ...runtime.scope ? { scope: runtime.scope } : {}
150
- },
151
- stats
152
- }).map((part) => ({ ...part, ...stream ? { stream } : {} }));
153
- if (coverage) {
154
- const gaps = coverageSections(coverage, {
155
- compact: true,
156
- ...options.detail ? { detail: true } : {}
157
- });
158
- if (gaps.length > 0) {
159
- parts.push({
160
- kind: "findings",
161
- sections: gaps,
162
- ...coverageFailed ? { stream: "err" } : {}
163
- });
164
- }
165
- }
166
- const sections = [];
167
- const steps = [];
168
- if (coverage && coverage.unreached.length > 0) {
169
- steps.push(
170
- `mount the ${coverage.unreached.length} unreached capabilit${coverage.unreached.length === 1 ? "y" : "ies"} from a scenario, or record the decision in ${displayPath(coverage.allowlistPath)}`
171
- );
172
- }
173
- if (coverage && coverage.unresolved.length > 0 && options.allowUnresolved !== true) {
174
- steps.push(
175
- `make the ${coverage.unresolved.length} unread call site${coverage.unresolved.length === 1 ? "" : "s"} readable, or paste each printed key into ${displayPath(coverage.unreadAllowlistPath)}`
176
- );
177
- }
178
- if (rejected.length > 0) {
179
- const total = rejected.reduce((sum, entry) => sum + entry.rejections.length, 0);
180
- sections.push({
181
- title: "REJECTED REGISTRATIONS",
182
- gloss: "the runtime refused authored surface during the mount",
183
- count: total,
184
- lines: rejected.flatMap(
185
- (entry) => entry.rejections.map(
186
- (rejection) => `${entry.scenario}: ${rejection.componentType}@${rejection.instanceId} (${rejection.reason})`
187
- )
188
- ),
189
- hint: "a dead handle registers nothing \u2014 give the second registration its own instanceId, or remove the duplicated component type"
190
- });
191
- steps.push(
192
- `resolve ${total} rejected registration${total === 1 ? "" : "s"} \u2014 the capabilities behind them reach no agent`
193
- );
194
- }
195
- if (scenarioManifestMismatch || staleBaselineFiles.length > 0) {
196
- sections.push({
197
- title: "SCENARIO DRIFT",
198
- gloss: "the committed baselines do not match the config",
199
- count: 0,
200
- lines: [
201
- `config: ${declared.join(", ")}`,
202
- `manifest: ${committedScenarios?.join(", ") ?? "missing"}`,
203
- ...staleBaselineFiles.length > 0 ? [`stale: ${staleBaselineFiles.join(", ")}`] : []
204
- ],
205
- hint: "run `agent-surface snapshot`, commit the manifest, and delete any baseline for a scenario the config no longer declares"
206
- });
207
- }
208
- if (missing.length > 0) {
209
- sections.push({
210
- title: "NO BASELINE",
211
- gloss: "nothing to compare against, which is not the same as a match",
212
- count: missing.length,
213
- lines: missing.map(
214
- (entry) => `${entry.scenario}: ${displayPath(
215
- baselinePath(runtime.baselineDir, entry.scenario)
216
- )} does not exist`
217
- ),
218
- hint: "run `agent-surface snapshot` and commit the files it writes"
219
- });
220
- }
221
- if (changed.length > 0) {
222
- sections.push({
223
- title: "DRIFT",
224
- gloss: "the surface changed against its baseline",
225
- count: changed.length,
226
- lines: changed.flatMap((entry) => renderDriftPlain(entry.scenario, entry.entries)),
227
- hint: "review the change, then `agent-surface snapshot` to accept it" + (runtime.scope ? ". A scope filters the projection while baselines are written from whatever scope wrote them, so re-check without --scope before believing this one" : "")
228
- });
229
- }
230
- const baselinesStale = missing.length > 0 || changed.length > 0 || scenarioManifestMismatch || staleBaselineFiles.length > 0;
231
- if (baselinesStale) {
232
- steps.push(
233
- "`agent-surface snapshot`, then commit .agent-surface/ \u2014 this accepts the surface above as reviewed"
234
- );
235
- }
236
- if (sections.length > 0) parts.push({ kind: "findings", stream: "err", sections });
237
- if (couldNotRun) {
238
- parts.push({
239
- kind: "findings",
240
- stream: "err",
241
- sections: [
242
- failureSection(runtime.failures),
243
- ...inventory ? [noVerdictSection(runtime.failures)] : []
244
- ]
245
- });
246
- steps.unshift(
247
- `fix the mount for ${runtime.failures.map((failure) => failure.scenario).join(", ")} \u2014 every count above is missing whatever ${runtime.failures.length === 1 ? "it" : "they"} would have surfaced`
248
- );
249
- }
250
- if (!ok && steps.length > 0) {
251
- parts.push({ kind: "steps", stream: "err", title: "NEXT STEPS", steps });
252
- }
253
- await present.emit(...parts);
254
- return couldNotRun ? 2 : ok ? 0 : 1;
255
- }
256
- export {
257
- runCheck
258
- };
259
- //# sourceMappingURL=check-VWRYNYLN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["import { existsSync, readdirSync } from \"node:fs\";\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 { renderDriftPlain } from \"../render/plain.js\";\nimport { createPresenter } from \"../render/present.js\";\nimport {\n checkOverviewParts,\n coverageSections,\n displayPath,\n failureSection,\n mountingLabel,\n noVerdictSection,\n READING_SOURCE,\n scenarioStats,\n type FindingSection,\n type ReportPart,\n type ScenarioStats,\n} from \"../render/summary.js\";\nimport { write } 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 * The report is read top-down: the verdict, what it was computed over, one row\n * per class of finding whether or not it fired, one row per scenario, then the\n * findings themselves and the commands that clear them — the same shapes, in\n * the same grid, that `inspect` uses for the same things. Its output is a\n * report pasted into a pull request and read out of a CI log, and neither of\n * those is a terminal — so neither of those gets a terminal UI: piped, `CI` and\n * `NO_COLOR` all render plain text, and that is decided by the presenter, once,\n * rather than by this command declining to have a renderer at all.\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 present = await createPresenter(options);\n if (!options.json) await present.wait(READING_SOURCE);\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 — so\n // the terminal is told what it is waiting for instead, in the same words and\n // with the same spinner `inspect` uses for the same wait.\n let scenarios: string[] = [];\n let mountedCount = 0;\n const runtime = await mountScenarios(analysis, {\n onPlan: async (plan) => {\n scenarios = plan.scenarios;\n if (scenarios.length > 0) await present.wait(mountingLabel(scenarios, 0));\n },\n onEach: async () => {\n mountedCount += 1;\n if (mountedCount < scenarios.length) {\n await present.wait(mountingLabel(scenarios, mountedCount));\n }\n },\n });\n present.settle();\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 = (\n existsSync(runtime.baselineDir) ? 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 // \"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 const baselineOf = (scenario: string): string => {\n const entry = drifted.find((candidate) => candidate.scenario === scenario);\n if (!entry) return \"current\";\n if (entry.missingBaseline) return \"missing\";\n return `drift (${entry.entries.length})`;\n };\n // One row per scenario the run attempted, in config order — including the\n // ones that threw, which otherwise appear only at the bottom of the report.\n const stats: ScenarioStats[] = runtime.scenarios.map((scenario) => {\n const result = runtime.results.find((candidate) => candidate.scenario === scenario);\n if (result) return { ...scenarioStats(result), baseline: baselineOf(scenario) };\n const failure = runtime.failures.find((entry) => entry.scenario === scenario);\n return {\n scenario,\n callable: 0,\n disabled: 0,\n hidden: 0,\n rejected: 0,\n failed: true,\n ...(failure?.message ? { failure: failure.message } : {}),\n };\n });\n\n // A failing report goes to stderr in full. The verdict is the part a reader\n // has to see, and a gate whose red is on the stream nobody captured is a gate\n // that reads as green.\n const stream = ok ? undefined : (\"err\" as const);\n const parts: ReportPart[] = checkOverviewParts({\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 context: {\n configPath: options.configPath,\n depth: options.depth,\n ...(runtime.scope ? { scope: runtime.scope } : {}),\n },\n stats,\n }).map((part) => ({ ...part, ...(stream ? { stream } : {}) }));\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 gaps = coverageSections(coverage, {\n compact: true,\n ...(options.detail ? { detail: true } : {}),\n });\n if (gaps.length > 0) {\n parts.push({\n kind: \"findings\",\n sections: gaps,\n ...(coverageFailed ? { stream: \"err\" as const } : {}),\n });\n }\n }\n\n const sections: FindingSection[] = [];\n const steps: string[] = [];\n if (coverage && coverage.unreached.length > 0) {\n steps.push(\n `mount the ${coverage.unreached.length} unreached capabilit${\n coverage.unreached.length === 1 ? \"y\" : \"ies\"\n } from a scenario, or record the decision in ${displayPath(coverage.allowlistPath)}`,\n );\n }\n if (coverage && coverage.unresolved.length > 0 && options.allowUnresolved !== true) {\n steps.push(\n `make the ${coverage.unresolved.length} unread call site${\n coverage.unresolved.length === 1 ? \"\" : \"s\"\n } readable, or paste each printed key into ${displayPath(coverage.unreadAllowlistPath)}`,\n );\n }\n\n if (rejected.length > 0) {\n const total = rejected.reduce((sum, entry) => sum + entry.rejections.length, 0);\n sections.push({\n title: \"REJECTED REGISTRATIONS\",\n gloss: \"the runtime refused authored surface during the mount\",\n count: total,\n lines: rejected.flatMap((entry) =>\n entry.rejections.map(\n (rejection) =>\n `${entry.scenario}: ${rejection.componentType}@${rejection.instanceId} (${rejection.reason})`,\n ),\n ),\n hint:\n \"a dead handle registers nothing — give the second registration its own instanceId, \" +\n \"or remove the duplicated component type\",\n });\n steps.push(\n `resolve ${total} rejected registration${total === 1 ? \"\" : \"s\"} — the capabilities ` +\n \"behind them reach no agent\",\n );\n }\n\n if (scenarioManifestMismatch || staleBaselineFiles.length > 0) {\n sections.push({\n title: \"SCENARIO DRIFT\",\n gloss: \"the committed baselines do not match the config\",\n count: 0,\n lines: [\n `config: ${declared.join(\", \")}`,\n `manifest: ${committedScenarios?.join(\", \") ?? \"missing\"}`,\n ...(staleBaselineFiles.length > 0 ? [`stale: ${staleBaselineFiles.join(\", \")}`] : []),\n ],\n hint:\n \"run `agent-surface snapshot`, commit the manifest, and delete any baseline for a \" +\n \"scenario the config no longer declares\",\n });\n }\n\n if (missing.length > 0) {\n sections.push({\n title: \"NO BASELINE\",\n gloss: \"nothing to compare against, which is not the same as a match\",\n count: missing.length,\n lines: missing.map(\n (entry) =>\n `${entry.scenario}: ${displayPath(\n baselinePath(runtime.baselineDir, entry.scenario),\n )} does not exist`,\n ),\n hint: \"run `agent-surface snapshot` and commit the files it writes\",\n });\n }\n\n if (changed.length > 0) {\n sections.push({\n title: \"DRIFT\",\n gloss: \"the surface changed against its baseline\",\n count: changed.length,\n lines: changed.flatMap((entry) => renderDriftPlain(entry.scenario, entry.entries)),\n hint:\n \"review the change, then `agent-surface snapshot` to accept it\" +\n (runtime.scope\n ? \". A scope filters the projection while baselines are written from whatever \" +\n \"scope wrote them, so re-check without --scope before believing this one\"\n : \"\"),\n });\n }\n\n const baselinesStale =\n missing.length > 0 ||\n changed.length > 0 ||\n scenarioManifestMismatch ||\n staleBaselineFiles.length > 0;\n if (baselinesStale) {\n steps.push(\n \"`agent-surface snapshot`, then commit .agent-surface/ — this accepts the surface \" +\n \"above as reviewed\",\n );\n }\n\n if (sections.length > 0) parts.push({ kind: \"findings\", stream: \"err\", sections });\n\n if (couldNotRun) {\n parts.push({\n kind: \"findings\",\n stream: \"err\",\n sections: [\n failureSection(runtime.failures),\n ...(inventory ? [noVerdictSection(runtime.failures)] : []),\n ],\n });\n // First, and above every other remedy: nothing else in this report can be\n // trusted while a scenario the config declares never ran.\n steps.unshift(\n `fix the mount for ${runtime.failures\n .map((failure) => failure.scenario)\n .join(\", \")} — every count above is missing whatever ${\n runtime.failures.length === 1 ? \"it\" : \"they\"\n } would have surfaced`,\n );\n }\n\n // Last, and only when something failed: the tail of a CI log is what a reader\n // sees first, and a list of findings without the commands that clear them\n // leaves the reader to derive those from six different sections.\n if (!ok && steps.length > 0) {\n parts.push({ kind: \"steps\", stream: \"err\", title: \"NEXT STEPS\", steps });\n }\n\n await present.emit(...parts);\n return couldNotRun ? 2 : ok ? 0 : 1;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,mBAAmB;AA8FxC,eAAsB,SAAS,SAAwC;AACrE,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,UAAU,MAAM,gBAAgB,OAAO;AAC7C,MAAI,CAAC,QAAQ,KAAM,OAAM,QAAQ,KAAK,cAAc;AAEpD,QAAM,YAAY,cAAc,QAAQ;AAKxC,MAAI,YAAsB,CAAC;AAC3B,MAAI,eAAe;AACnB,QAAM,UAAU,MAAM,eAAe,UAAU;AAAA,IAC7C,QAAQ,OAAO,SAAS;AACtB,kBAAY,KAAK;AACjB,UAAI,UAAU,SAAS,EAAG,OAAM,QAAQ,KAAK,cAAc,WAAW,CAAC,CAAC;AAAA,IAC1E;AAAA,IACA,QAAQ,YAAY;AAClB,sBAAgB;AAChB,UAAI,eAAe,UAAU,QAAQ;AACnC,cAAM,QAAQ,KAAK,cAAc,WAAW,YAAY,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF,CAAC;AACD,UAAQ,OAAO;AACf,MAAI,CAAC,QAAS,OAAM,IAAI,WAAW,mDAAmD;AAEtF,QAAM,UAA2B,CAAC;AAClC,aAAW,UAAU,QAAQ,SAAS;AACpC,UAAM,OAAO,aAAa,QAAQ,aAAa,OAAO,QAAQ;AAC9D,UAAM,WAAW,aAAa,IAAI;AAClC,QAAI,aAAa,QAAW;AAC1B,cAAQ,KAAK,EAAE,UAAU,OAAO,UAAU,iBAAiB,MAAM,SAAS,CAAC,EAAE,CAAC;AAC9E;AAAA,IACF;AACA,UAAM,SAAS,iBAAiB,MAAM;AACtC,UAAM,UAAU,SAAS,KAAK,UAAU,MAAM,GAAG,QAAQ,QAAQ;AACjE,QAAI,QAAQ,SAAS,EAAG,SAAQ,KAAK,EAAE,UAAU,OAAO,UAAU,QAAQ,CAAC;AAAA,EAC7E;AAEA,QAAM,qBAAqB,qBAAqB,QAAQ,WAAW;AACnE,QAAM,WAAW,CAAC,GAAG,QAAQ,iBAAiB,EAAE,KAAK;AACrD,QAAM,2BACJ,uBAAuB,UACvB,KAAK,UAAU,kBAAkB,MAAM,KAAK,UAAU,QAAQ;AAChE,QAAM,WAAW,oBAAI,IAAI,CAAC,wBAAwB,gBAAgB,qBAAqB,CAAC;AACxF,QAAM,sBACJ,WAAW,QAAQ,WAAW,IAAI,YAAY,QAAQ,aAAa,EAAE,eAAe,KAAK,CAAC,IAAI,CAAC,GAE9F,OAAO,CAAC,UAAU,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,OAAO,KAAK,CAAC,SAAS,IAAI,MAAM,IAAI,CAAC,EAC7F,IAAI,CAAC,UAAU,MAAM,KAAK,MAAM,GAAG,EAAE,CAAC,EACtC,OAAO,CAAC,aAAa,CAAC,QAAQ,kBAAkB,SAAS,QAAQ,CAAC,EAClE,KAAK;AAER,QAAM,WAA+B,QAAQ,QAC1C,OAAO,CAAC,WAAW,OAAO,WAAW,SAAS,CAAC,EAC/C,IAAI,CAAC,YAAY,EAAE,UAAU,OAAO,UAAU,YAAY,OAAO,WAAW,EAAE;AAEjF,QAAM,WAAW,aAAa,WAAW,SAAS,QAAQ;AAC1D,QAAM,iBACJ,aAAa,UACb,iBAAiB,UAAU,EAAE,iBAAiB,QAAQ,oBAAoB,KAAK,CAAC,MAAM;AACxF,QAAM,cAAc,QAAQ,SAAS,SAAS;AAC9C,QAAM,KACJ,QAAQ,WAAW,KACnB,CAAC,kBACD,CAAC,eACD,SAAS,WAAW,KACpB,CAAC,4BACD,mBAAmB,WAAW;AAEhC,MAAI,QAAQ,MAAM;AAChB;AAAA,MACE,KAAK;AAAA,QACH;AAAA,UACE;AAAA,UACA;AAAA,UACA,UAAU,QAAQ;AAAA,UAClB;AAAA,UACA,kBAAkB;AAAA,YAChB,UAAU;AAAA,YACV,WAAW,sBAAsB;AAAA,YACjC,gBAAgB;AAAA,UAClB;AAAA,UACA,UAAU,eAAe,QAAQ;AAAA,QACnC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO,cAAc,IAAI,KAAK,IAAI;AAAA,EACpC;AAIA,QAAM,UAAU,QAAQ,OAAO,CAAC,UAAU,MAAM,eAAe;AAC/D,QAAM,UAAU,QAAQ,OAAO,CAAC,UAAU,CAAC,MAAM,eAAe;AAEhE,QAAM,aAAa,CAAC,aAA6B;AAC/C,UAAM,QAAQ,QAAQ,KAAK,CAAC,cAAc,UAAU,aAAa,QAAQ;AACzE,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,MAAM,gBAAiB,QAAO;AAClC,WAAO,UAAU,MAAM,QAAQ,MAAM;AAAA,EACvC;AAGA,QAAM,QAAyB,QAAQ,UAAU,IAAI,CAAC,aAAa;AACjE,UAAM,SAAS,QAAQ,QAAQ,KAAK,CAAC,cAAc,UAAU,aAAa,QAAQ;AAClF,QAAI,OAAQ,QAAO,EAAE,GAAG,cAAc,MAAM,GAAG,UAAU,WAAW,QAAQ,EAAE;AAC9E,UAAM,UAAU,QAAQ,SAAS,KAAK,CAAC,UAAU,MAAM,aAAa,QAAQ;AAC5E,WAAO;AAAA,MACL;AAAA,MACA,UAAU;AAAA,MACV,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,GAAI,SAAS,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,IACzD;AAAA,EACF,CAAC;AAKD,QAAM,SAAS,KAAK,SAAa;AACjC,QAAM,QAAsB,mBAAmB;AAAA,IAC7C,QAAQ,cAAc,UAAU,KAAK,SAAS;AAAA,IAC9C,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B,mBAAmB,QAAQ,oBAAoB;AAAA,IAC/C,iBAAiB,KAAK,IAAI,GAAG,QAAQ,QAAQ,SAAS,QAAQ,MAAM;AAAA,IACpE,eAAe,QAAQ,UAAU;AAAA,IACjC,oBAAoB,CAAC,4BAA4B,mBAAmB,WAAW;AAAA,IAC/E,UAAU,SAAS,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,WAAW,QAAQ,CAAC;AAAA,IAC1E,eAAe,QAAQ,SAAS;AAAA,IAChC,SAAS;AAAA,MACP,YAAY,QAAQ;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAClD;AAAA,IACA;AAAA,EACF,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,MAAM,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG,EAAE;AAI7D,MAAI,UAAU;AACZ,UAAM,OAAO,iBAAiB,UAAU;AAAA,MACtC,SAAS;AAAA,MACT,GAAI,QAAQ,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC3C,CAAC;AACD,QAAI,KAAK,SAAS,GAAG;AACnB,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,UAAU;AAAA,QACV,GAAI,iBAAiB,EAAE,QAAQ,MAAe,IAAI,CAAC;AAAA,MACrD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,WAA6B,CAAC;AACpC,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY,SAAS,UAAU,SAAS,GAAG;AAC7C,UAAM;AAAA,MACJ,aAAa,SAAS,UAAU,MAAM,uBACpC,SAAS,UAAU,WAAW,IAAI,MAAM,KAC1C,+CAA+C,YAAY,SAAS,aAAa,CAAC;AAAA,IACpF;AAAA,EACF;AACA,MAAI,YAAY,SAAS,WAAW,SAAS,KAAK,QAAQ,oBAAoB,MAAM;AAClF,UAAM;AAAA,MACJ,YAAY,SAAS,WAAW,MAAM,oBACpC,SAAS,WAAW,WAAW,IAAI,KAAK,GAC1C,6CAA6C,YAAY,SAAS,mBAAmB,CAAC;AAAA,IACxF;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,QAAQ,SAAS,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,WAAW,QAAQ,CAAC;AAC9E,aAAS,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO,SAAS;AAAA,QAAQ,CAAC,UACvB,MAAM,WAAW;AAAA,UACf,CAAC,cACC,GAAG,MAAM,QAAQ,KAAK,UAAU,aAAa,IAAI,UAAU,UAAU,KAAK,UAAU,MAAM;AAAA,QAC9F;AAAA,MACF;AAAA,MACA,MACE;AAAA,IAEJ,CAAC;AACD,UAAM;AAAA,MACJ,WAAW,KAAK,yBAAyB,UAAU,IAAI,KAAK,GAAG;AAAA,IAEjE;AAAA,EACF;AAEA,MAAI,4BAA4B,mBAAmB,SAAS,GAAG;AAC7D,aAAS,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,QACL,aAAa,SAAS,KAAK,IAAI,CAAC;AAAA,QAChC,aAAa,oBAAoB,KAAK,IAAI,KAAK,SAAS;AAAA,QACxD,GAAI,mBAAmB,SAAS,IAAI,CAAC,aAAa,mBAAmB,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,MACxF;AAAA,MACA,MACE;AAAA,IAEJ,CAAC;AAAA,EACH;AAEA,MAAI,QAAQ,SAAS,GAAG;AACtB,aAAS,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO,QAAQ;AAAA,MACf,OAAO,QAAQ;AAAA,QACb,CAAC,UACC,GAAG,MAAM,QAAQ,KAAK;AAAA,UACpB,aAAa,QAAQ,aAAa,MAAM,QAAQ;AAAA,QAClD,CAAC;AAAA,MACL;AAAA,MACA,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,MAAI,QAAQ,SAAS,GAAG;AACtB,aAAS,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO,QAAQ;AAAA,MACf,OAAO,QAAQ,QAAQ,CAAC,UAAU,iBAAiB,MAAM,UAAU,MAAM,OAAO,CAAC;AAAA,MACjF,MACE,mEACC,QAAQ,QACL,uJAEA;AAAA,IACR,CAAC;AAAA,EACH;AAEA,QAAM,iBACJ,QAAQ,SAAS,KACjB,QAAQ,SAAS,KACjB,4BACA,mBAAmB,SAAS;AAC9B,MAAI,gBAAgB;AAClB,UAAM;AAAA,MACJ;AAAA,IAEF;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,EAAG,OAAM,KAAK,EAAE,MAAM,YAAY,QAAQ,OAAO,SAAS,CAAC;AAEjF,MAAI,aAAa;AACf,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,QACR,eAAe,QAAQ,QAAQ;AAAA,QAC/B,GAAI,YAAY,CAAC,iBAAiB,QAAQ,QAAQ,CAAC,IAAI,CAAC;AAAA,MAC1D;AAAA,IACF,CAAC;AAGD,UAAM;AAAA,MACJ,qBAAqB,QAAQ,SAC1B,IAAI,CAAC,YAAY,QAAQ,QAAQ,EACjC,KAAK,IAAI,CAAC,iDACX,QAAQ,SAAS,WAAW,IAAI,OAAO,MACzC;AAAA,IACF;AAAA,EACF;AAKA,MAAI,CAAC,MAAM,MAAM,SAAS,GAAG;AAC3B,UAAM,KAAK,EAAE,MAAM,SAAS,QAAQ,OAAO,OAAO,cAAc,MAAM,CAAC;AAAA,EACzE;AAEA,QAAM,QAAQ,KAAK,GAAG,KAAK;AAC3B,SAAO,cAAc,IAAI,KAAK,IAAI;AACpC;","names":[]}
@@ -1,29 +0,0 @@
1
- // src/mount.ts
2
- import { act } from "@testing-library/react";
3
- import { renderAgentSurface } from "@agent-surface/testing/react";
4
- var DEFAULT_CLI_CONSUMER = { id: "cli", kind: "test" };
5
- async function mountScenario(config, scenario, options = {}) {
6
- const props = config.scenarios[scenario];
7
- if (!props) {
8
- const known = Object.keys(config.scenarios);
9
- throw new Error(
10
- `unknown scenario "${scenario}" \u2014 this config defines ${known.length > 0 ? known.map((name) => `"${name}"`).join(", ") : "none"}`
11
- );
12
- }
13
- const consumer = options.consumer ?? config.consumer ?? DEFAULT_CLI_CONSUMER;
14
- const mounted = await config.mount({ ...props, scenario });
15
- const surface = await renderAgentSurface(mounted.ui, {
16
- registry: mounted.registry,
17
- consumer
18
- });
19
- await act(async () => {
20
- });
21
- await config.settle?.(mounted);
22
- return { scenario, surface, mounted, app: mounted.app, consumer };
23
- }
24
-
25
- export {
26
- DEFAULT_CLI_CONSUMER,
27
- mountScenario
28
- };
29
- //# sourceMappingURL=chunk-A2G4QLX5.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/mount.ts"],"sourcesContent":["import { act } from \"@testing-library/react\";\nimport { renderAgentSurface, type RenderedAgentSurface } from \"@agent-surface/testing/react\";\nimport type { AgentConsumer } from \"@agent-surface/core\";\nimport type { MountResult, ScenarioProps, SurfaceConfig } from \"./config.js\";\n\nexport const DEFAULT_CLI_CONSUMER: AgentConsumer = { id: \"cli\", kind: \"test\" };\n\nexport interface MountScenarioOptions {\n consumer?: AgentConsumer;\n}\n\nexport interface MountedScenario<TApp> {\n scenario: string;\n surface: RenderedAgentSurface;\n mounted: MountResult<TApp>;\n /**\n * Whatever `mount()` returned as `app`. Typed as `TApp` rather than\n * `TApp | undefined` because a config that never sets it infers `TApp` as\n * `unknown`, and forcing a `!` on every test that *does* set it is worse\n * than trusting the config's own return type.\n */\n app: TApp;\n consumer: AgentConsumer;\n}\n\n/**\n * The one mounting path. `agent-surface inspect` and the Vitest helper both\n * come through here, so a scenario cannot behave one way in CI and another in\n * the terminal — which is the entire reason scenarios live in one file.\n */\nexport async function mountScenario<TScenario extends ScenarioProps, TApp>(\n config: SurfaceConfig<TScenario, TApp>,\n scenario: string,\n options: MountScenarioOptions = {},\n): Promise<MountedScenario<TApp>> {\n const props = config.scenarios[scenario];\n if (!props) {\n const known = Object.keys(config.scenarios);\n throw new Error(\n `unknown scenario \"${scenario}\" — this config defines ${\n known.length > 0 ? known.map((name) => `\"${name}\"`).join(\", \") : \"none\"\n }`,\n );\n }\n\n const consumer = options.consumer ?? config.consumer ?? DEFAULT_CLI_CONSUMER;\n const mounted = await config.mount({ ...props, scenario });\n const surface = await renderAgentSurface(mounted.ui, {\n registry: mounted.registry,\n consumer,\n });\n\n // Mount effects have flushed, but whatever the first render *started* has\n // not settled — the initial fetch that fills a table, typically. This flush\n // drains pending microtasks; `settle` covers anything slower.\n await act(async () => {});\n await config.settle?.(mounted);\n\n return { scenario, surface, mounted, app: mounted.app as TApp, consumer };\n}\n"],"mappings":";AAAA,SAAS,WAAW;AACpB,SAAS,0BAAqD;AAIvD,IAAM,uBAAsC,EAAE,IAAI,OAAO,MAAM,OAAO;AAyB7E,eAAsB,cACpB,QACA,UACA,UAAgC,CAAC,GACD;AAChC,QAAM,QAAQ,OAAO,UAAU,QAAQ;AACvC,MAAI,CAAC,OAAO;AACV,UAAM,QAAQ,OAAO,KAAK,OAAO,SAAS;AAC1C,UAAM,IAAI;AAAA,MACR,qBAAqB,QAAQ,gCAC3B,MAAM,SAAS,IAAI,MAAM,IAAI,CAAC,SAAS,IAAI,IAAI,GAAG,EAAE,KAAK,IAAI,IAAI,MACnE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,QAAQ,YAAY,OAAO,YAAY;AACxD,QAAM,UAAU,MAAM,OAAO,MAAM,EAAE,GAAG,OAAO,SAAS,CAAC;AACzD,QAAM,UAAU,MAAM,mBAAmB,QAAQ,IAAI;AAAA,IACnD,UAAU,QAAQ;AAAA,IAClB;AAAA,EACF,CAAC;AAKD,QAAM,IAAI,YAAY;AAAA,EAAC,CAAC;AACxB,QAAM,OAAO,SAAS,OAAO;AAE7B,SAAO,EAAE,UAAU,SAAS,SAAS,KAAK,QAAQ,KAAa,SAAS;AAC1E;","names":[]}