@moda-ai/cli 1.55.94 → 1.55.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +23 -0
  2. package/dist/{agent-pncdccck.js → agent-wa8tx02f.js} +6 -5
  3. package/dist/{change-impact-2hqk7kj0.js → change-impact-3n23nhd8.js} +3 -3
  4. package/dist/{cli-5c37h7n5.js → cli-1sdz17sn.js} +40 -16
  5. package/dist/{cli-dk08ywkn.js → cli-764m5azg.js} +2 -1031
  6. package/dist/cli-92zfpz9f.js +1037 -0
  7. package/dist/{cli-gdm94hh4.js → cli-abfcqf4p.js} +2 -2
  8. package/dist/{index-61yya1cm.js → cli-f1sx9dsa.js} +3 -21
  9. package/dist/cli-gcqpbes6.js +1660 -0
  10. package/dist/cli.js +31 -17
  11. package/dist/{doctor-kxk2j3j2.js → doctor-ec2cqgnv.js} +5 -4
  12. package/dist/{harness-github-actions-75z9c68t.js → harness-github-actions-h9b973z0.js} +3 -3
  13. package/dist/{harness-wnye9acm.js → harness-jka07f99.js} +3 -3
  14. package/dist/{index-09t1rsf2.js → index-0na4ydkc.js} +3 -3
  15. package/dist/{index-e0b4bd74.js → index-w2hs49tb.js} +12 -11
  16. package/dist/index-zp7y6mft.js +31 -0
  17. package/dist/{production-intelligence-1y884f69.js → production-intelligence-3fdpsxce.js} +2 -2
  18. package/dist/{prompt-attribution-6zeqqqfy.js → prompt-attribution-armk5gqt.js} +3 -3
  19. package/dist/prompt-source-mcp.js +3 -3
  20. package/dist/{prompts-9ngt4wyh.js → prompts-x3vdeax6.js} +4 -3
  21. package/dist/{skills-hf8fa7w7.js → skills-1pv2mp9v.js} +3 -3
  22. package/dist/{sync-zsyy15p9.js → sync-4zeyb408.js} +8 -7
  23. package/package.json +1 -1
  24. package/skills/integration/index.json +2 -2
  25. package/dist/{cli-wm0p9kn2.js → cli-9kxz65fe.js} +3 -3
  26. package/dist/{cli-t8vmcbe0.js → cli-f0z97a7t.js} +3 -3
@@ -0,0 +1,1660 @@
1
+ import {
2
+ enumerateToolCatalog,
3
+ enumerateWorkflows
4
+ } from "./cli-f1sx9dsa.js";
5
+ import {
6
+ require_out
7
+ } from "./cli-764m5azg.js";
8
+ import {
9
+ EXTRACT_SCHEMA_VERSION,
10
+ TEST_PATH,
11
+ collectModels,
12
+ emitToolsCached,
13
+ enclosingClass,
14
+ enclosingFunction,
15
+ enumerateAgents,
16
+ enumerationRepository,
17
+ evalFileSignal,
18
+ evalSuiteKey,
19
+ extractHarnessWithContext,
20
+ functionName,
21
+ isFunctionLike,
22
+ lineOf,
23
+ modelArtifacts,
24
+ parse,
25
+ propertyNameText,
26
+ relPath,
27
+ resolvePromptArguments,
28
+ resolvedSymbol,
29
+ scopeOf,
30
+ stableId,
31
+ svelteVirtualName,
32
+ toolArtifacts,
33
+ toolNamesOf,
34
+ unwrap,
35
+ valueDeclaration,
36
+ visit
37
+ } from "./cli-1sdz17sn.js";
38
+ import"./cli-k0vxtzxb.js";
39
+ import"./cli-97fnztpy.js";
40
+ import {
41
+ detectBoundRuntimeClasses,
42
+ isTestWorkerConfig
43
+ } from "./cli-krm4w2v9.js";
44
+ import"./cli-13zdcv4w.js";
45
+ import"./cli-6ga6kmdp.js";
46
+ import"./cli-e0kbye4s.js";
47
+ import"./cli-dxajrq9h.js";
48
+ import {
49
+ EXIT_DEGRADED,
50
+ VERSION
51
+ } from "./cli-1nd4339b.js";
52
+ import {
53
+ CliInputError
54
+ } from "./cli-k4tqwah4.js";
55
+ import {
56
+ require_typescript
57
+ } from "./cli-r0pkdy83.js";
58
+ import"./cli-9kfxkxmk.js";
59
+ import"./cli-0semcakc.js";
60
+ import {
61
+ __toESM
62
+ } from "./cli-0v6na3yp.js";
63
+
64
+ // src/harness-extract/skeleton/cli.ts
65
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
66
+ import { dirname as dirname2, resolve as resolve3 } from "node:path";
67
+
68
+ // src/harness-extract/skeleton/index.ts
69
+ var import_fast_glob = __toESM(require_out(), 1);
70
+ var import_typescript2 = __toESM(require_typescript(), 1);
71
+ import { createHash } from "node:crypto";
72
+ import { execFileSync } from "node:child_process";
73
+ import { readFileSync } from "node:fs";
74
+ import { basename, dirname, resolve as resolve2 } from "node:path";
75
+
76
+ // src/harness-extract/skeleton/prompt-units.ts
77
+ var import_typescript = __toESM(require_typescript(), 1);
78
+ import { resolve } from "node:path";
79
+ var TEXT_METHODS = new Set(["trim", "trimStart", "trimEnd", "replace", "replaceAll", "toString", "normalize", "padEnd", "padStart", "slice"]);
80
+ var ASSEMBLY_METHODS = new Set(["join", "concat", "filter", "flat", "map", "flatMap"]);
81
+ var TEXT_FUNCTIONS = /^(?:String|dedent|outdent|stripIndent|stripIndents|trim|endent)$/;
82
+ var MAX_DEPTH = 8;
83
+ function roleOf(role) {
84
+ return ["system", "user", "messages", "prompt", "instructions", "questions"].find((r) => r === role) ?? "unknown";
85
+ }
86
+ function endLineOf(node) {
87
+ const sf = node.getSourceFile();
88
+ return sf.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
89
+ }
90
+ function inRepo(node) {
91
+ const sf = node.getSourceFile();
92
+ return !sf.isDeclarationFile && !/[\\/]node_modules[\\/]/.test(sf.fileName);
93
+ }
94
+ function sourceFileOf(ctx, path) {
95
+ const program = ctx.programFor(path);
96
+ if (!program)
97
+ return;
98
+ const abs = resolve(ctx.rootDir, path);
99
+ return program.getSourceFile(/\.svelte$/i.test(abs) ? svelteVirtualName(abs) : abs);
100
+ }
101
+ function declarationSymbol(decl) {
102
+ if (import_typescript.default.isVariableDeclaration(decl)) {
103
+ if (enclosingFunction(decl) || !import_typescript.default.isIdentifier(decl.name))
104
+ return;
105
+ return decl.name.text;
106
+ }
107
+ if (import_typescript.default.isPropertyDeclaration(decl) || import_typescript.default.isMethodDeclaration(decl) || import_typescript.default.isGetAccessorDeclaration(decl)) {
108
+ const cls = decl.parent;
109
+ const name = propertyNameText(decl.name);
110
+ if (!name)
111
+ return;
112
+ if (import_typescript.default.isClassLike(cls)) {
113
+ if (enclosingFunction(cls))
114
+ return;
115
+ return cls.name ? `${cls.name.text}.${name}` : undefined;
116
+ }
117
+ }
118
+ if (import_typescript.default.isPropertyAssignment(decl) || import_typescript.default.isShorthandPropertyAssignment(decl) || import_typescript.default.isMethodDeclaration(decl) || import_typescript.default.isGetAccessorDeclaration(decl)) {
119
+ const name = propertyNameText(decl.name);
120
+ const object = decl.parent;
121
+ if (!name || !import_typescript.default.isObjectLiteralExpression(object))
122
+ return;
123
+ const holder = object.parent;
124
+ if (import_typescript.default.isVariableDeclaration(holder) || import_typescript.default.isPropertyAssignment(holder) || import_typescript.default.isPropertyDeclaration(holder)) {
125
+ const outer = declarationSymbol(holder);
126
+ return outer ? `${outer}.${name}` : undefined;
127
+ }
128
+ if (import_typescript.default.isAsExpression(holder) || import_typescript.default.isSatisfiesExpression(holder) || import_typescript.default.isParenthesizedExpression(holder)) {
129
+ const inner = holder.parent;
130
+ if (import_typescript.default.isVariableDeclaration(inner)) {
131
+ const outer = declarationSymbol(inner);
132
+ return outer ? `${outer}.${name}` : undefined;
133
+ }
134
+ }
135
+ }
136
+ if (import_typescript.default.isFunctionDeclaration(decl) && decl.name && !enclosingFunction(decl))
137
+ return decl.name.text;
138
+ return;
139
+ }
140
+ function collectPromptUnits(input) {
141
+ const { rootDir, ctx } = input;
142
+ const warnings = [];
143
+ const timings = {};
144
+ let clock = Date.now();
145
+ const pseudo = [];
146
+ for (const site of input.sites) {
147
+ if (!/\.[cm]?[jt]sx?$/.test(site.path))
148
+ continue;
149
+ site.promptArgs.forEach((arg, index) => {
150
+ const id = `${site.id}\x00${index}`;
151
+ pseudo.push({ site, role: roleOf(arg.role), pseudo: { ...site, id, promptArgs: [arg], toolsArg: undefined } });
152
+ });
153
+ }
154
+ const byId = new Map(pseudo.map((entry) => [entry.pseudo.id, entry]));
155
+ let refsBySite = new Map;
156
+ try {
157
+ const views = {};
158
+ for (const entry of pseudo)
159
+ views[entry.pseudo.id] = (site) => site === entry.pseudo;
160
+ const resolution = resolvePromptArguments(pseudo.map((entry) => entry.pseudo), ctx, { views, viewOf: (site) => site.id });
161
+ refsBySite = new Map(Object.entries(resolution.views ?? {}));
162
+ } catch (error) {
163
+ warnings.push(`prompt units: resolver failed (${error.message})`);
164
+ }
165
+ timings.resolve = Date.now() - clock;
166
+ clock = Date.now();
167
+ const units = new Map;
168
+ const add = (head, site, refs) => {
169
+ let unit = units.get(head.identity);
170
+ if (!unit) {
171
+ unit = {
172
+ key: head.key,
173
+ kind: head.kind,
174
+ path: head.path,
175
+ line: head.line,
176
+ endLine: head.endLine,
177
+ ...head.symbol ? { symbol: head.symbol } : {},
178
+ name: head.name,
179
+ roles: new Set,
180
+ sites: new Set,
181
+ identity: head.identity
182
+ };
183
+ units.set(head.identity, unit);
184
+ }
185
+ unit.roles.add(head.role);
186
+ unit.sites.add(site.id);
187
+ const flags = new Set(refs.flatMap((ref) => ref.via));
188
+ unit.via = unit.via ? new Set([...unit.via].filter((flag) => flags.has(flag))) : flags;
189
+ if (head.kind === "inline" && !unit.excerpt) {
190
+ const text = refs.map((ref) => ref.text).find((value) => value.trim().length > 0);
191
+ if (text)
192
+ unit.excerpt = text.replace(/\s+/g, " ").trim().slice(0, 80);
193
+ }
194
+ };
195
+ for (const [pseudoId, refs] of refsBySite) {
196
+ const entry = byId.get(pseudoId);
197
+ if (!entry || refs.length === 0)
198
+ continue;
199
+ const { site, role } = entry;
200
+ const models = refs.filter((ref) => ref.role !== "tool-description" && ref.role !== "tool-result");
201
+ if (models.length === 0)
202
+ continue;
203
+ let heads = [];
204
+ try {
205
+ const node = ctx.nodeAt(entry.pseudo.promptArgs[0].arg) ?? undefined;
206
+ const arg = entry.pseudo.promptArgs[0].arg;
207
+ if (node)
208
+ heads = new HeadFinder(rootDir, ctx, site).argument(node, arg.key ? arg.key.split(".") : [], role);
209
+ } catch (error) {
210
+ warnings.push(`prompt units ${site.id}: ${error.message}`);
211
+ }
212
+ const claimed = new Map;
213
+ const rest = [];
214
+ for (const ref of models) {
215
+ const owner = heads.length === 1 ? heads[0] : heads.find((head) => contains(head, ref));
216
+ if (owner) {
217
+ const list = claimed.get(owner) ?? [];
218
+ list.push(ref);
219
+ claimed.set(owner, list);
220
+ } else
221
+ rest.push(ref);
222
+ }
223
+ const members = [...claimed];
224
+ for (const ref of rest) {
225
+ const head = new HeadFinder(rootDir, ctx, site).fromHops(ref, heads[0]?.role ?? role);
226
+ if (head)
227
+ members.push([head, [ref]]);
228
+ }
229
+ if (heads.length === 0 && input.wrappedSinkIds?.has(site.id))
230
+ continue;
231
+ const call = members.some(([head]) => head.kind === "inline") ? new HeadFinder(rootDir, ctx, site).callFunction(role) : undefined;
232
+ for (const [head, list] of members) {
233
+ const target = head.kind === "inline" && call ? call : head;
234
+ add(target, site, list);
235
+ if (target !== head) {
236
+ const unit = units.get(target.identity);
237
+ unit.members ??= new Map;
238
+ unit.members.set(head.identity, { path: head.path, line: head.line, ...head.symbol ? { symbol: head.symbol } : {}, kind: head.kind });
239
+ }
240
+ }
241
+ }
242
+ timings.heads = Date.now() - clock;
243
+ return { units: [...units.values()], warnings, timings };
244
+ }
245
+ function contains(head, ref) {
246
+ if (ref.path === head.path && ref.line >= head.line && ref.line <= head.endLine)
247
+ return true;
248
+ return ref.hops.some((hop) => hop.path === head.path && hop.line === head.hopLine);
249
+ }
250
+
251
+ class HeadFinder {
252
+ rootDir;
253
+ ctx;
254
+ site;
255
+ constructor(rootDir, ctx, site) {
256
+ this.rootDir = rootDir;
257
+ this.ctx = ctx;
258
+ this.site = site;
259
+ }
260
+ callFunction(role) {
261
+ if (this.site.sdk === "think-hook")
262
+ return;
263
+ const sf = sourceFileOf(this.ctx, this.site.path);
264
+ if (!sf)
265
+ return;
266
+ const pos = sf.getPositionOfLineAndCharacter(Math.max(0, this.site.line - 1), Math.max(0, this.site.col - 1));
267
+ let node;
268
+ const visit2 = (current) => {
269
+ if (current.getStart(sf) <= pos && pos < current.getEnd()) {
270
+ node = current;
271
+ import_typescript.default.forEachChild(current, visit2);
272
+ }
273
+ };
274
+ visit2(sf);
275
+ if (!node)
276
+ return;
277
+ let outer;
278
+ for (let fn = isFunctionLike(node) ? node : enclosingFunction(node);fn; fn = enclosingFunction(fn)) {
279
+ if (functionName(fn))
280
+ return this.builder(fn, role, "call");
281
+ outer = fn;
282
+ }
283
+ if (!outer?.body)
284
+ return;
285
+ const path = relPath(this.rootDir, sf.fileName);
286
+ const scope = scopeOf(this.rootDir, outer.body);
287
+ const symbol = scope.includes("#") ? scope.slice(scope.indexOf("#") + 1) : `L${lineOf(outer)}`;
288
+ return {
289
+ kind: "call",
290
+ path,
291
+ line: lineOf(outer),
292
+ endLine: endLineOf(outer),
293
+ hopLine: lineOf(outer),
294
+ symbol,
295
+ name: symbol,
296
+ role,
297
+ identity: `${path}@${outer.getStart()}`,
298
+ key: `prompt:${path}#${symbol}`
299
+ };
300
+ }
301
+ argument(node, project, role) {
302
+ let current = node;
303
+ for (const field of project) {
304
+ const value = import_typescript.default.isExpression(current) ? this.objectField(unwrap(current), field) : undefined;
305
+ if (!value)
306
+ return [];
307
+ current = value;
308
+ }
309
+ if (role === "messages")
310
+ return this.messages(current, 0);
311
+ return this.heads(current, role, 0);
312
+ }
313
+ objectField(expr, field, depth = 0) {
314
+ if (depth > 3)
315
+ return;
316
+ if (import_typescript.default.isObjectLiteralExpression(expr)) {
317
+ for (const property of [...expr.properties].reverse()) {
318
+ if (propertyNameText(property.name) === field) {
319
+ if (import_typescript.default.isPropertyAssignment(property))
320
+ return property.initializer;
321
+ if (import_typescript.default.isShorthandPropertyAssignment(property))
322
+ return property.name;
323
+ }
324
+ }
325
+ return;
326
+ }
327
+ if (import_typescript.default.isIdentifier(expr)) {
328
+ const decl = this.declarationOf(expr);
329
+ if (decl && import_typescript.default.isVariableDeclaration(decl) && decl.initializer)
330
+ return this.objectField(unwrap(decl.initializer), field, depth + 1);
331
+ }
332
+ if (import_typescript.default.isCallExpression(expr) && expr.expression.getText() === "JSON.stringify" && expr.arguments[0])
333
+ return this.objectField(unwrap(expr.arguments[0]), field, depth + 1);
334
+ return;
335
+ }
336
+ checker(node) {
337
+ return this.ctx.checkerFor(relPath(this.rootDir, node.getSourceFile().fileName));
338
+ }
339
+ declarationOf(node) {
340
+ const checker = this.checker(node);
341
+ return checker ? valueDeclaration(resolvedSymbol(node, checker)) : undefined;
342
+ }
343
+ messages(node, depth) {
344
+ if (depth > MAX_DEPTH || !import_typescript.default.isExpression(node))
345
+ return [];
346
+ const expr = unwrap(node);
347
+ if (import_typescript.default.isIdentifier(expr)) {
348
+ const decl = this.declarationOf(expr);
349
+ if (decl && import_typescript.default.isVariableDeclaration(decl) && decl.initializer && inRepo(decl))
350
+ return this.messages(decl.initializer, depth + 1);
351
+ return [];
352
+ }
353
+ if (import_typescript.default.isArrayLiteralExpression(expr)) {
354
+ return expr.elements.flatMap((element) => import_typescript.default.isSpreadElement(element) ? this.messages(element.expression, depth + 1) : this.message(element, depth + 1));
355
+ }
356
+ if (import_typescript.default.isConditionalExpression(expr))
357
+ return [...this.messages(expr.whenTrue, depth + 1), ...this.messages(expr.whenFalse, depth + 1)];
358
+ return [];
359
+ }
360
+ message(node, depth) {
361
+ const expr = unwrap(node);
362
+ if (!import_typescript.default.isObjectLiteralExpression(expr))
363
+ return [];
364
+ const roleValue = this.objectField(expr, "role");
365
+ const roleText = roleValue && import_typescript.default.isStringLiteralLike(unwrap(roleValue)) ? unwrap(roleValue).text : undefined;
366
+ if (roleText && /^(?:assistant|tool|function|model)$/.test(roleText))
367
+ return [];
368
+ const role = roleText === "system" || roleText === "developer" ? "system" : roleText === "user" ? "user" : "messages";
369
+ const content = this.objectField(expr, "content");
370
+ if (!content)
371
+ return [];
372
+ const inner = unwrap(content);
373
+ if (import_typescript.default.isArrayLiteralExpression(inner)) {
374
+ return inner.elements.flatMap((part) => {
375
+ const text = import_typescript.default.isObjectLiteralExpression(unwrap(part)) ? this.objectField(unwrap(part), "text") : undefined;
376
+ return text ? this.heads(text, role, depth + 1) : [];
377
+ });
378
+ }
379
+ return this.heads(content, role, depth + 1);
380
+ }
381
+ heads(node, role, depth) {
382
+ if (depth > MAX_DEPTH)
383
+ return [];
384
+ if (isFunctionLike(node) || import_typescript.default.isFunctionDeclaration(node)) {
385
+ const head = this.builder(node, role);
386
+ return head ? [head] : [this.assembly(node, role)];
387
+ }
388
+ if (!import_typescript.default.isExpression(node))
389
+ return [];
390
+ const expr = unwrap(node);
391
+ if (import_typescript.default.isStringLiteralLike(expr) || import_typescript.default.isTemplateExpression(expr) || import_typescript.default.isArrayLiteralExpression(expr) || import_typescript.default.isBinaryExpression(expr) && expr.operatorToken.kind === import_typescript.default.SyntaxKind.PlusToken || import_typescript.default.isTaggedTemplateExpression(expr)) {
392
+ return [this.assembly(expr, role)];
393
+ }
394
+ if (import_typescript.default.isConditionalExpression(expr))
395
+ return [...this.heads(expr.whenTrue, role, depth + 1), ...this.heads(expr.whenFalse, role, depth + 1)];
396
+ if (import_typescript.default.isBinaryExpression(expr) && (expr.operatorToken.kind === import_typescript.default.SyntaxKind.QuestionQuestionToken || expr.operatorToken.kind === import_typescript.default.SyntaxKind.BarBarToken)) {
397
+ return [...this.heads(expr.left, role, depth + 1), ...this.heads(expr.right, role, depth + 1)];
398
+ }
399
+ if (import_typescript.default.isArrowFunction(expr) || import_typescript.default.isFunctionExpression(expr)) {
400
+ const head = this.builder(expr, role);
401
+ return head ? [head] : [this.assembly(expr, role)];
402
+ }
403
+ if (import_typescript.default.isIdentifier(expr) || import_typescript.default.isPropertyAccessExpression(expr)) {
404
+ if (import_typescript.default.isPropertyAccessExpression(expr) && /^(?:process\.env|import\.meta\.env)\b/.test(expr.getText()))
405
+ return [];
406
+ const decl = this.declarationOf(import_typescript.default.isPropertyAccessExpression(expr) ? expr.name : expr);
407
+ if (!decl || !inRepo(decl)) {
408
+ const file = import_typescript.default.isIdentifier(expr) ? this.textFile(expr) : undefined;
409
+ return file ? [file] : [];
410
+ }
411
+ return this.declarationHeads(decl, role, depth);
412
+ }
413
+ if (import_typescript.default.isCallExpression(expr)) {
414
+ const callee = unwrap(expr.expression);
415
+ if (import_typescript.default.isPropertyAccessExpression(callee)) {
416
+ const method = callee.name.text;
417
+ if (TEXT_METHODS.has(method))
418
+ return this.heads(callee.expression, role, depth + 1);
419
+ if (ASSEMBLY_METHODS.has(method))
420
+ return [this.assembly(expr, role)];
421
+ }
422
+ if (import_typescript.default.isIdentifier(callee) && TEXT_FUNCTIONS.test(callee.text))
423
+ return expr.arguments[0] ? this.heads(expr.arguments[0], role, depth + 1) : [];
424
+ const decl = this.declarationOf(import_typescript.default.isPropertyAccessExpression(callee) ? callee.name : callee);
425
+ const fn = decl ? this.functionOf(decl) : undefined;
426
+ if (fn && fn.body && inRepo(fn)) {
427
+ const head = this.builder(fn, role);
428
+ if (head)
429
+ return [head];
430
+ }
431
+ return [];
432
+ }
433
+ return [];
434
+ }
435
+ functionOf(decl) {
436
+ if (isFunctionLike(decl))
437
+ return decl;
438
+ if ((import_typescript.default.isVariableDeclaration(decl) || import_typescript.default.isPropertyAssignment(decl) || import_typescript.default.isPropertyDeclaration(decl)) && decl.initializer) {
439
+ const init = unwrap(decl.initializer);
440
+ if (import_typescript.default.isArrowFunction(init) || import_typescript.default.isFunctionExpression(init))
441
+ return init;
442
+ }
443
+ return;
444
+ }
445
+ declarationHeads(decl, role, depth) {
446
+ if (import_typescript.default.isParameter(decl) || import_typescript.default.isBindingElement(decl))
447
+ return [];
448
+ const fn = this.functionOf(decl);
449
+ if (fn) {
450
+ const head = this.builder(fn, role);
451
+ return head ? [head] : [];
452
+ }
453
+ const symbol = declarationSymbol(decl);
454
+ if (import_typescript.default.isVariableDeclaration(decl) && !symbol) {
455
+ if (!decl.initializer)
456
+ return [this.assembly(decl, role)];
457
+ const init = unwrap(decl.initializer);
458
+ const mutable = !(decl.parent.flags & import_typescript.default.NodeFlags.Const) || import_typescript.default.isArrayLiteralExpression(init);
459
+ return mutable ? [this.assembly(decl, role)] : this.heads(init, role, depth + 1);
460
+ }
461
+ if (!symbol)
462
+ return [];
463
+ if (import_typescript.default.isVariableDeclaration(decl) && decl.initializer) {
464
+ const init = unwrap(decl.initializer);
465
+ if (import_typescript.default.isCallExpression(init) && !this.isTextCall(init)) {
466
+ const heads = this.heads(init, role, depth + 1);
467
+ if (heads.length > 0)
468
+ return heads;
469
+ }
470
+ }
471
+ return [this.template(decl, symbol, role)];
472
+ }
473
+ isTextCall(call) {
474
+ const callee = unwrap(call.expression);
475
+ if (import_typescript.default.isIdentifier(callee))
476
+ return TEXT_FUNCTIONS.test(callee.text);
477
+ return import_typescript.default.isPropertyAccessExpression(callee) && (TEXT_METHODS.has(callee.name.text) || ASSEMBLY_METHODS.has(callee.name.text));
478
+ }
479
+ textFile(identifier) {
480
+ const checker = this.checker(identifier);
481
+ const clause = checker?.getSymbolAtLocation(identifier)?.declarations?.find(import_typescript.default.isImportClause);
482
+ const statement = clause?.parent;
483
+ if (!statement || !import_typescript.default.isStringLiteral(statement.moduleSpecifier))
484
+ return;
485
+ const spec = statement.moduleSpecifier.text;
486
+ if (!/\.(?:md|mdx|txt|prompt)(?:\?raw)?$/.test(spec) || !spec.startsWith("."))
487
+ return;
488
+ const path = relPath(this.rootDir, resolve(statement.getSourceFile().fileName, "..", spec.replace(/\?raw$/, "")));
489
+ return { kind: "file", path, line: 1, endLine: 1, hopLine: 1, name: path.split("/").pop() ?? path, role: "system", identity: `file:${path}`, key: `prompt:${path}` };
490
+ }
491
+ builder(fn, role, kind = "builder") {
492
+ if (!functionName(fn) || !inRepo(fn) || !fn.body)
493
+ return;
494
+ const scope = scopeOf(this.rootDir, fn.body);
495
+ const path = relPath(this.rootDir, fn.getSourceFile().fileName);
496
+ const holder = import_typescript.default.isArrowFunction(fn) || import_typescript.default.isFunctionExpression(fn) ? fn.parent : fn;
497
+ const symbol = scope.includes("#") ? scope.slice(scope.indexOf("#") + 1) : functionName(fn);
498
+ return {
499
+ kind,
500
+ path,
501
+ line: lineOf(holder),
502
+ endLine: endLineOf(fn),
503
+ hopLine: lineOf(fn),
504
+ symbol,
505
+ name: symbol,
506
+ role,
507
+ identity: `${path}@${fn.getStart()}`,
508
+ key: `prompt:${path}#${symbol}`
509
+ };
510
+ }
511
+ template(decl, symbol, role) {
512
+ const path = relPath(this.rootDir, decl.getSourceFile().fileName);
513
+ return {
514
+ kind: "template",
515
+ path,
516
+ line: lineOf(decl),
517
+ endLine: endLineOf(decl),
518
+ hopLine: lineOf(decl),
519
+ symbol,
520
+ name: symbol,
521
+ role,
522
+ identity: `${path}@${decl.getStart()}`,
523
+ key: `prompt:${path}#${symbol}`
524
+ };
525
+ }
526
+ assembly(node, role) {
527
+ for (let current = node;current && !import_typescript.default.isSourceFile(current); current = current.parent) {
528
+ if (isFunctionLike(current) || import_typescript.default.isFunctionDeclaration(current)) {
529
+ const fn = current;
530
+ if (fn.body && scopeOf(this.rootDir, fn.body) !== this.site.scope && functionName(fn)) {
531
+ const head = this.builder(fn, role);
532
+ if (head)
533
+ return head;
534
+ }
535
+ break;
536
+ }
537
+ if (current !== node && (import_typescript.default.isCallExpression(current) || import_typescript.default.isNewExpression(current)) && !(import_typescript.default.isCallExpression(current) && this.isTextCall(current)))
538
+ break;
539
+ if (import_typescript.default.isVariableDeclaration(current) || import_typescript.default.isPropertyDeclaration(current) || import_typescript.default.isPropertyAssignment(current)) {
540
+ const symbol = declarationSymbol(current);
541
+ if (symbol)
542
+ return this.template(current, symbol, role);
543
+ }
544
+ }
545
+ const path = relPath(this.rootDir, node.getSourceFile().fileName);
546
+ const scope = this.site.scope;
547
+ const owner = scope.includes("#") ? scope.slice(scope.indexOf("#") + 1) : scope.split("/").pop() ?? scope;
548
+ return {
549
+ kind: "inline",
550
+ path,
551
+ line: lineOf(node),
552
+ endLine: endLineOf(node),
553
+ hopLine: lineOf(node),
554
+ name: `${owner} ${role} prompt`,
555
+ role,
556
+ identity: `${path}@${node.getStart()}`,
557
+ key: `prompt:${scope}:${role}`
558
+ };
559
+ }
560
+ fromHops(ref, role) {
561
+ for (const hop of ref.hops) {
562
+ const builder = /^builder (\S+) called at /.exec(hop.reason);
563
+ const defined = / defined here$/.test(hop.reason) ? hop.reason.slice(0, -" defined here".length) : undefined;
564
+ if (!builder && (!defined || defined === "value"))
565
+ continue;
566
+ const sf2 = sourceFileOf(this.ctx, hop.path);
567
+ if (!sf2)
568
+ continue;
569
+ const name = builder ? builder[1] : defined;
570
+ const found = findDeclaration(sf2, hop.line, name);
571
+ if (!found)
572
+ continue;
573
+ const fn = isFunctionLike(found) ? found : this.functionOf(found);
574
+ if (fn) {
575
+ const head = this.builder(fn, role);
576
+ if (head)
577
+ return head;
578
+ continue;
579
+ }
580
+ const symbol = declarationSymbol(found);
581
+ if (symbol)
582
+ return this.template(found, symbol, role);
583
+ }
584
+ if (ref.kind === "file") {
585
+ return { kind: "file", path: ref.path, line: 1, endLine: 1, hopLine: 1, name: ref.path.split("/").pop() ?? ref.path, role, identity: `file:${ref.path}`, key: `prompt:${ref.path}` };
586
+ }
587
+ const sf = sourceFileOf(this.ctx, ref.path);
588
+ const anchor = sf ? nodeAtLine(sf, ref.line) : undefined;
589
+ if (anchor)
590
+ return this.assembly(anchor, role);
591
+ return;
592
+ }
593
+ }
594
+ function findDeclaration(sf, line, name) {
595
+ let found;
596
+ const start = sf.getPositionOfLineAndCharacter(Math.max(0, line - 1), 0);
597
+ const end = line < sf.getLineStarts().length ? sf.getPositionOfLineAndCharacter(line, 0) : sf.getEnd();
598
+ const visit2 = (node) => {
599
+ if (found || node.getEnd() < start || node.getStart(sf) > end)
600
+ return;
601
+ const named = import_typescript.default.isVariableDeclaration(node) || import_typescript.default.isPropertyAssignment(node) || import_typescript.default.isPropertyDeclaration(node) || import_typescript.default.isMethodDeclaration(node) || import_typescript.default.isFunctionDeclaration(node) || import_typescript.default.isGetAccessorDeclaration(node) || import_typescript.default.isShorthandPropertyAssignment(node) ? propertyNameText(node.name) : isFunctionLike(node) ? functionName(node) : undefined;
602
+ if (named === name && sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1 === line) {
603
+ found = node;
604
+ return;
605
+ }
606
+ import_typescript.default.forEachChild(node, visit2);
607
+ };
608
+ visit2(sf);
609
+ return found;
610
+ }
611
+ function nodeAtLine(sf, line) {
612
+ const start = sf.getPositionOfLineAndCharacter(Math.max(0, line - 1), 0);
613
+ let best;
614
+ const visit2 = (node) => {
615
+ if (best || node.getEnd() < start)
616
+ return;
617
+ const nodeLine = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
618
+ if (nodeLine === line && (import_typescript.default.isExpression(node) || import_typescript.default.isVariableDeclaration(node) || import_typescript.default.isPropertyAssignment(node))) {
619
+ best = node;
620
+ return;
621
+ }
622
+ if (nodeLine > line)
623
+ return;
624
+ import_typescript.default.forEachChild(node, visit2);
625
+ };
626
+ visit2(sf);
627
+ return best;
628
+ }
629
+
630
+ // src/harness-extract/skeleton/edges.ts
631
+ function skeletonEdgeId(edge) {
632
+ return `${edge.type}:${edge.from}->${edge.to}`;
633
+ }
634
+ function encodeSkeletonEdges(edges) {
635
+ const sorted = [...edges].sort((a, b) => skeletonEdgeId(a).localeCompare(skeletonEdgeId(b)));
636
+ const types = [...new Set(sorted.map((edge) => edge.type))].sort();
637
+ const nodes = [...new Set(sorted.flatMap((edge) => [edge.from, edge.to]))].sort();
638
+ const typeIndex = new Map(types.map((type, index) => [type, index]));
639
+ const nodeIndex = new Map(nodes.map((node, index) => [node, index]));
640
+ const rows = [];
641
+ const extra = {};
642
+ sorted.forEach((edge, row) => {
643
+ rows.push(typeIndex.get(edge.type), nodeIndex.get(edge.from), nodeIndex.get(edge.to));
644
+ const { id: _id, type: _type, from: _from, to: _to, source, evidence, ...rest } = edge;
645
+ const fields = { ...rest };
646
+ if (source !== "code")
647
+ fields.source = source;
648
+ if (evidence.length)
649
+ fields.evidence = evidence;
650
+ if (Object.keys(fields).length)
651
+ extra[String(row)] = fields;
652
+ });
653
+ return { types, nodes, rows, ...Object.keys(extra).length ? { extra } : {} };
654
+ }
655
+ function decodeSkeletonEdges(table) {
656
+ if (!table)
657
+ return [];
658
+ const edges = [];
659
+ for (let row = 0;row * 3 < table.rows.length; row += 1) {
660
+ const type = table.types[table.rows[row * 3]];
661
+ const from = table.nodes[table.rows[row * 3 + 1]], to = table.nodes[table.rows[row * 3 + 2]];
662
+ const { source, evidence, ...rest } = table.extra?.[String(row)] ?? {};
663
+ edges.push({ id: skeletonEdgeId({ type, from, to }), type, from, to, source: source ?? "code", evidence: evidence ?? [], ...rest });
664
+ }
665
+ return edges;
666
+ }
667
+
668
+ // src/harness-extract/skeleton/types.ts
669
+ var SKELETON_VERSION = "harness-skeleton/v1";
670
+ var SKELETON_DIFF_VERSION = "harness-skeleton-diff/v1";
671
+ // src/harness-extract/skeleton/diff.ts
672
+ import { isDeepStrictEqual } from "node:util";
673
+ var COMPONENTS = ["agent", "entrypoint", "call_site", "prompt", "tool", "model", "skill", "eval", "edge", "control_flow"];
674
+ var FIELDS = {
675
+ agent: ["entry", "liveKey"],
676
+ entrypoint: ["name", "trigger"],
677
+ call_site: ["agentIds", "modelIds", "modelSource", "promptIds", "toolIds", "viaHelper"],
678
+ prompt: ["unitKind", "roles", "usedByAgentIds"],
679
+ tool: ["usedByAgentIds", "schema.path", "implementation.path"],
680
+ model: ["usedByAgentIds", "resolution"],
681
+ skill: ["name", "liveKey", "skillKind", "usedByAgentIds"],
682
+ eval: ["usedByAgentIds", "covers"],
683
+ edge: [],
684
+ control_flow: ["kind", "actsOn", "condition"]
685
+ };
686
+ function components(graph) {
687
+ const map = new Map(COMPONENTS.map((kind) => [kind, []]));
688
+ const add = (kind, value) => {
689
+ map.get(kind).push(value);
690
+ };
691
+ for (const agent of graph.runtimeAgents)
692
+ add("agent", agent);
693
+ for (const artifact of graph.artifacts)
694
+ add(artifact.type === "model_config" ? "model" : artifact.type, artifact);
695
+ for (const call of graph.skeleton.callSites)
696
+ add("call_site", { ...call, name: call.callee });
697
+ for (const item of graph.skeleton.controlFlow)
698
+ add("control_flow", { ...item, name: `${item.kind}:${item.scope}` });
699
+ for (const edge of decodeSkeletonEdges(graph.skeleton.edges))
700
+ add("edge", { ...edge, name: `${edge.type}: ${edge.from} -> ${edge.to}` });
701
+ return map;
702
+ }
703
+ function at(item) {
704
+ return item.sourcePath ? { path: item.sourcePath, line: item.line ?? 1 } : undefined;
705
+ }
706
+ function field(item, path) {
707
+ let value = item;
708
+ for (const key of path.split("."))
709
+ value = value && typeof value === "object" ? value[key] : undefined;
710
+ return value ?? null;
711
+ }
712
+ function moveKey(item, kind) {
713
+ return JSON.stringify([item.name, kind === "prompt" ? item.unitKind : null]);
714
+ }
715
+ function diffHarnessSkeletons(before, after) {
716
+ const left = components(before), right = components(after);
717
+ const changes = [];
718
+ const summary = Object.fromEntries(COMPONENTS.map((kind) => [kind, { added: 0, removed: 0, moved: 0, changed: 0 }]));
719
+ const emit = (change) => {
720
+ changes.push(change);
721
+ summary[change.component][change.change]++;
722
+ };
723
+ const compare = (a, b, kind) => {
724
+ if (a.sourcePath !== b.sourcePath)
725
+ emit({ change: "moved", component: kind, id: b.id, ...a.id !== b.id ? { fromId: a.id } : {}, name: b.name, from: at(a), to: at(b) });
726
+ const fields = FIELDS[kind].filter((key) => !isDeepStrictEqual(field(a, key), field(b, key))).map((key) => ({ field: key, from: field(a, key), to: field(b, key) }));
727
+ if (fields.length)
728
+ emit({ change: "changed", component: kind, id: b.id, name: b.name, from: at(a), to: at(b), fields });
729
+ };
730
+ for (const kind of COMPONENTS) {
731
+ const old = new Map(left.get(kind).map((item) => [item.id, item])), next = new Map(right.get(kind).map((item) => [item.id, item]));
732
+ for (const [id, item] of old)
733
+ if (next.has(id)) {
734
+ compare(item, next.get(id), kind);
735
+ old.delete(id);
736
+ next.delete(id);
737
+ }
738
+ for (const [id, item] of old) {
739
+ const key = moveKey(item, kind);
740
+ const from = [...old.values()].filter((candidate) => moveKey(candidate, kind) === key);
741
+ const to = [...next.values()].filter((candidate) => moveKey(candidate, kind) === key);
742
+ if (from.length !== 1 || to.length !== 1 || item.sourcePath === to[0].sourcePath)
743
+ continue;
744
+ compare(item, to[0], kind);
745
+ old.delete(id);
746
+ next.delete(to[0].id);
747
+ }
748
+ for (const item of old.values())
749
+ emit({ change: "removed", component: kind, id: item.id, name: item.name, from: at(item) });
750
+ for (const item of next.values())
751
+ emit({ change: "added", component: kind, id: item.id, name: item.name, to: at(item) });
752
+ }
753
+ changes.sort((a, b) => a.component.localeCompare(b.component) || a.change.localeCompare(b.change) || a.id.localeCompare(b.id));
754
+ return {
755
+ version: SKELETON_DIFF_VERSION,
756
+ from: { commit: before.skeleton.commit, contentHash: before.skeleton.contentHash },
757
+ to: { commit: after.skeleton.commit, contentHash: after.skeleton.contentHash },
758
+ identical: before.skeleton.contentHash === after.skeleton.contentHash,
759
+ summary,
760
+ changes
761
+ };
762
+ }
763
+
764
+ // src/harness-extract/skeleton/index.ts
765
+ var controlFlowDetector;
766
+ var COVERAGE = {
767
+ agents: "complete",
768
+ workflows: "complete",
769
+ tools: "complete",
770
+ skills: "complete",
771
+ evalFiles: "partial",
772
+ models: "partial",
773
+ modelConfig: "partial",
774
+ evalSuites: "partial",
775
+ subagentEdges: "partial",
776
+ prompts: "partial",
777
+ controlFlow: "partial"
778
+ };
779
+ var KNOWN_GAPS = {
780
+ prompts: ["text that reaches an agent as a chat message from a client (no model call in its file)", "templates selected from a table by a runtime key", "model calls in Svelte components (the prompt resolver reads JavaScript and TypeScript)"],
781
+ subagentEdges: ["dispatch through a runner object built by a host and passed in as data", "logical initiators (a component that asks the host to start a sub-agent)"],
782
+ models: ["model ids in flag or alias tables read with a computed key (calls list them as modelCandidates)", "rate-card and compatibility ids no call uses"],
783
+ tools: ["tool sets passed through a shared helper whose tools parameter is not traced to its callers (a host that calls the helper is credited with the registry)"],
784
+ modelConfig: ["configuration rows outside model call arguments (CI workflows, tables)"],
785
+ evalSuites: ["suites grouped by runner rather than directory"],
786
+ controlFlow: ["filled by the registered control-flow detector only"]
787
+ };
788
+ function agentKey(name) {
789
+ return name.replace(/\s*\([^)]*\)\s*$/, "").toLowerCase().replace(/[^a-z0-9]/g, "");
790
+ }
791
+ function toolKey(name) {
792
+ return name.replace(/\s*\([^)]*\)\s*$/, "").trim();
793
+ }
794
+ function skillKey(name) {
795
+ return name.trim().toLowerCase();
796
+ }
797
+ function modelKey(id) {
798
+ return id.trim().toLowerCase().replace(/^[a-z0-9-]+[/:]/, "").replace(/[._]/g, "-").replace(/-(?:low|medium|high|xhigh|max|minimal|none|fast|thinking)$/, "").replace(/-(?:latest|\d{8}|\d{4}-\d{2}-\d{2})$/, "");
799
+ }
800
+ function canonicalJson(value) {
801
+ return JSON.stringify(value, (_key, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))) : item);
802
+ }
803
+ var ROUTE_METHOD = /^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/;
804
+ var sorted = (values) => [...new Set(values)].sort();
805
+ var byId = (a, b) => a.id.localeCompare(b.id);
806
+ var staticStatus = (status) => status === "reachable" || status === "in_prod" ? "reachable" : "defined_only";
807
+ var location = (node, ctx) => ({ path: relPath(ctx.rootDir, node.getSourceFile().fileName), line: lineOf(node) });
808
+ function setOwners(item, ids, global = false) {
809
+ delete item.ownedByAgentId;
810
+ Object.assign(item, owned(ids, global));
811
+ }
812
+ function owned(ids, global = false) {
813
+ const users = sorted(ids);
814
+ return {
815
+ scope: users.length === 1 ? "agent" : global && users.length === 0 ? "global" : "shared",
816
+ usedByAgentIds: users,
817
+ ...users.length === 1 ? { ownedByAgentId: users[0] } : {}
818
+ };
819
+ }
820
+ function git(root, args) {
821
+ try {
822
+ return execFileSync("git", ["-C", root, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
823
+ } catch {
824
+ return;
825
+ }
826
+ }
827
+ function repoName(root) {
828
+ try {
829
+ const pkg = JSON.parse(readFileSync(resolve2(root, "package.json"), "utf8"));
830
+ if (typeof pkg.name === "string")
831
+ return pkg.name;
832
+ } catch {}
833
+ try {
834
+ const project = parse(readFileSync(resolve2(root, "pyproject.toml"), "utf8")).project;
835
+ if (project && typeof project === "object" && typeof project.name === "string")
836
+ return project.name;
837
+ } catch {}
838
+ return basename(root);
839
+ }
840
+ function buildHarnessSkeleton(rootDir, options = {}) {
841
+ const start = Date.now();
842
+ const { result, ctx } = extractHarnessWithContext(resolve2(rootDir), { ...options, tracePrompts: false });
843
+ options.onTiming?.({ phase: "extract", durationMs: Date.now() - start });
844
+ for (const [phase, durationMs] of Object.entries(result.timings))
845
+ options.onTiming?.({ phase: `extract.${phase}`, durationMs });
846
+ const graph = skeletonFromExtract(result, ctx, options);
847
+ graph.skeleton.durationMs = Date.now() - start;
848
+ return graph;
849
+ }
850
+ function skeletonFromExtract(result, ctx, options = {}) {
851
+ const start = Date.now();
852
+ let clock = start;
853
+ const mark = (phase) => {
854
+ const now = Date.now();
855
+ options.onTiming?.({ phase: `build.${phase}`, durationMs: now - clock });
856
+ clock = now;
857
+ };
858
+ const repo = enumerationRepository(ctx);
859
+ const enumeration = enumerateAgents(result, ctx);
860
+ const agentForScope = enumeration.agentForScope;
861
+ const warnings = [...result.warnings];
862
+ const sites = result.callSites.filter((site) => !site.isTest).sort((a, b) => a.path.localeCompare(b.path) || a.line - b.line || a.col - b.col);
863
+ const ordinals = new Map;
864
+ const unique = (key) => {
865
+ const n = (ordinals.get(key) ?? 0) + 1;
866
+ ordinals.set(key, n);
867
+ return n === 1 ? key : `${key}~${n}`;
868
+ };
869
+ const callIds = new Map(sites.map((site) => [site.id, unique(`call:${site.scope}:${site.callee}`)]));
870
+ const callSites = sites.map((site) => ({
871
+ id: callIds.get(site.id),
872
+ sourcePath: site.path,
873
+ line: site.line,
874
+ col: site.col,
875
+ scope: site.scope,
876
+ callee: site.callee,
877
+ sdk: site.sdk,
878
+ ...site.provider ? { provider: site.provider } : {},
879
+ ...site.viaHelper && callIds.has(site.viaHelper) ? { viaHelper: callIds.get(site.viaHelper) } : {},
880
+ agentIds: sorted(agentForScope(site.scope)),
881
+ modelIds: [],
882
+ promptIds: [],
883
+ toolIds: [],
884
+ status: staticStatus(site.status),
885
+ ...site.isEval ? { isEval: true } : {}
886
+ }));
887
+ const callByExtractId = new Map(sites.map((site, i) => [site.id, callSites[i]]));
888
+ const agents = enumeration.runtimeAgents.map((agent) => {
889
+ const evidence = agent.evidence[0];
890
+ const path = evidence?.path ?? ".";
891
+ const line = evidence?.line ?? 1;
892
+ const file = repo.files.find((file2) => file2.path === path);
893
+ let anchor;
894
+ if (file)
895
+ visit(file.source, (node) => {
896
+ if (!anchor && (import_typescript2.default.isClassLike(node) || import_typescript2.default.isFunctionDeclaration(node) || import_typescript2.default.isArrowFunction(node) || import_typescript2.default.isMethodDeclaration(node)) && lineOf(node) === line)
897
+ anchor = node;
898
+ });
899
+ const method = ROUTE_METHOD.exec(agent.name)?.[1];
900
+ if (file && method) {
901
+ let handler;
902
+ visit(file.source, (node) => {
903
+ if (!handler && isFunctionLike(node) && functionName(node) === method && lineOf(node) <= line && line <= node.getSourceFile().getLineAndCharacterOfPosition(node.getEnd()).line + 1)
904
+ handler = node;
905
+ });
906
+ if (handler) {
907
+ anchor = import_typescript2.default.isVariableDeclaration(handler.parent) ? handler.parent : handler;
908
+ }
909
+ }
910
+ const cls = anchor && import_typescript2.default.isClassLike(anchor) ? anchor : undefined;
911
+ const symbol = cls?.name?.text ?? agent.name;
912
+ const base = cls?.heritageClauses?.flatMap((clause) => clause.types)[0]?.expression.getText();
913
+ return {
914
+ ...agent,
915
+ root: ".",
916
+ status: staticStatus(agent.status),
917
+ evidence: agent.evidence.slice(0, 3),
918
+ sourcePath: path,
919
+ line: anchor ? lineOf(anchor) : line,
920
+ endLine: anchor ? anchor.getSourceFile().getLineAndCharacterOfPosition(anchor.getEnd()).line + 1 : line,
921
+ entry: { kind: cls ? "class" : ROUTE_METHOD.test(agent.name) ? "route" : "function", symbol, ...base ? { base } : {} },
922
+ callSiteIds: callSites.filter((site) => site.agentIds.includes(agent.id)).map((site) => site.id).sort(),
923
+ artifactIds: [],
924
+ entrypointIds: []
925
+ };
926
+ });
927
+ mark("agents");
928
+ const artifacts = [];
929
+ const prompts = collectPromptUnits({ rootDir: ctx.rootDir, ctx, sites, wrappedSinkIds: new Set(Object.values(result.wrapperSinks).map((wrapper) => wrapper.sinkId)) });
930
+ warnings.push(...prompts.warnings);
931
+ for (const [phase, durationMs] of Object.entries(prompts.timings))
932
+ options.onTiming?.({ phase: `build.prompts.${phase}`, durationMs });
933
+ for (const unit of prompts.units.sort((a, b) => a.path.localeCompare(b.path) || a.line - b.line || a.identity.localeCompare(b.identity))) {
934
+ const id = unique(unit.key);
935
+ const calls = [...unit.sites].flatMap((id2) => callByExtractId.get(id2) ?? []);
936
+ artifacts.push({
937
+ id,
938
+ type: "prompt",
939
+ name: unit.name,
940
+ sourcePath: unit.path,
941
+ line: unit.line,
942
+ endLine: unit.endLine,
943
+ unitKind: unit.kind,
944
+ ...unit.symbol ? { symbol: unit.symbol } : {},
945
+ roles: [...unit.roles].sort(),
946
+ callSiteIds: calls.map((call) => call.id).sort(),
947
+ ...unit.via?.size ? { via: [...unit.via].sort() } : {},
948
+ ...unit.excerpt ? { excerpt: unit.excerpt.slice(0, 80) } : {},
949
+ ...unit.members?.size ? { members: [...unit.members.values()].sort((a, b) => a.path.localeCompare(b.path) || a.line - b.line).slice(0, 20) } : {},
950
+ ...owned(calls.flatMap((call) => call.agentIds)),
951
+ status: repo.status(unit.path),
952
+ confidence: 0.9,
953
+ evidence: [{ path: unit.path, line: unit.line, reason: `skeleton prompt-unit ${unit.kind}` }]
954
+ });
955
+ for (const call of calls)
956
+ call.promptIds.push(id);
957
+ }
958
+ mark("prompts");
959
+ const tools = emitToolsCached(result, ctx);
960
+ const emittedTools = toolArtifacts(tools, agents);
961
+ for (let i = 0;i < tools.length; i++) {
962
+ const tool = tools[i], emitted = emittedTools[i];
963
+ const definition = tool.definitions[0];
964
+ const node = definition && ctx.nodeAt(definition);
965
+ const details = node ? toolLocations(node, ctx) : { schema: null, implementation: null };
966
+ artifacts.push({
967
+ id: emitted.id,
968
+ type: "tool",
969
+ name: tool.name,
970
+ ...owned(emitted.usedByAgentIds),
971
+ sourcePath: definition?.path ?? tool.evidence[0]?.path,
972
+ line: definition?.line ?? tool.evidence[0]?.line,
973
+ ...definition ? { definition: { path: definition.path, line: definition.line } } : {},
974
+ ...tool.definitions.length > 1 ? { definitionCount: tool.definitions.length } : {},
975
+ ...details,
976
+ via: tool.via,
977
+ status: tool.status,
978
+ confidence: emitted.confidence,
979
+ evidence: emitted.evidence.slice(0, 3)
980
+ });
981
+ }
982
+ const catalog = enumerateToolCatalog(result, ctx);
983
+ warnings.push(...catalog.warnings);
984
+ for (const tool of catalog.artifacts) {
985
+ if (artifacts.some((item) => item.type === "tool" && item.name === tool.name && item.sourcePath === tool.sourcePath))
986
+ continue;
987
+ artifacts.push({
988
+ id: tool.id,
989
+ type: "tool",
990
+ name: tool.name,
991
+ sourcePath: tool.sourcePath,
992
+ line: tool.evidence[0]?.line,
993
+ ...owned(tool.usedByAgentIds),
994
+ confidence: tool.confidence,
995
+ status: staticStatus(tool.status),
996
+ evidence: tool.evidence.slice(0, 3),
997
+ via: "catalog",
998
+ schema: null,
999
+ implementation: null
1000
+ });
1001
+ }
1002
+ for (const role of result.roles.filter((role2) => role2.role === "tools" && !role2.sink.isTest)) {
1003
+ const call = callByExtractId.get(role.sink.id);
1004
+ if (!call)
1005
+ continue;
1006
+ for (const hit of toolNamesOf(role.tree).names) {
1007
+ const candidates = artifacts.filter((item) => item.type === "tool" && item.name === hit.name);
1008
+ const exact = candidates.filter((item) => tools.find((tool) => tool.id === item.id)?.definitions.some((definition) => definition.path === hit.path && (hit.start === undefined || hit.start >= definition.start && hit.start <= definition.end)));
1009
+ const matches = exact.length ? exact : candidates.length === 1 ? candidates : candidates.filter((item) => item.usedByAgentIds.some((id) => call.agentIds.includes(id)));
1010
+ for (const tool of matches) {
1011
+ call.toolIds.push(tool.id);
1012
+ setOwners(tool, [...tool.usedByAgentIds, ...call.agentIds]);
1013
+ }
1014
+ }
1015
+ }
1016
+ const dynamicTools = new Set(result.roles.filter((role) => role.role === "tools" && (role.sink.sdk === "think-hook" || toolNamesOf(role.tree).unresolved.length > 0)).map((role) => role.sink.id));
1017
+ const forwardsTools = new Set(Object.values(result.wrapperSinks).filter((wrapper) => wrapper.roles.some((role) => role.role === "tools")).map((wrapper) => wrapper.sinkId));
1018
+ const receivingSites = sites.filter((site) => site.toolsArg && !forwardsTools.has(site.id) && dynamicTools.has(site.id));
1019
+ const offeredTo = new Map;
1020
+ for (const call of callSites)
1021
+ for (const id of call.toolIds)
1022
+ for (const agent of call.agentIds)
1023
+ offeredTo.set(id, (offeredTo.get(id) ?? new Set).add(agent));
1024
+ const receivers = new Set(receivingSites.flatMap((site) => agentForScope(site.scope)));
1025
+ for (const item of artifacts)
1026
+ if (item.type === "tool")
1027
+ setOwners(item, item.usedByAgentIds.filter((id) => receivers.has(id) || offeredTo.get(item.id)?.has(id)));
1028
+ mark("tools");
1029
+ let modelResult = result;
1030
+ if (result.models?.some((model) => model.calledFrom.length >= 50)) {
1031
+ modelResult = { ...result, warnings: [] };
1032
+ collectModels(modelResult, ctx, { maxCalledFrom: Math.max(1, result.callSites.length) });
1033
+ warnings.push(...modelResult.warnings);
1034
+ }
1035
+ const models = modelArtifacts(modelResult);
1036
+ const reached = new Map;
1037
+ const extracted = modelResult.models ?? [];
1038
+ extracted.forEach((model, i) => {
1039
+ for (const caller of model.calledFrom)
1040
+ reached.set(caller.callSite, [...reached.get(caller.callSite) ?? [], i]);
1041
+ });
1042
+ const computed = new Set([...reached].filter(([, list]) => list.length >= 3 && list.some((i) => extracted[i].resolution === "oneOf")).map(([id]) => id));
1043
+ for (const id of computed) {
1044
+ const call = callByExtractId.get(id);
1045
+ if (!call)
1046
+ continue;
1047
+ call.modelSource = "factory (computed)";
1048
+ call.modelCandidates = sorted(reached.get(id).map((i) => models[i].id));
1049
+ for (const agent of agents)
1050
+ if (call.agentIds.includes(agent.id))
1051
+ (agent.modelChoices ??= []).push({ callSiteId: call.id, sourcePath: call.sourcePath, line: call.line, source: "computed", candidateIds: call.modelCandidates });
1052
+ }
1053
+ for (let i = 0;i < models.length; i++) {
1054
+ const model = extracted[i], emitted = models[i];
1055
+ const calls = model.calledFrom.filter((caller) => !computed.has(caller.callSite)).flatMap((caller) => callByExtractId.get(caller.callSite) ?? []);
1056
+ artifacts.push({
1057
+ id: emitted.id,
1058
+ type: "model_config",
1059
+ name: emitted.name,
1060
+ sourcePath: model.definition.path,
1061
+ line: model.definition.line,
1062
+ resolution: model.resolution,
1063
+ configLocations: (model.otherDefinitions ?? []).slice(0, 10),
1064
+ callSiteIds: sorted(calls.map((call) => call.id)),
1065
+ ...owned(calls.flatMap((call) => call.agentIds)),
1066
+ confidence: emitted.confidence,
1067
+ evidence: emitted.evidence.slice(0, 3),
1068
+ status: repo.status(model.definition.path)
1069
+ });
1070
+ for (const call of calls)
1071
+ call.modelIds.push(emitted.id);
1072
+ }
1073
+ for (const unresolved of result.unresolvedModels ?? []) {
1074
+ const call = callByExtractId.get(unresolved.callSite);
1075
+ if (call && !call.modelSource)
1076
+ call.modelSource = unresolved.source;
1077
+ }
1078
+ mark("models");
1079
+ const workflows = enumerateWorkflows(result, ctx);
1080
+ warnings.push(...workflows.warnings);
1081
+ for (const entry of workflows.artifacts) {
1082
+ const meta = entry;
1083
+ const users = agentForScope(`${entry.sourcePath}#${entry.name}`);
1084
+ artifacts.push({
1085
+ id: entry.id,
1086
+ type: "entrypoint",
1087
+ name: entry.name,
1088
+ sourcePath: entry.sourcePath,
1089
+ line: entry.evidence[0]?.line ?? 1,
1090
+ ...meta.componentKind ? { componentKind: meta.componentKind } : {},
1091
+ ...meta.trigger ? { trigger: meta.trigger } : {},
1092
+ ...owned(users),
1093
+ confidence: entry.confidence,
1094
+ evidence: entry.evidence.slice(0, 3),
1095
+ status: staticStatus(entry.status)
1096
+ });
1097
+ for (const agent of agents.filter((agent2) => users.includes(agent2.id)))
1098
+ agent.entrypointIds.push(entry.id);
1099
+ }
1100
+ collectTriggers({ ctx, agents, artifacts, enumeration, handoffs: result.handoffs ?? [], status: (path) => staticStatus(repo.status(path)) });
1101
+ mark("workflows");
1102
+ for (const path of import_fast_glob.default.sync(["**/SKILL.md", "**/skills/**/*.md"], {
1103
+ cwd: ctx.rootDir,
1104
+ dot: false,
1105
+ onlyFiles: true,
1106
+ followSymbolicLinks: false,
1107
+ ignore: ["**/node_modules/**", "**/.git/**", "**/.claude/worktrees/**", "**/dist/**", "**/vendor/**", "**/test/**", "**/tests/**", "**/fixtures/**"]
1108
+ }).sort()) {
1109
+ const text = readFileSync(resolve2(ctx.rootDir, path), "utf8");
1110
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text)?.[1];
1111
+ const name2 = frontmatter && /^name:\s*['"]?(.+?)['"]?\s*$/m.exec(frontmatter)?.[1];
1112
+ if (basename(path) !== "SKILL.md" && !(name2 && /^description:/m.test(frontmatter ?? "")))
1113
+ continue;
1114
+ const users = repo.files.filter((file) => !TEST_PATH.test(file.path) && (file.text.includes(path) || file.text.includes(dirname(path)))).flatMap((file) => {
1115
+ const ids = [];
1116
+ visit(file.source, (node) => {
1117
+ if (import_typescript2.default.isStringLiteralLike(node) && (node.text.includes(path) || node.text === dirname(path)))
1118
+ ids.push(...agentForScope(scopeOf(ctx.rootDir, node)));
1119
+ });
1120
+ return ids;
1121
+ });
1122
+ artifacts.push({
1123
+ id: `skill:${path}`,
1124
+ type: "skill",
1125
+ name: name2 ?? basename(dirname(path)),
1126
+ sourcePath: path,
1127
+ line: 1,
1128
+ skillKind: basename(path) === "SKILL.md" ? "skill_md" : "frontmatter",
1129
+ ...owned(users, true),
1130
+ confidence: 1,
1131
+ evidence: [{ path, line: 1, reason: "skeleton skill file" }],
1132
+ status: repo.status(path)
1133
+ });
1134
+ }
1135
+ for (const entry of markdownCatalogs(ctx)) {
1136
+ if (artifacts.some((item) => item.type === "skill" && item.sourcePath === entry.path))
1137
+ continue;
1138
+ artifacts.push({
1139
+ id: `skill:${entry.path}`,
1140
+ type: "skill",
1141
+ name: entry.name,
1142
+ sourcePath: entry.path,
1143
+ line: 1,
1144
+ skillKind: "catalog",
1145
+ ...owned([], true),
1146
+ confidence: 0.8,
1147
+ evidence: [{ path: entry.path, line: 1, reason: "skeleton skill file" }, { path: entry.catalogPath, line: entry.catalogLine, reason: "skeleton markdown catalog entry" }],
1148
+ status: repo.status(entry.catalogPath)
1149
+ });
1150
+ }
1151
+ mark("skills");
1152
+ const relationships = [];
1153
+ const edgeIds = new Set;
1154
+ const addEdge = (edge) => {
1155
+ const id = `${edge.type}:${edge.from}->${edge.to}`;
1156
+ if (!edgeIds.has(id)) {
1157
+ edgeIds.add(id);
1158
+ relationships.push({ id, source: "code", ...edge });
1159
+ }
1160
+ };
1161
+ for (const handoff of result.handoffs ?? []) {
1162
+ const scoped = agentForScope(handoff.scope);
1163
+ const parents = scoped.length ? scoped : agents.filter((agent) => handoff.owners.some((owner) => owner.path === agent.sourcePath && owner.name === agent.entry.symbol)).map((agent) => agent.id);
1164
+ for (const target of handoff.targets)
1165
+ for (const from of parents) {
1166
+ const child = agents.find((agent) => agent.sourcePath === target.path && agent.entry.symbol === target.name);
1167
+ if (child && child.id !== from)
1168
+ addEdge({
1169
+ type: "invokes_subagent",
1170
+ from,
1171
+ to: child.id,
1172
+ sourcePath: handoff.path,
1173
+ line: handoff.line,
1174
+ via: handoff.callee,
1175
+ evidence: [{ path: handoff.path, line: handoff.line, reason: "skeleton subagent class call" }]
1176
+ });
1177
+ }
1178
+ }
1179
+ collectDeclaredHandoffs(ctx, agents, agentForScope, addEdge);
1180
+ mark("handoffs");
1181
+ const suites = new Map;
1182
+ for (const file of repo.files)
1183
+ if (evalFileSignal(file.path, file.text)) {
1184
+ const key = evalSuiteKey(file.path);
1185
+ suites.set(key, [...suites.get(key) ?? [], file]);
1186
+ }
1187
+ const importsByPath = new Map;
1188
+ for (const [key, members] of suites) {
1189
+ const runner = [...members].sort((a, b) => Number(!/(?:^|\/)(?:run|runner|index|main|eval)\.[^.]+$/.test(a.path)) - Number(!/(?:^|\/)(?:run|runner|index|main|eval)\.[^.]+$/.test(b.path)) || a.path.localeCompare(b.path))[0];
1190
+ const paths = importClosure(members.map((file) => file.path), ctx, importsByPath, new Set(members.map((file) => file.path)));
1191
+ const drivesWorker = members.some((file) => /from\s+['"]cloudflare:(?:workers|test)['"]/.test(file.text) && /\b(?:exports|SELF)\b/.test(file.text));
1192
+ const configs = [...new Set(agents.flatMap((agent) => agent.entrypointIds).map((id2) => /^binding:((?:[^#]*\/)?wrangler\.(?:jsonc?|toml))#/.exec(id2)?.[1]).filter((path) => Boolean(path)))];
1193
+ const worker = configs.filter((path) => {
1194
+ const dir = path.includes("/") ? path.slice(0, path.lastIndexOf("/") + 1) : "";
1195
+ return runner.path.startsWith(dir);
1196
+ }).sort((a, b) => b.length - a.length)[0];
1197
+ const workerAgents = drivesWorker && worker ? agents.filter((agent) => agent.entrypointIds.some((id2) => id2.startsWith(`binding:${worker}#`))).map((agent) => agent.id) : [];
1198
+ const coveredAgents = agents.filter((agent) => paths.has(agent.sourcePath) || workerAgents.includes(agent.id));
1199
+ const covered = artifacts.filter((item) => item.sourcePath && paths.has(item.sourcePath));
1200
+ const evalCalls = callSites.filter((call) => members.some((file) => file.path === call.sourcePath));
1201
+ const covers = {
1202
+ agentIds: coveredAgents.map((agent) => agent.id).sort(),
1203
+ promptIds: sorted([...covered.filter((item) => item.type === "prompt").map((item) => item.id), ...evalCalls.flatMap((call) => call.promptIds)]),
1204
+ toolIds: sorted([...covered.filter((item) => item.type === "tool").map((item) => item.id), ...evalCalls.flatMap((call) => call.toolIds)]),
1205
+ modelIds: sorted([...covered.filter((item) => item.type === "model_config").map((item) => item.id), ...evalCalls.flatMap((call) => call.modelIds)])
1206
+ };
1207
+ const id = `eval:${key}`;
1208
+ artifacts.push({
1209
+ id,
1210
+ type: "eval",
1211
+ name: key,
1212
+ sourcePath: runner.path,
1213
+ line: 1,
1214
+ memberCount: members.length,
1215
+ covers,
1216
+ ...owned(covers.agentIds, true),
1217
+ confidence: 0.9,
1218
+ evidence: [{ path: runner.path, line: 1, reason: "skeleton eval suite" }],
1219
+ status: repo.status(runner.path)
1220
+ });
1221
+ for (const from of [...covers.agentIds, ...covers.toolIds])
1222
+ addEdge({ type: "covered_by_eval", from, to: id, evidence: [] });
1223
+ }
1224
+ mark("evals");
1225
+ for (const call of callSites) {
1226
+ call.promptIds = sorted(call.promptIds);
1227
+ call.toolIds = sorted(call.toolIds);
1228
+ call.modelIds = sorted(call.modelIds);
1229
+ }
1230
+ for (const agent of agents) {
1231
+ agent.artifactIds = artifacts.filter((item) => item.usedByAgentIds.includes(agent.id)).map((item) => item.id).sort();
1232
+ agent.entrypointIds.sort();
1233
+ }
1234
+ let controlFlow = [];
1235
+ let controlFlowStatus = "not_run";
1236
+ if (controlFlowDetector) {
1237
+ try {
1238
+ const knownCalls = new Set(callSites.map((call) => call.id)), knownAgents = new Set(agents.map((agent) => agent.id));
1239
+ controlFlow = controlFlowDetector({ result, ctx, callSites, agents, callSiteIdOf: (id) => callIds.get(id) }).filter((item) => item.actsOn.callSiteIds.every((id) => knownCalls.has(id)) && item.actsOn.agentIds.every((id) => knownAgents.has(id))).map((item) => ({ ...item, condition: item.condition.slice(0, 160) })).sort(byId);
1240
+ controlFlowStatus = "ran";
1241
+ } catch (error) {
1242
+ controlFlowStatus = "failed";
1243
+ warnings.push(`control-flow detector: ${error.message}`);
1244
+ }
1245
+ }
1246
+ for (const agent of agents)
1247
+ for (const entry of agent.entrypointIds)
1248
+ addEdge({ type: "triggers", from: entry, to: agent.id, evidence: [] });
1249
+ for (const item of artifacts) {
1250
+ if (item.type === "prompt" || item.type === "tool" || item.type === "model_config")
1251
+ for (const agent of item.usedByAgentIds)
1252
+ addEdge({ type: "uses_artifact", from: agent, to: item.id, evidence: [] });
1253
+ if (item.type === "tool" && item.implementation)
1254
+ addEdge({ type: "implemented_by", from: item.id, to: `code:${item.implementation.path}#${item.implementation.symbol ?? item.id}`, evidence: [] });
1255
+ }
1256
+ for (const item of controlFlow) {
1257
+ for (const agent of item.actsOn.agentIds)
1258
+ addEdge({ type: "has_control_flow", from: agent, to: item.id, evidence: [] });
1259
+ for (const call of item.actsOn.callSiteIds)
1260
+ addEdge({ type: "guards", from: item.id, to: call, evidence: [] });
1261
+ }
1262
+ for (const agent of agents)
1263
+ agent.liveKey = agentKey(agent.entry.kind === "class" ? agent.entry.symbol : agent.name);
1264
+ for (const item of artifacts) {
1265
+ const key = item.type === "tool" ? toolKey(item.name) : item.type === "model_config" ? modelKey(item.name) : item.type === "skill" ? skillKey(item.name) : undefined;
1266
+ if (key)
1267
+ item.liveKey = key;
1268
+ else
1269
+ delete item.liveKey;
1270
+ }
1271
+ agents.sort(byId);
1272
+ artifacts.sort(byId);
1273
+ callSites.sort(byId);
1274
+ relationships.sort((a, b) => a.id.localeCompare(b.id));
1275
+ const edgeTable = encodeSkeletonEdges(relationships);
1276
+ const graphEdges = relationships.filter((edge) => edge.type === "invokes_subagent" || edge.type === "hands_off_to").map(({ id: _id, source: _source, ...edge }) => {
1277
+ return edge;
1278
+ });
1279
+ const name = repoName(ctx.rootDir), generatorVersion = VERSION;
1280
+ mark("ownershipAndControlFlow");
1281
+ const graph = {
1282
+ schemaVersion: "harness.v0.1",
1283
+ harnessId: stableId("harness", name),
1284
+ repo: {
1285
+ root: ".",
1286
+ name,
1287
+ languages: sorted(sites.map((site) => site.language === "py" ? "python" : "typescript")),
1288
+ packageManagers: import_fast_glob.default.sync(["package-lock.json", "bun.lock", "bun.lockb", "pnpm-lock.yaml", "yarn.lock", "uv.lock"], { cwd: ctx.rootDir }).map((path) => path.startsWith("bun") ? "bun" : path.startsWith("pnpm") ? "pnpm" : path.startsWith("yarn") ? "yarn" : path.startsWith("uv") ? "uv" : "npm").sort()
1289
+ },
1290
+ scan: {
1291
+ createdAt: new Date().toISOString(),
1292
+ scannerVersion: generatorVersion,
1293
+ mode: "permissioned",
1294
+ rootPaths: ["."],
1295
+ skipped: [],
1296
+ readPlan: { scope: "broad", rootPaths: ["."], willRead: ["Local source and harness configuration"], willSkip: ["Dependencies, generated files, ordinary unit tests"], skipsSecretFiles: true, followsSymlinks: false, uploadsSourceByDefault: false }
1297
+ },
1298
+ agentFamilies: [],
1299
+ runtimeAgents: agents,
1300
+ developerAgentAdapters: [],
1301
+ channelAdapters: [],
1302
+ artifacts,
1303
+ identityNamespaces: [],
1304
+ environments: [],
1305
+ agentFrameworks: [],
1306
+ modelProviders: [],
1307
+ deployments: [],
1308
+ relationships: graphEdges,
1309
+ unknowns: [],
1310
+ skeleton: {
1311
+ version: SKELETON_VERSION,
1312
+ commit: git(ctx.rootDir, ["rev-parse", "HEAD"]) ?? null,
1313
+ dirty: Boolean(git(ctx.rootDir, ["status", "--porcelain"])),
1314
+ contentHash: createHash("sha256").update(canonicalJson({ runtimeAgents: agents, artifacts, relationships, callSites, controlFlow })).digest("hex"),
1315
+ generator: { tool: "moda-cli", version: generatorVersion, extractSchemaVersion: EXTRACT_SCHEMA_VERSION },
1316
+ durationMs: Date.now() - start,
1317
+ counts: {
1318
+ agents: agents.length,
1319
+ entrypoints: artifacts.filter((a) => a.type === "entrypoint").length,
1320
+ callSites: callSites.length,
1321
+ prompts: artifacts.filter((a) => a.type === "prompt").length,
1322
+ tools: artifacts.filter((a) => a.type === "tool").length,
1323
+ models: artifacts.filter((a) => a.type === "model_config").length,
1324
+ skills: artifacts.filter((a) => a.type === "skill").length,
1325
+ evals: artifacts.filter((a) => a.type === "eval").length,
1326
+ edges: relationships.length,
1327
+ controlFlow: controlFlow.length
1328
+ },
1329
+ callSites,
1330
+ edges: edgeTable,
1331
+ controlFlow,
1332
+ controlFlowStatus,
1333
+ coverage: { ...COVERAGE },
1334
+ knownGaps: KNOWN_GAPS,
1335
+ warnings: sorted(warnings)
1336
+ }
1337
+ };
1338
+ mark("finalize");
1339
+ return graph;
1340
+ }
1341
+ function toolLocations(node, ctx) {
1342
+ const output = { schema: null, implementation: null };
1343
+ const seen = new Set;
1344
+ const walk = (node2) => {
1345
+ if (seen.has(node2))
1346
+ return;
1347
+ seen.add(node2);
1348
+ if (import_typescript2.default.isPropertyAssignment(node2) || import_typescript2.default.isMethodDeclaration(node2) || import_typescript2.default.isShorthandPropertyAssignment(node2)) {
1349
+ const name = propertyNameText(node2.name);
1350
+ const key = name && /^(?:inputSchema|input_schema|parameters|schema)$/.test(name) ? "schema" : name && /^(?:execute|handler|run)$/.test(name) ? "implementation" : undefined;
1351
+ if (key && !output[key]) {
1352
+ const value = import_typescript2.default.isPropertyAssignment(node2) ? unwrap(node2.initializer) : import_typescript2.default.isShorthandPropertyAssignment(node2) ? node2.name : node2;
1353
+ const checker = ctx.checkerFor(relPath(ctx.rootDir, node2.getSourceFile().fileName));
1354
+ const declaration = checker && (import_typescript2.default.isIdentifier(value) || import_typescript2.default.isPropertyAccessExpression(value)) ? valueDeclaration(resolvedSymbol(import_typescript2.default.isPropertyAccessExpression(value) ? value.name : value, checker)) : undefined;
1355
+ const target = declaration && !declaration.getSourceFile().isDeclarationFile && !declaration.getSourceFile().fileName.includes("/node_modules/") ? declaration : node2;
1356
+ const symbol = key === "implementation" && target !== node2 ? propertyNameText(target.name) : undefined;
1357
+ output[key] = { ...location(target, ctx), ...symbol ? { symbol } : {} };
1358
+ }
1359
+ }
1360
+ if (import_typescript2.default.isFunctionLike(node2))
1361
+ return;
1362
+ import_typescript2.default.forEachChild(node2, walk);
1363
+ };
1364
+ walk(node);
1365
+ return output;
1366
+ }
1367
+ function importClosure(roots, ctx, importsByPath, expand) {
1368
+ const paths = new Set, queue = [...roots];
1369
+ const follow = (path) => {
1370
+ if (expand.has(path))
1371
+ return true;
1372
+ const source = ctx.programFor(path)?.getSourceFile(resolve2(ctx.rootDir, path));
1373
+ return Boolean(source && source.statements.length > 0 && source.statements.every((statement) => import_typescript2.default.isImportDeclaration(statement) || import_typescript2.default.isExportDeclaration(statement) && statement.moduleSpecifier));
1374
+ };
1375
+ for (let i = 0;i < queue.length; i++) {
1376
+ const path = queue[i];
1377
+ if (paths.has(path))
1378
+ continue;
1379
+ paths.add(path);
1380
+ if (!follow(path))
1381
+ continue;
1382
+ const cached = importsByPath.get(path);
1383
+ if (cached) {
1384
+ queue.push(...cached);
1385
+ continue;
1386
+ }
1387
+ const imports = [];
1388
+ importsByPath.set(path, imports);
1389
+ const source = ctx.programFor(path)?.getSourceFile(resolve2(ctx.rootDir, path)), checker = ctx.checkerFor(path);
1390
+ if (!source || !checker)
1391
+ continue;
1392
+ visit(source, (node) => {
1393
+ if (!import_typescript2.default.isStringLiteralLike(node) || !(import_typescript2.default.isImportDeclaration(node.parent) || import_typescript2.default.isExportDeclaration(node.parent)))
1394
+ return;
1395
+ const symbol = checker.getSymbolAtLocation(node);
1396
+ for (const declaration of symbol?.declarations ?? []) {
1397
+ const sf = declaration.getSourceFile(), relative = relPath(ctx.rootDir, sf.fileName);
1398
+ if (!sf.isDeclarationFile && !relative.startsWith("../") && !relative.includes("node_modules/"))
1399
+ imports.push(relative);
1400
+ }
1401
+ });
1402
+ queue.push(...imports);
1403
+ }
1404
+ return paths;
1405
+ }
1406
+ function collectDeclaredHandoffs(ctx, agents, agentForScope, add) {
1407
+ for (const file of ctx.sourceFiles()) {
1408
+ const path = relPath(ctx.rootDir, file.fileName);
1409
+ if (TEST_PATH.test(path))
1410
+ continue;
1411
+ const checker = ctx.checkerFor(path);
1412
+ if (!checker)
1413
+ continue;
1414
+ const targets = (node, seen = new Set) => {
1415
+ if (seen.has(node))
1416
+ return [];
1417
+ seen.add(node);
1418
+ if (import_typescript2.default.isArrayLiteralExpression(node))
1419
+ return node.elements.flatMap((element) => targets(element, seen));
1420
+ if (import_typescript2.default.isCallExpression(node) || import_typescript2.default.isNewExpression(node))
1421
+ return [...targets(node.expression, seen), ...(node.arguments ?? []).flatMap((arg) => targets(arg, seen))];
1422
+ if (import_typescript2.default.isIdentifier(node) || import_typescript2.default.isPropertyAccessExpression(node)) {
1423
+ const declaration = valueDeclaration(resolvedSymbol(import_typescript2.default.isPropertyAccessExpression(node) ? node.name : node, checker));
1424
+ if (!declaration)
1425
+ return [];
1426
+ const ids = agents.filter((agent) => agent.sourcePath === relPath(ctx.rootDir, declaration.getSourceFile().fileName) && import_typescript2.default.isClassLike(declaration) && declaration.name?.text === agent.entry.symbol).map((agent) => agent.id);
1427
+ if (ids.length)
1428
+ return ids;
1429
+ if (import_typescript2.default.isVariableDeclaration(declaration) && declaration.initializer)
1430
+ return targets(unwrap(declaration.initializer), seen);
1431
+ }
1432
+ return [];
1433
+ };
1434
+ visit(file, (node) => {
1435
+ const expr = import_typescript2.default.isPropertyAssignment(node) && propertyNameText(node.name) === "handoffs" ? node.initializer : import_typescript2.default.isPropertyDeclaration(node) && propertyNameText(node.name) === "handoffs" ? node.initializer : import_typescript2.default.isCallExpression(node) && /(?:^|\.)handoff$/.test(node.expression.getText()) ? node.arguments[0] : undefined;
1436
+ if (!expr)
1437
+ return;
1438
+ const cls = enclosingClass(node);
1439
+ const owners = agentForScope(scopeOf(ctx.rootDir, node));
1440
+ if (!owners.length && cls?.name)
1441
+ owners.push(...agentForScope(`${path}#${cls.name.text}`));
1442
+ for (const from of owners)
1443
+ for (const to of sorted(targets(expr)))
1444
+ if (from !== to)
1445
+ add({
1446
+ type: "hands_off_to",
1447
+ from,
1448
+ to,
1449
+ sourcePath: path,
1450
+ line: lineOf(node),
1451
+ via: import_typescript2.default.isCallExpression(node) ? node.expression.getText() : "handoffs",
1452
+ evidence: [{ path, line: lineOf(node), reason: "skeleton framework handoff declaration" }]
1453
+ });
1454
+ });
1455
+ }
1456
+ }
1457
+ function markdownCatalogs(ctx) {
1458
+ const out = [];
1459
+ for (const file of ctx.sourceFiles()) {
1460
+ const path = relPath(ctx.rootDir, file.fileName);
1461
+ if (TEST_PATH.test(path))
1462
+ continue;
1463
+ const imports = new Map;
1464
+ for (const statement of file.statements) {
1465
+ if (!import_typescript2.default.isImportDeclaration(statement) || !import_typescript2.default.isStringLiteral(statement.moduleSpecifier) || !statement.importClause?.name)
1466
+ continue;
1467
+ const spec = statement.moduleSpecifier.text;
1468
+ if (!spec.startsWith(".") || !/\.(?:md|mdx|txt)(?:\?raw)?$/.test(spec))
1469
+ continue;
1470
+ imports.set(statement.importClause.name.text, relPath(ctx.rootDir, resolve2(dirname(file.fileName), spec.replace(/\?raw$/, ""))));
1471
+ }
1472
+ if (imports.size < 3)
1473
+ continue;
1474
+ const markdownOf = (node) => {
1475
+ const value = node && unwrap(node);
1476
+ return value && import_typescript2.default.isIdentifier(value) ? imports.get(value.text) : undefined;
1477
+ };
1478
+ visit(file, (node) => {
1479
+ if (import_typescript2.default.isObjectLiteralExpression(node)) {
1480
+ const entries = node.properties.flatMap((property) => {
1481
+ const md = import_typescript2.default.isPropertyAssignment(property) ? markdownOf(property.initializer) : import_typescript2.default.isShorthandPropertyAssignment(property) ? imports.get(property.name.text) : undefined;
1482
+ const name = propertyNameText(property.name);
1483
+ return md && name ? [{ path: md, name, catalogPath: path, catalogLine: lineOf(property) }] : [];
1484
+ });
1485
+ if (entries.length >= 3)
1486
+ out.push(...entries);
1487
+ }
1488
+ if (import_typescript2.default.isArrayLiteralExpression(node)) {
1489
+ const entries = node.elements.flatMap((element) => {
1490
+ const object = unwrap(element);
1491
+ if (!import_typescript2.default.isObjectLiteralExpression(object))
1492
+ return [];
1493
+ let name;
1494
+ let md;
1495
+ for (const property of object.properties) {
1496
+ if (!import_typescript2.default.isPropertyAssignment(property))
1497
+ continue;
1498
+ const key = propertyNameText(property.name), value = unwrap(property.initializer);
1499
+ if (key && /^(?:name|id|key)$/.test(key) && import_typescript2.default.isStringLiteralLike(value))
1500
+ name = value.text;
1501
+ md ??= markdownOf(property.initializer);
1502
+ }
1503
+ return md && name ? [{ path: md, name, catalogPath: path, catalogLine: lineOf(object) }] : [];
1504
+ });
1505
+ if (entries.length >= 3)
1506
+ out.push(...entries);
1507
+ }
1508
+ });
1509
+ }
1510
+ return out.sort((a, b) => a.path.localeCompare(b.path));
1511
+ }
1512
+ function collectTriggers(input) {
1513
+ const { ctx, agents, artifacts, enumeration } = input;
1514
+ const entry = (artifact, targets) => {
1515
+ if (targets.length === 0)
1516
+ return;
1517
+ if (!artifacts.some((item) => item.id === artifact.id))
1518
+ artifacts.push(artifact);
1519
+ for (const agent of agents)
1520
+ if (targets.includes(agent.id) && !agent.entrypointIds.includes(artifact.id))
1521
+ agent.entrypointIds.push(artifact.id);
1522
+ };
1523
+ const base = (id, name, path, line, trigger, reason) => ({ id, type: "entrypoint", name, sourcePath: path, line, trigger, scope: "global", usedByAgentIds: [], confidence: 0.9, evidence: [{ path, line, reason }], status: input.status(path) });
1524
+ for (const agent of agents)
1525
+ if (agent.entry.kind === "route") {
1526
+ const method = ROUTE_METHOD.exec(agent.name)?.[1] ?? "route";
1527
+ entry(base(`route:${agent.sourcePath}#${method}`, `${method} ${agent.sourcePath}`, agent.sourcePath, agent.line, "http_route", "skeleton exported route handler"), [agent.id]);
1528
+ }
1529
+ const classes = new Map;
1530
+ for (const source of ctx.sourceFiles())
1531
+ visit(source, (node) => {
1532
+ if (import_typescript2.default.isClassDeclaration(node) && node.name)
1533
+ classes.set(node.name.text, [...classes.get(node.name.text) ?? [], node]);
1534
+ });
1535
+ const agentOfClass = (node) => agents.find((agent) => agent.entry.kind === "class" && agent.entry.symbol === node.name?.text && agent.sourcePath === relPath(ctx.rootDir, node.getSourceFile().fileName));
1536
+ for (const bound of [...detectBoundRuntimeClasses(ctx.rootDir), ...tomlDurableObjects(ctx.rootDir)].filter((item) => item.kind === "durable_object" && !item.scriptName)) {
1537
+ const found = classes.get(bound.className) ?? [];
1538
+ if (found.length !== 1)
1539
+ continue;
1540
+ const cls = found[0], checker = ctx.checkerFor(relPath(ctx.rootDir, cls.getSourceFile().fileName));
1541
+ const targets = new Set;
1542
+ const own = agentOfClass(cls);
1543
+ if (own)
1544
+ targets.add(own.id);
1545
+ else if (checker)
1546
+ visit(cls, (node) => {
1547
+ if (!import_typescript2.default.isNewExpression(node))
1548
+ return;
1549
+ const declaration = valueDeclaration(resolvedSymbol(node.expression, checker));
1550
+ const constructed = declaration && import_typescript2.default.isClassDeclaration(declaration) ? agentOfClass(declaration) : undefined;
1551
+ if (constructed)
1552
+ targets.add(constructed.id);
1553
+ });
1554
+ entry(base(`binding:${bound.path}#${bound.binding}`, bound.binding, bound.path, bound.line ?? 1, "do_binding", `skeleton Durable Object binding of ${bound.className}`), [...targets]);
1555
+ }
1556
+ const scopes = new Map(enumeration.scopes.map((scope) => [scope.key, scope]));
1557
+ const workflows = artifacts.filter((item) => item.type === "entrypoint" && (item.trigger === "workflow_binding" || item.trigger === "cron" || item.id.startsWith("workflow:")));
1558
+ for (const workflow of workflows) {
1559
+ const roots = enumeration.scopes.filter((scope) => scope.file.path === workflow.sourcePath && (scope.className === workflow.name || lineOf(scope.node) === workflow.line || scope.label === workflow.name));
1560
+ const reached = new Set, queue = roots.map((scope) => ({ key: scope.key, depth: 0 }));
1561
+ for (let i = 0;i < queue.length; i++) {
1562
+ const { key, depth } = queue[i];
1563
+ if (reached.has(key))
1564
+ continue;
1565
+ reached.add(key);
1566
+ if (depth < 8)
1567
+ for (const next of scopes.get(key)?.calls ?? [])
1568
+ queue.push({ key: next, depth: depth + 1 });
1569
+ }
1570
+ const aliases = new Set([...reached].flatMap((key) => scopes.get(key)?.aliases ?? []));
1571
+ const targets = new Set;
1572
+ for (const agent of agents)
1573
+ if (agent.entry.kind !== "class") {
1574
+ const origin = agent.evidence[0];
1575
+ if ([...reached].some((key) => {
1576
+ const scope = scopes.get(key);
1577
+ return scope.file.path === origin?.path && lineOf(scope.node) === origin.line;
1578
+ }))
1579
+ targets.add(agent.id);
1580
+ }
1581
+ for (const handoff of input.handoffs)
1582
+ if (aliases.has(handoff.scope))
1583
+ for (const target of handoff.targets) {
1584
+ const child = agents.find((agent) => agent.sourcePath === target.path && agent.entry.symbol === target.name);
1585
+ if (child)
1586
+ targets.add(child.id);
1587
+ }
1588
+ entry(workflow, [...targets]);
1589
+ }
1590
+ }
1591
+ function tomlDurableObjects(rootDir) {
1592
+ const out = [];
1593
+ for (const path of import_fast_glob.default.sync(["**/wrangler.toml", "**/wrangler.*.toml"], { cwd: rootDir, onlyFiles: true, followSymbolicLinks: false, ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**", "**/vendor/**", "**/.*/**"] }).sort()) {
1594
+ let text, config;
1595
+ try {
1596
+ text = readFileSync(resolve2(rootDir, path), "utf8");
1597
+ config = parse(text);
1598
+ } catch {
1599
+ continue;
1600
+ }
1601
+ if (isTestWorkerConfig({ path, name: config.name, mains: [config.main] }))
1602
+ continue;
1603
+ const sections = [config, ...Object.values(config.env ?? {})];
1604
+ const lines = text.split(`
1605
+ `);
1606
+ for (const section of sections) {
1607
+ const bindings = section?.durable_objects?.bindings ?? [];
1608
+ for (const binding of bindings) {
1609
+ if (typeof binding.name !== "string" || typeof binding.class_name !== "string")
1610
+ continue;
1611
+ if (out.some((item) => item.path === path && item.binding === binding.name))
1612
+ continue;
1613
+ const line = lines.findIndex((entry) => entry.includes(binding.class_name)) + 1;
1614
+ out.push({ className: binding.class_name, kind: "durable_object", binding: binding.name, ...typeof binding.script_name === "string" ? { scriptName: binding.script_name } : {}, path, ...line > 0 ? { line } : {} });
1615
+ }
1616
+ }
1617
+ }
1618
+ return out;
1619
+ }
1620
+
1621
+ // src/harness-extract/skeleton/cli.ts
1622
+ function readSkeleton(path) {
1623
+ let value;
1624
+ try {
1625
+ value = JSON.parse(readFileSync2(path, "utf8"));
1626
+ } catch (error) {
1627
+ throw new CliInputError(`Cannot read skeleton ${path}: ${error.message}`);
1628
+ }
1629
+ if (value && typeof value === "object" && "data" in value)
1630
+ value = value.data;
1631
+ const graph = value;
1632
+ if (graph?.skeleton?.version !== SKELETON_VERSION || !Array.isArray(graph.runtimeAgents) || !Array.isArray(graph.artifacts) || !Array.isArray(graph.relationships) || !Array.isArray(graph.skeleton.callSites) || !Array.isArray(graph.skeleton.edges?.rows) || !Array.isArray(graph.skeleton.controlFlow) || typeof graph.skeleton.contentHash !== "string") {
1633
+ throw new CliInputError(`Not a ${SKELETON_VERSION} skeleton: ${path}`);
1634
+ }
1635
+ return graph;
1636
+ }
1637
+ function runSkeletonCommand(context) {
1638
+ if (context.positionals[0] === "skeleton-diff") {
1639
+ const from = context.flags.from ?? context.positionals[1], to = context.flags.to ?? context.positionals[2];
1640
+ if (!from || !to)
1641
+ throw new CliInputError("Usage: moda harness skeleton-diff <before.json> <after.json> (or --from=<file> --to=<file>)");
1642
+ context.output.writeData(diffHarnessSkeletons(readSkeleton(resolve3(context.cwd, from)), readSkeleton(resolve3(context.cwd, to))));
1643
+ return { exitCode: 0 };
1644
+ }
1645
+ if (context.flags.out === "true")
1646
+ throw new CliInputError("Missing output path for --out.", "Usage: moda harness skeleton --out=<file>");
1647
+ const graph = buildHarnessSkeleton(resolve3(context.cwd, context.flags.path ?? "."), { fresh: true });
1648
+ if (context.flags.out) {
1649
+ const path = resolve3(context.cwd, context.flags.out);
1650
+ mkdirSync(dirname2(path), { recursive: true });
1651
+ writeFileSync(path, `${JSON.stringify(graph, null, 2)}
1652
+ `, "utf8");
1653
+ }
1654
+ const warnings = graph.skeleton.warnings;
1655
+ context.output.writeData(graph, { status: warnings.length ? "degraded" : "success", ...warnings.length ? { warnings } : {} });
1656
+ return { exitCode: warnings.length ? EXIT_DEGRADED : 0 };
1657
+ }
1658
+ export {
1659
+ runSkeletonCommand
1660
+ };