@agent-surface/cli 0.12.1 → 0.13.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.
Files changed (32) hide show
  1. package/README.md +44 -6
  2. package/dist/bin.js +5 -5
  3. package/dist/{check-POE5VRSE.js → check-TJIX7NDE.js} +111 -53
  4. package/dist/check-TJIX7NDE.js.map +1 -0
  5. package/dist/{chunk-QIVOZAWX.js → chunk-3AJ343NA.js} +12 -3
  6. package/dist/{chunk-QIVOZAWX.js.map → chunk-3AJ343NA.js.map} +1 -1
  7. package/dist/chunk-IALBMW3R.js +278 -0
  8. package/dist/chunk-IALBMW3R.js.map +1 -0
  9. package/dist/{chunk-J2NN3J5N.js → chunk-L7GHSC2Z.js} +146 -222
  10. package/dist/chunk-L7GHSC2Z.js.map +1 -0
  11. package/dist/{chunk-Q5WOLWEW.js → chunk-UGCLJ5JX.js} +26 -286
  12. package/dist/chunk-UGCLJ5JX.js.map +1 -0
  13. package/dist/chunk-VX6GBEP3.js +539 -0
  14. package/dist/chunk-VX6GBEP3.js.map +1 -0
  15. package/dist/{init-LFQ5R3G7.js → init-6XV64LK2.js} +9 -8
  16. package/dist/{init-LFQ5R3G7.js.map → init-6XV64LK2.js.map} +1 -1
  17. package/dist/{ink-P23VKP4H.js → ink-QR7X7TAC.js} +99 -25
  18. package/dist/ink-QR7X7TAC.js.map +1 -0
  19. package/dist/inspect-NJ4J3MPP.js +242 -0
  20. package/dist/inspect-NJ4J3MPP.js.map +1 -0
  21. package/dist/{snapshot-FLXE5UC6.js → snapshot-ZGTAF2Y5.js} +28 -12
  22. package/dist/snapshot-ZGTAF2Y5.js.map +1 -0
  23. package/package.json +4 -4
  24. package/dist/check-POE5VRSE.js.map +0 -1
  25. package/dist/chunk-DYDSJM7R.js +0 -170
  26. package/dist/chunk-DYDSJM7R.js.map +0 -1
  27. package/dist/chunk-J2NN3J5N.js.map +0 -1
  28. package/dist/chunk-Q5WOLWEW.js.map +0 -1
  29. package/dist/ink-P23VKP4H.js.map +0 -1
  30. package/dist/inspect-XMDZBSK2.js +0 -119
  31. package/dist/inspect-XMDZBSK2.js.map +0 -1
  32. package/dist/snapshot-FLXE5UC6.js.map +0 -1
package/README.md CHANGED
@@ -49,10 +49,30 @@ agent-surface check [scenario] # fail drift, gaps, rejections, stale scenar
49
49
  Every command covers all scenarios in the config unless you name one. `inspect` prints each in turn:
50
50
 
51
51
  ```text
52
- 11 authored (upper bound) · 10 call sites across 21 files · 1 domain manifest capability
52
+ SURFACE INSPECT
53
+ Config agent-surface.config.tsx
54
+ Depth full — the source is read and every scenario is mounted
55
+ Scope whole surface — no component-type prefix filter
56
+ Scenarios 1 of 2 — admin
57
+
58
+ SURFACE SUMMARY
59
+ Reach 11/11 authored capabilities reached
60
+ Callable 9/11 mounted capabilities are callable in at least one scenario · 2 never callable
61
+ Risk 1 destructive · 1 confirmation-gated · 1 with bound input
62
+ Domain 1 capability reached against the authoritative oRPC manifest
63
+ Catalog every call site read
64
+ Scenarios 1 mounted
65
+ Verdict every authored capability is reached by a scenario
66
+
67
+ STATIC CATALOG
68
+ STATUS COMPLETE — every capability identity resolved
69
+ Capabilities 11 authored (upper bound) · 10 resolved call sites
70
+ Program 21 files analyzed · 40 agent-surface implementation files excluded
71
+ Metadata 5 call sites partially read · identity remains resolved
72
+ Domain 1 manifest capability
53
73
 
54
74
  scenario admin route /devices
55
- 9 callable, 2 visible-disabled, 0 hidden
75
+ 9 callable, 2 visible-disabled, 0 hidden · 1 destructive, 1 confirmation-gated
56
76
 
57
77
  CAPABILITY KIND EFFECT STATE FLAGS
58
78
  app.navigation.goTo action navigation callable reversible
@@ -62,11 +82,9 @@ devices.drawer.close action local-state disabled reversible
62
82
  devices.table.sort action local-state callable idempotent · reversible
63
83
  devices.disable procedure destructive disabled confirmation:required · deviceIds bound+locked
64
84
  ⤷ Select at least one device first
65
-
66
- 11 authored · 11 reached · 0 unreached · 1 scenario (admin)
67
85
  ```
68
86
 
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".
87
+ Summaries first, details after. The run header states what every number below it is relative to — the config, the depth, the scope, the scenarios and prints before the mounts it will spend its time on. Each scenario's own table repeats the qualifier that is local to it: a surface is a projection of one mounted context, never "the app".
70
88
 
71
89
  `--detail` shows full capability, origin, and diagnostic detail; `--explain` and `--schemas` imply it.
72
90
 
@@ -110,8 +128,28 @@ A route nobody visits never registers, so it is in no snapshot and drifts agains
110
128
  UNREACHED — authored, and no scenario mounts it (1)
111
129
  CAPABILITY ORIGIN
112
130
  view:cov.unmounted.toCsv Unmounted.tsx:26
131
+ → add a scenario that mounts them, delete the dead component, or record the decision in
132
+ .agent-surface/coverage-allow.json
133
+ ```
113
134
 
114
- 3 authored · 2 reached · 1 unreached · 1 scenario (default)
135
+ `check` says the same thing as a report a verdict, the health matrix behind it, one row per scenario, then the findings and the commands that clear them:
136
+
137
+ ```text
138
+ SURFACE CHECK FAIL
139
+ Config agent-surface.config.tsx
140
+ Depth full — the source is read and every scenario is mounted
141
+ Scope whole surface — no component-type prefix filter
142
+
143
+ Coverage FAIL 2/3 authored capabilities reached · 1 unreached
144
+ Catalog PASS all static sites resolved
145
+ Domain PASS 1 manifest capability reached
146
+ Baselines FAIL 1/2 scenario baselines current
147
+ Runtime PASS 2 scenarios mounted
148
+
149
+ SCENARIOS (2)
150
+ SCENARIO ROUTE CALLABLE DISABLED HIDDEN REJECTED BASELINE
151
+ admin /devices 9 2 0 — drift (1)
152
+ anonymous /devices 0 0 11 — current
115
153
  ```
116
154
 
117
155
  `inspect` reports it and `snapshot` reports it; **`check` fails on it**. It also fails on an unread call site, because the catalog is `unreached`'s denominator and holes in it make that count a floor rather than an answer — pass `--allow-unresolved` to accept that knowingly, which still prints the gap. Adoption ratchets through a committed `.agent-surface/coverage-allow.json`, whose stale entries fail the command.
package/dist/bin.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  isDepth,
6
6
  write,
7
7
  writeError
8
- } from "./chunk-QIVOZAWX.js";
8
+ } from "./chunk-3AJ343NA.js";
9
9
 
10
10
  // src/bin.ts
11
11
  import { realpathSync } from "fs";
@@ -131,7 +131,7 @@ async function main(argv = process.argv.slice(2)) {
131
131
  const depth = values.depth;
132
132
  try {
133
133
  if (command === "init") {
134
- const { runInit } = await import("./init-LFQ5R3G7.js");
134
+ const { runInit } = await import("./init-6XV64LK2.js");
135
135
  return await runInit({
136
136
  cwd: process.cwd(),
137
137
  ...values.tsconfig ? { tsconfig: values.tsconfig } : {},
@@ -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-XMDZBSK2.js");
161
+ const { runInspect } = await import("./inspect-NJ4J3MPP.js");
162
162
  return await runInspect({
163
163
  ...shared,
164
164
  ...values.detail ? { detail: true } : {},
@@ -167,10 +167,10 @@ async function main(argv = process.argv.slice(2)) {
167
167
  });
168
168
  }
169
169
  if (command === "snapshot") {
170
- const { runSnapshot } = await import("./snapshot-FLXE5UC6.js");
170
+ const { runSnapshot } = await import("./snapshot-ZGTAF2Y5.js");
171
171
  return await runSnapshot(shared);
172
172
  }
173
- const { runCheck } = await import("./check-POE5VRSE.js");
173
+ const { runCheck } = await import("./check-TJIX7NDE.js");
174
174
  return await runCheck({
175
175
  ...shared,
176
176
  ...values.detail ? { detail: true } : {},
@@ -4,33 +4,39 @@ import {
4
4
  renderCoveragePlain,
5
5
  renderDriftPlain,
6
6
  renderFailuresPlain,
7
+ renderNextStepsPlain,
7
8
  renderNoVerdictPlain,
9
+ renderSectionsPlain,
8
10
  scenarioBaseline
9
- } from "./chunk-J2NN3J5N.js";
10
- import "./chunk-DYDSJM7R.js";
11
+ } from "./chunk-L7GHSC2Z.js";
12
+ import {
13
+ displayPath,
14
+ scenarioStats
15
+ } from "./chunk-VX6GBEP3.js";
11
16
  import {
12
- ALLOWLIST_FILE,
13
17
  SCENARIO_MANIFEST_FILE,
14
- UNREAD_ALLOWLIST_FILE,
15
18
  annotate,
16
19
  baselinePath,
17
- coverageExitCode,
18
20
  diff,
19
21
  joinCoverage,
20
22
  mountScenarios,
21
23
  readBaseline,
22
24
  readInventory,
23
25
  readScenarioManifest
24
- } from "./chunk-Q5WOLWEW.js";
26
+ } from "./chunk-IALBMW3R.js";
25
27
  import {
26
28
  UsageError,
27
29
  write,
28
30
  writeError
29
- } from "./chunk-QIVOZAWX.js";
31
+ } from "./chunk-3AJ343NA.js";
32
+ import {
33
+ ALLOWLIST_FILE,
34
+ UNREAD_ALLOWLIST_FILE,
35
+ coverageExitCode
36
+ } from "./chunk-UGCLJ5JX.js";
30
37
 
31
38
  // src/commands/check.ts
32
39
  import { existsSync, readdirSync } from "fs";
33
- import { relative } from "path";
34
40
  async function runCheck(options) {
35
41
  if (options.depth === "static") {
36
42
  throw new UsageError(
@@ -91,6 +97,28 @@ async function runCheck(options) {
91
97
  );
92
98
  return couldNotRun ? 2 : ok ? 0 : 1;
93
99
  }
100
+ const missing = drifted.filter((entry) => entry.missingBaseline);
101
+ const changed = drifted.filter((entry) => !entry.missingBaseline);
102
+ const baselineOf = (scenario) => {
103
+ const entry = drifted.find((candidate) => candidate.scenario === scenario);
104
+ if (!entry) return "current";
105
+ if (entry.missingBaseline) return "missing";
106
+ return `drift (${entry.entries.length})`;
107
+ };
108
+ const stats = runtime.scenarios.map((scenario) => {
109
+ const result = runtime.results.find((candidate) => candidate.scenario === scenario);
110
+ return result ? { ...scenarioStats(result), baseline: baselineOf(scenario) } : {
111
+ scenario,
112
+ callable: 0,
113
+ disabled: 0,
114
+ hidden: 0,
115
+ rejected: 0,
116
+ failed: true,
117
+ ...runtime.failures.find((failure) => failure.scenario === scenario)?.message ? {
118
+ failure: runtime.failures.find((failure) => failure.scenario === scenario).message
119
+ } : {}
120
+ };
121
+ });
94
122
  const overview = renderCheckOverviewPlain({
95
123
  status: couldNotRun ? "ERROR" : ok ? "PASS" : "FAIL",
96
124
  ...coverage ? { coverage } : {},
@@ -100,7 +128,13 @@ async function runCheck(options) {
100
128
  scenarioManifestOk: !scenarioManifestMismatch && staleBaselineFiles.length === 0,
101
129
  rejected: rejected.reduce((sum, entry) => sum + entry.rejections.length, 0),
102
130
  mountFailures: runtime.failures.length,
103
- scenarios: runtime.scenarios
131
+ scenarios: runtime.scenarios,
132
+ context: {
133
+ configPath: options.configPath,
134
+ depth: options.depth,
135
+ ...runtime.scope ? { scope: runtime.scope } : {}
136
+ },
137
+ stats
104
138
  });
105
139
  if (ok) write(overview);
106
140
  else writeError(overview);
@@ -116,68 +150,92 @@ ${rendered}`);
116
150
  ${rendered}`);
117
151
  }
118
152
  }
119
- const missing = drifted.filter((entry) => entry.missingBaseline);
120
- const changed = drifted.filter((entry) => !entry.missingBaseline);
153
+ const sections = [];
154
+ const steps = [];
155
+ if (coverage && coverage.unreached.length > 0) {
156
+ steps.push(
157
+ `mount the ${coverage.unreached.length} unreached capabilit${coverage.unreached.length === 1 ? "y" : "ies"} from a scenario, or record the decision in ${displayPath(coverage.allowlistPath)}`
158
+ );
159
+ }
160
+ if (coverage && coverage.unresolved.length > 0 && options.allowUnresolved !== true) {
161
+ steps.push(
162
+ `make the ${coverage.unresolved.length} unread call site${coverage.unresolved.length === 1 ? "" : "s"} readable, or paste each printed key into ${displayPath(coverage.unreadAllowlistPath)}`
163
+ );
164
+ }
121
165
  if (rejected.length > 0) {
122
- writeError(
123
- [
124
- `${coverage ? "\n" : ""}REJECTED REGISTRATIONS \u2014 the runtime refused authored surface (${rejected.reduce((sum, entry) => sum + entry.rejections.length, 0)})`,
125
- ...rejected.flatMap(
126
- (entry) => entry.rejections.map(
127
- (rejection) => ` ${entry.scenario}: ${rejection.componentType}@${rejection.instanceId} (${rejection.reason})`
128
- )
166
+ const total = rejected.reduce((sum, entry) => sum + entry.rejections.length, 0);
167
+ sections.push({
168
+ title: "REJECTED REGISTRATIONS",
169
+ gloss: "the runtime refused authored surface during the mount",
170
+ count: total,
171
+ lines: rejected.flatMap(
172
+ (entry) => entry.rejections.map(
173
+ (rejection) => `${entry.scenario}: ${rejection.componentType}@${rejection.instanceId} (${rejection.reason})`
129
174
  )
130
- ].join("\n")
175
+ ),
176
+ hint: "a dead handle registers nothing \u2014 give the second registration its own instanceId, or remove the duplicated component type"
177
+ });
178
+ steps.push(
179
+ `resolve ${total} rejected registration${total === 1 ? "" : "s"} \u2014 the capabilities behind them reach no agent`
131
180
  );
132
181
  }
133
182
  if (scenarioManifestMismatch || staleBaselineFiles.length > 0) {
134
- const committed = committedScenarios?.join(", ") ?? "missing";
135
- writeError(
136
- [
137
- `${coverage || rejected.length > 0 ? "\n" : ""}SCENARIO DRIFT \u2014 committed baselines do not match config`,
138
- ` config: ${declared.join(", ")}`,
139
- ` manifest: ${committed}`,
140
- ...staleBaselineFiles.length > 0 ? [` stale baselines: ${staleBaselineFiles.join(", ")}`] : [],
141
- " run `agent-surface snapshot` and commit the manifest"
142
- ].join("\n")
143
- );
183
+ sections.push({
184
+ title: "SCENARIO DRIFT",
185
+ gloss: "the committed baselines do not match the config",
186
+ count: 0,
187
+ lines: [
188
+ `config: ${declared.join(", ")}`,
189
+ `manifest: ${committedScenarios?.join(", ") ?? "missing"}`,
190
+ ...staleBaselineFiles.length > 0 ? [`stale: ${staleBaselineFiles.join(", ")}`] : []
191
+ ],
192
+ hint: "run `agent-surface snapshot`, commit the manifest, and delete any baseline for a scenario the config no longer declares"
193
+ });
144
194
  }
145
195
  if (missing.length > 0) {
146
- writeError(
147
- [
148
- `${coverage ? "\n" : ""}NO BASELINE \u2014 nothing to compare against, which is not the same as a match (${missing.length})`,
149
- ...missing.map(
150
- (entry) => ` ${entry.scenario}: ${relative(
151
- process.cwd(),
152
- baselinePath(runtime.baselineDir, entry.scenario)
153
- )} does not exist \u2014 run \`agent-surface snapshot\` and commit it`
154
- )
155
- ].join("\n")
156
- );
196
+ sections.push({
197
+ title: "NO BASELINE",
198
+ gloss: "nothing to compare against, which is not the same as a match",
199
+ count: missing.length,
200
+ lines: missing.map(
201
+ (entry) => `${entry.scenario}: ${displayPath(
202
+ baselinePath(runtime.baselineDir, entry.scenario)
203
+ )} does not exist`
204
+ ),
205
+ hint: "run `agent-surface snapshot` and commit the files it writes"
206
+ });
157
207
  }
158
208
  if (changed.length > 0) {
159
- writeError(
160
- [
161
- `${coverage || missing.length > 0 ? "\n" : ""}DRIFT \u2014 the surface changed against its baseline (${changed.length})`,
162
- ...changed.map((entry) => renderDriftPlain(entry.scenario, entry.entries)),
163
- "",
164
- `surface drift in ${changed.length} scenario${changed.length === 1 ? "" : "s"} \u2014 review the change, then \`agent-surface snapshot\` to accept it`
165
- ].join("\n")
209
+ sections.push({
210
+ title: "DRIFT",
211
+ gloss: "the surface changed against its baseline",
212
+ count: changed.length,
213
+ lines: changed.flatMap((entry) => renderDriftPlain(entry.scenario, entry.entries)),
214
+ 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" : "")
215
+ });
216
+ }
217
+ const baselinesStale = missing.length > 0 || changed.length > 0 || scenarioManifestMismatch || staleBaselineFiles.length > 0;
218
+ if (baselinesStale) {
219
+ steps.push(
220
+ "`agent-surface snapshot`, then commit .agent-surface/ \u2014 this accepts the surface above as reviewed"
166
221
  );
167
222
  }
223
+ if (sections.length > 0) writeError(`
224
+ ${renderSectionsPlain(sections)}`);
168
225
  if (couldNotRun) {
169
226
  writeError(`
170
227
  ${renderFailuresPlain(runtime.failures)}`);
171
228
  if (inventory) writeError(`
172
229
  ${renderNoVerdictPlain(runtime.failures)}`);
173
- return 2;
174
- }
175
- if (ok) {
176
- return 0;
230
+ steps.unshift(
231
+ `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`
232
+ );
177
233
  }
178
- return 1;
234
+ if (!ok && steps.length > 0) writeError(`
235
+ ${renderNextStepsPlain(steps)}`);
236
+ return couldNotRun ? 2 : ok ? 0 : 1;
179
237
  }
180
238
  export {
181
239
  runCheck
182
240
  };
183
- //# sourceMappingURL=check-POE5VRSE.js.map
241
+ //# sourceMappingURL=check-TJIX7NDE.js.map
@@ -0,0 +1 @@
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 {\n renderCoveragePlain,\n renderCheckOverviewPlain,\n renderDriftPlain,\n renderFailuresPlain,\n renderNextStepsPlain,\n renderNoVerdictPlain,\n renderSectionsPlain,\n} from \"../render/plain.js\";\nimport {\n displayPath,\n scenarioStats,\n type FindingSection,\n type ScenarioStats,\n} from \"../render/summary.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 * 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. Output is always plain,\n * with no rendering framework in its path at all: this is a report that gets\n * pasted into a pull request and read out of a CI log, and neither of those is\n * 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 = (\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 return result\n ? { ...scenarioStats(result), baseline: baselineOf(scenario) }\n : {\n scenario,\n callable: 0,\n disabled: 0,\n hidden: 0,\n rejected: 0,\n failed: true,\n ...(runtime.failures.find((failure) => failure.scenario === scenario)?.message\n ? {\n failure: runtime.failures.find((failure) => failure.scenario === scenario)!.message,\n }\n : {}),\n };\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 context: {\n configPath: options.configPath,\n depth: options.depth,\n ...(runtime.scope ? { scope: runtime.scope } : {}),\n },\n stats,\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 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\n ? [`stale: ${staleBaselineFiles.join(\", \")}`]\n : []),\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) writeError(`\\n${renderSectionsPlain(sections)}`);\n\n if (couldNotRun) {\n writeError(`\\n${renderFailuresPlain(runtime.failures)}`);\n if (inventory) writeError(`\\n${renderNoVerdictPlain(runtime.failures)}`);\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) writeError(`\\n${renderNextStepsPlain(steps)}`);\n\n return couldNotRun ? 2 : ok ? 0 : 1;\n}\n"],"mappings":"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","names":[]}
@@ -141,7 +141,7 @@ var cached;
141
141
  async function loadInk() {
142
142
  if (cached !== void 0) return cached;
143
143
  try {
144
- cached = await import("./ink-P23VKP4H.js");
144
+ cached = await import("./ink-QR7X7TAC.js");
145
145
  } catch {
146
146
  cached = null;
147
147
  }
@@ -153,6 +153,14 @@ async function paint(element) {
153
153
  instance.unmount();
154
154
  await instance.waitUntilExit();
155
155
  }
156
+ async function transient(element) {
157
+ const { render } = await import("ink");
158
+ const instance = render(element);
159
+ return () => {
160
+ instance.clear();
161
+ instance.unmount();
162
+ };
163
+ }
156
164
 
157
165
  export {
158
166
  DEPTHS,
@@ -164,6 +172,7 @@ export {
164
172
  write,
165
173
  writeError,
166
174
  loadInk,
167
- paint
175
+ paint,
176
+ transient
168
177
  };
169
- //# sourceMappingURL=chunk-QIVOZAWX.js.map
178
+ //# sourceMappingURL=chunk-3AJ343NA.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/contract.ts","../src/load.ts","../src/output.ts"],"sourcesContent":["/**\n * The vocabulary every layer shares, and nothing else.\n *\n * It is its own module because `bin.ts` needs both of these before it has\n * decided which command to run, and everything else in this package pulls in\n * either the TypeScript compiler or Vite the moment it is imported. A `--help`\n * that boots a TypeScript program to print a paragraph is a `--help` nobody\n * runs twice.\n */\n\nexport const DEPTHS = [\"static\", \"runtime\", \"full\"] as const;\n\n/**\n * How much of the surface a command is asked to compute.\n *\n * A presentation surface has two sources of truth and every command needs some\n * mix of both — the **catalog** this codebase authors, which is static, and the\n * **projection** a mounted scenario surfaces, which is not. Splitting those\n * across separate commands is what let a green `check` sit on top of a route no\n * scenario visits, so the split lives here instead.\n *\n * `static` reads the TypeScript program and mounts nothing — no Vite server, no\n * jsdom, no scenarios. It is the only depth that survives an app which will not\n * mount, and the only one that needs no scenarios to exist yet.\n *\n * `runtime` mounts and skips the program read, for a repository whose tsconfig\n * is wide enough that booting it costs more than the answer is worth.\n *\n * `full` does both and joins them, which is the only depth that can answer\n * *did we author something no scenario reaches*. It is the default because a\n * tool that has to be asked for the complete answer mostly gives the\n * incomplete one.\n */\nexport type Depth = (typeof DEPTHS)[number];\n\nexport function isDepth(value: unknown): value is Depth {\n return typeof value === \"string\" && (DEPTHS as readonly string[]).includes(value);\n}\n\n/**\n * The caller asked for something impossible — as opposed to the app being\n * broken, which is what a mount failure is. Both exit `2`: CI has to tell \"the\n * surface changed\" apart from \"the tool never ran\", and these are both the\n * second one.\n */\nexport class UsageError extends Error {}\n","import { existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createServer, type ViteDevServer } from \"vite\";\nimport { ViteNodeServer } from \"vite-node/server\";\nimport { ViteNodeRunner } from \"vite-node/client\";\nimport { installSourcemapsSupport } from \"vite-node/source-map\";\nimport type { CollectOptions, CollectResult } from \"./collect.js\";\nimport type { SurfaceConfig } from \"./config.js\";\n\nconst CONFIG_NAMES = [\n \"agent-surface.config.tsx\",\n \"agent-surface.config.ts\",\n \"agent-surface.config.mjs\",\n \"agent-surface.config.js\",\n];\n\n/** Walks up from `from` looking for an `agent-surface.config.*`. */\nexport function findConfig(from: string = process.cwd()): string | undefined {\n let dir = resolve(from);\n for (;;) {\n for (const name of CONFIG_NAMES) {\n const candidate = join(dir, name);\n if (existsSync(candidate)) return candidate;\n }\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\n/** `dist/collect.js` when installed; `src/collect.ts` when run from source. */\nfunction collectorPath(): string {\n for (const ext of [\"js\", \"ts\"]) {\n const candidate = fileURLToPath(new URL(`./collect.${ext}`, import.meta.url));\n if (existsSync(candidate)) return candidate;\n }\n throw new Error(\"could not locate the agent-surface collector module\");\n}\n\nexport interface SurfaceRunner {\n config: SurfaceConfig;\n scenarioNames: string[];\n collect(options: CollectOptions): Promise<CollectResult>;\n close(): Promise<void>;\n}\n\n/**\n * Boots a Vite dev server on the app's own config, so the config file and the\n * app modules it imports are transformed and resolved exactly as the app\n * resolves them — its aliases, its plugins, its TSX.\n */\nexport async function createSurfaceRunner(configPath: string): Promise<SurfaceRunner> {\n const absoluteConfig = isAbsolute(configPath) ? configPath : resolve(configPath);\n if (!existsSync(absoluteConfig)) {\n throw new Error(`config not found: ${absoluteConfig}`);\n }\n const root = dirname(absoluteConfig);\n\n let server: ViteDevServer;\n try {\n server = await createServer({\n root,\n logLevel: \"error\",\n // `serve` so plugins behave as they do in dev; nothing is ever served.\n server: { middlewareMode: true, watch: null, fs: { strict: false } },\n optimizeDeps: { noDiscovery: true, include: [] },\n resolve: {\n // Both halves of the graph must agree on these. React because two\n // copies break hooks; core because `explainSurface` finds the registry\n // through a Symbol, which is per-module-instance (see collect.ts).\n dedupe: [\n \"react\",\n \"react-dom\",\n \"@agent-surface/core\",\n \"@agent-surface/react\",\n \"@agent-surface/testing\",\n ],\n },\n });\n } catch (error) {\n throw new Error(\n `could not start Vite for ${root}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n try {\n await server.pluginContainer.buildStart({});\n } catch {\n // Vite keeps moving this; a plugin that needs buildStart will say so itself.\n }\n\n const nodeServer = new ViteNodeServer(server);\n installSourcemapsSupport({ getSourceMap: (source) => nodeServer.getSourceMap(source) });\n\n const runner = new ViteNodeRunner({\n root: server.config.root,\n base: server.config.base,\n fetchModule: (id) => nodeServer.fetchModule(id),\n resolveId: (id, importer) => nodeServer.resolveId(id, importer),\n });\n\n const close = async (): Promise<void> => {\n await server.close();\n };\n\n try {\n const configModule = (await runner.executeFile(absoluteConfig)) as {\n default?: SurfaceConfig;\n };\n const config = configModule.default;\n if (!config || typeof config.mount !== \"function\") {\n throw new Error(\n `${absoluteConfig} must \\`export default defineSurface({ mount, scenarios })\\``,\n );\n }\n const scenarioNames = Object.keys(config.scenarios ?? {});\n if (scenarioNames.length === 0) {\n throw new Error(`${absoluteConfig} defines no scenarios`);\n }\n\n // Same runner ⇒ same module graph ⇒ the collector shares React and core\n // with the app tree it is about to mount.\n const collector = (await runner.executeFile(collectorPath())) as {\n collect(config: SurfaceConfig, options: CollectOptions): Promise<CollectResult>;\n };\n\n return {\n config,\n scenarioNames,\n collect: async (options) => {\n // Scoped to the mount, never process-wide: `act()` needs it, and Ink\n // renders its own React tree afterwards — with the flag still set,\n // every frame of the CLI's own UI prints React's \"not wrapped in\n // act(...)\" warning at the user.\n const globals = globalThis as Record<string, unknown>;\n const previous = globals[\"IS_REACT_ACT_ENVIRONMENT\"];\n globals[\"IS_REACT_ACT_ENVIRONMENT\"] = true;\n try {\n return await collector.collect(config, options);\n } finally {\n globals[\"IS_REACT_ACT_ENVIRONMENT\"] = previous;\n }\n },\n close,\n };\n } catch (error) {\n await close();\n throw error;\n }\n}\n","import type { ReactElement } from \"react\";\n\nexport interface OutputFlags {\n plain?: boolean;\n json?: boolean;\n}\n\n/**\n * Terminal-aware only when there is a terminal. Piped output, `--plain`, `CI`\n * and `NO_COLOR` all fall back to plain text — a CLI whose output changes shape\n * when redirected is unusable in a build log.\n */\nexport function isPlain(flags: OutputFlags): boolean {\n if (flags.json) return true;\n if (flags.plain) return true;\n if (process.env[\"CI\"]) return true;\n if (process.env[\"NO_COLOR\"]) return true;\n if (process.stdout.isTTY !== true) return true;\n // A TTY that cannot report its width (some CI ptys, `script` on macOS) makes\n // Ink lay out at zero columns and emit one character per line. Plain text is\n // the only honest rendering for a terminal whose size is unknown.\n return !process.stdout.columns;\n}\n\nexport function write(text: string): void {\n process.stdout.write(`${text}\\n`);\n}\n\nexport function writeError(text: string): void {\n process.stderr.write(`${text}\\n`);\n}\n\ntype InkModule = typeof import(\"./render/ink.js\");\n\nlet cached: InkModule | null | undefined;\n\n/**\n * Loads the Ink renderer, or returns `null` when it cannot run here.\n *\n * Two reasons this is lazy rather than a top-level import. It keeps `--plain`\n * and `--json` from paying for a terminal UI they never draw — and Ink drives\n * React through `react-reconciler`, which reads React 19 internals, so a host\n * that pins React 18 globally cannot load it at all. Neither is a reason to\n * fail a command that was about to print text.\n */\nexport async function loadInk(): Promise<InkModule | null> {\n if (cached !== undefined) return cached;\n try {\n cached = await import(\"./render/ink.js\");\n } catch {\n cached = null;\n }\n return cached;\n}\n\n/** Paints an Ink element once and returns when the frame has been flushed. */\nexport async function paint(element: ReactElement): Promise<void> {\n const { render } = await import(\"ink\");\n const instance = render(element);\n instance.unmount();\n await instance.waitUntilExit();\n}\n\n/** A live Ink frame (spinner) that is cleared before the real output lands. */\nexport async function transient(element: ReactElement): Promise<() => void> {\n const { render } = await import(\"ink\");\n const instance = render(element);\n return () => {\n instance.clear();\n instance.unmount();\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/contract.ts","../src/load.ts","../src/output.ts"],"sourcesContent":["/**\n * The vocabulary every layer shares, and nothing else.\n *\n * It is its own module because `bin.ts` needs both of these before it has\n * decided which command to run, and everything else in this package pulls in\n * either the TypeScript compiler or Vite the moment it is imported. A `--help`\n * that boots a TypeScript program to print a paragraph is a `--help` nobody\n * runs twice.\n */\n\nexport const DEPTHS = [\"static\", \"runtime\", \"full\"] as const;\n\n/**\n * How much of the surface a command is asked to compute.\n *\n * A presentation surface has two sources of truth and every command needs some\n * mix of both — the **catalog** this codebase authors, which is static, and the\n * **projection** a mounted scenario surfaces, which is not. Splitting those\n * across separate commands is what let a green `check` sit on top of a route no\n * scenario visits, so the split lives here instead.\n *\n * `static` reads the TypeScript program and mounts nothing — no Vite server, no\n * jsdom, no scenarios. It is the only depth that survives an app which will not\n * mount, and the only one that needs no scenarios to exist yet.\n *\n * `runtime` mounts and skips the program read, for a repository whose tsconfig\n * is wide enough that booting it costs more than the answer is worth.\n *\n * `full` does both and joins them, which is the only depth that can answer\n * *did we author something no scenario reaches*. It is the default because a\n * tool that has to be asked for the complete answer mostly gives the\n * incomplete one.\n */\nexport type Depth = (typeof DEPTHS)[number];\n\nexport function isDepth(value: unknown): value is Depth {\n return typeof value === \"string\" && (DEPTHS as readonly string[]).includes(value);\n}\n\n/**\n * The caller asked for something impossible — as opposed to the app being\n * broken, which is what a mount failure is. Both exit `2`: CI has to tell \"the\n * surface changed\" apart from \"the tool never ran\", and these are both the\n * second one.\n */\nexport class UsageError extends Error {}\n","import { existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createServer, type ViteDevServer } from \"vite\";\nimport { ViteNodeServer } from \"vite-node/server\";\nimport { ViteNodeRunner } from \"vite-node/client\";\nimport { installSourcemapsSupport } from \"vite-node/source-map\";\nimport type { CollectOptions, CollectResult } from \"./collect.js\";\nimport type { SurfaceConfig } from \"./config.js\";\n\nconst CONFIG_NAMES = [\n \"agent-surface.config.tsx\",\n \"agent-surface.config.ts\",\n \"agent-surface.config.mjs\",\n \"agent-surface.config.js\",\n];\n\n/** Walks up from `from` looking for an `agent-surface.config.*`. */\nexport function findConfig(from: string = process.cwd()): string | undefined {\n let dir = resolve(from);\n for (;;) {\n for (const name of CONFIG_NAMES) {\n const candidate = join(dir, name);\n if (existsSync(candidate)) return candidate;\n }\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\n/** `dist/collect.js` when installed; `src/collect.ts` when run from source. */\nfunction collectorPath(): string {\n for (const ext of [\"js\", \"ts\"]) {\n const candidate = fileURLToPath(new URL(`./collect.${ext}`, import.meta.url));\n if (existsSync(candidate)) return candidate;\n }\n throw new Error(\"could not locate the agent-surface collector module\");\n}\n\nexport interface SurfaceRunner {\n config: SurfaceConfig;\n scenarioNames: string[];\n collect(options: CollectOptions): Promise<CollectResult>;\n close(): Promise<void>;\n}\n\n/**\n * Boots a Vite dev server on the app's own config, so the config file and the\n * app modules it imports are transformed and resolved exactly as the app\n * resolves them — its aliases, its plugins, its TSX.\n */\nexport async function createSurfaceRunner(configPath: string): Promise<SurfaceRunner> {\n const absoluteConfig = isAbsolute(configPath) ? configPath : resolve(configPath);\n if (!existsSync(absoluteConfig)) {\n throw new Error(`config not found: ${absoluteConfig}`);\n }\n const root = dirname(absoluteConfig);\n\n let server: ViteDevServer;\n try {\n server = await createServer({\n root,\n logLevel: \"error\",\n // `serve` so plugins behave as they do in dev; nothing is ever served.\n server: { middlewareMode: true, watch: null, fs: { strict: false } },\n optimizeDeps: { noDiscovery: true, include: [] },\n resolve: {\n // Both halves of the graph must agree on these. React because two\n // copies break hooks; core because `explainSurface` finds the registry\n // through a Symbol, which is per-module-instance (see collect.ts).\n dedupe: [\n \"react\",\n \"react-dom\",\n \"@agent-surface/core\",\n \"@agent-surface/react\",\n \"@agent-surface/testing\",\n ],\n },\n });\n } catch (error) {\n throw new Error(\n `could not start Vite for ${root}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n try {\n await server.pluginContainer.buildStart({});\n } catch {\n // Vite keeps moving this; a plugin that needs buildStart will say so itself.\n }\n\n const nodeServer = new ViteNodeServer(server);\n installSourcemapsSupport({ getSourceMap: (source) => nodeServer.getSourceMap(source) });\n\n const runner = new ViteNodeRunner({\n root: server.config.root,\n base: server.config.base,\n fetchModule: (id) => nodeServer.fetchModule(id),\n resolveId: (id, importer) => nodeServer.resolveId(id, importer),\n });\n\n const close = async (): Promise<void> => {\n await server.close();\n };\n\n try {\n const configModule = (await runner.executeFile(absoluteConfig)) as {\n default?: SurfaceConfig;\n };\n const config = configModule.default;\n if (!config || typeof config.mount !== \"function\") {\n throw new Error(\n `${absoluteConfig} must \\`export default defineSurface({ mount, scenarios })\\``,\n );\n }\n const scenarioNames = Object.keys(config.scenarios ?? {});\n if (scenarioNames.length === 0) {\n throw new Error(`${absoluteConfig} defines no scenarios`);\n }\n\n // Same runner ⇒ same module graph ⇒ the collector shares React and core\n // with the app tree it is about to mount.\n const collector = (await runner.executeFile(collectorPath())) as {\n collect(config: SurfaceConfig, options: CollectOptions): Promise<CollectResult>;\n };\n\n return {\n config,\n scenarioNames,\n collect: async (options) => {\n // Scoped to the mount, never process-wide: `act()` needs it, and Ink\n // renders its own React tree afterwards — with the flag still set,\n // every frame of the CLI's own UI prints React's \"not wrapped in\n // act(...)\" warning at the user.\n const globals = globalThis as Record<string, unknown>;\n const previous = globals[\"IS_REACT_ACT_ENVIRONMENT\"];\n globals[\"IS_REACT_ACT_ENVIRONMENT\"] = true;\n try {\n return await collector.collect(config, options);\n } finally {\n globals[\"IS_REACT_ACT_ENVIRONMENT\"] = previous;\n }\n },\n close,\n };\n } catch (error) {\n await close();\n throw error;\n }\n}\n","import type { ReactElement } from \"react\";\n\nexport interface OutputFlags {\n plain?: boolean;\n json?: boolean;\n}\n\n/**\n * Terminal-aware only when there is a terminal. Piped output, `--plain`, `CI`\n * and `NO_COLOR` all fall back to plain text — a CLI whose output changes shape\n * when redirected is unusable in a build log.\n */\nexport function isPlain(flags: OutputFlags): boolean {\n if (flags.json) return true;\n if (flags.plain) return true;\n if (process.env[\"CI\"]) return true;\n if (process.env[\"NO_COLOR\"]) return true;\n if (process.stdout.isTTY !== true) return true;\n // A TTY that cannot report its width (some CI ptys, `script` on macOS) makes\n // Ink lay out at zero columns and emit one character per line. Plain text is\n // the only honest rendering for a terminal whose size is unknown.\n return !process.stdout.columns;\n}\n\nexport function write(text: string): void {\n process.stdout.write(`${text}\\n`);\n}\n\nexport function writeError(text: string): void {\n process.stderr.write(`${text}\\n`);\n}\n\ntype InkModule = typeof import(\"./render/ink.js\");\n\nlet cached: InkModule | null | undefined;\n\n/**\n * Loads the Ink renderer, or returns `null` when it cannot run here.\n *\n * Two reasons this is lazy rather than a top-level import. It keeps `--plain`\n * and `--json` from paying for a terminal UI they never draw — and Ink drives\n * React through `react-reconciler`, which reads React 19 internals, so a host\n * that pins React 18 globally cannot load it at all. Neither is a reason to\n * fail a command that was about to print text.\n */\nexport async function loadInk(): Promise<InkModule | null> {\n if (cached !== undefined) return cached;\n try {\n cached = await import(\"./render/ink.js\");\n } catch {\n cached = null;\n }\n return cached;\n}\n\n/** Paints an Ink element once and returns when the frame has been flushed. */\nexport async function paint(element: ReactElement): Promise<void> {\n const { render } = await import(\"ink\");\n const instance = render(element);\n instance.unmount();\n await instance.waitUntilExit();\n}\n\n/** A live Ink frame (spinner) that is cleared before the real output lands. */\nexport async function transient(element: ReactElement): Promise<() => void> {\n const { render } = await import(\"ink\");\n const instance = render(element);\n return () => {\n instance.clear();\n instance.unmount();\n };\n}\n"],"mappings":"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","names":[]}