@agent-surface/cli 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +4 -4
- package/dist/{check-BYQ34OVQ.js → check-XU3FYNGJ.js} +3 -3
- package/dist/{chunk-GXXKZTQB.js → chunk-2FG527AM.js} +198 -11
- package/dist/chunk-2FG527AM.js.map +1 -0
- package/dist/{chunk-RXX63JSL.js → chunk-AFLVTBI6.js} +21 -3
- package/dist/chunk-AFLVTBI6.js.map +1 -0
- package/dist/index.d.ts +21 -1
- package/dist/{init-LYYVXFEQ.js → init-ODFEGU3P.js} +2 -2
- package/dist/{inspect-3CBRKTDM.js → inspect-6XKULUH2.js} +3 -3
- package/dist/{snapshot-DJ22WCT4.js → snapshot-YGEDJVTG.js} +3 -3
- package/package.json +4 -4
- package/dist/chunk-GXXKZTQB.js.map +0 -1
- package/dist/chunk-RXX63JSL.js.map +0 -1
- /package/dist/{check-BYQ34OVQ.js.map → check-XU3FYNGJ.js.map} +0 -0
- /package/dist/{init-LYYVXFEQ.js.map → init-ODFEGU3P.js.map} +0 -0
- /package/dist/{inspect-3CBRKTDM.js.map → inspect-6XKULUH2.js.map} +0 -0
- /package/dist/{snapshot-DJ22WCT4.js.map → snapshot-YGEDJVTG.js.map} +0 -0
package/dist/bin.js
CHANGED
|
@@ -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-
|
|
134
|
+
const { runInit } = await import("./init-ODFEGU3P.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-
|
|
161
|
+
const { runInspect } = await import("./inspect-6XKULUH2.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-
|
|
170
|
+
const { runSnapshot } = await import("./snapshot-YGEDJVTG.js");
|
|
171
171
|
return await runSnapshot(shared);
|
|
172
172
|
}
|
|
173
|
-
const { runCheck } = await import("./check-
|
|
173
|
+
const { runCheck } = await import("./check-XU3FYNGJ.js");
|
|
174
174
|
return await runCheck({
|
|
175
175
|
...shared,
|
|
176
176
|
...values["allow-unresolved"] ? { allowUnresolved: true } : {}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
renderDriftPlain,
|
|
4
4
|
renderFailuresPlain,
|
|
5
5
|
renderNoVerdictPlain
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-AFLVTBI6.js";
|
|
7
7
|
import "./chunk-DYDSJM7R.js";
|
|
8
8
|
import {
|
|
9
9
|
annotate,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
normalize,
|
|
16
16
|
readBaseline,
|
|
17
17
|
readInventory
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-2FG527AM.js";
|
|
19
19
|
import {
|
|
20
20
|
UsageError,
|
|
21
21
|
write,
|
|
@@ -119,4 +119,4 @@ ${renderNoVerdictPlain(runtime.failures)}`);
|
|
|
119
119
|
export {
|
|
120
120
|
runCheck
|
|
121
121
|
};
|
|
122
|
-
//# sourceMappingURL=check-
|
|
122
|
+
//# sourceMappingURL=check-XU3FYNGJ.js.map
|
|
@@ -100,10 +100,14 @@ function formatValue(value) {
|
|
|
100
100
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
101
101
|
import { join as join2 } from "path";
|
|
102
102
|
var ALLOWLIST_FILE = "coverage-allow.json";
|
|
103
|
+
var UNREAD_ALLOWLIST_FILE = "unresolved-allow.json";
|
|
103
104
|
function allowlistPathFor(baselineDir) {
|
|
104
105
|
return join2(baselineDir, ALLOWLIST_FILE);
|
|
105
106
|
}
|
|
106
|
-
function
|
|
107
|
+
function unreadAllowlistPathFor(baselineDir) {
|
|
108
|
+
return join2(baselineDir, UNREAD_ALLOWLIST_FILE);
|
|
109
|
+
}
|
|
110
|
+
function readAllowlist(path, keyName = "capabilityId") {
|
|
107
111
|
if (!existsSync(path)) return {};
|
|
108
112
|
let parsed;
|
|
109
113
|
try {
|
|
@@ -114,7 +118,7 @@ function readAllowlist(path) {
|
|
|
114
118
|
);
|
|
115
119
|
}
|
|
116
120
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
117
|
-
throw new Error(`${path} must be a JSON object of { "
|
|
121
|
+
throw new Error(`${path} must be a JSON object of { "${keyName}": "reason" }`);
|
|
118
122
|
}
|
|
119
123
|
const allowlist = {};
|
|
120
124
|
for (const [id, reason] of Object.entries(parsed)) {
|
|
@@ -125,6 +129,9 @@ function readAllowlist(path) {
|
|
|
125
129
|
}
|
|
126
130
|
return allowlist;
|
|
127
131
|
}
|
|
132
|
+
function unreadKey(entry) {
|
|
133
|
+
return `${entry.origin.file}#${entry.reason ?? "unknown"}`;
|
|
134
|
+
}
|
|
128
135
|
function buildCoverageReport(input) {
|
|
129
136
|
const unreached = [];
|
|
130
137
|
const allowed = [];
|
|
@@ -137,6 +144,16 @@ function buildCoverageReport(input) {
|
|
|
137
144
|
unreached.push({ capabilityId: id, origin: input.origins.get(id) ?? { file: "?", line: 0 } });
|
|
138
145
|
}
|
|
139
146
|
const staleAllowlist = Object.keys(input.allowlist).filter((id) => input.reachedIds.has(id) || !input.authored.has(id)).sort();
|
|
147
|
+
const unreadAllowlist = input.unreadAllowlist ?? {};
|
|
148
|
+
const unread = [];
|
|
149
|
+
const allowedUnread = /* @__PURE__ */ new Set();
|
|
150
|
+
for (const entry of input.unresolved) {
|
|
151
|
+
const key = unreadKey(entry);
|
|
152
|
+
if (key in unreadAllowlist) allowedUnread.add(key);
|
|
153
|
+
else unread.push(entry);
|
|
154
|
+
}
|
|
155
|
+
const stillUnread = new Set(input.unresolved.map(unreadKey));
|
|
156
|
+
const staleUnreadAllowlist = Object.keys(unreadAllowlist).filter((key) => !stillUnread.has(key)).sort();
|
|
140
157
|
const unaccounted = [...input.reachedIds].filter((id) => !input.authored.has(id)).sort();
|
|
141
158
|
const domainReached = unaccounted.filter((id) => id.startsWith("domain:"));
|
|
142
159
|
const undeclared = unaccounted.filter((id) => !id.startsWith("domain:"));
|
|
@@ -149,16 +166,20 @@ function buildCoverageReport(input) {
|
|
|
149
166
|
unreached,
|
|
150
167
|
undeclared,
|
|
151
168
|
domainReached,
|
|
152
|
-
unresolved:
|
|
169
|
+
unresolved: unread,
|
|
153
170
|
allowed,
|
|
154
171
|
staleAllowlist,
|
|
155
|
-
allowlistPath: input.allowlistPath
|
|
172
|
+
allowlistPath: input.allowlistPath,
|
|
173
|
+
allowedUnread: [...allowedUnread].sort(),
|
|
174
|
+
staleUnreadAllowlist,
|
|
175
|
+
unreadAllowlistPath: input.unreadAllowlistPath
|
|
156
176
|
};
|
|
157
177
|
}
|
|
158
178
|
function coverageExitCode(report, options = {}) {
|
|
159
179
|
if (report.unreached.length > 0) return 1;
|
|
160
180
|
if (report.unresolved.length > 0 && !options.allowUnresolved) return 1;
|
|
161
181
|
if (report.staleAllowlist.length > 0) return 1;
|
|
182
|
+
if (report.staleUnreadAllowlist.length > 0) return 1;
|
|
162
183
|
return 0;
|
|
163
184
|
}
|
|
164
185
|
|
|
@@ -203,12 +224,43 @@ function propertyOf(object, wanted) {
|
|
|
203
224
|
if (ts.isPropertyAssignment(property) && propertyName(property.name) === wanted) {
|
|
204
225
|
return property.initializer;
|
|
205
226
|
}
|
|
227
|
+
if (ts.isShorthandPropertyAssignment(property) && property.name.text === wanted) {
|
|
228
|
+
return property.name;
|
|
229
|
+
}
|
|
206
230
|
}
|
|
207
231
|
return void 0;
|
|
208
232
|
}
|
|
209
233
|
function hasSpread(object) {
|
|
210
234
|
return object.properties.some((property) => ts.isSpreadAssignment(property));
|
|
211
235
|
}
|
|
236
|
+
var CAPABILITY_GROUPS = ["observations", "actions"];
|
|
237
|
+
function spreadKeys(expression, source, depth = 0) {
|
|
238
|
+
if (depth > 1) return void 0;
|
|
239
|
+
if (ts.isParenthesizedExpression(expression)) {
|
|
240
|
+
return spreadKeys(expression.expression, source, depth);
|
|
241
|
+
}
|
|
242
|
+
if (ts.isConditionalExpression(expression)) {
|
|
243
|
+
const whenTrue = spreadKeys(expression.whenTrue, source, depth);
|
|
244
|
+
const whenFalse = spreadKeys(expression.whenFalse, source, depth);
|
|
245
|
+
if (!whenTrue || !whenFalse) return void 0;
|
|
246
|
+
return [...whenTrue, ...whenFalse];
|
|
247
|
+
}
|
|
248
|
+
const resolved = objectLiteralFor(expression, source);
|
|
249
|
+
if (!resolved.object) return void 0;
|
|
250
|
+
const keys = [];
|
|
251
|
+
for (const property of resolved.object.properties) {
|
|
252
|
+
if (ts.isSpreadAssignment(property)) {
|
|
253
|
+
const nested = spreadKeys(property.expression, source, depth + 1);
|
|
254
|
+
if (!nested) return void 0;
|
|
255
|
+
keys.push(...nested);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const name = ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property) ? propertyName(property.name) : ts.isShorthandPropertyAssignment(property) ? property.name.text : void 0;
|
|
259
|
+
if (name === void 0) return void 0;
|
|
260
|
+
keys.push(name);
|
|
261
|
+
}
|
|
262
|
+
return keys;
|
|
263
|
+
}
|
|
212
264
|
function literalText(node) {
|
|
213
265
|
if (!node) return void 0;
|
|
214
266
|
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
@@ -262,6 +314,7 @@ function capabilitiesFromGroup(group, kind, componentType, componentPartial, emi
|
|
|
262
314
|
kind,
|
|
263
315
|
origin: emit.origin(group),
|
|
264
316
|
resolution: "unresolved",
|
|
317
|
+
reason: "dynamic-group",
|
|
265
318
|
note: `\`${kind}s\` on "${componentType}" is not an object literal: ${resolved.note}`
|
|
266
319
|
});
|
|
267
320
|
return;
|
|
@@ -273,6 +326,7 @@ function capabilitiesFromGroup(group, kind, componentType, componentPartial, emi
|
|
|
273
326
|
kind,
|
|
274
327
|
origin: emit.origin(property),
|
|
275
328
|
resolution: "unresolved",
|
|
329
|
+
reason: "spread-members",
|
|
276
330
|
note: `\`${kind}s\` on "${componentType}" spreads another object, which may contribute capabilities this inventory cannot name`
|
|
277
331
|
});
|
|
278
332
|
continue;
|
|
@@ -284,6 +338,7 @@ function capabilitiesFromGroup(group, kind, componentType, componentPartial, emi
|
|
|
284
338
|
kind,
|
|
285
339
|
origin: emit.origin(property),
|
|
286
340
|
resolution: "unresolved",
|
|
341
|
+
reason: "computed-name",
|
|
287
342
|
note: `a capability on "${componentType}" has a computed name`
|
|
288
343
|
});
|
|
289
344
|
continue;
|
|
@@ -320,7 +375,7 @@ function capabilitiesFromGroup(group, kind, componentType, componentPartial, emi
|
|
|
320
375
|
emit.push(capability);
|
|
321
376
|
}
|
|
322
377
|
}
|
|
323
|
-
function visitCall(call, emit, source) {
|
|
378
|
+
function visitCall(call, emit, source, deferred, enclosing) {
|
|
324
379
|
const callee = calleeName(call);
|
|
325
380
|
if (callee === void 0) return;
|
|
326
381
|
if (GRANULAR_HOOKS.has(callee)) {
|
|
@@ -329,6 +384,7 @@ function visitCall(call, emit, source) {
|
|
|
329
384
|
kind: callee === "useAgentAction" ? "action" : "observation",
|
|
330
385
|
origin: emit.origin(call),
|
|
331
386
|
resolution: "unresolved",
|
|
387
|
+
reason: "granular-hook",
|
|
332
388
|
note: `${callee}() registers against a render-scope link, so its component type is not at this call site`
|
|
333
389
|
});
|
|
334
390
|
return;
|
|
@@ -343,24 +399,47 @@ function visitCall(call, emit, source) {
|
|
|
343
399
|
kind: "action",
|
|
344
400
|
origin: emit.origin(call),
|
|
345
401
|
resolution: "unresolved",
|
|
402
|
+
reason: "dynamic-config",
|
|
346
403
|
note: `${callee}() call site could not be read: ${resolved.note}`
|
|
347
404
|
});
|
|
348
405
|
return;
|
|
349
406
|
}
|
|
350
407
|
const config = resolved.object;
|
|
351
|
-
const
|
|
408
|
+
const typeNode = propertyOf(config, "type");
|
|
409
|
+
const type = literalText(typeNode);
|
|
352
410
|
if (type === void 0) {
|
|
353
|
-
if (callee === "register" &&
|
|
411
|
+
if (callee === "register" && typeNode === void 0) return;
|
|
412
|
+
const slot = enclosing && typeNode && ts.isIdentifier(typeNode) ? parameterSlot(typeNode.text, enclosing.fn) : void 0;
|
|
413
|
+
if (slot && enclosing?.name) {
|
|
414
|
+
deferred.push({ config, source, emit, wrapperName: enclosing.name, slot, site: call });
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
354
417
|
emit.push({
|
|
355
418
|
capabilityId: UNRESOLVED_ID,
|
|
356
419
|
kind: "action",
|
|
357
420
|
origin: emit.origin(call),
|
|
358
421
|
resolution: "unresolved",
|
|
422
|
+
reason: "dynamic-type",
|
|
359
423
|
note: `\`type\` is not a string literal, so no capability id on this component can be determined`
|
|
360
424
|
});
|
|
361
425
|
return;
|
|
362
426
|
}
|
|
363
427
|
const componentPartial = hasSpread(config) ? "the component config spreads another object, so some metadata here may be dynamic" : void 0;
|
|
428
|
+
for (const property of config.properties) {
|
|
429
|
+
if (!ts.isSpreadAssignment(property)) continue;
|
|
430
|
+
const keys = spreadKeys(property.expression, source);
|
|
431
|
+
if (keys && !keys.some((key) => CAPABILITY_GROUPS.includes(key))) {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
emit.push({
|
|
435
|
+
capabilityId: `view:${type}.${UNRESOLVED_ID}`,
|
|
436
|
+
kind: "action",
|
|
437
|
+
origin: emit.origin(property),
|
|
438
|
+
resolution: "unresolved",
|
|
439
|
+
reason: "spread-members",
|
|
440
|
+
note: keys ? `"${type}" spreads ${describeConstruct(property.expression)}, which contributes \`${keys.filter((key) => CAPABILITY_GROUPS.includes(key)).join("`/`")}\` this inventory cannot name` : `"${type}" spreads ${describeConstruct(property.expression)}, whose keys this inventory cannot read \u2014 it may contribute capabilities not listed here`
|
|
441
|
+
});
|
|
442
|
+
}
|
|
364
443
|
capabilitiesFromGroup(
|
|
365
444
|
propertyOf(config, "observations"),
|
|
366
445
|
"observation",
|
|
@@ -378,6 +457,51 @@ function visitCall(call, emit, source) {
|
|
|
378
457
|
source
|
|
379
458
|
);
|
|
380
459
|
}
|
|
460
|
+
function functionLike(node) {
|
|
461
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)) {
|
|
462
|
+
return node;
|
|
463
|
+
}
|
|
464
|
+
return void 0;
|
|
465
|
+
}
|
|
466
|
+
function parameterSlot(name, fn) {
|
|
467
|
+
for (const [index, parameter] of fn.parameters.entries()) {
|
|
468
|
+
if (ts.isIdentifier(parameter.name)) {
|
|
469
|
+
if (parameter.name.text === name) return { index };
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (ts.isObjectBindingPattern(parameter.name)) {
|
|
473
|
+
for (const element of parameter.name.elements) {
|
|
474
|
+
if (!ts.isIdentifier(element.name) || element.name.text !== name) continue;
|
|
475
|
+
const property = element.propertyName && ts.isIdentifier(element.propertyName) ? element.propertyName.text : name;
|
|
476
|
+
return { index, property };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return void 0;
|
|
481
|
+
}
|
|
482
|
+
function callsWrapper(site, wrapper, compilerOptions) {
|
|
483
|
+
const callee = site.call.expression;
|
|
484
|
+
if (!ts.isIdentifier(callee) || callee.text !== wrapper.wrapperName) return false;
|
|
485
|
+
if (site.source.fileName === wrapper.source.fileName) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
for (const statement of site.source.statements) {
|
|
489
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
490
|
+
const clause = statement.importClause;
|
|
491
|
+
if (!clause) continue;
|
|
492
|
+
const named = clause.name?.text === wrapper.wrapperName || clause.namedBindings && ts.isNamedImports(clause.namedBindings) && clause.namedBindings.elements.some((element) => element.name.text === wrapper.wrapperName);
|
|
493
|
+
if (!named) continue;
|
|
494
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
495
|
+
const resolved = ts.resolveModuleName(
|
|
496
|
+
statement.moduleSpecifier.text,
|
|
497
|
+
site.source.fileName,
|
|
498
|
+
compilerOptions,
|
|
499
|
+
ts.sys
|
|
500
|
+
).resolvedModule;
|
|
501
|
+
if (resolved?.resolvedFileName === wrapper.source.fileName) return true;
|
|
502
|
+
}
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
381
505
|
function extractCapabilities(options) {
|
|
382
506
|
const root = resolve2(options.root);
|
|
383
507
|
const tsconfigPath = options.tsconfig ? isAbsolute(options.tsconfig) ? options.tsconfig : join3(root, options.tsconfig) : findTsconfig(root);
|
|
@@ -391,6 +515,8 @@ function extractCapabilities(options) {
|
|
|
391
515
|
const capabilities = [];
|
|
392
516
|
let filesAnalyzed = 0;
|
|
393
517
|
let filesOutsideRoot = 0;
|
|
518
|
+
const deferred = [];
|
|
519
|
+
const callsByName = /* @__PURE__ */ new Map();
|
|
394
520
|
for (const source of program.getSourceFiles()) {
|
|
395
521
|
if (source.isDeclarationFile) continue;
|
|
396
522
|
if (source.fileName.includes("/node_modules/")) continue;
|
|
@@ -406,12 +532,69 @@ function extractCapabilities(options) {
|
|
|
406
532
|
line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1
|
|
407
533
|
})
|
|
408
534
|
};
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
535
|
+
let pendingName;
|
|
536
|
+
const visit = (node, enclosing) => {
|
|
537
|
+
const fn = functionLike(node);
|
|
538
|
+
if (fn) {
|
|
539
|
+
const named = ts.isFunctionDeclaration(node) && node.name ? node.name.text : pendingName;
|
|
540
|
+
enclosing = { fn, ...named ? { name: named } : {} };
|
|
541
|
+
pendingName = void 0;
|
|
542
|
+
} else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
543
|
+
pendingName = node.name.text;
|
|
544
|
+
}
|
|
545
|
+
if (ts.isCallExpression(node)) {
|
|
546
|
+
visitCall(node, emit, source, deferred, enclosing);
|
|
547
|
+
if (ts.isIdentifier(node.expression)) {
|
|
548
|
+
const name = node.expression.text;
|
|
549
|
+
const sites = callsByName.get(name) ?? [];
|
|
550
|
+
sites.push({ call: node, source });
|
|
551
|
+
callsByName.set(name, sites);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
ts.forEachChild(node, (child) => visit(child, enclosing));
|
|
412
555
|
};
|
|
413
556
|
visit(source);
|
|
414
557
|
}
|
|
558
|
+
for (const wrapper of deferred) {
|
|
559
|
+
const sites = (callsByName.get(wrapper.wrapperName) ?? []).filter(
|
|
560
|
+
(site) => callsWrapper(site, wrapper, compilerOptions)
|
|
561
|
+
);
|
|
562
|
+
const types = /* @__PURE__ */ new Map();
|
|
563
|
+
const dynamic = [];
|
|
564
|
+
for (const site of sites) {
|
|
565
|
+
const argument = site.call.arguments[wrapper.slot.index];
|
|
566
|
+
const value = wrapper.slot.property && argument && ts.isObjectLiteralExpression(argument) ? propertyOf(argument, wrapper.slot.property) : argument;
|
|
567
|
+
const text = value ? literalText(value) : void 0;
|
|
568
|
+
if (text !== void 0) types.set(text, site.call);
|
|
569
|
+
else dynamic.push(site);
|
|
570
|
+
}
|
|
571
|
+
for (const type of [...types.keys()].sort()) {
|
|
572
|
+
const componentPartial = hasSpread(wrapper.config) ? "the component config spreads another object, so some metadata here may be dynamic" : void 0;
|
|
573
|
+
for (const [group, kind] of [
|
|
574
|
+
["observations", "observation"],
|
|
575
|
+
["actions", "action"]
|
|
576
|
+
]) {
|
|
577
|
+
capabilitiesFromGroup(
|
|
578
|
+
propertyOf(wrapper.config, group),
|
|
579
|
+
kind,
|
|
580
|
+
type,
|
|
581
|
+
componentPartial,
|
|
582
|
+
wrapper.emit,
|
|
583
|
+
wrapper.source
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (types.size === 0 || dynamic.length > 0) {
|
|
588
|
+
wrapper.emit.push({
|
|
589
|
+
capabilityId: UNRESOLVED_ID,
|
|
590
|
+
kind: "action",
|
|
591
|
+
origin: wrapper.emit.origin(wrapper.site),
|
|
592
|
+
resolution: "unresolved",
|
|
593
|
+
reason: "dynamic-type",
|
|
594
|
+
note: types.size === 0 ? `\`type\` is a parameter of ${wrapper.wrapperName}(), and no call site of it in this program passes a string literal` : `\`type\` is a parameter of ${wrapper.wrapperName}(); ${types.size} call site${types.size === 1 ? "" : "s"} resolved, ${dynamic.length} pass${dynamic.length === 1 ? "es" : ""} a non-literal`
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
415
598
|
capabilities.sort(
|
|
416
599
|
(a, b) => a.capabilityId.localeCompare(b.capabilityId) || a.origin.file.localeCompare(b.origin.file) || a.origin.line - b.origin.line
|
|
417
600
|
);
|
|
@@ -520,7 +703,10 @@ function joinCoverage(inventory, runtime, options) {
|
|
|
520
703
|
const allowlist = Object.fromEntries(
|
|
521
704
|
Object.entries(wholeAllowlist).filter(([id]) => inScope(id))
|
|
522
705
|
);
|
|
706
|
+
const unreadAllowlistPath = unreadAllowlistPathFor(runtime.baselineDir);
|
|
523
707
|
return buildCoverageReport({
|
|
708
|
+
unreadAllowlist: readAllowlist(unreadAllowlistPath, "file#reason"),
|
|
709
|
+
unreadAllowlistPath,
|
|
524
710
|
authored,
|
|
525
711
|
origins,
|
|
526
712
|
reachedIds,
|
|
@@ -541,6 +727,7 @@ export {
|
|
|
541
727
|
annotate,
|
|
542
728
|
diff,
|
|
543
729
|
formatValue,
|
|
730
|
+
unreadKey,
|
|
544
731
|
coverageExitCode,
|
|
545
732
|
findTsconfig,
|
|
546
733
|
extractCapabilities,
|
|
@@ -550,4 +737,4 @@ export {
|
|
|
550
737
|
mountScenarios,
|
|
551
738
|
joinCoverage
|
|
552
739
|
};
|
|
553
|
-
//# sourceMappingURL=chunk-
|
|
740
|
+
//# sourceMappingURL=chunk-2FG527AM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/baseline.ts","../src/coverage.ts","../src/extract.ts","../src/analysis.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { serializeSurfaceSnapshot } from \"@agent-surface/testing\";\nimport type { AgentSurfaceSnapshot } from \"@agent-surface/core\";\n\nexport const DEFAULT_BASELINE_DIR = \".agent-surface\";\n\nexport function baselineDirFor(configPath: string, configured?: string): string {\n return resolve(dirname(configPath), configured ?? DEFAULT_BASELINE_DIR);\n}\n\nexport function baselinePath(dir: string, scenario: string): string {\n return join(dir, `${scenario}.json`);\n}\n\n/**\n * The committed form. `serializeSurfaceSnapshot` is the same normalizer the\n * Vitest matcher uses: registration ids become stable placeholders and the\n * volatile fields (surfaceId, capturedAt, version) drop out, so a baseline\n * diff is a diff of *what agents can see* and nothing else.\n */\nexport function normalize(snapshot: AgentSurfaceSnapshot): unknown {\n return serializeSurfaceSnapshot(snapshot);\n}\n\nexport function readBaseline(path: string): unknown | undefined {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n } catch {\n return undefined;\n }\n}\n\nexport function writeBaseline(path: string, value: unknown): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nexport interface DiffEntry {\n path: string;\n kind: \"added\" | \"removed\" | \"changed\";\n before?: unknown;\n after?: unknown;\n /**\n * The capability the change belongs to. A reviewer needs to know that\n * `view:devices.table.sort` changed — `components[3].actions[1]` is the same\n * fact in a form nobody can act on.\n */\n subject?: string;\n}\n\nconst PATH_SEGMENT = /([^.[\\]]+)|\\[(\\d+)\\]/g;\n\n/** Nearest enclosing capability id for a diff path, if the path sits inside one. */\nfunction subjectFor(document: unknown, path: string): string | undefined {\n let node: unknown = document;\n let subject: string | undefined;\n for (const match of path.matchAll(PATH_SEGMENT)) {\n if (typeof node !== \"object\" || node === null) return subject;\n const record = node as Record<string, unknown>;\n const candidate = record[\"capabilityId\"] ?? record[\"procedureId\"];\n if (typeof candidate === \"string\") subject = candidate;\n const key = match[1] ?? match[2];\n if (key === undefined) return subject;\n node = record[key];\n }\n if (typeof node === \"object\" && node !== null) {\n const record = node as Record<string, unknown>;\n const candidate = record[\"capabilityId\"] ?? record[\"procedureId\"];\n if (typeof candidate === \"string\") subject = candidate;\n }\n return subject;\n}\n\n/** Labels each entry with the capability it belongs to, when there is one. */\nexport function annotate(entries: DiffEntry[], after: unknown, before: unknown): DiffEntry[] {\n return entries.map((entry) => {\n const subject = subjectFor(after, entry.path) ?? subjectFor(before, entry.path);\n return subject ? { ...entry, subject } : entry;\n });\n}\n\n/**\n * Structural diff, deliberately total: every difference is drift, including a\n * changed description. Descriptions are the provider's cached prompt prefix\n * (D28) — a silent edit re-bills every conversation, so it is exactly the kind\n * of change a reviewer should see.\n */\nexport function diff(before: unknown, after: unknown, path = \"\"): DiffEntry[] {\n if (Object.is(before, after)) return [];\n\n const bothArrays = Array.isArray(before) && Array.isArray(after);\n const bothObjects =\n !bothArrays &&\n typeof before === \"object\" &&\n typeof after === \"object\" &&\n before !== null &&\n after !== null;\n\n if (bothArrays) {\n const entries: DiffEntry[] = [];\n const max = Math.max(before.length, after.length);\n for (let i = 0; i < max; i++) {\n const at = `${path}[${i}]`;\n if (i >= before.length) entries.push({ path: at, kind: \"added\", after: after[i] });\n else if (i >= after.length) entries.push({ path: at, kind: \"removed\", before: before[i] });\n else entries.push(...diff(before[i], after[i], at));\n }\n return entries;\n }\n\n if (bothObjects) {\n const entries: DiffEntry[] = [];\n const beforeRecord = before as Record<string, unknown>;\n const afterRecord = after as Record<string, unknown>;\n const keys = new Set([...Object.keys(beforeRecord), ...Object.keys(afterRecord)]);\n for (const key of [...keys].sort()) {\n const at = path ? `${path}.${key}` : key;\n if (!(key in beforeRecord)) {\n entries.push({ path: at, kind: \"added\", after: afterRecord[key] });\n } else if (!(key in afterRecord)) {\n entries.push({ path: at, kind: \"removed\", before: beforeRecord[key] });\n } else {\n entries.push(...diff(beforeRecord[key], afterRecord[key], at));\n }\n }\n return entries;\n }\n\n if (JSON.stringify(before) === JSON.stringify(after)) return [];\n return [{ path: path || \"<root>\", kind: \"changed\", before, after }];\n}\n\nexport function formatValue(value: unknown): string {\n if (value === undefined) return \"—\";\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\n return text.length > 120 ? `${text.slice(0, 117)}…` : text;\n}\n","/**\n * `coverage` — authored minus reached (`AS-COVER-004…005`, D36).\n *\n * The inventory says what the codebase authors; the scenarios say what a mount\n * surfaces. Neither half alone answers \"which authored capability does no\n * scenario reach\", because that is a set difference no command computed.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AuthoredCapability } from \"./extract.js\";\n\nexport const ALLOWLIST_FILE = \"coverage-allow.json\";\nexport const UNREAD_ALLOWLIST_FILE = \"unresolved-allow.json\";\n\n/**\n * A committed list of unreached capabilities a repository has decided not to\n * fix yet, each with a reason. Adoption has to ratchet rather than gate: a\n * codebase turning this on with 200 unreached capabilities cannot fix them in\n * one pull request, and a check that can only be adopted big-bang is a check\n * that never gets adopted.\n */\nexport type CoverageAllowlist = Record<string, string>;\n\nexport function allowlistPathFor(baselineDir: string): string {\n return join(baselineDir, ALLOWLIST_FILE);\n}\n\nexport function unreadAllowlistPathFor(baselineDir: string): string {\n return join(baselineDir, UNREAD_ALLOWLIST_FILE);\n}\n\nexport function readAllowlist(path: string, keyName = \"capabilityId\"): CoverageAllowlist {\n if (!existsSync(path)) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, \"utf8\"));\n } catch (error) {\n throw new Error(\n `could not parse ${path}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(`${path} must be a JSON object of { \"${keyName}\": \"reason\" }`);\n }\n const allowlist: CoverageAllowlist = {};\n for (const [id, reason] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof reason !== \"string\" || reason.trim() === \"\") {\n throw new Error(`${path}: \"${id}\" needs a non-empty reason string`);\n }\n allowlist[id] = reason;\n }\n return allowlist;\n}\n\n/**\n * The key an unread call site is allowlisted under: `file#reason`.\n *\n * Neither half alone works. The line number churns on every edit above the call\n * site, so a ratchet keyed on it fails for a reason that has nothing to do with\n * the surface. The `note` is prose written for a human and gets reworded — the\n * spread note changed in the release that introduced it — so a key built from it\n * would invalidate committed entries on an edit nobody thought was behavioural.\n *\n * File plus a stable reason code survives both. It is coarser than a line: a\n * second call site in the same file failing the *same* way is covered silently.\n * That is the accepted trade, because the ratchet's job is \"no new *kinds* of\n * unread site\", and it is bounded to one file and one construct.\n */\nexport function unreadKey(entry: AuthoredCapability): string {\n return `${entry.origin.file}#${entry.reason ?? \"unknown\"}`;\n}\n\nexport interface UnreachedCapability {\n capabilityId: string;\n origin: { file: string; line: number };\n}\n\nexport interface CoverageReport {\n /** Distinct capability ids the inventory resolved, within any active scope. */\n authored: number;\n /** How many of them at least one scenario surfaced. */\n reached: number;\n scenarios: string[];\n /**\n * The scope every number here was computed under (`AS-CLI-007`). A scope\n * filters the catalog *and* the mount, so `10 authored` without it on screen\n * reads as a claim about the whole codebase when it is a claim about one\n * prefix of it.\n */\n scope?: string[];\n /**\n * Allowlist entries outside the active scope, which a scoped run cannot\n * judge: not unreached (nothing looked), not stale (nothing reached them).\n * Counted rather than silently dropped, so a scoped run never reads as a\n * verdict on the whole allowlist.\n */\n allowlistOutOfScope: number;\n /** Authored, surfaced by no scenario, and not allowlisted — the finding. */\n unreached: UnreachedCapability[];\n /**\n * Present at runtime with no static origin: a dynamic registration, or a gap\n * in the extractor. `view:` only — see `domainReached`.\n */\n undeclared: string[];\n /**\n * `domain:` capabilities a scenario surfaced. Held apart from `undeclared`\n * because the inventory never claimed to analyze that plane: filing them as\n * \"no static origin\" would report the design's own stated boundary as a\n * defect, which is the misleading check this whole command rejects.\n */\n domainReached: string[];\n /** Carried forward from the inventory, minus anything allowlisted. */\n unresolved: AuthoredCapability[];\n /** Unreached, but listed in the allowlist. */\n allowed: string[];\n /** Listed in the allowlist and reached anyway — the list has rotted. */\n staleAllowlist: string[];\n allowlistPath: string;\n /** Unread, but listed in `unresolved-allow.json`. Keys, not entries. */\n allowedUnread: string[];\n /** Listed there and no longer unread — that list has rotted too. */\n staleUnreadAllowlist: string[];\n unreadAllowlistPath: string;\n}\n\nexport interface BuildCoverageInput {\n authored: Set<string>;\n /** First origin seen for each authored id, for the report. */\n origins: Map<string, { file: string; line: number }>;\n /**\n * Every capability id any scenario's *explanation* held.\n *\n * The explanation, not the snapshot. A capability a policy hid **was**\n * reached: a scenario mounted it and the policy made a deliberate decision\n * about it. Classifying those as unreached would flood the report with the\n * library's own correct behaviour — in the example app the `anonymous`\n * scenario alone would contribute eleven false gaps.\n */\n reachedIds: Set<string>;\n scenarios: string[];\n scope?: string[];\n unresolved: AuthoredCapability[];\n /**\n * Already filtered to the active scope by the caller, which owns the scope\n * predicate. Entries outside it are counted in `allowlistOutOfScope`.\n */\n allowlist: CoverageAllowlist;\n allowlistOutOfScope?: number;\n allowlistPath: string;\n /** Keyed by `unreadKey()`. Absent is an empty list, not \"accept everything\". */\n unreadAllowlist?: CoverageAllowlist;\n unreadAllowlistPath: string;\n}\n\nexport function buildCoverageReport(input: BuildCoverageInput): CoverageReport {\n const unreached: UnreachedCapability[] = [];\n const allowed: string[] = [];\n\n for (const id of [...input.authored].sort()) {\n if (input.reachedIds.has(id)) continue;\n if (id in input.allowlist) {\n allowed.push(id);\n continue;\n }\n unreached.push({ capabilityId: id, origin: input.origins.get(id) ?? { file: \"?\", line: 0 } });\n }\n\n // An allowlist entry that is no longer unreached fails the command, so the\n // list shrinks and cannot silently rot — the same idiom as the baselines\n // `check` already commits.\n const staleAllowlist = Object.keys(input.allowlist)\n .filter((id) => input.reachedIds.has(id) || !input.authored.has(id))\n .sort();\n\n // The same ratchet, one bucket over. An unread call site a repository has\n // decided to live with — a shared wrapper hook, say — stops failing `check`\n // without turning the whole bucket off, and an entry that stops being unread\n // fails so the list shrinks.\n const unreadAllowlist = input.unreadAllowlist ?? {};\n const unread: AuthoredCapability[] = [];\n const allowedUnread = new Set<string>();\n for (const entry of input.unresolved) {\n const key = unreadKey(entry);\n if (key in unreadAllowlist) allowedUnread.add(key);\n else unread.push(entry);\n }\n const stillUnread = new Set(input.unresolved.map(unreadKey));\n const staleUnreadAllowlist = Object.keys(unreadAllowlist)\n .filter((key) => !stillUnread.has(key))\n .sort();\n\n const unaccounted = [...input.reachedIds].filter((id) => !input.authored.has(id)).sort();\n const domainReached = unaccounted.filter((id) => id.startsWith(\"domain:\"));\n const undeclared = unaccounted.filter((id) => !id.startsWith(\"domain:\"));\n\n return {\n authored: input.authored.size,\n reached: [...input.authored].filter((id) => input.reachedIds.has(id)).length,\n scenarios: input.scenarios,\n ...(input.scope ? { scope: input.scope } : {}),\n allowlistOutOfScope: input.allowlistOutOfScope ?? 0,\n unreached,\n undeclared,\n domainReached,\n unresolved: unread,\n allowed,\n staleAllowlist,\n allowlistPath: input.allowlistPath,\n allowedUnread: [...allowedUnread].sort(),\n staleUnreadAllowlist,\n unreadAllowlistPath: input.unreadAllowlistPath,\n };\n}\n\n/**\n * `0` clean, `1` a gap.\n *\n * `undeclared` deliberately does not fail (OQ-4): a dynamically registered\n * capability is legitimate, and from the outside it is indistinguishable from\n * an extractor that missed something. Failing on it would punish the honest\n * case to catch the other one. It is reported, loudly, and revisited when a\n * codebase does it deliberately.\n *\n * `unresolved` does fail, and `--allow-unresolved` is the only way past it\n * (`AS-COVER-003`). A partial understanding of a codebase that reports itself\n * as complete is the failure the whole static half exists to remove: `unreached`\n * is computed against the catalog, so a catalog with holes in it makes that\n * count a floor rather than an answer. Accepting the gap still prints it.\n */\nexport function coverageExitCode(\n report: CoverageReport,\n options: { allowUnresolved?: boolean } = {},\n): number {\n if (report.unreached.length > 0) return 1;\n // `report.unresolved` already excludes allowlisted entries, so the per-entry\n // ratchet and the blanket flag compose: the list holds the sites you have\n // accepted, and the flag is still there for a codebase not ready to enumerate\n // them. Both stale lists fail regardless of either — a ratchet that can rot\n // is a ratchet that stops meaning anything.\n if (report.unresolved.length > 0 && !options.allowUnresolved) return 1;\n if (report.staleAllowlist.length > 0) return 1;\n if (report.staleUnreadAllowlist.length > 0) return 1;\n return 0;\n}\n","/**\n * The static capability inventory (`AS-COVER-001…003`, D35).\n *\n * `inspect` answers *what can an agent do on this page right now*. It cannot\n * answer *did we author something no scenario ever reaches*, because a surface\n * is a projection of what is mounted: a route no scenario visits registers\n * nothing, so there is nothing to report and nothing to diff. The denominator\n * has to come from somewhere that does not require mounting.\n *\n * It comes from here. A registration call site is far more static than the\n * surface it produces:\n *\n * ```tsx\n * useAgentComponent({\n * type: \"devices.table\", // string literal\n * actions: { sort: action({ … }) }, // capability name is a key\n * });\n * ```\n *\n * `view:devices.table.sort` is fully determined by source text. What is\n * genuinely dynamic — availability, policy outcome, binding — is the\n * *projection*, and none of it is claimed here.\n *\n * ## This creates no exposure path (directive §2.1)\n *\n * No DOM is scanned, nothing is registered, no annotation is suggested. This\n * module *reads the same reviewed registration code* a human reads and counts\n * what is already there. It lives in `@agent-surface/cli` — which no adapter\n * imports and no application ships — and must never be re-exported from\n * `@agent-surface/core`, mirroring `AS-EXPLAIN-004` (`AS-COVER-006`).\n *\n * ## Failure discipline is the substance, not a detail\n *\n * > Better a missing check than a misleading check.\n *\n * A call site this module cannot understand is **reported with its file and\n * line**, never dropped. An inventory that silently omitted the constructs it\n * failed to parse would understate the denominator, and a coverage number built\n * on it would claim completeness it never had.\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport ts from \"typescript\";\n\n/** Identity could not be recovered from the call site at all. */\nexport const UNRESOLVED_ID = \"<unresolved>\";\n\nexport interface AuthoredCapability {\n /** Canonical id, instance-independent: `view:devices.table.sort`. */\n capabilityId: string;\n kind: \"observation\" | \"action\" | \"procedure\";\n /** Where a human can go and read it. */\n origin: { file: string; line: number };\n /** Literals recovered from the call site; absent when not statically known. */\n description?: string;\n effect?: string;\n /**\n * How much of this call site the extractor understood.\n *\n * `static` — identity and metadata both recovered from literals.\n * `partial` — identity resolved, some metadata dynamic. The common case: a\n * spread `instanceId`, or a description built from a template.\n * `unresolved` — identity NOT resolved. Reported, never dropped.\n */\n resolution: \"static\" | \"partial\" | \"unresolved\";\n /** Present on `partial`/`unresolved`: what defeated the extractor. */\n note?: string;\n /**\n * Present on `unresolved`: *which* construct defeated the extractor, as a\n * stable code rather than prose.\n *\n * `note` is written for a human and gets reworded — the spread note changed\n * in the same release that introduced it. Anything keyed on that prose would\n * silently invalidate itself on an edit no one thought was behavioural, which\n * is exactly what `unresolved-allow.json` must not do. This is the key.\n */\n reason?: UnreadReason;\n}\n\n/**\n * Why a registration could not be fully read. Stable identifiers: adding one is\n * fine, renaming one invalidates committed allowlists and is a breaking change.\n */\nexport type UnreadReason =\n | \"dynamic-type\"\n | \"dynamic-config\"\n | \"dynamic-group\"\n | \"spread-members\"\n | \"computed-name\"\n | \"granular-hook\";\n\nexport interface CapabilityInventory {\n capabilities: AuthoredCapability[];\n /** Absolute path to the tsconfig whose file list was analyzed. */\n tsconfig: string;\n /** Directory the analysis was rooted at — the surface config's own. */\n root: string;\n /** Files the program actually walked — the inventory's blast radius. */\n filesAnalyzed: number;\n /**\n * Program files skipped for living outside `root` — workspace packages the\n * app's tsconfig aliases in, typically the library's own source. Reported\n * rather than dropped silently: a boundary nobody can see is a boundary\n * nobody can check.\n */\n filesOutsideRoot: number;\n /**\n * The `domain:` plane is deliberately *not* analyzed here. Those capabilities\n * come from the oRPC router, which is already a static export (OQ-1), and\n * reporting zero of them would read as \"there are none\" rather than \"nobody\n * looked\".\n */\n domain: \"not-analyzed\";\n}\n\n/* ── locating the program ─────────────────────────────────────────────── */\n\nexport function findTsconfig(from: string): string | undefined {\n return ts.findConfigFile(resolve(from), ts.sys.fileExists, \"tsconfig.json\");\n}\n\ninterface ProgramFiles {\n fileNames: string[];\n options: ts.CompilerOptions;\n}\n\nfunction readProgramFiles(tsconfigPath: string): ProgramFiles {\n const read = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n if (read.error) {\n throw new Error(\n `could not read ${tsconfigPath}: ${ts.flattenDiagnosticMessageText(read.error.messageText, \" \")}`,\n );\n }\n const parsed = ts.parseJsonConfigFileContent(\n read.config as object,\n ts.sys,\n dirname(tsconfigPath),\n );\n if (parsed.errors.length > 0 && parsed.fileNames.length === 0) {\n throw new Error(\n `could not resolve any files from ${tsconfigPath}: ${parsed.errors\n .map((error) => ts.flattenDiagnosticMessageText(error.messageText, \" \"))\n .join(\"; \")}`,\n );\n }\n return { fileNames: parsed.fileNames, options: parsed.options };\n}\n\n/* ── small AST helpers ────────────────────────────────────────────────── */\n\nfunction calleeName(call: ts.CallExpression): string | undefined {\n if (ts.isIdentifier(call.expression)) return call.expression.text;\n if (ts.isPropertyAccessExpression(call.expression)) return call.expression.name.text;\n return undefined;\n}\n\nfunction propertyName(name: ts.PropertyName): string | undefined {\n if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;\n return undefined;\n}\n\nfunction propertyOf(\n object: ts.ObjectLiteralExpression,\n wanted: string,\n): ts.Expression | undefined {\n for (const property of object.properties) {\n if (ts.isPropertyAssignment(property) && propertyName(property.name) === wanted) {\n return property.initializer;\n }\n // `{ type }` is `{ type: type }`, and the shorthand is what a wrapper hook\n // forwarding a parameter actually writes. Reading only the long form made\n // every such config look like it had no `type` at all.\n if (ts.isShorthandPropertyAssignment(property) && property.name.text === wanted) {\n return property.name;\n }\n }\n return undefined;\n}\n\nfunction hasSpread(object: ts.ObjectLiteralExpression): boolean {\n return object.properties.some((property) => ts.isSpreadAssignment(property));\n}\n\n/** The capability groups a spread would have to contribute to matter here. */\nconst CAPABILITY_GROUPS = [\"observations\", \"actions\"] as const;\n\n/**\n * Every key a spread could contribute, or `undefined` when that is not knowable.\n *\n * This exists to separate two spreads that look alike and are not:\n *\n * ```tsx\n * ...(props.instance ? { instanceId: props.instance } : {}) // keys: instanceId\n * ...buildMembers() // keys: unknown\n * ```\n *\n * The first cannot contribute a capability, because its key set is written out\n * and `instanceId` is not part of a capability id. The second could contribute\n * any number of them. Reporting both would flood the documented common case;\n * reporting neither is what let a whole registration disappear.\n *\n * Resolution is deliberately shallow, matching `objectLiteralFor`'s one hop: a\n * literal, a conditional over two knowable branches, or a same-module `const`.\n * Anything else is unknown, and unknown is reported rather than assumed empty.\n */\nfunction spreadKeys(\n expression: ts.Expression,\n source: ts.SourceFile,\n depth = 0,\n): string[] | undefined {\n if (depth > 1) return undefined;\n\n if (ts.isParenthesizedExpression(expression)) {\n return spreadKeys(expression.expression, source, depth);\n }\n\n // `cond ? { a } : {}` contributes whichever branch runs, so the key set is\n // the union — knowable only if both branches are.\n if (ts.isConditionalExpression(expression)) {\n const whenTrue = spreadKeys(expression.whenTrue, source, depth);\n const whenFalse = spreadKeys(expression.whenFalse, source, depth);\n if (!whenTrue || !whenFalse) return undefined;\n return [...whenTrue, ...whenFalse];\n }\n\n const resolved = objectLiteralFor(expression, source);\n if (!resolved.object) return undefined;\n\n const keys: string[] = [];\n for (const property of resolved.object.properties) {\n // A spread inside a spread: recurse while the hop budget allows, then admit\n // defeat rather than reporting a partial key set as a complete one.\n if (ts.isSpreadAssignment(property)) {\n const nested = spreadKeys(property.expression, source, depth + 1);\n if (!nested) return undefined;\n keys.push(...nested);\n continue;\n }\n const name =\n ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property)\n ? propertyName(property.name)\n : ts.isShorthandPropertyAssignment(property)\n ? property.name.text\n : undefined;\n // A computed key could be anything, including a capability group.\n if (name === undefined) return undefined;\n keys.push(name);\n }\n return keys;\n}\n\nfunction literalText(node: ts.Expression | undefined): string | undefined {\n if (!node) return undefined;\n if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;\n if (ts.isParenthesizedExpression(node)) return literalText(node.expression);\n // `\"one long \" + \"description split over two lines\"` is as statically known\n // as either half. Descriptions are the provider's cached prompt prefix (D28),\n // so they are long enough that authors wrap them — calling that `partial`\n // would report the codebase's most common formatting choice as a defect.\n if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {\n const left = literalText(node.left);\n const right = literalText(node.right);\n if (left !== undefined && right !== undefined) return left + right;\n }\n return undefined;\n}\n\n/** A short name for whatever construct defeated us, for the `note`. */\nfunction describeConstruct(node: ts.Expression): string {\n if (ts.isCallExpression(node)) {\n const callee = calleeName(node);\n return callee ? `built by ${callee}()` : \"built by a call expression\";\n }\n if (ts.isIdentifier(node)) return `a variable (${node.text}) this extractor could not follow`;\n if (ts.isConditionalExpression(node)) return \"a conditional expression\";\n if (ts.isTemplateExpression(node)) return \"a template with substitutions\";\n if (ts.isPropertyAccessExpression(node)) return \"a property access\";\n return \"a non-literal expression\";\n}\n\n/**\n * Resolves a config argument to an object literal, following **one hop** to a\n * same-module `const`.\n *\n * One hop is the whole rule. `useAgentComponent(CONFIG)` where `CONFIG` is a\n * module constant is common and cheap; `useAgentComponent(buildConfig(props))`\n * is not resolvable at any depth worth implementing. Stopping at one hop keeps\n * the limit *visible in the output* rather than buried in the implementation —\n * the deeper case is reported as `unresolved` with the construct named, which\n * is the behaviour this module exists to guarantee.\n */\nfunction objectLiteralFor(\n expression: ts.Expression,\n source: ts.SourceFile,\n): { object?: ts.ObjectLiteralExpression; note?: string } {\n if (ts.isObjectLiteralExpression(expression)) return { object: expression };\n\n if (ts.isIdentifier(expression)) {\n const target = expression.text;\n let found: ts.ObjectLiteralExpression | undefined;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === target &&\n node.initializer &&\n ts.isObjectLiteralExpression(node.initializer)\n ) {\n found = node.initializer;\n return;\n }\n ts.forEachChild(node, visit);\n };\n visit(source);\n if (found) return { object: found };\n return {\n note: `the config is \\`${target}\\`, which is not a same-module object literal — the extractor follows one hop only`,\n };\n }\n\n return { note: `the config is ${describeConstruct(expression)}` };\n}\n\n/* ── the extraction itself ────────────────────────────────────────────── */\n\n/** Hooks that register one capability against the enclosing render scope. */\nconst GRANULAR_HOOKS = new Set([\"useAgentAction\", \"useAgentObservation\"]);\n\ninterface Emitter {\n push(capability: AuthoredCapability): void;\n origin(node: ts.Node): { file: string; line: number };\n}\n\nfunction capabilitiesFromGroup(\n group: ts.Expression | undefined,\n kind: \"observation\" | \"action\",\n componentType: string,\n componentPartial: string | undefined,\n emit: Emitter,\n source: ts.SourceFile,\n): void {\n if (!group) return;\n\n const resolved = objectLiteralFor(group, source);\n if (!resolved.object) {\n emit.push({\n capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,\n kind,\n origin: emit.origin(group),\n resolution: \"unresolved\",\n reason: \"dynamic-group\",\n note: `\\`${kind}s\\` on \"${componentType}\" is not an object literal: ${resolved.note}`,\n });\n return;\n }\n\n for (const property of resolved.object.properties) {\n // A spread inside the capability map can add capabilities this extractor\n // cannot name. That is an identity gap, not a metadata gap.\n if (ts.isSpreadAssignment(property)) {\n emit.push({\n capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,\n kind,\n origin: emit.origin(property),\n resolution: \"unresolved\",\n reason: \"spread-members\",\n note: `\\`${kind}s\\` on \"${componentType}\" spreads another object, which may contribute capabilities this inventory cannot name`,\n });\n continue;\n }\n\n const name =\n ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property)\n ? propertyName(property.name)\n : ts.isShorthandPropertyAssignment(property)\n ? property.name.text\n : undefined;\n\n if (name === undefined) {\n emit.push({\n capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,\n kind,\n origin: emit.origin(property),\n resolution: \"unresolved\",\n reason: \"computed-name\",\n note: `a capability on \"${componentType}\" has a computed name`,\n });\n continue;\n }\n\n // Identity is recovered from the key alone; the value only carries metadata.\n const capability: AuthoredCapability = {\n capabilityId: `view:${componentType}.${name}`,\n kind,\n origin: emit.origin(property),\n resolution: \"static\",\n };\n const notes: string[] = [];\n if (componentPartial) notes.push(componentPartial);\n\n const value = ts.isPropertyAssignment(property) ? property.initializer : undefined;\n const definition =\n value && ts.isCallExpression(value) && value.arguments.length > 0\n ? value.arguments[0]\n : value;\n\n if (definition && ts.isObjectLiteralExpression(definition)) {\n const description = literalText(propertyOf(definition, \"description\"));\n if (description !== undefined) capability.description = description;\n else notes.push(\"description is not a string literal\");\n\n if (kind === \"action\") {\n const effect = literalText(propertyOf(definition, \"effect\"));\n if (effect !== undefined) capability.effect = effect;\n else notes.push(\"effect is not a string literal\");\n }\n if (hasSpread(definition)) notes.push(\"the definition spreads another object\");\n } else {\n notes.push(\n value\n ? `the definition is ${describeConstruct(value)}`\n : \"the definition is not an object literal\",\n );\n }\n\n if (notes.length > 0) {\n capability.resolution = \"partial\";\n capability.note = notes.join(\"; \");\n }\n emit.push(capability);\n }\n}\n\nfunction visitCall(\n call: ts.CallExpression,\n emit: Emitter,\n source: ts.SourceFile,\n deferred: DeferredWrapper[],\n enclosing: EnclosingFunction | undefined,\n): void {\n const callee = calleeName(call);\n if (callee === undefined) return;\n\n if (GRANULAR_HOOKS.has(callee)) {\n // OQ-3: the granular hooks register through a render-scope link rather than\n // one aggregated descriptor, so the component `type` is not at this call\n // site at all. Reporting the call site as unresolved is the honest state\n // until that join key is settled — silently ignoring it would make a\n // codebase that uses them look fully covered.\n emit.push({\n capabilityId: UNRESOLVED_ID,\n kind: callee === \"useAgentAction\" ? \"action\" : \"observation\",\n origin: emit.origin(call),\n resolution: \"unresolved\",\n reason: \"granular-hook\",\n note: `${callee}() registers against a render-scope link, so its component type is not at this call site`,\n });\n return;\n }\n\n if (callee !== \"useAgentComponent\" && callee !== \"register\") return;\n const argument = call.arguments[0];\n if (!argument) return;\n\n const resolved = objectLiteralFor(argument, source);\n if (!resolved.object) {\n emit.push({\n capabilityId: UNRESOLVED_ID,\n kind: \"action\",\n origin: emit.origin(call),\n resolution: \"unresolved\",\n reason: \"dynamic-config\",\n note: `${callee}() call site could not be read: ${resolved.note}`,\n });\n return;\n }\n\n const config = resolved.object;\n const typeNode = propertyOf(config, \"type\");\n const type = literalText(typeNode);\n if (type === undefined) {\n // `register` is a common method name; only treat it as ours once the call\n // actually looks like a registration. A `type` that exists but is dynamic\n // *is* ours, and is a genuine finding.\n if (callee === \"register\" && typeNode === undefined) return;\n\n // A `type` that is a parameter of the enclosing function is not dynamic —\n // it is decided one frame up, at the wrapper's call sites, and those are\n // string literals often enough to be worth following (OQ-13). Defer it;\n // the second pass either resolves it or reports it unread as before.\n const slot =\n enclosing && typeNode && ts.isIdentifier(typeNode)\n ? parameterSlot(typeNode.text, enclosing.fn)\n : undefined;\n if (slot && enclosing?.name) {\n deferred.push({ config, source, emit, wrapperName: enclosing.name, slot, site: call });\n return;\n }\n\n emit.push({\n capabilityId: UNRESOLVED_ID,\n kind: \"action\",\n origin: emit.origin(call),\n resolution: \"unresolved\",\n reason: \"dynamic-type\",\n note: `\\`type\\` is not a string literal, so no capability id on this component can be determined`,\n });\n return;\n }\n\n // A spread at the component level is the documented common case — the\n // conditional `...(props.instance ? { instanceId } : {})`. `instanceId` is not\n // part of a capability id, so identity survives; metadata may not.\n const componentPartial = hasSpread(config)\n ? \"the component config spreads another object, so some metadata here may be dynamic\"\n : undefined;\n\n // A spread whose key set cannot be read may carry `observations` or `actions`,\n // and those capabilities are then nowhere in this program's reach. Left\n // unreported, the registration disappears from *both* lists: absent from the\n // catalog, and absent from the unread call sites that exist to say the catalog\n // is incomplete. The count then claims a completeness it does not have, which\n // is the one failure this whole module is built to prevent.\n //\n // Enumerating the literal groups below is not enough on its own, either: a\n // config with a literal `observations` *and* an unreadable spread is missing\n // whatever `actions` the spread contributes, and the half that resolved would\n // otherwise read as the whole.\n for (const property of config.properties) {\n if (!ts.isSpreadAssignment(property)) continue;\n const keys = spreadKeys(property.expression, source);\n if (keys && !keys.some((key) => CAPABILITY_GROUPS.includes(key as \"observations\" | \"actions\"))) {\n continue;\n }\n emit.push({\n capabilityId: `view:${type}.${UNRESOLVED_ID}`,\n kind: \"action\",\n origin: emit.origin(property),\n resolution: \"unresolved\",\n reason: \"spread-members\",\n note: keys\n ? `\"${type}\" spreads ${describeConstruct(property.expression)}, which contributes \\`${keys\n .filter((key) => CAPABILITY_GROUPS.includes(key as \"observations\" | \"actions\"))\n .join(\"`/`\")}\\` this inventory cannot name`\n : `\"${type}\" spreads ${describeConstruct(property.expression)}, whose keys this inventory cannot read — it may contribute capabilities not listed here`,\n });\n }\n\n capabilitiesFromGroup(\n propertyOf(config, \"observations\"),\n \"observation\",\n type,\n componentPartial,\n emit,\n source,\n );\n capabilitiesFromGroup(\n propertyOf(config, \"actions\"),\n \"action\",\n type,\n componentPartial,\n emit,\n source,\n );\n}\n\n/* ── wrapper hooks: one hop *up* the call graph (OQ-13) ───────────────── */\n\n/**\n * A registration whose `type` is a parameter of the enclosing function.\n *\n * ```tsx\n * function useRegisteredPanel(type: string) {\n * useAgentComponent({ type, observations: { … } }); // ← here\n * }\n * useRegisteredPanel(\"devices.table\"); // ← type lives here\n * ```\n *\n * The capability ids are still fully determined by source text; they are just\n * determined *one frame up*. Reported unread, a single such wrapper hides the\n * whole surface built on it — 91% of one real application's capabilities\n * ([#31](https://github.com/Wiseair-srl/agent-surface/issues/31)).\n *\n * Resolution is deferred to a second pass because a call site may live in a\n * file walked before the wrapper's own.\n */\ninterface DeferredWrapper {\n config: ts.ObjectLiteralExpression;\n source: ts.SourceFile;\n emit: Emitter;\n /** The name a call site would use. */\n wrapperName: string;\n /** Where `type` sits in the wrapper's signature. */\n slot: { index: number; property?: string };\n /** The node to blame when this cannot be resolved. */\n site: ts.Node;\n}\n\ninterface CallSite {\n call: ts.CallExpression;\n source: ts.SourceFile;\n}\n\n/**\n * The function a call sits inside, and the name a caller would use for it.\n *\n * Tracked on the way *down* the tree rather than read from `node.parent`:\n * `ts.createProgram` leaves parent pointers unset until something forces the\n * binder to run, and forcing it means constructing a type checker — real work\n * on a large program, for an answer the walk already has in hand.\n */\ninterface EnclosingFunction {\n fn: ts.SignatureDeclaration;\n /** `function useX()` and `const useX = () => {}` both name the wrapper. */\n name?: string;\n}\n\nfunction functionLike(node: ts.Node): ts.SignatureDeclaration | undefined {\n if (\n ts.isFunctionDeclaration(node) ||\n ts.isFunctionExpression(node) ||\n ts.isArrowFunction(node) ||\n ts.isMethodDeclaration(node)\n ) {\n return node;\n }\n return undefined;\n}\n\n/**\n * Where a `type` identifier comes from in the enclosing signature, if it is a\n * parameter at all. Both spellings authors actually use:\n *\n * ```ts\n * function useX(type: string) // { index: 0 }\n * function useX({ type }: Props) // { index: 0, property: \"type\" }\n * ```\n */\nfunction parameterSlot(\n name: string,\n fn: ts.SignatureDeclaration,\n): { index: number; property?: string } | undefined {\n for (const [index, parameter] of fn.parameters.entries()) {\n if (ts.isIdentifier(parameter.name)) {\n if (parameter.name.text === name) return { index };\n continue;\n }\n if (ts.isObjectBindingPattern(parameter.name)) {\n for (const element of parameter.name.elements) {\n if (!ts.isIdentifier(element.name) || element.name.text !== name) continue;\n // `{ type: kind }` binds `kind` locally but reads the `type` property.\n const property =\n element.propertyName && ts.isIdentifier(element.propertyName)\n ? element.propertyName.text\n : name;\n return { index, property };\n }\n }\n }\n return undefined;\n}\n\n/**\n * Whether `site` is provably a call of `wrapper`, and not of something else\n * that happens to share its name.\n *\n * This predicate is the whole safety argument. Attributing a wrapper's\n * capabilities to the wrong call site would put ids in the catalog that no\n * component authors — **fabricating** entries, a failure this package has never\n * had and must not acquire. Reporting unread is always the safe answer, so\n * anything short of certainty returns `false`:\n *\n * - declared in the same file — lexically certain;\n * - imported, and the specifier resolves to the wrapper's own file;\n * - anything else — a re-export chain, a namespace import, a dynamic import —\n * is not certain, and is left unread.\n */\nfunction callsWrapper(\n site: CallSite,\n wrapper: DeferredWrapper,\n compilerOptions: ts.CompilerOptions,\n): boolean {\n const callee = site.call.expression;\n if (!ts.isIdentifier(callee) || callee.text !== wrapper.wrapperName) return false;\n\n if (site.source.fileName === wrapper.source.fileName) {\n // Same file: the only way this is a different function is a local shadow,\n // which would also shadow it for the reader.\n return true;\n }\n\n for (const statement of site.source.statements) {\n if (!ts.isImportDeclaration(statement)) continue;\n const clause = statement.importClause;\n if (!clause) continue;\n\n const named =\n clause.name?.text === wrapper.wrapperName ||\n (clause.namedBindings &&\n ts.isNamedImports(clause.namedBindings) &&\n clause.namedBindings.elements.some((element) => element.name.text === wrapper.wrapperName));\n if (!named) continue;\n if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;\n\n const resolved = ts.resolveModuleName(\n statement.moduleSpecifier.text,\n site.source.fileName,\n compilerOptions,\n ts.sys,\n ).resolvedModule;\n if (resolved?.resolvedFileName === wrapper.source.fileName) return true;\n }\n return false;\n}\n\nexport interface ExtractOptions {\n /** Directory the analysis is rooted at — normally the surface config's dir. */\n root: string;\n /** Explicit tsconfig; found upward from `root` when omitted. */\n tsconfig?: string;\n}\n\n/**\n * Reads the program and returns every capability its registration call sites\n * author. Nothing is executed: no Vite server, no jsdom, no scenarios, no mount.\n */\nexport function extractCapabilities(options: ExtractOptions): CapabilityInventory {\n const root = resolve(options.root);\n const tsconfigPath = options.tsconfig\n ? isAbsolute(options.tsconfig)\n ? options.tsconfig\n : join(root, options.tsconfig)\n : findTsconfig(root);\n\n if (!tsconfigPath || !existsSync(tsconfigPath)) {\n throw new Error(\n `no tsconfig.json found from ${root} — \\`capabilities\\` reads the TypeScript program, ` +\n \"so it needs one (pass --tsconfig to point at it)\",\n );\n }\n\n const { fileNames, options: compilerOptions } = readProgramFiles(tsconfigPath);\n const program = ts.createProgram(fileNames, compilerOptions);\n\n const capabilities: AuthoredCapability[] = [];\n let filesAnalyzed = 0;\n\n let filesOutsideRoot = 0;\n\n // Registrations whose `type` comes from a parameter, and every call that\n // could supply it. Both are filled during the single walk below; the join\n // happens after, because a call site may live in a file walked earlier.\n const deferred: DeferredWrapper[] = [];\n const callsByName = new Map<string, CallSite[]>();\n\n for (const source of program.getSourceFiles()) {\n if (source.isDeclarationFile) continue;\n if (source.fileName.includes(\"/node_modules/\")) continue;\n // A tsconfig with workspace path aliases pulls the library's *own* source\n // into the program, where `registry.register(definition)` inside\n // `useAgentComponent` reads as an unresolvable registration call site. It\n // is not one: it is the implementation every real call site goes through.\n // The inventory covers the app the surface config points at, and says so\n // rather than quietly widening or narrowing.\n if (!isInside(root, source.fileName)) {\n filesOutsideRoot += 1;\n continue;\n }\n filesAnalyzed += 1;\n\n const emit: Emitter = {\n push: (capability) => capabilities.push(capability),\n origin: (node) => ({\n file: relative(root, source.fileName),\n line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1,\n }),\n };\n\n let pendingName: string | undefined;\n const visit = (node: ts.Node, enclosing?: EnclosingFunction): void => {\n const fn = functionLike(node);\n if (fn) {\n // A variable declaration names the arrow it initialises, and the walk\n // sees the declaration first — so the name is already in hand here.\n const named = ts.isFunctionDeclaration(node) && node.name ? node.name.text : pendingName;\n enclosing = { fn, ...(named ? { name: named } : {}) };\n pendingName = undefined;\n } else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n pendingName = node.name.text;\n }\n\n if (ts.isCallExpression(node)) {\n visitCall(node, emit, source, deferred, enclosing);\n // Every identifier-callee call is a potential wrapper call site. Indexed\n // by name here so the second pass does not re-walk the program.\n if (ts.isIdentifier(node.expression)) {\n const name = node.expression.text;\n const sites = callsByName.get(name) ?? [];\n sites.push({ call: node, source });\n callsByName.set(name, sites);\n }\n }\n ts.forEachChild(node, (child) => visit(child, enclosing));\n };\n visit(source);\n }\n\n // Second pass: one hop *up* the call graph. The first pass follows one hop\n // sideways to a same-module `const`; this is the same budget pointed the\n // other way, and it is what turns a shared wrapper hook from a single unread\n // line into the capabilities it actually authors.\n for (const wrapper of deferred) {\n const sites = (callsByName.get(wrapper.wrapperName) ?? []).filter((site) =>\n callsWrapper(site, wrapper, compilerOptions),\n );\n\n const types = new Map<string, ts.CallExpression>();\n const dynamic: CallSite[] = [];\n for (const site of sites) {\n const argument = site.call.arguments[wrapper.slot.index];\n const value =\n wrapper.slot.property && argument && ts.isObjectLiteralExpression(argument)\n ? propertyOf(argument, wrapper.slot.property)\n : argument;\n const text = value ? literalText(value) : undefined;\n if (text !== undefined) types.set(text, site.call);\n else dynamic.push(site);\n }\n\n // Partial resolution beats all-or-nothing: 15 literals plus 2 unread lines\n // is a truer catalog than 17 unread lines, and it stays honest about which\n // two it could not read.\n for (const type of [...types.keys()].sort()) {\n const componentPartial = hasSpread(wrapper.config)\n ? \"the component config spreads another object, so some metadata here may be dynamic\"\n : undefined;\n for (const [group, kind] of [\n [\"observations\", \"observation\"],\n [\"actions\", \"action\"],\n ] as const) {\n capabilitiesFromGroup(\n propertyOf(wrapper.config, group),\n kind,\n type,\n componentPartial,\n wrapper.emit,\n wrapper.source,\n );\n }\n }\n\n if (types.size === 0 || dynamic.length > 0) {\n // Nothing resolved, or some call sites pass a non-literal. Either way the\n // catalog is a floor here and has to say so.\n wrapper.emit.push({\n capabilityId: UNRESOLVED_ID,\n kind: \"action\",\n origin: wrapper.emit.origin(wrapper.site),\n resolution: \"unresolved\",\n reason: \"dynamic-type\",\n note:\n types.size === 0\n ? `\\`type\\` is a parameter of ${wrapper.wrapperName}(), and no call site of it in this program passes a string literal`\n : `\\`type\\` is a parameter of ${wrapper.wrapperName}(); ${types.size} call site${\n types.size === 1 ? \"\" : \"s\"\n } resolved, ${dynamic.length} pass${dynamic.length === 1 ? \"es\" : \"\"} a non-literal`,\n });\n }\n }\n\n capabilities.sort(\n (a, b) =>\n a.capabilityId.localeCompare(b.capabilityId) ||\n a.origin.file.localeCompare(b.origin.file) ||\n a.origin.line - b.origin.line,\n );\n\n return {\n capabilities,\n tsconfig: tsconfigPath,\n root,\n filesAnalyzed,\n filesOutsideRoot,\n domain: \"not-analyzed\",\n };\n}\n\n/** True when `file` lives under `root` — the analyzed boundary. */\nfunction isInside(root: string, file: string): boolean {\n const rel = relative(root, file);\n return rel !== \"\" && !rel.startsWith(\"..\") && !isAbsolute(rel);\n}\n\n/** Distinct capability ids the inventory resolved — the coverage denominator. */\nexport function authoredIds(inventory: CapabilityInventory): Set<string> {\n const ids = new Set<string>();\n for (const capability of inventory.capabilities) {\n if (capability.resolution === \"unresolved\") continue;\n if (capability.capabilityId.endsWith(UNRESOLVED_ID)) continue;\n ids.add(capability.capabilityId);\n }\n return ids;\n}\n\nexport function unresolved(inventory: CapabilityInventory): AuthoredCapability[] {\n return inventory.capabilities.filter((capability) => capability.resolution === \"unresolved\");\n}\n","/**\n * The two halves of the surface, and the join between them.\n *\n * A presentation surface has two sources of truth, and every command needs\n * some mix of both:\n *\n * - the **catalog** — what this codebase authors. Static: `type` is a string\n * literal, capability names are object keys, so `view:devices.table.sort` is\n * fully determined by source text ([`extract.ts`](./extract.ts)).\n * - the **projection** — what a mounted scenario actually surfaces, after\n * availability, policy and binding have had their say ([`collect.ts`](./collect.ts)).\n *\n * Splitting those across separate commands is what let a green `check` sit on\n * top of a route no scenario visits. So the split lives here, behind a `depth`\n * dial, and the commands compose the same three steps in whatever order their\n * output needs: `inspect` streams and closes with the verdict, `check` collects\n * and leads with it.\n */\nimport { dirname } from \"node:path\";\nimport { matchesScope } from \"@agent-surface/core/explain\";\nimport { baselineDirFor } from \"./baseline.js\";\nimport { UsageError, type Depth } from \"./contract.js\";\nimport type { CollectResult } from \"./collect.js\";\nimport {\n allowlistPathFor,\n buildCoverageReport,\n readAllowlist,\n unreadAllowlistPathFor,\n type CoverageReport,\n} from \"./coverage.js\";\nimport {\n authoredIds,\n extractCapabilities,\n unresolved,\n type CapabilityInventory,\n} from \"./extract.js\";\nimport { createSurfaceRunner } from \"./load.js\";\n\nexport type { Depth } from \"./contract.js\";\nexport { UsageError } from \"./contract.js\";\n\nexport interface AnalysisOptions {\n configPath: string;\n depth: Depth;\n scenario?: string;\n scope?: string[];\n tsconfig?: string;\n baselineDir?: string;\n}\n\n/**\n * The static half. `undefined` at `--depth runtime`, which is the caller\n * saying it does not want this computed rather than it having failed.\n */\nexport function readInventory(options: AnalysisOptions): CapabilityInventory | undefined {\n if (options.depth === \"runtime\") return undefined;\n return extractCapabilities({\n root: dirname(options.configPath),\n ...(options.tsconfig ? { tsconfig: options.tsconfig } : {}),\n });\n}\n\n/** A scenario the config declares whose mount threw. Named, never swallowed. */\nexport interface ScenarioFailure {\n scenario: string;\n message: string;\n}\n\nexport interface RuntimeAnalysis {\n /** Every scenario this run set out to mount, in config order. */\n scenarios: string[];\n /** The ones that mounted. */\n results: CollectResult[];\n failures: ScenarioFailure[];\n baselineDir: string;\n}\n\n/**\n * The runtime half. `undefined` at `--depth static`.\n *\n * `onEach` is awaited as each scenario finishes, so a command can print as it\n * goes instead of after the last mount — a config with ten scenarios is a long\n * time to look at nothing.\n *\n * A scenario that throws is recorded and the run continues. Before this was\n * one command, `capabilities` was the only thing that still worked on an app\n * that would not mount; merging the commands would have thrown that away if a\n * single bad scenario could abort the run.\n */\nexport async function mountScenarios(\n options: AnalysisOptions,\n onEach?: (result: CollectResult) => void | Promise<void>,\n): Promise<RuntimeAnalysis | undefined> {\n if (options.depth === \"static\") return undefined;\n\n const runner = await createSurfaceRunner(options.configPath);\n try {\n if (options.scenario && !runner.scenarioNames.includes(options.scenario)) {\n throw new UsageError(\n `unknown scenario \"${options.scenario}\" — this config defines ` +\n runner.scenarioNames.map((name) => `\"${name}\"`).join(\", \"),\n );\n }\n const scenarios = options.scenario ? [options.scenario] : runner.scenarioNames;\n const results: CollectResult[] = [];\n const failures: ScenarioFailure[] = [];\n\n for (const scenario of scenarios) {\n let result: CollectResult;\n try {\n result = await runner.collect({\n scenario,\n ...(options.scope ? { scope: options.scope } : {}),\n });\n } catch (error) {\n failures.push({\n scenario,\n message: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n results.push(result);\n await onEach?.(result);\n }\n\n return {\n scenarios,\n results,\n failures,\n baselineDir: baselineDirFor(\n options.configPath,\n options.baselineDir ?? runner.config.baselineDir,\n ),\n };\n } finally {\n await runner.close();\n }\n}\n\n/**\n * The component type a capability id belongs to: `view:devices.table.sort` →\n * `devices.table`. Capability names are object keys and cannot contain a dot,\n * so the last one is always the boundary; component types can and do.\n */\nfunction componentTypeOf(capabilityId: string): string {\n const withoutPlane = capabilityId.replace(/^(view|domain):/, \"\");\n const dot = withoutPlane.lastIndexOf(\".\");\n return dot === -1 ? withoutPlane : withoutPlane.slice(0, dot);\n}\n\n/**\n * Authored minus reached (`AS-COVER-004…005`).\n *\n * Two ways this returns `undefined`, and neither is \"no gaps\":\n *\n * - a half was not computed, because the depth did not ask for it;\n * - **a scenario failed to mount.** That scenario reached nothing, so every\n * capability it would have surfaced would be reported as one no scenario\n * reaches. A coverage verdict computed over a partial run is precisely the\n * misleading check this package refuses to emit, so there is no verdict\n * until every scenario mounted. The renderer says which of the two it was.\n */\nexport function joinCoverage(\n inventory: CapabilityInventory | undefined,\n runtime: RuntimeAnalysis | undefined,\n options: AnalysisOptions,\n): CoverageReport | undefined {\n if (!inventory || !runtime) return undefined;\n if (runtime.failures.length > 0) return undefined;\n\n // A scope filters the mount, so it has to filter the catalog by the same\n // predicate — core's own, not a second copy of it. Without this, `--scope\n // devices` reported every `app.navigation` capability as unreached, with the\n // words \"no scenario mounts it\" over two that both scenarios mount.\n const inScope = (capabilityId: string): boolean =>\n matchesScope(componentTypeOf(capabilityId), options.scope);\n\n const origins = new Map<string, { file: string; line: number }>();\n for (const capability of inventory.capabilities) {\n if (!origins.has(capability.capabilityId)) {\n origins.set(capability.capabilityId, capability.origin);\n }\n }\n\n const authored = new Set([...authoredIds(inventory)].filter(inScope));\n const reachedIds = new Set<string>();\n for (const result of runtime.results) {\n for (const capability of result.explanation.capabilities) {\n reachedIds.add(capability.capabilityId);\n }\n }\n\n // The allowlist is a statement about the whole catalog, and a scoped run has\n // only looked at part of it. Judging an out-of-scope entry either way would\n // be wrong in both directions — it is not an unreached capability this run\n // waved through, and it is not a stale entry either, because nothing here\n // reached it.\n const allowlistPath = allowlistPathFor(runtime.baselineDir);\n const wholeAllowlist = readAllowlist(allowlistPath);\n const allowlist = Object.fromEntries(\n Object.entries(wholeAllowlist).filter(([id]) => inScope(id)),\n );\n\n // Not scope-filtered, deliberately: an unread call site has no capability id,\n // so there is no component type to test a scope prefix against. A scoped run\n // simply reports the same unread sites as an unscoped one.\n const unreadAllowlistPath = unreadAllowlistPathFor(runtime.baselineDir);\n\n return buildCoverageReport({\n unreadAllowlist: readAllowlist(unreadAllowlistPath, \"file#reason\"),\n unreadAllowlistPath,\n authored,\n origins,\n reachedIds,\n scenarios: runtime.scenarios,\n ...(options.scope ? { scope: options.scope } : {}),\n unresolved: unresolved(inventory),\n allowlist,\n allowlistOutOfScope: Object.keys(wholeAllowlist).length - Object.keys(allowlist).length,\n allowlistPath,\n });\n}\n"],"mappings":"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","names":["readFileSync","join","existsSync","dirname","join","resolve","dirname","dirname"]}
|
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
authoredIds,
|
|
6
6
|
formatValue,
|
|
7
|
+
unreadKey,
|
|
7
8
|
unresolved
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-2FG527AM.js";
|
|
9
10
|
|
|
10
11
|
// src/render/plain.ts
|
|
11
12
|
import { relative } from "path";
|
|
@@ -178,6 +179,7 @@ function renderUnread(entries) {
|
|
|
178
179
|
for (const capability of entries) {
|
|
179
180
|
lines.push(` ? ${capability.origin.file}:${capability.origin.line}`);
|
|
180
181
|
lines.push(` ${capability.note ?? "the extractor could not read this call site"}`);
|
|
182
|
+
lines.push(` allowlist key: ${unreadKey(capability)}`);
|
|
181
183
|
}
|
|
182
184
|
return lines;
|
|
183
185
|
}
|
|
@@ -219,6 +221,17 @@ function renderCoveragePlain(report) {
|
|
|
219
221
|
for (const id of report.staleAllowlist) lines.push(` ${id}`);
|
|
220
222
|
lines.push("");
|
|
221
223
|
}
|
|
224
|
+
if (report.staleUnreadAllowlist.length > 0) {
|
|
225
|
+
lines.push(
|
|
226
|
+
section(
|
|
227
|
+
"STALE UNREAD ALLOWLIST",
|
|
228
|
+
"the extractor reads these now, so delete them before the list rots",
|
|
229
|
+
report.staleUnreadAllowlist.length
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
for (const key of report.staleUnreadAllowlist) lines.push(` ${key}`);
|
|
233
|
+
lines.push("");
|
|
234
|
+
}
|
|
222
235
|
if (report.unresolved.length > 0) {
|
|
223
236
|
lines.push(...renderUnread(report.unresolved), "");
|
|
224
237
|
}
|
|
@@ -245,12 +258,17 @@ function renderCoverageSummary(report) {
|
|
|
245
258
|
`${report.allowed.length} unreached capabilit${report.allowed.length === 1 ? "y is" : "ies are"} allowlisted in ${relative(process.cwd(), report.allowlistPath)}`
|
|
246
259
|
);
|
|
247
260
|
}
|
|
261
|
+
if (report.allowedUnread.length > 0) {
|
|
262
|
+
lines.push(
|
|
263
|
+
`${report.allowedUnread.length} unread call site${report.allowedUnread.length === 1 ? " is" : "s are"} allowlisted in ${relative(process.cwd(), report.unreadAllowlistPath)}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
248
266
|
if (report.allowlistOutOfScope > 0) {
|
|
249
267
|
lines.push(
|
|
250
268
|
`${report.allowlistOutOfScope} allowlist entr${report.allowlistOutOfScope === 1 ? "y" : "ies"} outside this scope were not judged either way`
|
|
251
269
|
);
|
|
252
270
|
}
|
|
253
|
-
if (report.unreached.length === 0 && report.unresolved.length === 0 && report.staleAllowlist.length === 0) {
|
|
271
|
+
if (report.unreached.length === 0 && report.unresolved.length === 0 && report.staleAllowlist.length === 0 && report.staleUnreadAllowlist.length === 0) {
|
|
254
272
|
lines.push(
|
|
255
273
|
report.allowed.length > 0 ? "no new surface coverage gaps \u2014 the allowlist still holds the known ones" : "every authored capability is reached by a scenario"
|
|
256
274
|
);
|
|
@@ -295,4 +313,4 @@ export {
|
|
|
295
313
|
renderFailuresPlain,
|
|
296
314
|
renderDriftPlain
|
|
297
315
|
};
|
|
298
|
-
//# sourceMappingURL=chunk-
|
|
316
|
+
//# sourceMappingURL=chunk-AFLVTBI6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/render/plain.ts"],"sourcesContent":["import { relative } from \"node:path\";\nimport type { CapabilityRow, SurfaceView } from \"./model.js\";\nimport { flatRows } from \"./model.js\";\nimport type { DiffEntry } from \"../baseline.js\";\nimport { formatValue } from \"../baseline.js\";\nimport type { ScenarioFailure } from \"../analysis.js\";\nimport {\n authoredIds,\n unresolved,\n type AuthoredCapability,\n type CapabilityInventory,\n} from \"../extract.js\";\nimport { unreadKey, type CoverageReport } from \"../coverage.js\";\n\n/**\n * The no-colour, no-cursor rendering used when stdout is piped or when\n * `--plain`, `CI` or `NO_COLOR` is set. Same view model as the Ink UI, so the\n * two cannot disagree about what the surface contains.\n */\n\nconst MARK = { expose: \"+\", disable: \"~\", hide: \"-\" } as const;\nconst STATE = { expose: \"callable\", disable: \"disabled\", hide: \"hidden\" } as const;\nconst NONE = \"—\";\n\n/**\n * Column widths come from the *content*, never from `process.stdout.columns`.\n *\n * `AS-CLI-003` requires plain output to be byte-stable across runs, and a table\n * laid out against the terminal it happened to run in is stable only until two\n * people diff the same CI log from different windows. Same rows in, same bytes\n * out, everywhere.\n *\n * The last column is not padded, so no line ever carries trailing whitespace —\n * which some diff tools render and others strip, i.e. another way for identical\n * output to look different.\n */\nfunction renderTable(headers: string[], rows: Array<{ cells: string[]; note?: string }>): string[] {\n const widths = headers.map((header, column) =>\n Math.max(header.length, ...rows.map((row) => (row.cells[column] ?? \"\").length)),\n );\n const line = (cells: string[]): string =>\n cells\n .map((cell, column) => (column === headers.length - 1 ? cell : cell.padEnd(widths[column]!)))\n .join(\" \")\n .trimEnd();\n\n const lines = [line(headers)];\n for (const row of rows) {\n lines.push(line(row.cells));\n // The unavailability reason is prose of unbounded length. A column for it\n // would set the table's width by its longest sentence; a continuation line\n // keeps the grid aligned and puts the reason directly under its capability.\n if (row.note) lines.push(` ⤷ ${row.note}`);\n }\n return lines;\n}\n\n/** `UNREACHED — authored, and no scenario mounts it (1)` */\nfunction section(title: string, gloss: string, count: number): string {\n return `${title} — ${gloss} (${count})`;\n}\n\nfunction renderDetailRow(row: CapabilityRow, lines: string[]): void {\n const tags = row.tags.length > 0 ? ` [${row.tags.join(\", \")}]` : \"\";\n lines.push(` ${MARK[row.outcome]} ${row.name}${tags}`);\n lines.push(` ${row.description}`);\n if (row.reason) lines.push(` reason: ${row.reason}`);\n\n if (row.policies) {\n if (row.policies.length === 0) {\n lines.push(\" policies: none\");\n } else {\n for (const policy of row.policies) {\n const vote = policy.discovery\n ? policy.discovery.decision === \"disable\"\n ? `disable — ${policy.discovery.reason}`\n : policy.discovery.decision\n : \"no discovery hook\";\n const phases = policy.phases.length > 0 ? policy.phases.join(\"/\") : NONE;\n const flags = [\n policy.threw ? \"THREW\" : \"\",\n policy.confirmationEscalation ? \"escalates-confirmation\" : \"\",\n ]\n .filter(Boolean)\n .join(\", \");\n lines.push(\n ` policy ${policy.name} (${policy.scope}, ${phases}): ${vote}${\n flags ? ` [${flags}]` : \"\"\n }`,\n );\n }\n }\n if (row.availability && !row.availability.available) {\n lines.push(\n ` availability: unavailable${\n row.availability.reason ? ` — ${row.availability.reason}` : \"\"\n }`,\n );\n }\n }\n\n if (row.schemas) {\n if (row.schemas.input !== undefined) {\n lines.push(` input: ${JSON.stringify(row.schemas.input)}`);\n }\n if (row.schemas.output !== undefined) {\n lines.push(` output: ${JSON.stringify(row.schemas.output)}`);\n }\n }\n}\n\n/**\n * The counts line, and everything it is relative to (`AS-CLI-007`).\n *\n * `hidden` is printed unconditionally. It is computed on every run — the\n * explanation is always collected — and suppressing it outside `--explain`\n * meant a surface with a policy-hidden half rendered as a complete one. The\n * *attribution* still needs `--explain`; the count and the rows do not.\n */\nexport function renderCountsPlain(view: SurfaceView): string {\n return (\n `${view.counts.callable} callable, ${view.counts.disabled} visible-disabled, ` +\n `${view.counts.hidden} hidden` +\n (view.rejections.length > 0\n ? `, ${view.rejections.length} registration${view.rejections.length === 1 ? \"\" : \"s\"} rejected`\n : \"\")\n );\n}\n\nfunction renderHeader(view: SurfaceView, lines: string[]): void {\n lines.push(\n `scenario ${view.scenario}${view.route ? ` route ${view.route}` : \"\"}${\n view.scope && view.scope.length > 0 ? ` scope ${view.scope.join(\" \")}` : \"\"\n }`,\n );\n lines.push(renderCountsPlain(view));\n}\n\nfunction renderRejections(view: SurfaceView, lines: string[]): void {\n if (view.rejections.length === 0) return;\n lines.push(\"\");\n lines.push(\n section(\"REJECTED\", \"the registry refused these during the mount\", view.rejections.length),\n );\n for (const rejection of view.rejections) {\n const why =\n rejection.reason === \"duplicate\"\n ? \"duplicate — an earlier registration holds this key\"\n : \"guard — onRegister rejected this registration\";\n lines.push(` ! ${rejection.componentType} (${rejection.instanceId}) ${why}`);\n }\n}\n\nfunction renderEmpty(view: SurfaceView, lines: string[]): void {\n lines.push(\"\");\n // \"Nothing is registered\" is only true when nothing was hidden. Saying it\n // over a surface a policy emptied sends the reader to the wrong file.\n if (view.counts.hidden > 0) {\n lines.push(\n `Nothing is callable here — all ${view.counts.hidden} registered capabilities were hidden by policy.`,\n );\n if (!view.explained) lines.push(\"Re-run with --explain to see which policy hid them.\");\n } else {\n lines.push(\"Nothing is registered for this scenario — the agent has no surface here.\");\n if (!view.explained) lines.push(\"Re-run with --explain to see whether a policy hid it.\");\n }\n}\n\nexport interface SurfaceRenderOptions {\n /** The grouped, one-capability-per-paragraph view. Implied by --explain/--schemas. */\n detail?: boolean;\n}\n\n/**\n * One capability per line, aligned. The default, because the question `inspect`\n * is usually asked is *what is on this surface* — which is a scanning question,\n * and prose does not scan.\n *\n * Policy chains and JSON Schemas are multi-line by nature and cannot live in a\n * cell, so `--explain` and `--schemas` fall back to the detail view rather than\n * producing a table with most of the answer missing.\n */\nexport function renderSurfacePlain(\n view: SurfaceView,\n options: SurfaceRenderOptions = {},\n): string {\n const lines: string[] = [];\n renderHeader(view, lines);\n renderRejections(view, lines);\n\n const rows = flatRows(view);\n if (rows.length === 0) {\n renderEmpty(view, lines);\n return lines.join(\"\\n\");\n }\n\n if (options.detail) {\n for (const group of view.groups.filter((group) => group.rows.length > 0)) {\n lines.push(\"\");\n lines.push(`${group.heading} (${group.rows.length})`);\n for (const row of group.rows) renderDetailRow(row, lines);\n }\n return lines.join(\"\\n\");\n }\n\n lines.push(\"\");\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"KIND\", \"EFFECT\", \"STATE\", \"FLAGS\"],\n rows.map((row) => ({\n cells: [\n row.path,\n row.kind,\n row.effect ?? NONE,\n STATE[row.outcome],\n row.flags.length > 0 ? row.flags.join(\" · \") : NONE,\n ],\n ...(row.reason ? { note: row.reason } : {}),\n })),\n ),\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * The static catalog (`AS-COVER-001…003`). The summary says \"upper bound\" in so\n * many words: a tsconfig's include globs are wider than what a bundle reaches,\n * so a capability in a component no route renders any more is in here. That is\n * dead code — a different finding, not a false positive — and the reader has to\n * be told which number they are holding.\n */\nexport interface CatalogRenderOptions {\n /**\n * This catalog *is* the command's output, rather than its preamble.\n *\n * True at `--depth static`: there are no scenario tables and no verdict, so\n * the listing and the unread call sites have nowhere else to appear.\n *\n * False at `--depth full`, where the scenario tables below name every\n * capability a scenario reached, the `UNREACHED` section names the ones it did\n * not, and the verdict carries the unread call sites — so printing any of it\n * here is the same information a second time, above the answer instead of in\n * it. Only the summary line survives.\n */\n standalone?: boolean;\n}\n\nexport function renderCatalogPlain(\n inventory: CapabilityInventory,\n options: CatalogRenderOptions = {},\n): string {\n const lines: string[] = [];\n const resolved = inventory.capabilities.filter((c) => c.resolution !== \"unresolved\");\n const ids = authoredIds(inventory);\n\n lines.push(\n `${ids.size} authored (upper bound) · ${resolved.length} call site${\n resolved.length === 1 ? \"\" : \"s\"\n } across ${inventory.filesAnalyzed} file${inventory.filesAnalyzed === 1 ? \"\" : \"s\"}` +\n \" · domain not analyzed, it comes from the oRPC router (OQ-1)\",\n );\n if (inventory.filesOutsideRoot > 0) {\n // Relative, not absolute: plain output is byte-stable across runs\n // (`AS-CLI-003`), and an absolute path makes it machine-specific the moment\n // two people diff a CI log.\n lines.push(\n `${inventory.filesOutsideRoot} program file${\n inventory.filesOutsideRoot === 1 ? \"\" : \"s\"\n } outside the config's directory were not analyzed`,\n );\n }\n\n if (!options.standalone) return lines.join(\"\\n\");\n\n const byId = [...resolved].sort((a, b) => a.capabilityId.localeCompare(b.capabilityId));\n if (byId.length > 0) {\n lines.push(\"\");\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"KIND\", \"ORIGIN\", \"READ\"],\n byId.map((capability) => ({\n cells: [\n capability.capabilityId,\n capability.kind,\n `${capability.origin.file}:${capability.origin.line}`,\n capability.resolution,\n ],\n ...(capability.note ? { note: capability.note } : {}),\n })),\n ),\n );\n }\n\n const unread = renderUnread(unresolved(inventory));\n if (unread.length > 0) lines.push(\"\", ...unread);\n return lines.join(\"\\n\");\n}\n\n/**\n * Call sites the extractor could not read. Reported with file and line, never\n * dropped: an inventory that silently omitted what it failed to parse would\n * understate the denominator, and every number built on it would claim a\n * completeness it never had.\n */\nfunction renderUnread(entries: AuthoredCapability[]): string[] {\n if (entries.length === 0) return [];\n const lines: string[] = [];\n lines.push(\n section(\n \"UNREAD CALL SITES\",\n \"the catalog is incomplete, so every count above is a floor\",\n entries.length,\n ),\n );\n for (const capability of entries) {\n lines.push(` ? ${capability.origin.file}:${capability.origin.line}`);\n lines.push(` ${capability.note ?? \"the extractor could not read this call site\"}`);\n // The allowlist key, spelled out. It is `file#reason` rather than the line\n // the reader is looking at, so leaving them to infer it guarantees a wrong\n // guess and an entry that never matches.\n lines.push(` allowlist key: ${unreadKey(capability)}`);\n }\n return lines;\n}\n\n/**\n * The verdict: authored minus reached (`AS-COVER-004…005`).\n *\n * This is the finding the command surface used to hide behind a fifth command,\n * so it is the last thing printed and the thing a reader stops on.\n */\nexport function renderCoveragePlain(report: CoverageReport): string {\n const lines: string[] = [];\n\n if (report.unreached.length > 0) {\n lines.push(\n section(\"UNREACHED\", \"authored, and no scenario mounts it\", report.unreached.length),\n );\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"ORIGIN\"],\n report.unreached.map((entry) => ({\n cells: [entry.capabilityId, `${entry.origin.file}:${entry.origin.line}`],\n })),\n ),\n );\n lines.push(\"\");\n }\n\n if (report.undeclared.length > 0) {\n lines.push(\n section(\n \"UNDECLARED\",\n \"present at runtime with no static origin — a dynamic registration, or a gap here\",\n report.undeclared.length,\n ),\n );\n for (const id of report.undeclared) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.staleAllowlist.length > 0) {\n lines.push(\n section(\n \"STALE ALLOWLIST\",\n \"a scenario reaches these now, so delete them before the list rots\",\n report.staleAllowlist.length,\n ),\n );\n for (const id of report.staleAllowlist) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.staleUnreadAllowlist.length > 0) {\n lines.push(\n section(\n \"STALE UNREAD ALLOWLIST\",\n \"the extractor reads these now, so delete them before the list rots\",\n report.staleUnreadAllowlist.length,\n ),\n );\n for (const key of report.staleUnreadAllowlist) lines.push(` ${key}`);\n lines.push(\"\");\n }\n\n if (report.unresolved.length > 0) {\n lines.push(...renderUnread(report.unresolved), \"\");\n }\n\n lines.push(...renderCoverageSummary(report));\n return lines.join(\"\\n\");\n}\n\n/** The one line a reader who stops at the bottom takes away. */\nfunction renderCoverageSummary(report: CoverageReport): string[] {\n const qualifiers = [\n `${report.scenarios.length} scenario${report.scenarios.length === 1 ? \"\" : \"s\"} (${report.scenarios.join(\n \", \",\n )})`,\n ];\n // Every count is relative to the scope, so the scope is printed with them\n // (`AS-CLI-007`) — `10 authored` under a scope is a claim about one prefix of\n // the codebase, not about the codebase.\n if (report.scope && report.scope.length > 0) qualifiers.push(`scope ${report.scope.join(\" \")}`);\n\n const lines = [\n `${report.authored} authored · ${report.reached} reached · ${report.unreached.length} unreached` +\n ` · ${qualifiers.join(\" · \")}`,\n ];\n\n if (report.domainReached.length > 0) {\n lines.push(\n `${report.domainReached.length} domain capabilit${\n report.domainReached.length === 1 ? \"y\" : \"ies\"\n } reached and held apart — that plane is the oRPC router's, and this catalog never claimed it`,\n );\n }\n if (report.allowed.length > 0) {\n lines.push(\n `${report.allowed.length} unreached capabilit${\n report.allowed.length === 1 ? \"y is\" : \"ies are\"\n } allowlisted in ${relative(process.cwd(), report.allowlistPath)}`,\n );\n }\n if (report.allowedUnread.length > 0) {\n lines.push(\n `${report.allowedUnread.length} unread call site${\n report.allowedUnread.length === 1 ? \" is\" : \"s are\"\n } allowlisted in ${relative(process.cwd(), report.unreadAllowlistPath)}`,\n );\n }\n if (report.allowlistOutOfScope > 0) {\n lines.push(\n `${report.allowlistOutOfScope} allowlist entr${\n report.allowlistOutOfScope === 1 ? \"y\" : \"ies\"\n } outside this scope were not judged either way`,\n );\n }\n\n // Each bucket gets its own remedy. \"Add a scenario, or delete the component\"\n // is the right advice for an unreached capability and useless advice for a\n // call site the extractor could not read.\n if (\n report.unreached.length === 0 &&\n report.unresolved.length === 0 &&\n report.staleAllowlist.length === 0 &&\n report.staleUnreadAllowlist.length === 0\n ) {\n lines.push(\n report.allowed.length > 0\n ? \"no new surface coverage gaps — the allowlist still holds the known ones\"\n : \"every authored capability is reached by a scenario\",\n );\n }\n return lines;\n}\n\n/**\n * Why there is no coverage verdict. Never silence: a reader who asked for the\n * complete answer and got a partial one has to be told which part is missing,\n * or the partial one reads as the complete one.\n */\nexport function renderNoVerdictPlain(failures: ScenarioFailure[]): string {\n return [\n section(\"NO COVERAGE VERDICT\", \"a scenario did not mount, so nothing reached anything\", failures.length),\n ...failures.flatMap((failure) => [` ${failure.scenario}`, ` ${failure.message}`]),\n \"\",\n \"Every capability those scenarios would have surfaced would be reported unreached,\",\n \"so no verdict is printed at all. Fix the mount, or name a scenario that works.\",\n ].join(\"\\n\");\n}\n\nexport function renderFailuresPlain(failures: ScenarioFailure[]): string {\n return [\n section(\"DID NOT MOUNT\", \"these scenarios threw, and were skipped\", failures.length),\n ...failures.flatMap((failure) => [` ${failure.scenario}`, ` ${failure.message}`]),\n ].join(\"\\n\");\n}\n\nexport function renderDriftPlain(scenario: string, entries: DiffEntry[]): string {\n const lines = [` ${scenario}: ${entries.length} change${entries.length === 1 ? \"\" : \"s\"}`];\n for (const entry of entries) {\n const where = entry.subject ? `${entry.subject} (${entry.path})` : entry.path;\n if (entry.kind === \"added\") lines.push(` + ${where} ${formatValue(entry.after)}`);\n else if (entry.kind === \"removed\") lines.push(` - ${where} ${formatValue(entry.before)}`);\n else {\n lines.push(` ~ ${where}`);\n lines.push(` before: ${formatValue(entry.before)}`);\n lines.push(` after: ${formatValue(entry.after)}`);\n }\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["group"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -70,7 +70,22 @@ interface AuthoredCapability {
|
|
|
70
70
|
resolution: "static" | "partial" | "unresolved";
|
|
71
71
|
/** Present on `partial`/`unresolved`: what defeated the extractor. */
|
|
72
72
|
note?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Present on `unresolved`: *which* construct defeated the extractor, as a
|
|
75
|
+
* stable code rather than prose.
|
|
76
|
+
*
|
|
77
|
+
* `note` is written for a human and gets reworded — the spread note changed
|
|
78
|
+
* in the same release that introduced it. Anything keyed on that prose would
|
|
79
|
+
* silently invalidate itself on an edit no one thought was behavioural, which
|
|
80
|
+
* is exactly what `unresolved-allow.json` must not do. This is the key.
|
|
81
|
+
*/
|
|
82
|
+
reason?: UnreadReason;
|
|
73
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Why a registration could not be fully read. Stable identifiers: adding one is
|
|
86
|
+
* fine, renaming one invalidates committed allowlists and is a breaking change.
|
|
87
|
+
*/
|
|
88
|
+
type UnreadReason = "dynamic-type" | "dynamic-config" | "dynamic-group" | "spread-members" | "computed-name" | "granular-hook";
|
|
74
89
|
interface CapabilityInventory {
|
|
75
90
|
capabilities: AuthoredCapability[];
|
|
76
91
|
/** Absolute path to the tsconfig whose file list was analyzed. */
|
|
@@ -144,13 +159,18 @@ interface CoverageReport {
|
|
|
144
159
|
* defect, which is the misleading check this whole command rejects.
|
|
145
160
|
*/
|
|
146
161
|
domainReached: string[];
|
|
147
|
-
/** Carried forward from the inventory. */
|
|
162
|
+
/** Carried forward from the inventory, minus anything allowlisted. */
|
|
148
163
|
unresolved: AuthoredCapability[];
|
|
149
164
|
/** Unreached, but listed in the allowlist. */
|
|
150
165
|
allowed: string[];
|
|
151
166
|
/** Listed in the allowlist and reached anyway — the list has rotted. */
|
|
152
167
|
staleAllowlist: string[];
|
|
153
168
|
allowlistPath: string;
|
|
169
|
+
/** Unread, but listed in `unresolved-allow.json`. Keys, not entries. */
|
|
170
|
+
allowedUnread: string[];
|
|
171
|
+
/** Listed there and no longer unread — that list has rotted too. */
|
|
172
|
+
staleUnreadAllowlist: string[];
|
|
173
|
+
unreadAllowlistPath: string;
|
|
154
174
|
}
|
|
155
175
|
|
|
156
176
|
export type { AuthoredCapability, CapabilityInventory, CollectResult, CoverageAllowlist, CoverageReport, RegistrationRejection };
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
extractCapabilities,
|
|
4
4
|
findTsconfig,
|
|
5
5
|
unresolved
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2FG527AM.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
isPlain,
|
|
@@ -138,4 +138,4 @@ async function ask(options, question) {
|
|
|
138
138
|
export {
|
|
139
139
|
runInit
|
|
140
140
|
};
|
|
141
|
-
//# sourceMappingURL=init-
|
|
141
|
+
//# sourceMappingURL=init-ODFEGU3P.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
renderFailuresPlain,
|
|
5
5
|
renderNoVerdictPlain,
|
|
6
6
|
renderSurfacePlain
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-AFLVTBI6.js";
|
|
8
8
|
import {
|
|
9
9
|
buildView
|
|
10
10
|
} from "./chunk-DYDSJM7R.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
joinCoverage,
|
|
13
13
|
mountScenarios,
|
|
14
14
|
readInventory
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-2FG527AM.js";
|
|
16
16
|
import {
|
|
17
17
|
isPlain,
|
|
18
18
|
loadInk,
|
|
@@ -105,4 +105,4 @@ ${renderNoVerdictPlain(runtime.failures)}`);
|
|
|
105
105
|
export {
|
|
106
106
|
runInspect
|
|
107
107
|
};
|
|
108
|
-
//# sourceMappingURL=inspect-
|
|
108
|
+
//# sourceMappingURL=inspect-6XKULUH2.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
renderCoveragePlain,
|
|
3
3
|
renderFailuresPlain
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-AFLVTBI6.js";
|
|
5
5
|
import "./chunk-DYDSJM7R.js";
|
|
6
6
|
import {
|
|
7
7
|
baselinePath,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
normalize,
|
|
11
11
|
readInventory,
|
|
12
12
|
writeBaseline
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-2FG527AM.js";
|
|
14
14
|
import {
|
|
15
15
|
UsageError,
|
|
16
16
|
write,
|
|
@@ -56,4 +56,4 @@ ${renderFailuresPlain(runtime.failures)}`);
|
|
|
56
56
|
export {
|
|
57
57
|
runSnapshot
|
|
58
58
|
};
|
|
59
|
-
//# sourceMappingURL=snapshot-
|
|
59
|
+
//# sourceMappingURL=snapshot-YGEDJVTG.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-surface/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Inspect and check the agent surface your app exposes — in the terminal and in CI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"typescript": "^5.9.2",
|
|
32
32
|
"vite": "^7.1.3",
|
|
33
33
|
"vite-node": "^3.2.4",
|
|
34
|
-
"@agent-surface/core": "^0.11.
|
|
35
|
-
"@agent-surface/react": "^0.
|
|
36
|
-
"@agent-surface/testing": "^0.
|
|
34
|
+
"@agent-surface/core": "^0.11.1",
|
|
35
|
+
"@agent-surface/react": "^0.11.1",
|
|
36
|
+
"@agent-surface/testing": "^0.11.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@testing-library/react": ">=14",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/baseline.ts","../src/coverage.ts","../src/extract.ts","../src/analysis.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { serializeSurfaceSnapshot } from \"@agent-surface/testing\";\nimport type { AgentSurfaceSnapshot } from \"@agent-surface/core\";\n\nexport const DEFAULT_BASELINE_DIR = \".agent-surface\";\n\nexport function baselineDirFor(configPath: string, configured?: string): string {\n return resolve(dirname(configPath), configured ?? DEFAULT_BASELINE_DIR);\n}\n\nexport function baselinePath(dir: string, scenario: string): string {\n return join(dir, `${scenario}.json`);\n}\n\n/**\n * The committed form. `serializeSurfaceSnapshot` is the same normalizer the\n * Vitest matcher uses: registration ids become stable placeholders and the\n * volatile fields (surfaceId, capturedAt, version) drop out, so a baseline\n * diff is a diff of *what agents can see* and nothing else.\n */\nexport function normalize(snapshot: AgentSurfaceSnapshot): unknown {\n return serializeSurfaceSnapshot(snapshot);\n}\n\nexport function readBaseline(path: string): unknown | undefined {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n } catch {\n return undefined;\n }\n}\n\nexport function writeBaseline(path: string, value: unknown): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nexport interface DiffEntry {\n path: string;\n kind: \"added\" | \"removed\" | \"changed\";\n before?: unknown;\n after?: unknown;\n /**\n * The capability the change belongs to. A reviewer needs to know that\n * `view:devices.table.sort` changed — `components[3].actions[1]` is the same\n * fact in a form nobody can act on.\n */\n subject?: string;\n}\n\nconst PATH_SEGMENT = /([^.[\\]]+)|\\[(\\d+)\\]/g;\n\n/** Nearest enclosing capability id for a diff path, if the path sits inside one. */\nfunction subjectFor(document: unknown, path: string): string | undefined {\n let node: unknown = document;\n let subject: string | undefined;\n for (const match of path.matchAll(PATH_SEGMENT)) {\n if (typeof node !== \"object\" || node === null) return subject;\n const record = node as Record<string, unknown>;\n const candidate = record[\"capabilityId\"] ?? record[\"procedureId\"];\n if (typeof candidate === \"string\") subject = candidate;\n const key = match[1] ?? match[2];\n if (key === undefined) return subject;\n node = record[key];\n }\n if (typeof node === \"object\" && node !== null) {\n const record = node as Record<string, unknown>;\n const candidate = record[\"capabilityId\"] ?? record[\"procedureId\"];\n if (typeof candidate === \"string\") subject = candidate;\n }\n return subject;\n}\n\n/** Labels each entry with the capability it belongs to, when there is one. */\nexport function annotate(entries: DiffEntry[], after: unknown, before: unknown): DiffEntry[] {\n return entries.map((entry) => {\n const subject = subjectFor(after, entry.path) ?? subjectFor(before, entry.path);\n return subject ? { ...entry, subject } : entry;\n });\n}\n\n/**\n * Structural diff, deliberately total: every difference is drift, including a\n * changed description. Descriptions are the provider's cached prompt prefix\n * (D28) — a silent edit re-bills every conversation, so it is exactly the kind\n * of change a reviewer should see.\n */\nexport function diff(before: unknown, after: unknown, path = \"\"): DiffEntry[] {\n if (Object.is(before, after)) return [];\n\n const bothArrays = Array.isArray(before) && Array.isArray(after);\n const bothObjects =\n !bothArrays &&\n typeof before === \"object\" &&\n typeof after === \"object\" &&\n before !== null &&\n after !== null;\n\n if (bothArrays) {\n const entries: DiffEntry[] = [];\n const max = Math.max(before.length, after.length);\n for (let i = 0; i < max; i++) {\n const at = `${path}[${i}]`;\n if (i >= before.length) entries.push({ path: at, kind: \"added\", after: after[i] });\n else if (i >= after.length) entries.push({ path: at, kind: \"removed\", before: before[i] });\n else entries.push(...diff(before[i], after[i], at));\n }\n return entries;\n }\n\n if (bothObjects) {\n const entries: DiffEntry[] = [];\n const beforeRecord = before as Record<string, unknown>;\n const afterRecord = after as Record<string, unknown>;\n const keys = new Set([...Object.keys(beforeRecord), ...Object.keys(afterRecord)]);\n for (const key of [...keys].sort()) {\n const at = path ? `${path}.${key}` : key;\n if (!(key in beforeRecord)) {\n entries.push({ path: at, kind: \"added\", after: afterRecord[key] });\n } else if (!(key in afterRecord)) {\n entries.push({ path: at, kind: \"removed\", before: beforeRecord[key] });\n } else {\n entries.push(...diff(beforeRecord[key], afterRecord[key], at));\n }\n }\n return entries;\n }\n\n if (JSON.stringify(before) === JSON.stringify(after)) return [];\n return [{ path: path || \"<root>\", kind: \"changed\", before, after }];\n}\n\nexport function formatValue(value: unknown): string {\n if (value === undefined) return \"—\";\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\n return text.length > 120 ? `${text.slice(0, 117)}…` : text;\n}\n","/**\n * `coverage` — authored minus reached (`AS-COVER-004…005`, D36).\n *\n * The inventory says what the codebase authors; the scenarios say what a mount\n * surfaces. Neither half alone answers \"which authored capability does no\n * scenario reach\", because that is a set difference no command computed.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AuthoredCapability } from \"./extract.js\";\n\nexport const ALLOWLIST_FILE = \"coverage-allow.json\";\n\n/**\n * A committed list of unreached capabilities a repository has decided not to\n * fix yet, each with a reason. Adoption has to ratchet rather than gate: a\n * codebase turning this on with 200 unreached capabilities cannot fix them in\n * one pull request, and a check that can only be adopted big-bang is a check\n * that never gets adopted.\n */\nexport type CoverageAllowlist = Record<string, string>;\n\nexport function allowlistPathFor(baselineDir: string): string {\n return join(baselineDir, ALLOWLIST_FILE);\n}\n\nexport function readAllowlist(path: string): CoverageAllowlist {\n if (!existsSync(path)) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, \"utf8\"));\n } catch (error) {\n throw new Error(\n `could not parse ${path}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(`${path} must be a JSON object of { \"capabilityId\": \"reason\" }`);\n }\n const allowlist: CoverageAllowlist = {};\n for (const [id, reason] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof reason !== \"string\" || reason.trim() === \"\") {\n throw new Error(`${path}: \"${id}\" needs a non-empty reason string`);\n }\n allowlist[id] = reason;\n }\n return allowlist;\n}\n\nexport interface UnreachedCapability {\n capabilityId: string;\n origin: { file: string; line: number };\n}\n\nexport interface CoverageReport {\n /** Distinct capability ids the inventory resolved, within any active scope. */\n authored: number;\n /** How many of them at least one scenario surfaced. */\n reached: number;\n scenarios: string[];\n /**\n * The scope every number here was computed under (`AS-CLI-007`). A scope\n * filters the catalog *and* the mount, so `10 authored` without it on screen\n * reads as a claim about the whole codebase when it is a claim about one\n * prefix of it.\n */\n scope?: string[];\n /**\n * Allowlist entries outside the active scope, which a scoped run cannot\n * judge: not unreached (nothing looked), not stale (nothing reached them).\n * Counted rather than silently dropped, so a scoped run never reads as a\n * verdict on the whole allowlist.\n */\n allowlistOutOfScope: number;\n /** Authored, surfaced by no scenario, and not allowlisted — the finding. */\n unreached: UnreachedCapability[];\n /**\n * Present at runtime with no static origin: a dynamic registration, or a gap\n * in the extractor. `view:` only — see `domainReached`.\n */\n undeclared: string[];\n /**\n * `domain:` capabilities a scenario surfaced. Held apart from `undeclared`\n * because the inventory never claimed to analyze that plane: filing them as\n * \"no static origin\" would report the design's own stated boundary as a\n * defect, which is the misleading check this whole command rejects.\n */\n domainReached: string[];\n /** Carried forward from the inventory. */\n unresolved: AuthoredCapability[];\n /** Unreached, but listed in the allowlist. */\n allowed: string[];\n /** Listed in the allowlist and reached anyway — the list has rotted. */\n staleAllowlist: string[];\n allowlistPath: string;\n}\n\nexport interface BuildCoverageInput {\n authored: Set<string>;\n /** First origin seen for each authored id, for the report. */\n origins: Map<string, { file: string; line: number }>;\n /**\n * Every capability id any scenario's *explanation* held.\n *\n * The explanation, not the snapshot. A capability a policy hid **was**\n * reached: a scenario mounted it and the policy made a deliberate decision\n * about it. Classifying those as unreached would flood the report with the\n * library's own correct behaviour — in the example app the `anonymous`\n * scenario alone would contribute eleven false gaps.\n */\n reachedIds: Set<string>;\n scenarios: string[];\n scope?: string[];\n unresolved: AuthoredCapability[];\n /**\n * Already filtered to the active scope by the caller, which owns the scope\n * predicate. Entries outside it are counted in `allowlistOutOfScope`.\n */\n allowlist: CoverageAllowlist;\n allowlistOutOfScope?: number;\n allowlistPath: string;\n}\n\nexport function buildCoverageReport(input: BuildCoverageInput): CoverageReport {\n const unreached: UnreachedCapability[] = [];\n const allowed: string[] = [];\n\n for (const id of [...input.authored].sort()) {\n if (input.reachedIds.has(id)) continue;\n if (id in input.allowlist) {\n allowed.push(id);\n continue;\n }\n unreached.push({ capabilityId: id, origin: input.origins.get(id) ?? { file: \"?\", line: 0 } });\n }\n\n // An allowlist entry that is no longer unreached fails the command, so the\n // list shrinks and cannot silently rot — the same idiom as the baselines\n // `check` already commits.\n const staleAllowlist = Object.keys(input.allowlist)\n .filter((id) => input.reachedIds.has(id) || !input.authored.has(id))\n .sort();\n\n const unaccounted = [...input.reachedIds].filter((id) => !input.authored.has(id)).sort();\n const domainReached = unaccounted.filter((id) => id.startsWith(\"domain:\"));\n const undeclared = unaccounted.filter((id) => !id.startsWith(\"domain:\"));\n\n return {\n authored: input.authored.size,\n reached: [...input.authored].filter((id) => input.reachedIds.has(id)).length,\n scenarios: input.scenarios,\n ...(input.scope ? { scope: input.scope } : {}),\n allowlistOutOfScope: input.allowlistOutOfScope ?? 0,\n unreached,\n undeclared,\n domainReached,\n unresolved: input.unresolved,\n allowed,\n staleAllowlist,\n allowlistPath: input.allowlistPath,\n };\n}\n\n/**\n * `0` clean, `1` a gap.\n *\n * `undeclared` deliberately does not fail (OQ-4): a dynamically registered\n * capability is legitimate, and from the outside it is indistinguishable from\n * an extractor that missed something. Failing on it would punish the honest\n * case to catch the other one. It is reported, loudly, and revisited when a\n * codebase does it deliberately.\n *\n * `unresolved` does fail, and `--allow-unresolved` is the only way past it\n * (`AS-COVER-003`). A partial understanding of a codebase that reports itself\n * as complete is the failure the whole static half exists to remove: `unreached`\n * is computed against the catalog, so a catalog with holes in it makes that\n * count a floor rather than an answer. Accepting the gap still prints it.\n */\nexport function coverageExitCode(\n report: CoverageReport,\n options: { allowUnresolved?: boolean } = {},\n): number {\n if (report.unreached.length > 0) return 1;\n if (report.unresolved.length > 0 && !options.allowUnresolved) return 1;\n if (report.staleAllowlist.length > 0) return 1;\n return 0;\n}\n","/**\n * The static capability inventory (`AS-COVER-001…003`, D35).\n *\n * `inspect` answers *what can an agent do on this page right now*. It cannot\n * answer *did we author something no scenario ever reaches*, because a surface\n * is a projection of what is mounted: a route no scenario visits registers\n * nothing, so there is nothing to report and nothing to diff. The denominator\n * has to come from somewhere that does not require mounting.\n *\n * It comes from here. A registration call site is far more static than the\n * surface it produces:\n *\n * ```tsx\n * useAgentComponent({\n * type: \"devices.table\", // string literal\n * actions: { sort: action({ … }) }, // capability name is a key\n * });\n * ```\n *\n * `view:devices.table.sort` is fully determined by source text. What is\n * genuinely dynamic — availability, policy outcome, binding — is the\n * *projection*, and none of it is claimed here.\n *\n * ## This creates no exposure path (directive §2.1)\n *\n * No DOM is scanned, nothing is registered, no annotation is suggested. This\n * module *reads the same reviewed registration code* a human reads and counts\n * what is already there. It lives in `@agent-surface/cli` — which no adapter\n * imports and no application ships — and must never be re-exported from\n * `@agent-surface/core`, mirroring `AS-EXPLAIN-004` (`AS-COVER-006`).\n *\n * ## Failure discipline is the substance, not a detail\n *\n * > Better a missing check than a misleading check.\n *\n * A call site this module cannot understand is **reported with its file and\n * line**, never dropped. An inventory that silently omitted the constructs it\n * failed to parse would understate the denominator, and a coverage number built\n * on it would claim completeness it never had.\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport ts from \"typescript\";\n\n/** Identity could not be recovered from the call site at all. */\nexport const UNRESOLVED_ID = \"<unresolved>\";\n\nexport interface AuthoredCapability {\n /** Canonical id, instance-independent: `view:devices.table.sort`. */\n capabilityId: string;\n kind: \"observation\" | \"action\" | \"procedure\";\n /** Where a human can go and read it. */\n origin: { file: string; line: number };\n /** Literals recovered from the call site; absent when not statically known. */\n description?: string;\n effect?: string;\n /**\n * How much of this call site the extractor understood.\n *\n * `static` — identity and metadata both recovered from literals.\n * `partial` — identity resolved, some metadata dynamic. The common case: a\n * spread `instanceId`, or a description built from a template.\n * `unresolved` — identity NOT resolved. Reported, never dropped.\n */\n resolution: \"static\" | \"partial\" | \"unresolved\";\n /** Present on `partial`/`unresolved`: what defeated the extractor. */\n note?: string;\n}\n\nexport interface CapabilityInventory {\n capabilities: AuthoredCapability[];\n /** Absolute path to the tsconfig whose file list was analyzed. */\n tsconfig: string;\n /** Directory the analysis was rooted at — the surface config's own. */\n root: string;\n /** Files the program actually walked — the inventory's blast radius. */\n filesAnalyzed: number;\n /**\n * Program files skipped for living outside `root` — workspace packages the\n * app's tsconfig aliases in, typically the library's own source. Reported\n * rather than dropped silently: a boundary nobody can see is a boundary\n * nobody can check.\n */\n filesOutsideRoot: number;\n /**\n * The `domain:` plane is deliberately *not* analyzed here. Those capabilities\n * come from the oRPC router, which is already a static export (OQ-1), and\n * reporting zero of them would read as \"there are none\" rather than \"nobody\n * looked\".\n */\n domain: \"not-analyzed\";\n}\n\n/* ── locating the program ─────────────────────────────────────────────── */\n\nexport function findTsconfig(from: string): string | undefined {\n return ts.findConfigFile(resolve(from), ts.sys.fileExists, \"tsconfig.json\");\n}\n\ninterface ProgramFiles {\n fileNames: string[];\n options: ts.CompilerOptions;\n}\n\nfunction readProgramFiles(tsconfigPath: string): ProgramFiles {\n const read = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n if (read.error) {\n throw new Error(\n `could not read ${tsconfigPath}: ${ts.flattenDiagnosticMessageText(read.error.messageText, \" \")}`,\n );\n }\n const parsed = ts.parseJsonConfigFileContent(\n read.config as object,\n ts.sys,\n dirname(tsconfigPath),\n );\n if (parsed.errors.length > 0 && parsed.fileNames.length === 0) {\n throw new Error(\n `could not resolve any files from ${tsconfigPath}: ${parsed.errors\n .map((error) => ts.flattenDiagnosticMessageText(error.messageText, \" \"))\n .join(\"; \")}`,\n );\n }\n return { fileNames: parsed.fileNames, options: parsed.options };\n}\n\n/* ── small AST helpers ────────────────────────────────────────────────── */\n\nfunction calleeName(call: ts.CallExpression): string | undefined {\n if (ts.isIdentifier(call.expression)) return call.expression.text;\n if (ts.isPropertyAccessExpression(call.expression)) return call.expression.name.text;\n return undefined;\n}\n\nfunction propertyName(name: ts.PropertyName): string | undefined {\n if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;\n return undefined;\n}\n\nfunction propertyOf(\n object: ts.ObjectLiteralExpression,\n wanted: string,\n): ts.Expression | undefined {\n for (const property of object.properties) {\n if (ts.isPropertyAssignment(property) && propertyName(property.name) === wanted) {\n return property.initializer;\n }\n }\n return undefined;\n}\n\nfunction hasSpread(object: ts.ObjectLiteralExpression): boolean {\n return object.properties.some((property) => ts.isSpreadAssignment(property));\n}\n\nfunction literalText(node: ts.Expression | undefined): string | undefined {\n if (!node) return undefined;\n if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;\n if (ts.isParenthesizedExpression(node)) return literalText(node.expression);\n // `\"one long \" + \"description split over two lines\"` is as statically known\n // as either half. Descriptions are the provider's cached prompt prefix (D28),\n // so they are long enough that authors wrap them — calling that `partial`\n // would report the codebase's most common formatting choice as a defect.\n if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {\n const left = literalText(node.left);\n const right = literalText(node.right);\n if (left !== undefined && right !== undefined) return left + right;\n }\n return undefined;\n}\n\n/** A short name for whatever construct defeated us, for the `note`. */\nfunction describeConstruct(node: ts.Expression): string {\n if (ts.isCallExpression(node)) {\n const callee = calleeName(node);\n return callee ? `built by ${callee}()` : \"built by a call expression\";\n }\n if (ts.isIdentifier(node)) return `a variable (${node.text}) this extractor could not follow`;\n if (ts.isConditionalExpression(node)) return \"a conditional expression\";\n if (ts.isTemplateExpression(node)) return \"a template with substitutions\";\n if (ts.isPropertyAccessExpression(node)) return \"a property access\";\n return \"a non-literal expression\";\n}\n\n/**\n * Resolves a config argument to an object literal, following **one hop** to a\n * same-module `const`.\n *\n * One hop is the whole rule. `useAgentComponent(CONFIG)` where `CONFIG` is a\n * module constant is common and cheap; `useAgentComponent(buildConfig(props))`\n * is not resolvable at any depth worth implementing. Stopping at one hop keeps\n * the limit *visible in the output* rather than buried in the implementation —\n * the deeper case is reported as `unresolved` with the construct named, which\n * is the behaviour this module exists to guarantee.\n */\nfunction objectLiteralFor(\n expression: ts.Expression,\n source: ts.SourceFile,\n): { object?: ts.ObjectLiteralExpression; note?: string } {\n if (ts.isObjectLiteralExpression(expression)) return { object: expression };\n\n if (ts.isIdentifier(expression)) {\n const target = expression.text;\n let found: ts.ObjectLiteralExpression | undefined;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === target &&\n node.initializer &&\n ts.isObjectLiteralExpression(node.initializer)\n ) {\n found = node.initializer;\n return;\n }\n ts.forEachChild(node, visit);\n };\n visit(source);\n if (found) return { object: found };\n return {\n note: `the config is \\`${target}\\`, which is not a same-module object literal — the extractor follows one hop only`,\n };\n }\n\n return { note: `the config is ${describeConstruct(expression)}` };\n}\n\n/* ── the extraction itself ────────────────────────────────────────────── */\n\n/** Hooks that register one capability against the enclosing render scope. */\nconst GRANULAR_HOOKS = new Set([\"useAgentAction\", \"useAgentObservation\"]);\n\ninterface Emitter {\n push(capability: AuthoredCapability): void;\n origin(node: ts.Node): { file: string; line: number };\n}\n\nfunction capabilitiesFromGroup(\n group: ts.Expression | undefined,\n kind: \"observation\" | \"action\",\n componentType: string,\n componentPartial: string | undefined,\n emit: Emitter,\n source: ts.SourceFile,\n): void {\n if (!group) return;\n\n const resolved = objectLiteralFor(group, source);\n if (!resolved.object) {\n emit.push({\n capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,\n kind,\n origin: emit.origin(group),\n resolution: \"unresolved\",\n note: `\\`${kind}s\\` on \"${componentType}\" is not an object literal: ${resolved.note}`,\n });\n return;\n }\n\n for (const property of resolved.object.properties) {\n // A spread inside the capability map can add capabilities this extractor\n // cannot name. That is an identity gap, not a metadata gap.\n if (ts.isSpreadAssignment(property)) {\n emit.push({\n capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,\n kind,\n origin: emit.origin(property),\n resolution: \"unresolved\",\n note: `\\`${kind}s\\` on \"${componentType}\" spreads another object, which may contribute capabilities this inventory cannot name`,\n });\n continue;\n }\n\n const name =\n ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property)\n ? propertyName(property.name)\n : ts.isShorthandPropertyAssignment(property)\n ? property.name.text\n : undefined;\n\n if (name === undefined) {\n emit.push({\n capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,\n kind,\n origin: emit.origin(property),\n resolution: \"unresolved\",\n note: `a capability on \"${componentType}\" has a computed name`,\n });\n continue;\n }\n\n // Identity is recovered from the key alone; the value only carries metadata.\n const capability: AuthoredCapability = {\n capabilityId: `view:${componentType}.${name}`,\n kind,\n origin: emit.origin(property),\n resolution: \"static\",\n };\n const notes: string[] = [];\n if (componentPartial) notes.push(componentPartial);\n\n const value = ts.isPropertyAssignment(property) ? property.initializer : undefined;\n const definition =\n value && ts.isCallExpression(value) && value.arguments.length > 0\n ? value.arguments[0]\n : value;\n\n if (definition && ts.isObjectLiteralExpression(definition)) {\n const description = literalText(propertyOf(definition, \"description\"));\n if (description !== undefined) capability.description = description;\n else notes.push(\"description is not a string literal\");\n\n if (kind === \"action\") {\n const effect = literalText(propertyOf(definition, \"effect\"));\n if (effect !== undefined) capability.effect = effect;\n else notes.push(\"effect is not a string literal\");\n }\n if (hasSpread(definition)) notes.push(\"the definition spreads another object\");\n } else {\n notes.push(\n value\n ? `the definition is ${describeConstruct(value)}`\n : \"the definition is not an object literal\",\n );\n }\n\n if (notes.length > 0) {\n capability.resolution = \"partial\";\n capability.note = notes.join(\"; \");\n }\n emit.push(capability);\n }\n}\n\nfunction visitCall(call: ts.CallExpression, emit: Emitter, source: ts.SourceFile): void {\n const callee = calleeName(call);\n if (callee === undefined) return;\n\n if (GRANULAR_HOOKS.has(callee)) {\n // OQ-3: the granular hooks register through a render-scope link rather than\n // one aggregated descriptor, so the component `type` is not at this call\n // site at all. Reporting the call site as unresolved is the honest state\n // until that join key is settled — silently ignoring it would make a\n // codebase that uses them look fully covered.\n emit.push({\n capabilityId: UNRESOLVED_ID,\n kind: callee === \"useAgentAction\" ? \"action\" : \"observation\",\n origin: emit.origin(call),\n resolution: \"unresolved\",\n note: `${callee}() registers against a render-scope link, so its component type is not at this call site`,\n });\n return;\n }\n\n if (callee !== \"useAgentComponent\" && callee !== \"register\") return;\n const argument = call.arguments[0];\n if (!argument) return;\n\n const resolved = objectLiteralFor(argument, source);\n if (!resolved.object) {\n emit.push({\n capabilityId: UNRESOLVED_ID,\n kind: \"action\",\n origin: emit.origin(call),\n resolution: \"unresolved\",\n note: `${callee}() call site could not be read: ${resolved.note}`,\n });\n return;\n }\n\n const config = resolved.object;\n const type = literalText(propertyOf(config, \"type\"));\n if (type === undefined) {\n // `register` is a common method name; only treat it as ours once the call\n // actually looks like a registration. A `type` that exists but is dynamic\n // *is* ours, and is a genuine finding.\n if (callee === \"register\" && propertyOf(config, \"type\") === undefined) return;\n emit.push({\n capabilityId: UNRESOLVED_ID,\n kind: \"action\",\n origin: emit.origin(call),\n resolution: \"unresolved\",\n note: `\\`type\\` is not a string literal, so no capability id on this component can be determined`,\n });\n return;\n }\n\n // A spread at the component level is the documented common case — the\n // conditional `...(props.instance ? { instanceId } : {})`. `instanceId` is not\n // part of a capability id, so identity survives; metadata may not.\n const componentPartial = hasSpread(config)\n ? \"the component config spreads another object, so some metadata here may be dynamic\"\n : undefined;\n\n capabilitiesFromGroup(\n propertyOf(config, \"observations\"),\n \"observation\",\n type,\n componentPartial,\n emit,\n source,\n );\n capabilitiesFromGroup(\n propertyOf(config, \"actions\"),\n \"action\",\n type,\n componentPartial,\n emit,\n source,\n );\n}\n\nexport interface ExtractOptions {\n /** Directory the analysis is rooted at — normally the surface config's dir. */\n root: string;\n /** Explicit tsconfig; found upward from `root` when omitted. */\n tsconfig?: string;\n}\n\n/**\n * Reads the program and returns every capability its registration call sites\n * author. Nothing is executed: no Vite server, no jsdom, no scenarios, no mount.\n */\nexport function extractCapabilities(options: ExtractOptions): CapabilityInventory {\n const root = resolve(options.root);\n const tsconfigPath = options.tsconfig\n ? isAbsolute(options.tsconfig)\n ? options.tsconfig\n : join(root, options.tsconfig)\n : findTsconfig(root);\n\n if (!tsconfigPath || !existsSync(tsconfigPath)) {\n throw new Error(\n `no tsconfig.json found from ${root} — \\`capabilities\\` reads the TypeScript program, ` +\n \"so it needs one (pass --tsconfig to point at it)\",\n );\n }\n\n const { fileNames, options: compilerOptions } = readProgramFiles(tsconfigPath);\n const program = ts.createProgram(fileNames, compilerOptions);\n\n const capabilities: AuthoredCapability[] = [];\n let filesAnalyzed = 0;\n\n let filesOutsideRoot = 0;\n\n for (const source of program.getSourceFiles()) {\n if (source.isDeclarationFile) continue;\n if (source.fileName.includes(\"/node_modules/\")) continue;\n // A tsconfig with workspace path aliases pulls the library's *own* source\n // into the program, where `registry.register(definition)` inside\n // `useAgentComponent` reads as an unresolvable registration call site. It\n // is not one: it is the implementation every real call site goes through.\n // The inventory covers the app the surface config points at, and says so\n // rather than quietly widening or narrowing.\n if (!isInside(root, source.fileName)) {\n filesOutsideRoot += 1;\n continue;\n }\n filesAnalyzed += 1;\n\n const emit: Emitter = {\n push: (capability) => capabilities.push(capability),\n origin: (node) => ({\n file: relative(root, source.fileName),\n line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1,\n }),\n };\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) visitCall(node, emit, source);\n ts.forEachChild(node, visit);\n };\n visit(source);\n }\n\n capabilities.sort(\n (a, b) =>\n a.capabilityId.localeCompare(b.capabilityId) ||\n a.origin.file.localeCompare(b.origin.file) ||\n a.origin.line - b.origin.line,\n );\n\n return {\n capabilities,\n tsconfig: tsconfigPath,\n root,\n filesAnalyzed,\n filesOutsideRoot,\n domain: \"not-analyzed\",\n };\n}\n\n/** True when `file` lives under `root` — the analyzed boundary. */\nfunction isInside(root: string, file: string): boolean {\n const rel = relative(root, file);\n return rel !== \"\" && !rel.startsWith(\"..\") && !isAbsolute(rel);\n}\n\n/** Distinct capability ids the inventory resolved — the coverage denominator. */\nexport function authoredIds(inventory: CapabilityInventory): Set<string> {\n const ids = new Set<string>();\n for (const capability of inventory.capabilities) {\n if (capability.resolution === \"unresolved\") continue;\n if (capability.capabilityId.endsWith(UNRESOLVED_ID)) continue;\n ids.add(capability.capabilityId);\n }\n return ids;\n}\n\nexport function unresolved(inventory: CapabilityInventory): AuthoredCapability[] {\n return inventory.capabilities.filter((capability) => capability.resolution === \"unresolved\");\n}\n","/**\n * The two halves of the surface, and the join between them.\n *\n * A presentation surface has two sources of truth, and every command needs\n * some mix of both:\n *\n * - the **catalog** — what this codebase authors. Static: `type` is a string\n * literal, capability names are object keys, so `view:devices.table.sort` is\n * fully determined by source text ([`extract.ts`](./extract.ts)).\n * - the **projection** — what a mounted scenario actually surfaces, after\n * availability, policy and binding have had their say ([`collect.ts`](./collect.ts)).\n *\n * Splitting those across separate commands is what let a green `check` sit on\n * top of a route no scenario visits. So the split lives here, behind a `depth`\n * dial, and the commands compose the same three steps in whatever order their\n * output needs: `inspect` streams and closes with the verdict, `check` collects\n * and leads with it.\n */\nimport { dirname } from \"node:path\";\nimport { matchesScope } from \"@agent-surface/core/explain\";\nimport { baselineDirFor } from \"./baseline.js\";\nimport { UsageError, type Depth } from \"./contract.js\";\nimport type { CollectResult } from \"./collect.js\";\nimport {\n allowlistPathFor,\n buildCoverageReport,\n readAllowlist,\n type CoverageReport,\n} from \"./coverage.js\";\nimport {\n authoredIds,\n extractCapabilities,\n unresolved,\n type CapabilityInventory,\n} from \"./extract.js\";\nimport { createSurfaceRunner } from \"./load.js\";\n\nexport type { Depth } from \"./contract.js\";\nexport { UsageError } from \"./contract.js\";\n\nexport interface AnalysisOptions {\n configPath: string;\n depth: Depth;\n scenario?: string;\n scope?: string[];\n tsconfig?: string;\n baselineDir?: string;\n}\n\n/**\n * The static half. `undefined` at `--depth runtime`, which is the caller\n * saying it does not want this computed rather than it having failed.\n */\nexport function readInventory(options: AnalysisOptions): CapabilityInventory | undefined {\n if (options.depth === \"runtime\") return undefined;\n return extractCapabilities({\n root: dirname(options.configPath),\n ...(options.tsconfig ? { tsconfig: options.tsconfig } : {}),\n });\n}\n\n/** A scenario the config declares whose mount threw. Named, never swallowed. */\nexport interface ScenarioFailure {\n scenario: string;\n message: string;\n}\n\nexport interface RuntimeAnalysis {\n /** Every scenario this run set out to mount, in config order. */\n scenarios: string[];\n /** The ones that mounted. */\n results: CollectResult[];\n failures: ScenarioFailure[];\n baselineDir: string;\n}\n\n/**\n * The runtime half. `undefined` at `--depth static`.\n *\n * `onEach` is awaited as each scenario finishes, so a command can print as it\n * goes instead of after the last mount — a config with ten scenarios is a long\n * time to look at nothing.\n *\n * A scenario that throws is recorded and the run continues. Before this was\n * one command, `capabilities` was the only thing that still worked on an app\n * that would not mount; merging the commands would have thrown that away if a\n * single bad scenario could abort the run.\n */\nexport async function mountScenarios(\n options: AnalysisOptions,\n onEach?: (result: CollectResult) => void | Promise<void>,\n): Promise<RuntimeAnalysis | undefined> {\n if (options.depth === \"static\") return undefined;\n\n const runner = await createSurfaceRunner(options.configPath);\n try {\n if (options.scenario && !runner.scenarioNames.includes(options.scenario)) {\n throw new UsageError(\n `unknown scenario \"${options.scenario}\" — this config defines ` +\n runner.scenarioNames.map((name) => `\"${name}\"`).join(\", \"),\n );\n }\n const scenarios = options.scenario ? [options.scenario] : runner.scenarioNames;\n const results: CollectResult[] = [];\n const failures: ScenarioFailure[] = [];\n\n for (const scenario of scenarios) {\n let result: CollectResult;\n try {\n result = await runner.collect({\n scenario,\n ...(options.scope ? { scope: options.scope } : {}),\n });\n } catch (error) {\n failures.push({\n scenario,\n message: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n results.push(result);\n await onEach?.(result);\n }\n\n return {\n scenarios,\n results,\n failures,\n baselineDir: baselineDirFor(\n options.configPath,\n options.baselineDir ?? runner.config.baselineDir,\n ),\n };\n } finally {\n await runner.close();\n }\n}\n\n/**\n * The component type a capability id belongs to: `view:devices.table.sort` →\n * `devices.table`. Capability names are object keys and cannot contain a dot,\n * so the last one is always the boundary; component types can and do.\n */\nfunction componentTypeOf(capabilityId: string): string {\n const withoutPlane = capabilityId.replace(/^(view|domain):/, \"\");\n const dot = withoutPlane.lastIndexOf(\".\");\n return dot === -1 ? withoutPlane : withoutPlane.slice(0, dot);\n}\n\n/**\n * Authored minus reached (`AS-COVER-004…005`).\n *\n * Two ways this returns `undefined`, and neither is \"no gaps\":\n *\n * - a half was not computed, because the depth did not ask for it;\n * - **a scenario failed to mount.** That scenario reached nothing, so every\n * capability it would have surfaced would be reported as one no scenario\n * reaches. A coverage verdict computed over a partial run is precisely the\n * misleading check this package refuses to emit, so there is no verdict\n * until every scenario mounted. The renderer says which of the two it was.\n */\nexport function joinCoverage(\n inventory: CapabilityInventory | undefined,\n runtime: RuntimeAnalysis | undefined,\n options: AnalysisOptions,\n): CoverageReport | undefined {\n if (!inventory || !runtime) return undefined;\n if (runtime.failures.length > 0) return undefined;\n\n // A scope filters the mount, so it has to filter the catalog by the same\n // predicate — core's own, not a second copy of it. Without this, `--scope\n // devices` reported every `app.navigation` capability as unreached, with the\n // words \"no scenario mounts it\" over two that both scenarios mount.\n const inScope = (capabilityId: string): boolean =>\n matchesScope(componentTypeOf(capabilityId), options.scope);\n\n const origins = new Map<string, { file: string; line: number }>();\n for (const capability of inventory.capabilities) {\n if (!origins.has(capability.capabilityId)) {\n origins.set(capability.capabilityId, capability.origin);\n }\n }\n\n const authored = new Set([...authoredIds(inventory)].filter(inScope));\n const reachedIds = new Set<string>();\n for (const result of runtime.results) {\n for (const capability of result.explanation.capabilities) {\n reachedIds.add(capability.capabilityId);\n }\n }\n\n // The allowlist is a statement about the whole catalog, and a scoped run has\n // only looked at part of it. Judging an out-of-scope entry either way would\n // be wrong in both directions — it is not an unreached capability this run\n // waved through, and it is not a stale entry either, because nothing here\n // reached it.\n const allowlistPath = allowlistPathFor(runtime.baselineDir);\n const wholeAllowlist = readAllowlist(allowlistPath);\n const allowlist = Object.fromEntries(\n Object.entries(wholeAllowlist).filter(([id]) => inScope(id)),\n );\n\n return buildCoverageReport({\n authored,\n origins,\n reachedIds,\n scenarios: runtime.scenarios,\n ...(options.scope ? { scope: options.scope } : {}),\n unresolved: unresolved(inventory),\n allowlist,\n allowlistOutOfScope: Object.keys(wholeAllowlist).length - Object.keys(allowlist).length,\n allowlistPath,\n });\n}\n"],"mappings":"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","names":["readFileSync","join","existsSync","dirname","join","resolve","dirname","dirname"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/render/plain.ts"],"sourcesContent":["import { relative } from \"node:path\";\nimport type { CapabilityRow, SurfaceView } from \"./model.js\";\nimport { flatRows } from \"./model.js\";\nimport type { DiffEntry } from \"../baseline.js\";\nimport { formatValue } from \"../baseline.js\";\nimport type { ScenarioFailure } from \"../analysis.js\";\nimport {\n authoredIds,\n unresolved,\n type AuthoredCapability,\n type CapabilityInventory,\n} from \"../extract.js\";\nimport type { CoverageReport } from \"../coverage.js\";\n\n/**\n * The no-colour, no-cursor rendering used when stdout is piped or when\n * `--plain`, `CI` or `NO_COLOR` is set. Same view model as the Ink UI, so the\n * two cannot disagree about what the surface contains.\n */\n\nconst MARK = { expose: \"+\", disable: \"~\", hide: \"-\" } as const;\nconst STATE = { expose: \"callable\", disable: \"disabled\", hide: \"hidden\" } as const;\nconst NONE = \"—\";\n\n/**\n * Column widths come from the *content*, never from `process.stdout.columns`.\n *\n * `AS-CLI-003` requires plain output to be byte-stable across runs, and a table\n * laid out against the terminal it happened to run in is stable only until two\n * people diff the same CI log from different windows. Same rows in, same bytes\n * out, everywhere.\n *\n * The last column is not padded, so no line ever carries trailing whitespace —\n * which some diff tools render and others strip, i.e. another way for identical\n * output to look different.\n */\nfunction renderTable(headers: string[], rows: Array<{ cells: string[]; note?: string }>): string[] {\n const widths = headers.map((header, column) =>\n Math.max(header.length, ...rows.map((row) => (row.cells[column] ?? \"\").length)),\n );\n const line = (cells: string[]): string =>\n cells\n .map((cell, column) => (column === headers.length - 1 ? cell : cell.padEnd(widths[column]!)))\n .join(\" \")\n .trimEnd();\n\n const lines = [line(headers)];\n for (const row of rows) {\n lines.push(line(row.cells));\n // The unavailability reason is prose of unbounded length. A column for it\n // would set the table's width by its longest sentence; a continuation line\n // keeps the grid aligned and puts the reason directly under its capability.\n if (row.note) lines.push(` ⤷ ${row.note}`);\n }\n return lines;\n}\n\n/** `UNREACHED — authored, and no scenario mounts it (1)` */\nfunction section(title: string, gloss: string, count: number): string {\n return `${title} — ${gloss} (${count})`;\n}\n\nfunction renderDetailRow(row: CapabilityRow, lines: string[]): void {\n const tags = row.tags.length > 0 ? ` [${row.tags.join(\", \")}]` : \"\";\n lines.push(` ${MARK[row.outcome]} ${row.name}${tags}`);\n lines.push(` ${row.description}`);\n if (row.reason) lines.push(` reason: ${row.reason}`);\n\n if (row.policies) {\n if (row.policies.length === 0) {\n lines.push(\" policies: none\");\n } else {\n for (const policy of row.policies) {\n const vote = policy.discovery\n ? policy.discovery.decision === \"disable\"\n ? `disable — ${policy.discovery.reason}`\n : policy.discovery.decision\n : \"no discovery hook\";\n const phases = policy.phases.length > 0 ? policy.phases.join(\"/\") : NONE;\n const flags = [\n policy.threw ? \"THREW\" : \"\",\n policy.confirmationEscalation ? \"escalates-confirmation\" : \"\",\n ]\n .filter(Boolean)\n .join(\", \");\n lines.push(\n ` policy ${policy.name} (${policy.scope}, ${phases}): ${vote}${\n flags ? ` [${flags}]` : \"\"\n }`,\n );\n }\n }\n if (row.availability && !row.availability.available) {\n lines.push(\n ` availability: unavailable${\n row.availability.reason ? ` — ${row.availability.reason}` : \"\"\n }`,\n );\n }\n }\n\n if (row.schemas) {\n if (row.schemas.input !== undefined) {\n lines.push(` input: ${JSON.stringify(row.schemas.input)}`);\n }\n if (row.schemas.output !== undefined) {\n lines.push(` output: ${JSON.stringify(row.schemas.output)}`);\n }\n }\n}\n\n/**\n * The counts line, and everything it is relative to (`AS-CLI-007`).\n *\n * `hidden` is printed unconditionally. It is computed on every run — the\n * explanation is always collected — and suppressing it outside `--explain`\n * meant a surface with a policy-hidden half rendered as a complete one. The\n * *attribution* still needs `--explain`; the count and the rows do not.\n */\nexport function renderCountsPlain(view: SurfaceView): string {\n return (\n `${view.counts.callable} callable, ${view.counts.disabled} visible-disabled, ` +\n `${view.counts.hidden} hidden` +\n (view.rejections.length > 0\n ? `, ${view.rejections.length} registration${view.rejections.length === 1 ? \"\" : \"s\"} rejected`\n : \"\")\n );\n}\n\nfunction renderHeader(view: SurfaceView, lines: string[]): void {\n lines.push(\n `scenario ${view.scenario}${view.route ? ` route ${view.route}` : \"\"}${\n view.scope && view.scope.length > 0 ? ` scope ${view.scope.join(\" \")}` : \"\"\n }`,\n );\n lines.push(renderCountsPlain(view));\n}\n\nfunction renderRejections(view: SurfaceView, lines: string[]): void {\n if (view.rejections.length === 0) return;\n lines.push(\"\");\n lines.push(\n section(\"REJECTED\", \"the registry refused these during the mount\", view.rejections.length),\n );\n for (const rejection of view.rejections) {\n const why =\n rejection.reason === \"duplicate\"\n ? \"duplicate — an earlier registration holds this key\"\n : \"guard — onRegister rejected this registration\";\n lines.push(` ! ${rejection.componentType} (${rejection.instanceId}) ${why}`);\n }\n}\n\nfunction renderEmpty(view: SurfaceView, lines: string[]): void {\n lines.push(\"\");\n // \"Nothing is registered\" is only true when nothing was hidden. Saying it\n // over a surface a policy emptied sends the reader to the wrong file.\n if (view.counts.hidden > 0) {\n lines.push(\n `Nothing is callable here — all ${view.counts.hidden} registered capabilities were hidden by policy.`,\n );\n if (!view.explained) lines.push(\"Re-run with --explain to see which policy hid them.\");\n } else {\n lines.push(\"Nothing is registered for this scenario — the agent has no surface here.\");\n if (!view.explained) lines.push(\"Re-run with --explain to see whether a policy hid it.\");\n }\n}\n\nexport interface SurfaceRenderOptions {\n /** The grouped, one-capability-per-paragraph view. Implied by --explain/--schemas. */\n detail?: boolean;\n}\n\n/**\n * One capability per line, aligned. The default, because the question `inspect`\n * is usually asked is *what is on this surface* — which is a scanning question,\n * and prose does not scan.\n *\n * Policy chains and JSON Schemas are multi-line by nature and cannot live in a\n * cell, so `--explain` and `--schemas` fall back to the detail view rather than\n * producing a table with most of the answer missing.\n */\nexport function renderSurfacePlain(\n view: SurfaceView,\n options: SurfaceRenderOptions = {},\n): string {\n const lines: string[] = [];\n renderHeader(view, lines);\n renderRejections(view, lines);\n\n const rows = flatRows(view);\n if (rows.length === 0) {\n renderEmpty(view, lines);\n return lines.join(\"\\n\");\n }\n\n if (options.detail) {\n for (const group of view.groups.filter((group) => group.rows.length > 0)) {\n lines.push(\"\");\n lines.push(`${group.heading} (${group.rows.length})`);\n for (const row of group.rows) renderDetailRow(row, lines);\n }\n return lines.join(\"\\n\");\n }\n\n lines.push(\"\");\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"KIND\", \"EFFECT\", \"STATE\", \"FLAGS\"],\n rows.map((row) => ({\n cells: [\n row.path,\n row.kind,\n row.effect ?? NONE,\n STATE[row.outcome],\n row.flags.length > 0 ? row.flags.join(\" · \") : NONE,\n ],\n ...(row.reason ? { note: row.reason } : {}),\n })),\n ),\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * The static catalog (`AS-COVER-001…003`). The summary says \"upper bound\" in so\n * many words: a tsconfig's include globs are wider than what a bundle reaches,\n * so a capability in a component no route renders any more is in here. That is\n * dead code — a different finding, not a false positive — and the reader has to\n * be told which number they are holding.\n */\nexport interface CatalogRenderOptions {\n /**\n * This catalog *is* the command's output, rather than its preamble.\n *\n * True at `--depth static`: there are no scenario tables and no verdict, so\n * the listing and the unread call sites have nowhere else to appear.\n *\n * False at `--depth full`, where the scenario tables below name every\n * capability a scenario reached, the `UNREACHED` section names the ones it did\n * not, and the verdict carries the unread call sites — so printing any of it\n * here is the same information a second time, above the answer instead of in\n * it. Only the summary line survives.\n */\n standalone?: boolean;\n}\n\nexport function renderCatalogPlain(\n inventory: CapabilityInventory,\n options: CatalogRenderOptions = {},\n): string {\n const lines: string[] = [];\n const resolved = inventory.capabilities.filter((c) => c.resolution !== \"unresolved\");\n const ids = authoredIds(inventory);\n\n lines.push(\n `${ids.size} authored (upper bound) · ${resolved.length} call site${\n resolved.length === 1 ? \"\" : \"s\"\n } across ${inventory.filesAnalyzed} file${inventory.filesAnalyzed === 1 ? \"\" : \"s\"}` +\n \" · domain not analyzed, it comes from the oRPC router (OQ-1)\",\n );\n if (inventory.filesOutsideRoot > 0) {\n // Relative, not absolute: plain output is byte-stable across runs\n // (`AS-CLI-003`), and an absolute path makes it machine-specific the moment\n // two people diff a CI log.\n lines.push(\n `${inventory.filesOutsideRoot} program file${\n inventory.filesOutsideRoot === 1 ? \"\" : \"s\"\n } outside the config's directory were not analyzed`,\n );\n }\n\n if (!options.standalone) return lines.join(\"\\n\");\n\n const byId = [...resolved].sort((a, b) => a.capabilityId.localeCompare(b.capabilityId));\n if (byId.length > 0) {\n lines.push(\"\");\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"KIND\", \"ORIGIN\", \"READ\"],\n byId.map((capability) => ({\n cells: [\n capability.capabilityId,\n capability.kind,\n `${capability.origin.file}:${capability.origin.line}`,\n capability.resolution,\n ],\n ...(capability.note ? { note: capability.note } : {}),\n })),\n ),\n );\n }\n\n const unread = renderUnread(unresolved(inventory));\n if (unread.length > 0) lines.push(\"\", ...unread);\n return lines.join(\"\\n\");\n}\n\n/**\n * Call sites the extractor could not read. Reported with file and line, never\n * dropped: an inventory that silently omitted what it failed to parse would\n * understate the denominator, and every number built on it would claim a\n * completeness it never had.\n */\nfunction renderUnread(entries: AuthoredCapability[]): string[] {\n if (entries.length === 0) return [];\n const lines: string[] = [];\n lines.push(\n section(\n \"UNREAD CALL SITES\",\n \"the catalog is incomplete, so every count above is a floor\",\n entries.length,\n ),\n );\n for (const capability of entries) {\n lines.push(` ? ${capability.origin.file}:${capability.origin.line}`);\n lines.push(` ${capability.note ?? \"the extractor could not read this call site\"}`);\n }\n return lines;\n}\n\n/**\n * The verdict: authored minus reached (`AS-COVER-004…005`).\n *\n * This is the finding the command surface used to hide behind a fifth command,\n * so it is the last thing printed and the thing a reader stops on.\n */\nexport function renderCoveragePlain(report: CoverageReport): string {\n const lines: string[] = [];\n\n if (report.unreached.length > 0) {\n lines.push(\n section(\"UNREACHED\", \"authored, and no scenario mounts it\", report.unreached.length),\n );\n lines.push(\n ...renderTable(\n [\"CAPABILITY\", \"ORIGIN\"],\n report.unreached.map((entry) => ({\n cells: [entry.capabilityId, `${entry.origin.file}:${entry.origin.line}`],\n })),\n ),\n );\n lines.push(\"\");\n }\n\n if (report.undeclared.length > 0) {\n lines.push(\n section(\n \"UNDECLARED\",\n \"present at runtime with no static origin — a dynamic registration, or a gap here\",\n report.undeclared.length,\n ),\n );\n for (const id of report.undeclared) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.staleAllowlist.length > 0) {\n lines.push(\n section(\n \"STALE ALLOWLIST\",\n \"a scenario reaches these now, so delete them before the list rots\",\n report.staleAllowlist.length,\n ),\n );\n for (const id of report.staleAllowlist) lines.push(` ${id}`);\n lines.push(\"\");\n }\n\n if (report.unresolved.length > 0) {\n lines.push(...renderUnread(report.unresolved), \"\");\n }\n\n lines.push(...renderCoverageSummary(report));\n return lines.join(\"\\n\");\n}\n\n/** The one line a reader who stops at the bottom takes away. */\nfunction renderCoverageSummary(report: CoverageReport): string[] {\n const qualifiers = [\n `${report.scenarios.length} scenario${report.scenarios.length === 1 ? \"\" : \"s\"} (${report.scenarios.join(\n \", \",\n )})`,\n ];\n // Every count is relative to the scope, so the scope is printed with them\n // (`AS-CLI-007`) — `10 authored` under a scope is a claim about one prefix of\n // the codebase, not about the codebase.\n if (report.scope && report.scope.length > 0) qualifiers.push(`scope ${report.scope.join(\" \")}`);\n\n const lines = [\n `${report.authored} authored · ${report.reached} reached · ${report.unreached.length} unreached` +\n ` · ${qualifiers.join(\" · \")}`,\n ];\n\n if (report.domainReached.length > 0) {\n lines.push(\n `${report.domainReached.length} domain capabilit${\n report.domainReached.length === 1 ? \"y\" : \"ies\"\n } reached and held apart — that plane is the oRPC router's, and this catalog never claimed it`,\n );\n }\n if (report.allowed.length > 0) {\n lines.push(\n `${report.allowed.length} unreached capabilit${\n report.allowed.length === 1 ? \"y is\" : \"ies are\"\n } allowlisted in ${relative(process.cwd(), report.allowlistPath)}`,\n );\n }\n if (report.allowlistOutOfScope > 0) {\n lines.push(\n `${report.allowlistOutOfScope} allowlist entr${\n report.allowlistOutOfScope === 1 ? \"y\" : \"ies\"\n } outside this scope were not judged either way`,\n );\n }\n\n // Each bucket gets its own remedy. \"Add a scenario, or delete the component\"\n // is the right advice for an unreached capability and useless advice for a\n // call site the extractor could not read.\n if (report.unreached.length === 0 && report.unresolved.length === 0 && report.staleAllowlist.length === 0) {\n lines.push(\n report.allowed.length > 0\n ? \"no new surface coverage gaps — the allowlist still holds the known ones\"\n : \"every authored capability is reached by a scenario\",\n );\n }\n return lines;\n}\n\n/**\n * Why there is no coverage verdict. Never silence: a reader who asked for the\n * complete answer and got a partial one has to be told which part is missing,\n * or the partial one reads as the complete one.\n */\nexport function renderNoVerdictPlain(failures: ScenarioFailure[]): string {\n return [\n section(\"NO COVERAGE VERDICT\", \"a scenario did not mount, so nothing reached anything\", failures.length),\n ...failures.flatMap((failure) => [` ${failure.scenario}`, ` ${failure.message}`]),\n \"\",\n \"Every capability those scenarios would have surfaced would be reported unreached,\",\n \"so no verdict is printed at all. Fix the mount, or name a scenario that works.\",\n ].join(\"\\n\");\n}\n\nexport function renderFailuresPlain(failures: ScenarioFailure[]): string {\n return [\n section(\"DID NOT MOUNT\", \"these scenarios threw, and were skipped\", failures.length),\n ...failures.flatMap((failure) => [` ${failure.scenario}`, ` ${failure.message}`]),\n ].join(\"\\n\");\n}\n\nexport function renderDriftPlain(scenario: string, entries: DiffEntry[]): string {\n const lines = [` ${scenario}: ${entries.length} change${entries.length === 1 ? \"\" : \"s\"}`];\n for (const entry of entries) {\n const where = entry.subject ? `${entry.subject} (${entry.path})` : entry.path;\n if (entry.kind === \"added\") lines.push(` + ${where} ${formatValue(entry.after)}`);\n else if (entry.kind === \"removed\") lines.push(` - ${where} ${formatValue(entry.before)}`);\n else {\n lines.push(` ~ ${where}`);\n lines.push(` before: ${formatValue(entry.before)}`);\n lines.push(` after: ${formatValue(entry.after)}`);\n }\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["group"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|