@agent-surface/cli 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +41 -14
- package/dist/bin.js.map +1 -1
- package/dist/{check-TJIX7NDE.js → check-VWRYNYLN.js} +66 -48
- package/dist/check-VWRYNYLN.js.map +1 -0
- package/dist/chunk-A5UCBF7D.js +246 -0
- package/dist/chunk-A5UCBF7D.js.map +1 -0
- package/dist/chunk-GYYWHZPM.js +532 -0
- package/dist/chunk-GYYWHZPM.js.map +1 -0
- package/dist/chunk-NFK3XWWH.js +1475 -0
- package/dist/chunk-NFK3XWWH.js.map +1 -0
- package/dist/chunk-Y2LSPEVK.js +61 -0
- package/dist/chunk-Y2LSPEVK.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/{init-6XV64LK2.js → init-BVZR6CRS.js} +62 -40
- package/dist/init-BVZR6CRS.js.map +1 -0
- package/dist/{ink-QR7X7TAC.js → ink-ZCQ26EY4.js} +71 -17
- package/dist/ink-ZCQ26EY4.js.map +1 -0
- package/dist/{inspect-NJ4J3MPP.js → inspect-3YFPCYQJ.js} +88 -104
- package/dist/inspect-3YFPCYQJ.js.map +1 -0
- package/dist/snapshot-5QKLZKZI.js +130 -0
- package/dist/snapshot-5QKLZKZI.js.map +1 -0
- package/package.json +4 -4
- package/dist/check-TJIX7NDE.js.map +0 -1
- package/dist/chunk-3AJ343NA.js +0 -178
- package/dist/chunk-3AJ343NA.js.map +0 -1
- package/dist/chunk-IALBMW3R.js +0 -278
- package/dist/chunk-IALBMW3R.js.map +0 -1
- package/dist/chunk-L7GHSC2Z.js +0 -465
- package/dist/chunk-L7GHSC2Z.js.map +0 -1
- package/dist/chunk-UGCLJ5JX.js +0 -724
- package/dist/chunk-UGCLJ5JX.js.map +0 -1
- package/dist/chunk-VX6GBEP3.js +0 -539
- package/dist/chunk-VX6GBEP3.js.map +0 -1
- package/dist/init-6XV64LK2.js.map +0 -1
- package/dist/ink-QR7X7TAC.js.map +0 -1
- package/dist/inspect-NJ4J3MPP.js.map +0 -1
- package/dist/snapshot-ZGTAF2Y5.js +0 -77
- package/dist/snapshot-ZGTAF2Y5.js.map +0 -1
package/dist/chunk-UGCLJ5JX.js
DELETED
|
@@ -1,724 +0,0 @@
|
|
|
1
|
-
// src/coverage.ts
|
|
2
|
-
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
var ALLOWLIST_FILE = "coverage-allow.json";
|
|
5
|
-
var UNREAD_ALLOWLIST_FILE = "unresolved-allow.json";
|
|
6
|
-
function allowlistPathFor(baselineDir) {
|
|
7
|
-
return join(baselineDir, ALLOWLIST_FILE);
|
|
8
|
-
}
|
|
9
|
-
function unreadAllowlistPathFor(baselineDir) {
|
|
10
|
-
return join(baselineDir, UNREAD_ALLOWLIST_FILE);
|
|
11
|
-
}
|
|
12
|
-
function readAllowlist(path, keyName = "capabilityId") {
|
|
13
|
-
if (!existsSync(path)) return {};
|
|
14
|
-
let parsed;
|
|
15
|
-
try {
|
|
16
|
-
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
17
|
-
} catch (error) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
`could not parse ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
23
|
-
throw new Error(`${path} must be a JSON object of { "${keyName}": "reason" }`);
|
|
24
|
-
}
|
|
25
|
-
const allowlist = {};
|
|
26
|
-
for (const [id, reason] of Object.entries(parsed)) {
|
|
27
|
-
if (typeof reason !== "string" || reason.trim() === "") {
|
|
28
|
-
throw new Error(`${path}: "${id}" needs a non-empty reason string`);
|
|
29
|
-
}
|
|
30
|
-
allowlist[id] = reason;
|
|
31
|
-
}
|
|
32
|
-
return allowlist;
|
|
33
|
-
}
|
|
34
|
-
function unreadKey(entry) {
|
|
35
|
-
return `${entry.origin.file}#${entry.reason ?? "unknown"}#${entry.origin.site}`;
|
|
36
|
-
}
|
|
37
|
-
function buildCoverageReport(input) {
|
|
38
|
-
const unreached = [];
|
|
39
|
-
const allowed = [];
|
|
40
|
-
for (const id of [...input.authored].sort()) {
|
|
41
|
-
if (input.reachedIds.has(id)) continue;
|
|
42
|
-
if (id in input.allowlist) {
|
|
43
|
-
allowed.push(id);
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
unreached.push({ capabilityId: id, origin: input.origins.get(id) ?? { file: "?", line: 0 } });
|
|
47
|
-
}
|
|
48
|
-
const staleAllowlist = Object.keys(input.allowlist).filter((id) => input.reachedIds.has(id) || !input.authored.has(id)).sort();
|
|
49
|
-
const unreadAllowlist = input.unreadAllowlist ?? {};
|
|
50
|
-
const unread = [];
|
|
51
|
-
const allowedUnread = /* @__PURE__ */ new Set();
|
|
52
|
-
for (const entry of input.unresolved) {
|
|
53
|
-
const key = unreadKey(entry);
|
|
54
|
-
if (key in unreadAllowlist) allowedUnread.add(key);
|
|
55
|
-
else unread.push(entry);
|
|
56
|
-
}
|
|
57
|
-
const stillUnread = new Set(input.unresolved.map(unreadKey));
|
|
58
|
-
const staleUnreadAllowlist = Object.keys(unreadAllowlist).filter((key) => !stillUnread.has(key)).sort();
|
|
59
|
-
const unaccounted = [...input.reachedIds].filter((id) => !input.authored.has(id)).sort();
|
|
60
|
-
const domainReached = [...input.reachedIds].filter((id) => id.startsWith("domain:")).sort();
|
|
61
|
-
const unmanifestedDomain = input.domainAuthoritative ? unaccounted.filter((id) => id.startsWith("domain:")) : [];
|
|
62
|
-
const undeclared = unaccounted.filter((id) => !id.startsWith("domain:"));
|
|
63
|
-
return {
|
|
64
|
-
authored: input.authored.size,
|
|
65
|
-
reached: [...input.authored].filter((id) => input.reachedIds.has(id)).length,
|
|
66
|
-
scenarios: input.scenarios,
|
|
67
|
-
...input.scope ? { scope: input.scope } : {},
|
|
68
|
-
allowlistOutOfScope: input.allowlistOutOfScope ?? 0,
|
|
69
|
-
unreached,
|
|
70
|
-
undeclared,
|
|
71
|
-
domainReached,
|
|
72
|
-
unmanifestedDomain,
|
|
73
|
-
domainAuthoritative: input.domainAuthoritative === true,
|
|
74
|
-
unresolved: unread,
|
|
75
|
-
allowed,
|
|
76
|
-
staleAllowlist,
|
|
77
|
-
allowlistPath: input.allowlistPath,
|
|
78
|
-
allowedUnread: [...allowedUnread].sort(),
|
|
79
|
-
staleUnreadAllowlist,
|
|
80
|
-
unreadAllowlistPath: input.unreadAllowlistPath
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function coverageExitCode(report, options = {}) {
|
|
84
|
-
if (report.unreached.length > 0) return 1;
|
|
85
|
-
if (report.unmanifestedDomain.length > 0) return 1;
|
|
86
|
-
if (report.unresolved.length > 0 && !options.allowUnresolved) return 1;
|
|
87
|
-
if (report.staleAllowlist.length > 0) return 1;
|
|
88
|
-
if (report.staleUnreadAllowlist.length > 0) return 1;
|
|
89
|
-
return 0;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// src/extract.ts
|
|
93
|
-
import { createHash } from "crypto";
|
|
94
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
95
|
-
import { dirname, isAbsolute, join as join2, relative, resolve } from "path";
|
|
96
|
-
import ts from "typescript";
|
|
97
|
-
var UNRESOLVED_ID = "<unresolved>";
|
|
98
|
-
function findTsconfig(from) {
|
|
99
|
-
return ts.findConfigFile(resolve(from), ts.sys.fileExists, "tsconfig.json");
|
|
100
|
-
}
|
|
101
|
-
function readLiteralConfigScope(configPath) {
|
|
102
|
-
const source = ts.createSourceFile(
|
|
103
|
-
configPath,
|
|
104
|
-
readFileSync2(configPath, "utf8"),
|
|
105
|
-
ts.ScriptTarget.Latest,
|
|
106
|
-
true,
|
|
107
|
-
configPath.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
108
|
-
);
|
|
109
|
-
let scope;
|
|
110
|
-
const visit = (node) => {
|
|
111
|
-
if (scope) return;
|
|
112
|
-
if (ts.isPropertyAssignment(node) && propertyName(node.name) === "scope" && ts.isArrayLiteralExpression(node.initializer)) {
|
|
113
|
-
const values = node.initializer.elements.map((entry) => literalText(entry));
|
|
114
|
-
if (values.every((value) => value !== void 0)) scope = values;
|
|
115
|
-
}
|
|
116
|
-
ts.forEachChild(node, visit);
|
|
117
|
-
};
|
|
118
|
-
visit(source);
|
|
119
|
-
return scope;
|
|
120
|
-
}
|
|
121
|
-
function readProgramFiles(tsconfigPath) {
|
|
122
|
-
const read = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
123
|
-
if (read.error) {
|
|
124
|
-
throw new Error(
|
|
125
|
-
`could not read ${tsconfigPath}: ${ts.flattenDiagnosticMessageText(read.error.messageText, " ")}`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
129
|
-
read.config,
|
|
130
|
-
ts.sys,
|
|
131
|
-
dirname(tsconfigPath)
|
|
132
|
-
);
|
|
133
|
-
if (parsed.errors.length > 0 && parsed.fileNames.length === 0) {
|
|
134
|
-
throw new Error(
|
|
135
|
-
`could not resolve any files from ${tsconfigPath}: ${parsed.errors.map((error) => ts.flattenDiagnosticMessageText(error.messageText, " ")).join("; ")}`
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
return { fileNames: parsed.fileNames, options: parsed.options };
|
|
139
|
-
}
|
|
140
|
-
var REGISTRATION_HOOKS = /* @__PURE__ */ new Set(["useAgentComponent", "useAgentAction", "useAgentObservation"]);
|
|
141
|
-
function isRegistrationModule(specifier) {
|
|
142
|
-
return specifier.startsWith("@agent-surface/");
|
|
143
|
-
}
|
|
144
|
-
var NO_IMPORTS = { locals: /* @__PURE__ */ new Map(), namespaces: /* @__PURE__ */ new Set() };
|
|
145
|
-
function importedRegistrations(source) {
|
|
146
|
-
const locals = /* @__PURE__ */ new Map();
|
|
147
|
-
const namespaces = /* @__PURE__ */ new Set();
|
|
148
|
-
for (const statement of source.statements) {
|
|
149
|
-
if (!ts.isImportDeclaration(statement)) continue;
|
|
150
|
-
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
151
|
-
if (!isRegistrationModule(statement.moduleSpecifier.text)) continue;
|
|
152
|
-
const clause = statement.importClause;
|
|
153
|
-
if (!clause || clause.isTypeOnly || !clause.namedBindings) continue;
|
|
154
|
-
if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
155
|
-
namespaces.add(clause.namedBindings.name.text);
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
for (const element of clause.namedBindings.elements) {
|
|
159
|
-
if (element.isTypeOnly) continue;
|
|
160
|
-
const imported = (element.propertyName ?? element.name).text;
|
|
161
|
-
if (REGISTRATION_HOOKS.has(imported)) locals.set(element.name.text, imported);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return { locals, namespaces };
|
|
165
|
-
}
|
|
166
|
-
function renamedRegistrationExports(source, imports) {
|
|
167
|
-
const renamed = [];
|
|
168
|
-
for (const statement of source.statements) {
|
|
169
|
-
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly) continue;
|
|
170
|
-
const clause = statement.exportClause;
|
|
171
|
-
if (!clause || !ts.isNamedExports(clause)) continue;
|
|
172
|
-
const from = statement.moduleSpecifier;
|
|
173
|
-
const fromOurs = from !== void 0 && ts.isStringLiteral(from) && isRegistrationModule(from.text);
|
|
174
|
-
if (from && !fromOurs) continue;
|
|
175
|
-
for (const element of clause.elements) {
|
|
176
|
-
if (element.isTypeOnly) continue;
|
|
177
|
-
const local = (element.propertyName ?? element.name).text;
|
|
178
|
-
const hook = fromOurs ? REGISTRATION_HOOKS.has(local) ? local : void 0 : imports.locals.get(local);
|
|
179
|
-
if (hook === void 0 || element.name.text === hook) continue;
|
|
180
|
-
renamed.push({ node: element, hook, exported: element.name.text });
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return renamed;
|
|
184
|
-
}
|
|
185
|
-
function namespaceMember(object, member, imports) {
|
|
186
|
-
return ts.isIdentifier(object) && imports.namespaces.has(object.text) && REGISTRATION_HOOKS.has(member);
|
|
187
|
-
}
|
|
188
|
-
function calleeName(call, imports = NO_IMPORTS) {
|
|
189
|
-
const callee = call.expression;
|
|
190
|
-
if (ts.isIdentifier(callee)) return imports.locals.get(callee.text) ?? callee.text;
|
|
191
|
-
if (ts.isPropertyAccessExpression(callee)) {
|
|
192
|
-
if (namespaceMember(callee.expression, callee.name.text, imports)) return callee.name.text;
|
|
193
|
-
return callee.name.text;
|
|
194
|
-
}
|
|
195
|
-
if (ts.isElementAccessExpression(callee)) {
|
|
196
|
-
const member = literalText(callee.argumentExpression);
|
|
197
|
-
if (member !== void 0 && namespaceMember(callee.expression, member, imports)) return member;
|
|
198
|
-
}
|
|
199
|
-
return void 0;
|
|
200
|
-
}
|
|
201
|
-
function propertyName(name) {
|
|
202
|
-
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
203
|
-
return void 0;
|
|
204
|
-
}
|
|
205
|
-
function propertyOf(object, wanted) {
|
|
206
|
-
for (const property of object.properties) {
|
|
207
|
-
if (ts.isPropertyAssignment(property) && propertyName(property.name) === wanted) {
|
|
208
|
-
return property.initializer;
|
|
209
|
-
}
|
|
210
|
-
if (ts.isShorthandPropertyAssignment(property) && property.name.text === wanted) {
|
|
211
|
-
return property.name;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return void 0;
|
|
215
|
-
}
|
|
216
|
-
function hasSpread(object) {
|
|
217
|
-
return object.properties.some((property) => ts.isSpreadAssignment(property));
|
|
218
|
-
}
|
|
219
|
-
var CAPABILITY_GROUPS = ["observations", "actions"];
|
|
220
|
-
function spreadKeys(expression, source, depth = 0) {
|
|
221
|
-
if (depth > 1) return void 0;
|
|
222
|
-
if (ts.isParenthesizedExpression(expression)) {
|
|
223
|
-
return spreadKeys(expression.expression, source, depth);
|
|
224
|
-
}
|
|
225
|
-
if (ts.isConditionalExpression(expression)) {
|
|
226
|
-
const whenTrue = spreadKeys(expression.whenTrue, source, depth);
|
|
227
|
-
const whenFalse = spreadKeys(expression.whenFalse, source, depth);
|
|
228
|
-
if (!whenTrue || !whenFalse) return void 0;
|
|
229
|
-
return [...whenTrue, ...whenFalse];
|
|
230
|
-
}
|
|
231
|
-
const resolved = objectLiteralFor(expression, source);
|
|
232
|
-
if (!resolved.object) return void 0;
|
|
233
|
-
const keys = [];
|
|
234
|
-
for (const property of resolved.object.properties) {
|
|
235
|
-
if (ts.isSpreadAssignment(property)) {
|
|
236
|
-
const nested = spreadKeys(property.expression, source, depth + 1);
|
|
237
|
-
if (!nested) return void 0;
|
|
238
|
-
keys.push(...nested);
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
const name = ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property) ? propertyName(property.name) : ts.isShorthandPropertyAssignment(property) ? property.name.text : void 0;
|
|
242
|
-
if (name === void 0) return void 0;
|
|
243
|
-
keys.push(name);
|
|
244
|
-
}
|
|
245
|
-
return keys;
|
|
246
|
-
}
|
|
247
|
-
function literalText(node) {
|
|
248
|
-
if (!node) return void 0;
|
|
249
|
-
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
250
|
-
if (ts.isParenthesizedExpression(node)) return literalText(node.expression);
|
|
251
|
-
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
252
|
-
const left = literalText(node.left);
|
|
253
|
-
const right = literalText(node.right);
|
|
254
|
-
if (left !== void 0 && right !== void 0) return left + right;
|
|
255
|
-
}
|
|
256
|
-
return void 0;
|
|
257
|
-
}
|
|
258
|
-
function describeConstruct(node) {
|
|
259
|
-
if (ts.isCallExpression(node)) {
|
|
260
|
-
const callee = calleeName(node);
|
|
261
|
-
return callee ? `built by ${callee}()` : "built by a call expression";
|
|
262
|
-
}
|
|
263
|
-
if (ts.isIdentifier(node)) return `a variable (${node.text}) this extractor could not follow`;
|
|
264
|
-
if (ts.isConditionalExpression(node)) return "a conditional expression";
|
|
265
|
-
if (ts.isTemplateExpression(node)) return "a template with substitutions";
|
|
266
|
-
if (ts.isPropertyAccessExpression(node)) return "a property access";
|
|
267
|
-
return "a non-literal expression";
|
|
268
|
-
}
|
|
269
|
-
function objectLiteralFor(expression, source) {
|
|
270
|
-
if (ts.isObjectLiteralExpression(expression)) return { object: expression };
|
|
271
|
-
if (ts.isIdentifier(expression)) {
|
|
272
|
-
const target = expression.text;
|
|
273
|
-
let found;
|
|
274
|
-
const visit = (node) => {
|
|
275
|
-
if (found) return;
|
|
276
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === target && node.initializer && ts.isObjectLiteralExpression(node.initializer)) {
|
|
277
|
-
found = node.initializer;
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
ts.forEachChild(node, visit);
|
|
281
|
-
};
|
|
282
|
-
visit(source);
|
|
283
|
-
if (found) return { object: found };
|
|
284
|
-
return {
|
|
285
|
-
note: `the config is \`${target}\`, which is not a same-module object literal \u2014 the extractor follows one hop only`
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
return { note: `the config is ${describeConstruct(expression)}` };
|
|
289
|
-
}
|
|
290
|
-
var GRANULAR_HOOKS = /* @__PURE__ */ new Set(["useAgentAction", "useAgentObservation"]);
|
|
291
|
-
function capabilitiesFromGroup(group, kind, componentType, componentPartial, emit, source) {
|
|
292
|
-
if (!group) return;
|
|
293
|
-
const resolved = objectLiteralFor(group, source);
|
|
294
|
-
if (!resolved.object) {
|
|
295
|
-
emit.push({
|
|
296
|
-
capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,
|
|
297
|
-
kind,
|
|
298
|
-
origin: emit.origin(group),
|
|
299
|
-
resolution: "unresolved",
|
|
300
|
-
reason: "dynamic-group",
|
|
301
|
-
note: `\`${kind}s\` on "${componentType}" is not an object literal: ${resolved.note}`
|
|
302
|
-
});
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
for (const property of resolved.object.properties) {
|
|
306
|
-
if (ts.isSpreadAssignment(property)) {
|
|
307
|
-
emit.push({
|
|
308
|
-
capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,
|
|
309
|
-
kind,
|
|
310
|
-
origin: emit.origin(property),
|
|
311
|
-
resolution: "unresolved",
|
|
312
|
-
reason: "spread-members",
|
|
313
|
-
note: `\`${kind}s\` on "${componentType}" spreads another object, which may contribute capabilities this inventory cannot name`
|
|
314
|
-
});
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
const name = ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property) ? propertyName(property.name) : ts.isShorthandPropertyAssignment(property) ? property.name.text : void 0;
|
|
318
|
-
if (name === void 0) {
|
|
319
|
-
emit.push({
|
|
320
|
-
capabilityId: `view:${componentType}.${UNRESOLVED_ID}`,
|
|
321
|
-
kind,
|
|
322
|
-
origin: emit.origin(property),
|
|
323
|
-
resolution: "unresolved",
|
|
324
|
-
reason: "computed-name",
|
|
325
|
-
note: `a capability on "${componentType}" has a computed name`
|
|
326
|
-
});
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
const capability = {
|
|
330
|
-
capabilityId: `view:${componentType}.${name}`,
|
|
331
|
-
kind,
|
|
332
|
-
origin: emit.origin(property),
|
|
333
|
-
resolution: "static"
|
|
334
|
-
};
|
|
335
|
-
const notes = [];
|
|
336
|
-
if (componentPartial) notes.push(componentPartial);
|
|
337
|
-
const value = ts.isPropertyAssignment(property) ? property.initializer : void 0;
|
|
338
|
-
const definition = value && ts.isCallExpression(value) && value.arguments.length > 0 ? value.arguments[0] : value;
|
|
339
|
-
if (definition && ts.isObjectLiteralExpression(definition)) {
|
|
340
|
-
const description = literalText(propertyOf(definition, "description"));
|
|
341
|
-
if (description !== void 0) capability.description = description;
|
|
342
|
-
else notes.push("description is not a string literal");
|
|
343
|
-
if (kind === "action") {
|
|
344
|
-
const effect = literalText(propertyOf(definition, "effect"));
|
|
345
|
-
if (effect !== void 0) capability.effect = effect;
|
|
346
|
-
else notes.push("effect is not a string literal");
|
|
347
|
-
}
|
|
348
|
-
if (hasSpread(definition)) notes.push("the definition spreads another object");
|
|
349
|
-
} else {
|
|
350
|
-
notes.push(
|
|
351
|
-
value ? `the definition is ${describeConstruct(value)}` : "the definition is not an object literal"
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
if (notes.length > 0) {
|
|
355
|
-
capability.resolution = "partial";
|
|
356
|
-
capability.note = notes.join("; ");
|
|
357
|
-
}
|
|
358
|
-
emit.push(capability);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
function visitCall(call, emit, source, imports, deferred, enclosing) {
|
|
362
|
-
const callee = calleeName(call, imports);
|
|
363
|
-
if (callee === void 0) {
|
|
364
|
-
const object = ts.isElementAccessExpression(call.expression) ? call.expression.expression : void 0;
|
|
365
|
-
if (object && ts.isIdentifier(object) && imports.namespaces.has(object.text)) {
|
|
366
|
-
emit.push({
|
|
367
|
-
capabilityId: UNRESOLVED_ID,
|
|
368
|
-
kind: "action",
|
|
369
|
-
origin: emit.origin(call),
|
|
370
|
-
resolution: "unresolved",
|
|
371
|
-
reason: "dynamic-callee",
|
|
372
|
-
note: `a call reads a computed member of \`${object.text}\`, a namespace of this library \u2014 which export it calls, and so whether it registers anything, cannot be read here`
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
if (GRANULAR_HOOKS.has(callee)) {
|
|
378
|
-
emit.push({
|
|
379
|
-
capabilityId: UNRESOLVED_ID,
|
|
380
|
-
kind: callee === "useAgentAction" ? "action" : "observation",
|
|
381
|
-
origin: emit.origin(call),
|
|
382
|
-
resolution: "unresolved",
|
|
383
|
-
reason: "granular-hook",
|
|
384
|
-
note: `${callee}() registers against a render-scope link, so its component type is not at this call site`
|
|
385
|
-
});
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
if (callee !== "useAgentComponent" && callee !== "register") return;
|
|
389
|
-
const argument = call.arguments[0];
|
|
390
|
-
if (!argument) return;
|
|
391
|
-
const resolved = objectLiteralFor(argument, source);
|
|
392
|
-
if (!resolved.object) {
|
|
393
|
-
emit.push({
|
|
394
|
-
capabilityId: UNRESOLVED_ID,
|
|
395
|
-
kind: "action",
|
|
396
|
-
origin: emit.origin(call),
|
|
397
|
-
resolution: "unresolved",
|
|
398
|
-
reason: "dynamic-config",
|
|
399
|
-
note: `${callee}() call site could not be read: ${resolved.note}`
|
|
400
|
-
});
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
const config = resolved.object;
|
|
404
|
-
const typeNode = propertyOf(config, "type");
|
|
405
|
-
const type = literalText(typeNode);
|
|
406
|
-
if (type === void 0) {
|
|
407
|
-
if (callee === "register" && typeNode === void 0) return;
|
|
408
|
-
const slot = enclosing && typeNode && ts.isIdentifier(typeNode) ? parameterSlot(typeNode.text, enclosing.fn) : void 0;
|
|
409
|
-
if (slot && enclosing?.name) {
|
|
410
|
-
deferred.push({ config, source, emit, wrapperName: enclosing.name, slot, site: call });
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
emit.push({
|
|
414
|
-
capabilityId: UNRESOLVED_ID,
|
|
415
|
-
kind: "action",
|
|
416
|
-
origin: emit.origin(call),
|
|
417
|
-
resolution: "unresolved",
|
|
418
|
-
reason: "dynamic-type",
|
|
419
|
-
note: `\`type\` is not a string literal, so no capability id on this component can be determined`
|
|
420
|
-
});
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
const componentPartial = hasSpread(config) ? "the component config spreads another object, so some metadata here may be dynamic" : void 0;
|
|
424
|
-
for (const property of config.properties) {
|
|
425
|
-
if (!ts.isSpreadAssignment(property)) continue;
|
|
426
|
-
const keys = spreadKeys(property.expression, source);
|
|
427
|
-
if (keys && !keys.some((key) => CAPABILITY_GROUPS.includes(key))) {
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
emit.push({
|
|
431
|
-
capabilityId: `view:${type}.${UNRESOLVED_ID}`,
|
|
432
|
-
kind: "action",
|
|
433
|
-
origin: emit.origin(property),
|
|
434
|
-
resolution: "unresolved",
|
|
435
|
-
reason: "spread-members",
|
|
436
|
-
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`
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
capabilitiesFromGroup(
|
|
440
|
-
propertyOf(config, "observations"),
|
|
441
|
-
"observation",
|
|
442
|
-
type,
|
|
443
|
-
componentPartial,
|
|
444
|
-
emit,
|
|
445
|
-
source
|
|
446
|
-
);
|
|
447
|
-
capabilitiesFromGroup(
|
|
448
|
-
propertyOf(config, "actions"),
|
|
449
|
-
"action",
|
|
450
|
-
type,
|
|
451
|
-
componentPartial,
|
|
452
|
-
emit,
|
|
453
|
-
source
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
function functionLike(node) {
|
|
457
|
-
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)) {
|
|
458
|
-
return node;
|
|
459
|
-
}
|
|
460
|
-
return void 0;
|
|
461
|
-
}
|
|
462
|
-
function parameterSlot(name, fn) {
|
|
463
|
-
for (const [index, parameter] of fn.parameters.entries()) {
|
|
464
|
-
if (ts.isIdentifier(parameter.name)) {
|
|
465
|
-
if (parameter.name.text === name) return { index };
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
if (ts.isObjectBindingPattern(parameter.name)) {
|
|
469
|
-
for (const element of parameter.name.elements) {
|
|
470
|
-
if (!ts.isIdentifier(element.name) || element.name.text !== name) continue;
|
|
471
|
-
const property = element.propertyName && ts.isIdentifier(element.propertyName) ? element.propertyName.text : name;
|
|
472
|
-
return { index, property };
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
return void 0;
|
|
477
|
-
}
|
|
478
|
-
function callsWrapper(site, wrapper, compilerOptions) {
|
|
479
|
-
const callee = site.call.expression;
|
|
480
|
-
if (!ts.isIdentifier(callee) || callee.text !== wrapper.wrapperName) return false;
|
|
481
|
-
if (site.source.fileName === wrapper.source.fileName) {
|
|
482
|
-
return true;
|
|
483
|
-
}
|
|
484
|
-
for (const statement of site.source.statements) {
|
|
485
|
-
if (!ts.isImportDeclaration(statement)) continue;
|
|
486
|
-
const clause = statement.importClause;
|
|
487
|
-
if (!clause) continue;
|
|
488
|
-
const named = clause.name?.text === wrapper.wrapperName || clause.namedBindings && ts.isNamedImports(clause.namedBindings) && clause.namedBindings.elements.some((element) => element.name.text === wrapper.wrapperName);
|
|
489
|
-
if (!named) continue;
|
|
490
|
-
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
491
|
-
const resolved = ts.resolveModuleName(
|
|
492
|
-
statement.moduleSpecifier.text,
|
|
493
|
-
site.source.fileName,
|
|
494
|
-
compilerOptions,
|
|
495
|
-
ts.sys
|
|
496
|
-
).resolvedModule;
|
|
497
|
-
if (resolved?.resolvedFileName === wrapper.source.fileName) return true;
|
|
498
|
-
}
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
function normalizedText(node, source) {
|
|
502
|
-
return node.getText(source).replace(/\s+/g, " ").trim();
|
|
503
|
-
}
|
|
504
|
-
function siteIdentity(source, node) {
|
|
505
|
-
const labels = [];
|
|
506
|
-
let enclosingCall = "";
|
|
507
|
-
let scope;
|
|
508
|
-
for (let parent = node.parent; parent && parent !== source; parent = parent.parent) {
|
|
509
|
-
if (!enclosingCall && ts.isCallExpression(parent)) {
|
|
510
|
-
enclosingCall = normalizedText(parent, source);
|
|
511
|
-
}
|
|
512
|
-
const named = (ts.isFunctionDeclaration(parent) || ts.isMethodDeclaration(parent)) && parent.name && (ts.isIdentifier(parent.name) || ts.isStringLiteral(parent.name)) ? parent.name.text : ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name) ? parent.name.text : void 0;
|
|
513
|
-
if (named !== void 0) {
|
|
514
|
-
labels.push(named);
|
|
515
|
-
scope ??= parent;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return { labels: labels.reverse(), enclosingCall, scope: scope ?? source };
|
|
519
|
-
}
|
|
520
|
-
function occurrence(scope, node, source) {
|
|
521
|
-
const text = normalizedText(node, source);
|
|
522
|
-
const start = node.getStart(source);
|
|
523
|
-
let rank = 0;
|
|
524
|
-
const visit = (candidate) => {
|
|
525
|
-
if (candidate.kind === node.kind && candidate.getStart(source) < start && normalizedText(candidate, source) === text) {
|
|
526
|
-
rank += 1;
|
|
527
|
-
}
|
|
528
|
-
ts.forEachChild(candidate, visit);
|
|
529
|
-
};
|
|
530
|
-
ts.forEachChild(scope, visit);
|
|
531
|
-
return rank;
|
|
532
|
-
}
|
|
533
|
-
function stableSite(source, node) {
|
|
534
|
-
const { labels, enclosingCall, scope } = siteIdentity(source, node);
|
|
535
|
-
return createHash("sha256").update(
|
|
536
|
-
`${labels.join("/")}\0${enclosingCall}\0${normalizedText(node, source)}\0${occurrence(
|
|
537
|
-
scope,
|
|
538
|
-
node,
|
|
539
|
-
source
|
|
540
|
-
)}`
|
|
541
|
-
).digest("hex").slice(0, 12);
|
|
542
|
-
}
|
|
543
|
-
var packageNameCache = /* @__PURE__ */ new Map();
|
|
544
|
-
function packageNameFor(file) {
|
|
545
|
-
let dir = dirname(file);
|
|
546
|
-
for (; ; ) {
|
|
547
|
-
if (packageNameCache.has(dir)) return packageNameCache.get(dir);
|
|
548
|
-
const packagePath = join2(dir, "package.json");
|
|
549
|
-
if (existsSync2(packagePath)) {
|
|
550
|
-
let name;
|
|
551
|
-
try {
|
|
552
|
-
const parsed = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
553
|
-
if (typeof parsed.name === "string") name = parsed.name;
|
|
554
|
-
} catch {
|
|
555
|
-
}
|
|
556
|
-
packageNameCache.set(dir, name);
|
|
557
|
-
return name;
|
|
558
|
-
}
|
|
559
|
-
const parent = dirname(dir);
|
|
560
|
-
if (parent === dir) return void 0;
|
|
561
|
-
dir = parent;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
var IMPLEMENTATION_PACKAGES = /* @__PURE__ */ new Set([
|
|
565
|
-
"@agent-surface/core",
|
|
566
|
-
"@agent-surface/react",
|
|
567
|
-
"@agent-surface/orpc",
|
|
568
|
-
"@agent-surface/testing",
|
|
569
|
-
"@agent-surface/webmcp",
|
|
570
|
-
"@agent-surface/cli"
|
|
571
|
-
]);
|
|
572
|
-
function isAgentSurfaceImplementation(file) {
|
|
573
|
-
return IMPLEMENTATION_PACKAGES.has(packageNameFor(file) ?? "");
|
|
574
|
-
}
|
|
575
|
-
function extractCapabilities(options) {
|
|
576
|
-
const root = resolve(options.root);
|
|
577
|
-
const tsconfigPath = options.tsconfig ? isAbsolute(options.tsconfig) ? options.tsconfig : join2(root, options.tsconfig) : findTsconfig(root);
|
|
578
|
-
if (!tsconfigPath || !existsSync2(tsconfigPath)) {
|
|
579
|
-
throw new Error(
|
|
580
|
-
`no tsconfig.json found from ${root} \u2014 \`capabilities\` reads the TypeScript program, so it needs one (pass --tsconfig to point at it)`
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
const { fileNames, options: compilerOptions } = readProgramFiles(tsconfigPath);
|
|
584
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
585
|
-
const capabilities = [];
|
|
586
|
-
let filesAnalyzed = 0;
|
|
587
|
-
let filesOutsideRoot = 0;
|
|
588
|
-
const deferred = [];
|
|
589
|
-
const callsByName = /* @__PURE__ */ new Map();
|
|
590
|
-
for (const source of program.getSourceFiles()) {
|
|
591
|
-
if (source.isDeclarationFile) continue;
|
|
592
|
-
if (source.fileName.includes("/node_modules/")) continue;
|
|
593
|
-
if (!isInside(root, source.fileName) && isAgentSurfaceImplementation(source.fileName)) {
|
|
594
|
-
filesOutsideRoot += 1;
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
filesAnalyzed += 1;
|
|
598
|
-
const emit = {
|
|
599
|
-
push: (capability) => capabilities.push(capability),
|
|
600
|
-
origin: (node) => ({
|
|
601
|
-
file: relative(root, source.fileName),
|
|
602
|
-
line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1,
|
|
603
|
-
site: stableSite(source, node)
|
|
604
|
-
})
|
|
605
|
-
};
|
|
606
|
-
const imports = importedRegistrations(source);
|
|
607
|
-
for (const renamed of renamedRegistrationExports(source, imports)) {
|
|
608
|
-
emit.push({
|
|
609
|
-
capabilityId: UNRESOLVED_ID,
|
|
610
|
-
kind: "action",
|
|
611
|
-
origin: emit.origin(renamed.node),
|
|
612
|
-
resolution: "unresolved",
|
|
613
|
-
reason: "dynamic-callee",
|
|
614
|
-
note: `${renamed.hook}() leaves this module as \`${renamed.exported}\`, so nothing at its call sites elsewhere proves they register anything \u2014 whatever they author is not in this catalog`
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
let pendingName;
|
|
618
|
-
const visit = (node, enclosing) => {
|
|
619
|
-
const fn = functionLike(node);
|
|
620
|
-
if (fn) {
|
|
621
|
-
const named = ts.isFunctionDeclaration(node) && node.name ? node.name.text : pendingName;
|
|
622
|
-
enclosing = { fn, ...named ? { name: named } : {} };
|
|
623
|
-
pendingName = void 0;
|
|
624
|
-
} else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
625
|
-
pendingName = node.name.text;
|
|
626
|
-
}
|
|
627
|
-
if (ts.isCallExpression(node)) {
|
|
628
|
-
visitCall(node, emit, source, imports, deferred, enclosing);
|
|
629
|
-
if (ts.isIdentifier(node.expression)) {
|
|
630
|
-
const name = node.expression.text;
|
|
631
|
-
const sites = callsByName.get(name) ?? [];
|
|
632
|
-
sites.push({ call: node, source });
|
|
633
|
-
callsByName.set(name, sites);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
ts.forEachChild(node, (child) => visit(child, enclosing));
|
|
637
|
-
};
|
|
638
|
-
visit(source);
|
|
639
|
-
}
|
|
640
|
-
for (const wrapper of deferred) {
|
|
641
|
-
const sites = (callsByName.get(wrapper.wrapperName) ?? []).filter(
|
|
642
|
-
(site) => callsWrapper(site, wrapper, compilerOptions)
|
|
643
|
-
);
|
|
644
|
-
const types = /* @__PURE__ */ new Map();
|
|
645
|
-
const dynamic = [];
|
|
646
|
-
for (const site of sites) {
|
|
647
|
-
const argument = site.call.arguments[wrapper.slot.index];
|
|
648
|
-
const value = wrapper.slot.property && argument && ts.isObjectLiteralExpression(argument) ? propertyOf(argument, wrapper.slot.property) : argument;
|
|
649
|
-
const text = value ? literalText(value) : void 0;
|
|
650
|
-
if (text !== void 0) types.set(text, site.call);
|
|
651
|
-
else dynamic.push(site);
|
|
652
|
-
}
|
|
653
|
-
for (const type of [...types.keys()].sort()) {
|
|
654
|
-
const componentPartial = hasSpread(wrapper.config) ? "the component config spreads another object, so some metadata here may be dynamic" : void 0;
|
|
655
|
-
for (const [group, kind] of [
|
|
656
|
-
["observations", "observation"],
|
|
657
|
-
["actions", "action"]
|
|
658
|
-
]) {
|
|
659
|
-
capabilitiesFromGroup(
|
|
660
|
-
propertyOf(wrapper.config, group),
|
|
661
|
-
kind,
|
|
662
|
-
type,
|
|
663
|
-
componentPartial,
|
|
664
|
-
wrapper.emit,
|
|
665
|
-
wrapper.source
|
|
666
|
-
);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
if (types.size === 0 || dynamic.length > 0) {
|
|
670
|
-
wrapper.emit.push({
|
|
671
|
-
capabilityId: UNRESOLVED_ID,
|
|
672
|
-
kind: "action",
|
|
673
|
-
origin: wrapper.emit.origin(wrapper.site),
|
|
674
|
-
resolution: "unresolved",
|
|
675
|
-
reason: "dynamic-type",
|
|
676
|
-
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`
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
capabilities.sort(
|
|
681
|
-
(a, b) => a.capabilityId.localeCompare(b.capabilityId) || a.origin.file.localeCompare(b.origin.file) || a.origin.line - b.origin.line
|
|
682
|
-
);
|
|
683
|
-
return {
|
|
684
|
-
capabilities,
|
|
685
|
-
tsconfig: tsconfigPath,
|
|
686
|
-
root,
|
|
687
|
-
filesAnalyzed,
|
|
688
|
-
filesOutsideRoot,
|
|
689
|
-
domain: "not-analyzed"
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
function isInside(root, file) {
|
|
693
|
-
const rel = relative(root, file);
|
|
694
|
-
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
695
|
-
}
|
|
696
|
-
function authoredIds(inventory) {
|
|
697
|
-
const ids = /* @__PURE__ */ new Set();
|
|
698
|
-
for (const capability of inventory.capabilities) {
|
|
699
|
-
if (capability.resolution === "unresolved") continue;
|
|
700
|
-
if (capability.capabilityId.endsWith(UNRESOLVED_ID)) continue;
|
|
701
|
-
ids.add(capability.capabilityId);
|
|
702
|
-
}
|
|
703
|
-
return ids;
|
|
704
|
-
}
|
|
705
|
-
function unresolved(inventory) {
|
|
706
|
-
return inventory.capabilities.filter((capability) => capability.resolution === "unresolved");
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
export {
|
|
710
|
-
ALLOWLIST_FILE,
|
|
711
|
-
UNREAD_ALLOWLIST_FILE,
|
|
712
|
-
allowlistPathFor,
|
|
713
|
-
unreadAllowlistPathFor,
|
|
714
|
-
readAllowlist,
|
|
715
|
-
unreadKey,
|
|
716
|
-
buildCoverageReport,
|
|
717
|
-
coverageExitCode,
|
|
718
|
-
findTsconfig,
|
|
719
|
-
readLiteralConfigScope,
|
|
720
|
-
extractCapabilities,
|
|
721
|
-
authoredIds,
|
|
722
|
-
unresolved
|
|
723
|
-
};
|
|
724
|
-
//# sourceMappingURL=chunk-UGCLJ5JX.js.map
|