@moda-ai/cli 1.55.65 → 1.55.67

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildImportGraphReachability
3
- } from "./cli-mfvwvh84.js";
3
+ } from "./cli-ysh0mdwp.js";
4
4
  import {
5
5
  require_typescript
6
6
  } from "./cli-gmvbapx7.js";
@@ -258,8 +258,13 @@ var MAX_MEMBERS = 400;
258
258
  var MAX_MUTATIONS = 64;
259
259
  var DEFAULT_CALLER_DEPTH = 3;
260
260
  var TEXT_HELPERS = new Set(["dedent", "outdent", "stripIndent", "stripIndents", "oneLine", "oneLineTrim", "String"]);
261
- var TRIM_METHODS = new Set(["trim", "trimStart", "trimEnd"]);
262
- var CASE_METHODS = new Set(["toLowerCase", "toUpperCase"]);
261
+ var STRING_METHODS = {
262
+ trim: (text) => text.trim(),
263
+ trimStart: (text) => text.trimStart(),
264
+ trimEnd: (text) => text.trimEnd(),
265
+ toLowerCase: (text) => text.toLowerCase(),
266
+ toUpperCase: (text) => text.toUpperCase()
267
+ };
263
268
  var EMPTY_ENV = new Map;
264
269
  function envFrom(bindings) {
265
270
  const env = new Map;
@@ -366,7 +371,16 @@ function mutationsIn(scope) {
366
371
  const target = node.expression.expression;
367
372
  if (op === "splice") {
368
373
  const first = node.arguments[0] ? unwrap(node.arguments[0]) : undefined;
369
- add({ op, at: node, target, values: [...node.arguments.slice(2)], index: first && import_typescript2.default.isNumericLiteral(first) ? Number(first.text) : undefined });
374
+ const count = node.arguments[1] ? unwrap(node.arguments[1]) : undefined;
375
+ add({
376
+ op,
377
+ at: node,
378
+ target,
379
+ values: [...node.arguments.slice(2)],
380
+ index: first && import_typescript2.default.isNumericLiteral(first) ? Number(first.text) : undefined,
381
+ deleteCount: count && import_typescript2.default.isNumericLiteral(count) ? Number(count.text) : undefined,
382
+ deleteUnknown: count !== undefined && !import_typescript2.default.isNumericLiteral(count)
383
+ });
370
384
  } else {
371
385
  add({ op, at: node, target, values: [...node.arguments] });
372
386
  }
@@ -417,6 +431,7 @@ class ValueTracer {
417
431
  checker;
418
432
  calls;
419
433
  modelArgOf;
434
+ program;
420
435
  maxDepth;
421
436
  maxCallers;
422
437
  maxNodes;
@@ -425,11 +440,13 @@ class ValueTracer {
425
440
  callerLevel = 0;
426
441
  stack = new Set;
427
442
  reassigning = new Set;
428
- constructor(rootDir, checker, calls, options = {}, modelArgOf) {
443
+ writes = new Map;
444
+ constructor(rootDir, checker, calls, options = {}, modelArgOf, program) {
429
445
  this.rootDir = rootDir;
430
446
  this.checker = checker;
431
447
  this.calls = calls;
432
448
  this.modelArgOf = modelArgOf;
449
+ this.program = program;
433
450
  this.maxDepth = options.maxDepth ?? DEFAULT_TRACE_DEPTH;
434
451
  this.maxCallers = options.maxCallers ?? DEFAULT_MAX_CALLERS;
435
452
  this.maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;
@@ -487,12 +504,12 @@ class ValueTracer {
487
504
  return this.node("literal", expr, { text: expr.text, resolution: "resolved" });
488
505
  }
489
506
  if (import_typescript2.default.isNumericLiteral(expr) || import_typescript2.default.isBigIntLiteral(expr))
490
- return this.node("literal", expr, { text: expr.text, resolution: "resolved" });
507
+ return this.node("literal", expr, { text: expr.text, note: "number", resolution: "resolved" });
491
508
  if (expr.kind === import_typescript2.default.SyntaxKind.TrueKeyword || expr.kind === import_typescript2.default.SyntaxKind.FalseKeyword) {
492
- return this.node("literal", expr, { text: expr.getText(), resolution: "resolved" });
509
+ return this.node("literal", expr, { text: expr.getText(), note: "boolean", resolution: "resolved" });
493
510
  }
494
511
  if (expr.kind === import_typescript2.default.SyntaxKind.NullKeyword || import_typescript2.default.isVoidExpression(expr) || import_typescript2.default.isIdentifier(expr) && expr.text === "undefined") {
495
- return this.node("external", expr, { resolution: "unresolved", note: "nullish", name: "undefined", unresolvedReason: "external" });
512
+ return this.node("external", expr, { resolution: "unresolved", note: "nullish", name: expr.kind === import_typescript2.default.SyntaxKind.NullKeyword ? "null" : "undefined", unresolvedReason: "external" });
496
513
  }
497
514
  if (import_typescript2.default.isTemplateExpression(expr))
498
515
  return this.template(expr, depth, env);
@@ -569,9 +586,17 @@ class ValueTracer {
569
586
  const children = [left, this.value(expr.right, depth, env)];
570
587
  return this.node("oneOf", expr, { children, resolution: rollup(children), note: operator === import_typescript2.default.SyntaxKind.BarBarToken ? "||" : "??" });
571
588
  }
572
- if (operator === import_typescript2.default.SyntaxKind.AmpersandAmpersandToken || operator === import_typescript2.default.SyntaxKind.CommaToken) {
589
+ if (operator === import_typescript2.default.SyntaxKind.CommaToken) {
573
590
  const child = this.value(expr.right, depth, env);
574
- return this.node("oneOf", expr, { children: [child], resolution: child.resolution, note: operator === import_typescript2.default.SyntaxKind.CommaToken ? "," : "&&" });
591
+ return this.node("oneOf", expr, { children: [child], resolution: child.resolution, note: "," });
592
+ }
593
+ if (operator === import_typescript2.default.SyntaxKind.AmpersandAmpersandToken) {
594
+ const left = this.value(expr.left, depth, env);
595
+ if (provenFalsy(left))
596
+ return left;
597
+ const right = this.value(expr.right, depth, env);
598
+ const children = provenTruthy(left) ? [right] : [{ ...left, note: "falsy left of &&" }, right];
599
+ return this.node("oneOf", expr, { children, resolution: rollup(children), note: "&&" });
575
600
  }
576
601
  return this.external(expr, `binary ${import_typescript2.default.tokenToString(operator) ?? import_typescript2.default.SyntaxKind[operator]}`);
577
602
  }
@@ -617,15 +642,22 @@ class ValueTracer {
617
642
  return this.node("oneOf", id, { name: id.text, children: values, resolution: rollup(values), note: "for-of element" });
618
643
  }
619
644
  if (import_typescript2.default.isVariableDeclaration(declaration)) {
620
- const mutations = this.mutations(symbol, declaration);
645
+ const mutations = mutationsReaching(this.mutations(symbol, declaration), id);
621
646
  if (mutations.some((mutation) => mutation.op !== "="))
622
647
  return this.assembled(id, declaration, mutations, depth, env);
623
648
  const sources = [];
624
649
  if (declaration.initializer)
625
650
  sources.push(this.hop(declaration.initializer, depth, env));
626
- sources.push(...this.reassignments(declaration, mutations, depth, env));
651
+ for (const mutation of mutations) {
652
+ if (mutation.op === "=" && !mayNotRun(mutation.at, declaration))
653
+ sources.length = 0;
654
+ sources.push(...this.reassignments(declaration, [mutation], depth, env));
655
+ }
627
656
  if (sources.length === 0)
628
657
  return this.external(id, "declared without a value", id.text);
658
+ const written = this.propertyWrites(symbol, declaration, id, sources, depth, env);
659
+ if (written)
660
+ return this.node("ref", id, { name: id.text, children: [written], resolution: written.resolution, note: "properties assigned" });
629
661
  return this.node("ref", id, { name: id.text, children: sources, resolution: rollup(sources), note: sources.length > 1 ? "reassigned" : undefined });
630
662
  }
631
663
  if (import_typescript2.default.isEnumMember(declaration) || import_typescript2.default.isPropertyAssignment(declaration) || import_typescript2.default.isPropertyDeclaration(declaration)) {
@@ -679,15 +711,33 @@ class ValueTracer {
679
711
  }
680
712
  this.reassigning.add(declaration);
681
713
  try {
682
- for (const mutation of mutations) {
683
- const values = mutation.values.slice(0, MAX_MEMBERS).map((value) => tag(this.element(value, depth + 1, env), mutation.op));
714
+ for (const mutation of mutationsReaching(mutations, id)) {
715
+ const uncertain = mayNotRun(mutation.at, declaration);
716
+ const traced = (value) => {
717
+ const element = unwrap(value);
718
+ if (mutation.op === "concat" && import_typescript2.default.isArrayLiteralExpression(element))
719
+ return element.elements.slice(0, MAX_MEMBERS).map((item) => this.element(item, depth + 1, env));
720
+ if (mutation.op === "concat" && !import_typescript2.default.isSpreadElement(value)) {
721
+ const traced2 = this.value(value, depth + 1, env);
722
+ const type = this.checker.getTypeAtLocation(value);
723
+ return [this.checker.isArrayType(type) || this.checker.isTupleType(type) ? { ...traced2, key: "..." } : traced2];
724
+ }
725
+ return [this.element(value, depth + 1, env)];
726
+ };
727
+ const values = mutation.values.slice(0, MAX_MEMBERS).flatMap(traced).map((value) => tag(uncertain ? atMostPartial(value) : value, mutation.op));
684
728
  if (mutation.op === "=") {
685
- items.length = 0;
729
+ if (uncertain)
730
+ items.splice(0, items.length, ...items.map(atMostPartial));
731
+ else
732
+ items.length = 0;
686
733
  items.push(...values);
687
734
  } else if (mutation.op === "unshift") {
688
735
  items.unshift(...values);
689
- } else if (mutation.op === "splice" && mutation.index !== undefined) {
690
- items.splice(Math.min(mutation.index, items.length), 0, ...values);
736
+ } else if (mutation.op === "splice" && mutation.index !== undefined && !mutation.deleteUnknown && !uncertain) {
737
+ const start = mutation.index < 0 ? Math.max(items.length + mutation.index, 0) : Math.min(mutation.index, items.length);
738
+ items.splice(start, mutation.deleteCount ?? items.length, ...values);
739
+ } else if (mutation.op === "splice") {
740
+ items.splice(0, items.length, ...items.map(atMostPartial), ...values);
691
741
  } else {
692
742
  items.push(...values);
693
743
  }
@@ -703,6 +753,69 @@ class ValueTracer {
703
753
  text: arrayOps || init && import_typescript2.default.isArrayLiteralExpression(init) ? "array" : isString ? "string" : undefined
704
754
  });
705
755
  }
756
+ propertyWrites(symbol, declaration, at, sources, depth, env) {
757
+ const writes = this.writesOf(symbol, declaration);
758
+ if (writes.size === 0)
759
+ return;
760
+ const initial = sources.length === 1 ? sources[0] : this.node("oneOf", at, { children: sources, resolution: rollup(sources), note: "reassigned" });
761
+ const children = [{ ...initial, key: "..." }];
762
+ for (const [key, all] of writes) {
763
+ const replacements = mutationsReaching(this.mutations(symbol, declaration), at).filter((mutation) => mutation.op === "=" && !mayNotRun(mutation.at, declaration));
764
+ const lastReplacement = replacements.at(-1)?.at.pos ?? -1;
765
+ const list = all.filter((write) => mutationsReaching([{ at: write.node }], at).length > 0 && write.node.pos > lastReplacement);
766
+ if (list.length === 0)
767
+ continue;
768
+ const lastDirect = list.reduce((found, write, index) => write.direct ? index : found, -1);
769
+ const values = list.slice(Math.max(lastDirect, 0)).map((write) => this.hop(write.node.right, depth, env));
770
+ if (lastDirect < 0) {
771
+ const missing = this.node("external", at, { resolution: "unresolved", note: "nullish", name: "undefined" });
772
+ const before = selectKey(initial, key, missing);
773
+ values.unshift(before);
774
+ }
775
+ const value = values.length === 1 ? values[0] : this.node("oneOf", at, { children: values, resolution: rollup(values), note: "property assignment" });
776
+ children.push({ ...value, key });
777
+ }
778
+ if (children.length === 1)
779
+ return;
780
+ return this.node("object", at, { children, resolution: rollup(children), note: "properties assigned" });
781
+ }
782
+ writesOf(symbol, declaration) {
783
+ const cached = this.writes.get(symbol);
784
+ if (cached)
785
+ return cached;
786
+ const scope = enclosingScope(declaration);
787
+ const statement = import_typescript2.default.findAncestor(declaration, import_typescript2.default.isVariableStatement);
788
+ const writes = new Map;
789
+ let count = 0;
790
+ const visit = (node) => {
791
+ if (count >= MAX_RETURNS)
792
+ return;
793
+ if (import_typescript2.default.isBinaryExpression(node) && node.operatorToken.kind === import_typescript2.default.SyntaxKind.EqualsToken && (import_typescript2.default.isPropertyAccessExpression(node.left) || import_typescript2.default.isElementAccessExpression(node.left))) {
794
+ const receiver = unwrap(node.left.expression);
795
+ const key = import_typescript2.default.isPropertyAccessExpression(node.left) ? node.left.name.text : import_typescript2.default.isStringLiteralLike(node.left.argumentExpression) ? node.left.argumentExpression.text : undefined;
796
+ if (key && import_typescript2.default.isIdentifier(receiver) && receiver.text === symbol.name && resolvedSymbol(receiver, this.checker) === symbol) {
797
+ count += 1;
798
+ const holder = node.parent;
799
+ const direct = Boolean(statement && import_typescript2.default.isExpressionStatement(holder) && holder.parent === statement.parent && holder.pos > statement.pos);
800
+ const list = writes.get(key);
801
+ if (list)
802
+ list.push({ node, direct });
803
+ else
804
+ writes.set(key, [{ node, direct }]);
805
+ }
806
+ }
807
+ import_typescript2.default.forEachChild(node, visit);
808
+ };
809
+ visit(scope);
810
+ this.writes.set(symbol, writes);
811
+ return writes;
812
+ }
813
+ reuse(traced, at) {
814
+ this.count += treeSize(traced);
815
+ if (this.count > this.maxNodes)
816
+ return this.external(at, "node budget reached", undefined, "depth-limit");
817
+ return traced;
818
+ }
706
819
  parameter(declaration, at, depth, env) {
707
820
  const name = import_typescript2.default.isIdentifier(declaration.name) ? declaration.name.text : declaration.name.getText();
708
821
  const fn = declaration.parent;
@@ -714,7 +827,7 @@ class ValueTracer {
714
827
  if (bound) {
715
828
  const argument = bound.expr && !isUndefinedLiteral(bound.expr) ? bound.expr : undefined;
716
829
  if (argument)
717
- bindings.push(bound.traced ?? this.value(argument, depth, bound.env));
830
+ bindings.push(bound.traced ? this.reuse(bound.traced, declaration) : this.value(argument, depth, bound.env));
718
831
  else if (declaration.initializer)
719
832
  children.push(this.value(declaration.initializer, depth, env));
720
833
  else {
@@ -731,7 +844,7 @@ class ValueTracer {
731
844
  reason = "depth-limit";
732
845
  } else {
733
846
  const index = fn.parameters.indexOf(declaration);
734
- const found = fnName ? this.calls.callersOf(fn, fnName, this.maxCallers + 1) : [];
847
+ const found = fnName ? this.calls.callersOf(fn, fnName, this.maxCallers + 1, this.program) : [];
735
848
  const callers = found.slice(0, this.maxCallers);
736
849
  this.callerLevel += 1;
737
850
  try {
@@ -802,9 +915,10 @@ class ValueTracer {
802
915
  const missing = this.missing(at, `no property ${key}`, whole.resolution === "unresolved" ? whole.unresolvedReason : "external");
803
916
  let selected = selectKey(whole, key, missing);
804
917
  const extra = { name, key, object: shallow(whole), keyNode: this.keyLiteral(element.propertyName ?? element.name, key) };
805
- if (element.initializer && hasNode(selected, missing)) {
918
+ const takesDefault = (node) => node === missing || isUndefinedValue(node);
919
+ if (element.initializer && hasWhere(selected, takesDefault)) {
806
920
  const fallback = this.hop(element.initializer, depth, env);
807
- selected = substitute(selected, missing, fallback);
921
+ selected = substituteWhere(selected, takesDefault, fallback);
808
922
  return this.node("lookup", at, { ...extra, children: [selected], resolution: selected.resolution, note: "with default" });
809
923
  }
810
924
  return this.node("lookup", at, { ...extra, children: [selected], resolution: selected.resolution });
@@ -912,13 +1026,12 @@ class ValueTracer {
912
1026
  const method = callee.name.text;
913
1027
  if (method === "join")
914
1028
  return this.arrayJoin(expr, callee, depth, env);
915
- if (TRIM_METHODS.has(method) && expr.arguments.length === 0)
916
- return this.value(callee.expression, depth, env);
917
- if (CASE_METHODS.has(method) && expr.arguments.length === 0) {
1029
+ const apply = Object.hasOwn(STRING_METHODS, method) ? STRING_METHODS[method] : undefined;
1030
+ if (apply && expr.arguments.length === 0) {
918
1031
  const receiver = this.value(callee.expression, depth, env);
919
1032
  const texts = receiver.resolution === "resolved" ? renderings(receiver) : [];
920
1033
  if (texts.length > 0 && !texts.some((text) => text.includes("${...}"))) {
921
- const children = [...new Set(texts)].map((text) => this.node("literal", expr, { text: method === "toLowerCase" ? text.toLowerCase() : text.toUpperCase(), resolution: "resolved" }));
1034
+ const children = [...new Set(texts.map(apply))].map((text) => this.node("literal", expr, { text, resolution: "resolved" }));
922
1035
  return children.length === 1 ? children[0] : this.node("oneOf", expr, { children, resolution: "resolved", note: method });
923
1036
  }
924
1037
  return this.node("call", expr, { name: method, children: [receiver], resolution: receiver.resolution === "resolved" ? "partial" : receiver.resolution });
@@ -961,7 +1074,9 @@ class ValueTracer {
961
1074
  const live = returns.filter((ret) => this.reachableReturn(ret, fn, depth + 1, inner));
962
1075
  const values = (live.length > 0 ? live : returns).map((ret) => this.value(ret, depth + 1, inner));
963
1076
  const calleeTree = values.length === 1 ? values[0] : this.node("oneOf", fn, { children: values, resolution: rollup(values), note: "returns" });
964
- return this.node("call", expr, { name, callee: calleeTree, args, ...anchor, resolution: calleeTree.resolution });
1077
+ const embedded = embeddedNodes(calleeTree, new Set(args));
1078
+ const listed = args.map((arg) => embedded.has(arg) ? { ...shallow(arg), note: "bound in callee (see callee bindings)" } : arg);
1079
+ return this.node("call", expr, { name, callee: calleeTree, args: listed, ...anchor, resolution: calleeTree.resolution });
965
1080
  }
966
1081
  reachableReturn(ret, fn, depth, env) {
967
1082
  let node = ret;
@@ -1016,14 +1131,22 @@ class ValueTracer {
1016
1131
  }
1017
1132
  arrayJoin(expr, callee, depth, env) {
1018
1133
  let receiver = unwrap(callee.expression);
1134
+ let filtered = false;
1019
1135
  while (import_typescript2.default.isCallExpression(receiver) && import_typescript2.default.isPropertyAccessExpression(receiver.expression) && receiver.expression.name.text === "filter" && receiver.arguments.length === 1 && import_typescript2.default.isIdentifier(receiver.arguments[0]) && receiver.arguments[0].text === "Boolean") {
1136
+ filtered = true;
1020
1137
  receiver = unwrap(receiver.expression.expression);
1021
1138
  }
1022
1139
  const separatorArg = expr.arguments[0];
1023
1140
  const separator = separatorArg === undefined ? "," : import_typescript2.default.isStringLiteral(separatorArg) || import_typescript2.default.isNoSubstitutionTemplateLiteral(separatorArg) ? separatorArg.text : undefined;
1024
- const children = import_typescript2.default.isArrayLiteralExpression(receiver) ? receiver.elements.slice(0, MAX_MEMBERS).map((element) => this.element(element, depth, env)) : [this.value(receiver, depth, env)];
1025
- const resolution = separator === undefined && rollup(children) === "resolved" ? "partial" : rollup(children);
1026
- return this.node("arrayJoin", expr, { text: separator, children, resolution, note: separator === undefined ? "computed separator" : undefined });
1141
+ let children = import_typescript2.default.isArrayLiteralExpression(receiver) ? receiver.elements.slice(0, MAX_MEMBERS).map((element) => this.element(element, depth, env)) : [{ ...this.value(receiver, depth, env), key: "..." }];
1142
+ if (filtered) {
1143
+ const expanded = joinElements(children);
1144
+ if (expanded)
1145
+ children = expanded.filter((child) => !provenFalsy(child));
1146
+ }
1147
+ const resolution = children.length === 0 ? "resolved" : separator === undefined && rollup(children) === "resolved" ? "partial" : rollup(children);
1148
+ const notes = [separator === undefined ? "computed separator" : undefined, filtered ? "filter(Boolean)" : undefined].filter(Boolean);
1149
+ return this.node("arrayJoin", expr, { text: separator, children, resolution, note: notes.length > 0 ? notes.join("; ") : undefined });
1027
1150
  }
1028
1151
  }
1029
1152
  function alwaysReturns(statement) {
@@ -1080,10 +1203,14 @@ function selectKey(tree, key, missing) {
1080
1203
  return { ...member, key };
1081
1204
  if (member.key === "...") {
1082
1205
  const fromSpread = selectKey(member, key, missing);
1083
- if (fromSpread !== missing)
1206
+ if (fromSpread !== missing) {
1207
+ if (fromSpread.resolution === "unresolved") {
1208
+ const prior = selectKey({ ...tree, children: members.slice(0, index) }, key, missing);
1209
+ if (prior !== missing)
1210
+ return { ...fromSpread, kind: "oneOf", children: [prior, fromSpread], resolution: "partial", note: "unknown spread may override property" };
1211
+ }
1084
1212
  return fromSpread;
1085
- if (member.kind !== "object")
1086
- return { ...missing, note: `property ${key} may come from an unresolved spread` };
1213
+ }
1087
1214
  }
1088
1215
  }
1089
1216
  return missing;
@@ -1100,21 +1227,40 @@ function selectKey(tree, key, missing) {
1100
1227
  return { ...missing, note: `property ${key} of ${tree.kind}${tree.name ? ` ${tree.name}` : ""}` };
1101
1228
  }
1102
1229
  var FLOW_KINDS = new Set(["ref", "call", "param", "oneOf", "lookup"]);
1230
+ function isUndefinedValue(node) {
1231
+ return node.kind === "external" && node.note === "nullish" && node.name === "undefined";
1232
+ }
1233
+ function provenFalsy(node) {
1234
+ if (node.kind === "literal")
1235
+ return node.text === "" || node.note === "boolean" && node.text === "false" || node.note === "number" && /^0n?$/.test(node.text ?? "");
1236
+ if (node.kind === "external")
1237
+ return node.note === "nullish";
1238
+ const flow = FLOW_KINDS.has(node.kind) ? valueChildren(node) : [];
1239
+ return flow.length > 0 && flow.every(provenFalsy);
1240
+ }
1241
+ function provenTruthy(node) {
1242
+ if (node.kind === "literal")
1243
+ return !provenFalsy(node);
1244
+ if (node.kind === "object" || node.kind === "array")
1245
+ return true;
1246
+ const flow = FLOW_KINDS.has(node.kind) ? valueChildren(node) : [];
1247
+ return flow.length > 0 && flow.every(provenTruthy);
1248
+ }
1103
1249
  function leaves(tree) {
1104
1250
  const flow = valueChildren(tree);
1105
1251
  return flow.length > 0 && tree.kind !== "template" ? flow.flatMap(leaves) : [tree];
1106
1252
  }
1107
- function hasNode(tree, target) {
1108
- return tree === target || valueChildren(tree).some((child) => hasNode(child, target));
1253
+ function hasWhere(tree, match) {
1254
+ return match(tree) || valueChildren(tree).some((child) => hasWhere(child, match));
1109
1255
  }
1110
- function substitute(tree, target, replacement) {
1111
- if (tree === target)
1112
- return replacement;
1113
- if (!hasNode(tree, target))
1256
+ function substituteWhere(tree, match, replacement) {
1257
+ if (match(tree))
1258
+ return tree.key !== undefined ? { ...replacement, key: tree.key } : replacement;
1259
+ if (!hasWhere(tree, match))
1114
1260
  return tree;
1115
1261
  if (FLOW_KINDS.has(tree.kind))
1116
- return mapFlow(tree, (child) => substitute(child, target, replacement));
1117
- return { ...tree, children: (tree.children ?? []).map((child) => substitute(child, target, replacement)) };
1262
+ return mapFlow(tree, (child) => substituteWhere(child, match, replacement));
1263
+ return { ...tree, children: (tree.children ?? []).map((child) => substituteWhere(child, match, replacement)) };
1118
1264
  }
1119
1265
  function allValues(tree) {
1120
1266
  if (tree.kind === "object" || tree.kind === "array" || tree.kind === "assembled" && tree.text === "array") {
@@ -1155,6 +1301,18 @@ function renderings(tree, limit = 16) {
1155
1301
  return combine((tree.children ?? []).map((child) => renderings(child, limit)));
1156
1302
  case "assembled":
1157
1303
  return tree.text === "string" ? combine((tree.children ?? []).map((child) => renderings(child, limit))) : ["${...}"];
1304
+ case "arrayJoin": {
1305
+ const elements = tree.text === undefined ? undefined : joinElements(tree.children ?? []);
1306
+ if (!elements)
1307
+ return ["${...}"];
1308
+ const pieces = [];
1309
+ elements.forEach((element, index) => {
1310
+ if (index > 0)
1311
+ pieces.push([tree.text ?? ""]);
1312
+ pieces.push(renderings(element, limit));
1313
+ });
1314
+ return combine(pieces);
1315
+ }
1158
1316
  case "ref":
1159
1317
  case "lookup":
1160
1318
  case "param":
@@ -1167,6 +1325,113 @@ function renderings(tree, limit = 16) {
1167
1325
  return ["${...}"];
1168
1326
  }
1169
1327
  }
1328
+ function joinElements(children) {
1329
+ const out = [];
1330
+ for (const child of children) {
1331
+ if (child.key !== "...") {
1332
+ out.push(child);
1333
+ continue;
1334
+ }
1335
+ let array = child;
1336
+ while (array && array.kind !== "array") {
1337
+ const flow = FLOW_KINDS.has(array.kind) ? valueChildren(array) : [];
1338
+ array = flow.length === 1 ? flow[0] : undefined;
1339
+ }
1340
+ const inner = array ? joinElements(array.children ?? []) : undefined;
1341
+ if (!inner)
1342
+ return;
1343
+ out.push(...inner);
1344
+ }
1345
+ return out;
1346
+ }
1347
+ function queryParamValue(tree, param) {
1348
+ const pattern = new RegExp(`[?&]${param}=([^&#]*)`);
1349
+ const span = { id: tree.id, path: tree.path, line: tree.line, endLine: tree.endLine, start: tree.start, end: tree.end };
1350
+ const children = [];
1351
+ for (const url of new Set(renderings(tree))) {
1352
+ const value = pattern.exec(url)?.[1];
1353
+ if (value !== undefined && value !== "" && !value.includes("${")) {
1354
+ let text = value;
1355
+ try {
1356
+ text = decodeURIComponent(value);
1357
+ } catch {}
1358
+ if (!children.some((child) => child.text === text))
1359
+ children.push({ ...span, kind: "literal", text, resolution: "resolved" });
1360
+ } else {
1361
+ children.push({ ...span, kind: "external", resolution: "unresolved", note: `query parameter ${param} not literal`, unresolvedReason: "external" });
1362
+ }
1363
+ }
1364
+ if (children.length === 1)
1365
+ return { ...children[0], key: param };
1366
+ return { ...span, kind: "oneOf", key: param, children, resolution: rollup(children), note: "query parameter" };
1367
+ }
1368
+ function subtrees(tree) {
1369
+ const out = [...tree.children ?? [], ...tree.bindings ?? [], ...tree.args ?? []];
1370
+ if (tree.callee)
1371
+ out.push(tree.callee);
1372
+ if (tree.object)
1373
+ out.push(tree.object);
1374
+ if (tree.keyNode)
1375
+ out.push(tree.keyNode);
1376
+ return out;
1377
+ }
1378
+ var sizes = new WeakMap;
1379
+ function treeSize(tree) {
1380
+ const cached = sizes.get(tree);
1381
+ if (cached !== undefined)
1382
+ return cached;
1383
+ const size = 1 + subtrees(tree).reduce((sum, child) => sum + treeSize(child), 0);
1384
+ sizes.set(tree, size);
1385
+ return size;
1386
+ }
1387
+ function embeddedNodes(tree, targets) {
1388
+ const found = new Set;
1389
+ if (targets.size === 0)
1390
+ return found;
1391
+ const seen = new Set;
1392
+ const visit = (node) => {
1393
+ if (seen.has(node) || found.size === targets.size)
1394
+ return;
1395
+ seen.add(node);
1396
+ if (targets.has(node)) {
1397
+ found.add(node);
1398
+ return;
1399
+ }
1400
+ subtrees(node).forEach(visit);
1401
+ };
1402
+ visit(tree);
1403
+ return found;
1404
+ }
1405
+ function atMostPartial(node) {
1406
+ return node.resolution === "resolved" ? { ...node, resolution: "partial" } : node;
1407
+ }
1408
+ function isLoop(node) {
1409
+ return import_typescript2.default.isForStatement(node) || import_typescript2.default.isForOfStatement(node) || import_typescript2.default.isForInStatement(node) || import_typescript2.default.isWhileStatement(node) || import_typescript2.default.isDoStatement(node);
1410
+ }
1411
+ function mutationsReaching(mutations, read) {
1412
+ const readFn = import_typescript2.default.findAncestor(read.parent, isFunctionLike);
1413
+ return mutations.filter((mutation) => {
1414
+ if (mutation.at.getSourceFile() !== read.getSourceFile() || mutation.at.pos < read.pos)
1415
+ return true;
1416
+ if (import_typescript2.default.findAncestor(mutation.at.parent, isFunctionLike) !== readFn)
1417
+ return true;
1418
+ for (let node = read.parent;node && node !== readFn; node = node.parent) {
1419
+ if (isLoop(node) && mutation.at.pos >= node.pos && mutation.at.end <= node.end)
1420
+ return true;
1421
+ }
1422
+ return false;
1423
+ });
1424
+ }
1425
+ function mayNotRun(at, declaration) {
1426
+ const scope = enclosingScope(declaration);
1427
+ for (let node = at.parent;node && node !== scope; node = node.parent) {
1428
+ if (import_typescript2.default.isIfStatement(node) || import_typescript2.default.isConditionalExpression(node) || import_typescript2.default.isSwitchStatement(node) || import_typescript2.default.isCatchClause(node) || isLoop(node) || isFunctionLike(node))
1429
+ return true;
1430
+ if (import_typescript2.default.isBinaryExpression(node) && (node.operatorToken.kind === import_typescript2.default.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === import_typescript2.default.SyntaxKind.BarBarToken || node.operatorToken.kind === import_typescript2.default.SyntaxKind.QuestionQuestionToken))
1431
+ return true;
1432
+ }
1433
+ return false;
1434
+ }
1170
1435
 
1171
1436
  // src/harness-extract/calls.ts
1172
1437
  class CallIndex {
@@ -1175,6 +1440,7 @@ class CallIndex {
1175
1440
  byName;
1176
1441
  aliases = new Map;
1177
1442
  members = new Map;
1443
+ checkingDefaults = new Set;
1178
1444
  constructor(program, checker) {
1179
1445
  this.program = program;
1180
1446
  this.checker = checker;
@@ -1184,6 +1450,7 @@ class CallIndex {
1184
1450
  for (const source of this.program.getSourceFiles()) {
1185
1451
  if (source.isDeclarationFile || /[\\/]node_modules[\\/]/.test(source.fileName))
1186
1452
  continue;
1453
+ const imported = importAliases(source);
1187
1454
  const visit = (node) => {
1188
1455
  if ((import_typescript3.default.isParameter(node) || import_typescript3.default.isVariableDeclaration(node)) && node.initializer && import_typescript3.default.isIdentifier(node.name)) {
1189
1456
  let init = unwrap(node.initializer);
@@ -1214,11 +1481,14 @@ class CallIndex {
1214
1481
  const callee = unwrap(node.expression);
1215
1482
  const name = import_typescript3.default.isIdentifier(callee) ? callee.text : import_typescript3.default.isPropertyAccessExpression(callee) ? callee.name.text : undefined;
1216
1483
  if (name) {
1217
- const list = byName.get(name);
1218
- if (list)
1219
- list.push(node);
1220
- else
1221
- byName.set(name, [node]);
1484
+ const original = import_typescript3.default.isIdentifier(callee) ? imported.get(name) : undefined;
1485
+ for (const key of original && original !== name ? [name, original] : [name]) {
1486
+ const list = byName.get(key);
1487
+ if (list)
1488
+ list.push(node);
1489
+ else
1490
+ byName.set(key, [node]);
1491
+ }
1222
1492
  }
1223
1493
  }
1224
1494
  import_typescript3.default.forEachChild(node, visit);
@@ -1227,15 +1497,25 @@ class CallIndex {
1227
1497
  }
1228
1498
  return byName;
1229
1499
  }
1230
- callersOf(fn, name, limit) {
1500
+ callersOf(fn, name, limit, within) {
1501
+ const all = this.allCallersOf(fn, name, within ? Number.MAX_SAFE_INTEGER : limit);
1502
+ if (!within)
1503
+ return all;
1504
+ return all.filter((call) => within.getSourceFile(call.getSourceFile().fileName) === call.getSourceFile()).slice(0, limit);
1505
+ }
1506
+ allCallersOf(fn, name, limit) {
1231
1507
  this.byName ??= this.build();
1232
1508
  const out = this.direct(name, limit, (declaration) => declares(declaration, fn));
1509
+ if (isDefaultExport(fn) && name !== "default" && out.length < limit)
1510
+ out.push(...this.direct("default", limit - out.length, (declaration) => declares(declaration, fn)));
1233
1511
  for (const alias of this.aliases.get(name) ?? []) {
1234
1512
  if (out.length >= limit)
1235
1513
  break;
1236
1514
  const symbol = resolvedSymbol(alias.target, this.checker);
1237
1515
  if (!symbol?.declarations?.some((declaration) => declares(declaration, fn)))
1238
1516
  continue;
1517
+ if (!this.defaultCanApply(alias.declaration))
1518
+ continue;
1239
1519
  const aliasName = alias.declaration.name.text;
1240
1520
  out.push(...this.direct(aliasName, limit - out.length, (declaration) => declaration === alias.declaration));
1241
1521
  }
@@ -1294,6 +1574,40 @@ class CallIndex {
1294
1574
  }
1295
1575
  return out;
1296
1576
  }
1577
+ defaultCanApply(declaration) {
1578
+ if (!import_typescript3.default.isParameter(declaration) || this.checkingDefaults.has(declaration))
1579
+ return true;
1580
+ const init = declaration.initializer ? unwrap(declaration.initializer) : undefined;
1581
+ if (!init || !import_typescript3.default.isIdentifier(init))
1582
+ return true;
1583
+ const fn = declaration.parent;
1584
+ const name = isFunctionLike(fn) ? functionName(fn) : undefined;
1585
+ if (!name || !isFunctionLike(fn))
1586
+ return true;
1587
+ const index = fn.parameters.indexOf(declaration);
1588
+ this.checkingDefaults.add(declaration);
1589
+ try {
1590
+ const callers = this.callersOf(fn, name, 20);
1591
+ if (callers.length === 0 || callers.length >= 20)
1592
+ return true;
1593
+ return callers.some((call) => {
1594
+ if (call.arguments.slice(0, index + 1).some((argument2) => import_typescript3.default.isSpreadElement(argument2)))
1595
+ return true;
1596
+ const argument = call.arguments[index];
1597
+ if (!argument)
1598
+ return true;
1599
+ const inner = unwrap(argument);
1600
+ if (import_typescript3.default.isIdentifier(inner) && inner.text === "undefined" || import_typescript3.default.isVoidExpression(inner))
1601
+ return true;
1602
+ const passed = import_typescript3.default.isIdentifier(inner) ? valueDeclaration(resolvedSymbol(inner, this.checker)) : undefined;
1603
+ if (passed && declaration.initializer && declares(passed, resolvedFunction(declaration.initializer, this.checker) ?? fn))
1604
+ return true;
1605
+ return !definitelyDefined(inner, this.checker);
1606
+ });
1607
+ } finally {
1608
+ this.checkingDefaults.delete(declaration);
1609
+ }
1610
+ }
1297
1611
  direct(name, limit, matches) {
1298
1612
  const candidates = this.byName.get(name) ?? [];
1299
1613
  const out = [];
@@ -1311,13 +1625,114 @@ class CallIndex {
1311
1625
  return out;
1312
1626
  }
1313
1627
  }
1628
+
1629
+ class RepoCallIndex {
1630
+ entries;
1631
+ indexes = new Map;
1632
+ owned = new Map;
1633
+ constructor(entries) {
1634
+ this.entries = entries;
1635
+ }
1636
+ indexFor(entry) {
1637
+ let index = this.indexes.get(entry);
1638
+ if (!index) {
1639
+ index = new CallIndex(entry.program, entry.checker());
1640
+ this.indexes.set(entry, index);
1641
+ }
1642
+ return index;
1643
+ }
1644
+ indexedCallersOf(fn, name, limit, within) {
1645
+ const out = [];
1646
+ const seen = new Set;
1647
+ const fnFile = fn.getSourceFile().fileName;
1648
+ for (const entry of this.entries) {
1649
+ if (out.length >= limit)
1650
+ break;
1651
+ if (!entry.program.getSourceFile(fnFile))
1652
+ continue;
1653
+ let owned = this.owned.get(entry);
1654
+ if (!owned) {
1655
+ owned = new Set(entry.owned);
1656
+ this.owned.set(entry, owned);
1657
+ }
1658
+ for (const call of this.indexFor(entry).callersOf(fn, name, Number.MAX_SAFE_INTEGER)) {
1659
+ const source = call.getSourceFile();
1660
+ if (!owned.has(source.fileName))
1661
+ continue;
1662
+ const key = `${source.fileName}:${call.pos}`;
1663
+ if (seen.has(key))
1664
+ continue;
1665
+ if (within && within.getSourceFile(source.fileName) !== source)
1666
+ continue;
1667
+ seen.add(key);
1668
+ out.push({ call, entry });
1669
+ if (out.length >= limit)
1670
+ break;
1671
+ }
1672
+ }
1673
+ return out;
1674
+ }
1675
+ callersOf(fn, name, limit, within) {
1676
+ const own = within ? this.entries.find((entry) => entry.program === within) : undefined;
1677
+ if (own)
1678
+ return this.indexFor(own).callersOf(fn, name, limit);
1679
+ return this.indexedCallersOf(fn, name, limit, within).map((indexed) => indexed.call);
1680
+ }
1681
+ }
1682
+ function importAliases(source) {
1683
+ const aliases = new Map;
1684
+ for (const statement of source.statements) {
1685
+ const clause = import_typescript3.default.isImportDeclaration(statement) ? statement.importClause : undefined;
1686
+ if (!clause || clause.isTypeOnly)
1687
+ continue;
1688
+ if (clause.name)
1689
+ aliases.set(clause.name.text, "default");
1690
+ if (clause.namedBindings && import_typescript3.default.isNamedImports(clause.namedBindings)) {
1691
+ for (const element of clause.namedBindings.elements) {
1692
+ if (element.propertyName)
1693
+ aliases.set(element.name.text, element.propertyName.text);
1694
+ }
1695
+ }
1696
+ }
1697
+ return aliases;
1698
+ }
1699
+ function isDefaultExport(fn) {
1700
+ const holder = import_typescript3.default.isVariableDeclaration(fn.parent) ? fn.parent : fn;
1701
+ if (import_typescript3.default.canHaveModifiers(holder) && import_typescript3.default.getModifiers(holder)?.some((modifier) => modifier.kind === import_typescript3.default.SyntaxKind.DefaultKeyword))
1702
+ return true;
1703
+ if (import_typescript3.default.isExportAssignment(fn.parent))
1704
+ return true;
1705
+ const name = functionName(fn);
1706
+ return Boolean(name && fn.getSourceFile().statements.some((statement) => import_typescript3.default.isExportAssignment(statement) && !statement.isExportEquals && import_typescript3.default.isIdentifier(statement.expression) && statement.expression.text === name));
1707
+ }
1708
+ function sameNode(a, b) {
1709
+ return a === b || a.kind === b.kind && a.pos === b.pos && a.end === b.end && a.getSourceFile().fileName === b.getSourceFile().fileName;
1710
+ }
1314
1711
  function declares(declaration, fn) {
1315
- if (declaration === fn)
1712
+ if (sameNode(declaration, fn))
1316
1713
  return true;
1317
- if ((import_typescript3.default.isVariableDeclaration(declaration) || import_typescript3.default.isPropertyAssignment(declaration) || import_typescript3.default.isPropertyDeclaration(declaration)) && declaration.initializer && unwrap(declaration.initializer) === fn)
1714
+ if ((import_typescript3.default.isVariableDeclaration(declaration) || import_typescript3.default.isPropertyAssignment(declaration) || import_typescript3.default.isPropertyDeclaration(declaration)) && declaration.initializer && sameNode(unwrap(declaration.initializer), fn))
1715
+ return true;
1716
+ if (import_typescript3.default.isExportAssignment(declaration) && sameNode(unwrap(declaration.expression), fn))
1318
1717
  return true;
1319
1718
  return false;
1320
1719
  }
1720
+ function definitelyDefined(expression, checker, seen = new Set) {
1721
+ const node = unwrap(expression);
1722
+ if (seen.has(node))
1723
+ return false;
1724
+ seen.add(node);
1725
+ if (import_typescript3.default.isStringLiteralLike(node) || import_typescript3.default.isNumericLiteral(node) || import_typescript3.default.isObjectLiteralExpression(node) || import_typescript3.default.isArrayLiteralExpression(node) || import_typescript3.default.isArrowFunction(node) || import_typescript3.default.isFunctionExpression(node) || node.kind === import_typescript3.default.SyntaxKind.NullKeyword || node.kind === import_typescript3.default.SyntaxKind.TrueKeyword || node.kind === import_typescript3.default.SyntaxKind.FalseKeyword)
1726
+ return true;
1727
+ if (import_typescript3.default.isIdentifier(node)) {
1728
+ const declaration = valueDeclaration(resolvedSymbol(node, checker));
1729
+ if (declaration && (import_typescript3.default.isFunctionDeclaration(declaration) || import_typescript3.default.isClassDeclaration(declaration)))
1730
+ return true;
1731
+ if (declaration && import_typescript3.default.isVariableDeclaration(declaration) && declaration.initializer && import_typescript3.default.getCombinedNodeFlags(declaration) & import_typescript3.default.NodeFlags.Const)
1732
+ return definitelyDefined(declaration.initializer, checker, seen);
1733
+ }
1734
+ return false;
1735
+ }
1321
1736
 
1322
1737
  // src/harness-extract/messages.ts
1323
1738
  var ROLE_LITERALS = {
@@ -1356,14 +1771,9 @@ function elementRole(element, depth = 0) {
1356
1771
  if (depth > 6 || element.key === "...")
1357
1772
  return "unknown";
1358
1773
  if (element.kind === "object") {
1359
- const members = element.children ?? [];
1360
- for (let index = members.length - 1;index >= 0; index -= 1) {
1361
- if (members[index].key === "role") {
1362
- const text = singleLiteral(members[index]);
1363
- return text && ROLE_LITERALS[text.toLowerCase()] || "unknown";
1364
- }
1365
- }
1366
- return "unknown";
1774
+ const missing = { ...element, kind: "external", children: undefined, resolution: "unresolved" };
1775
+ const text = singleLiteral(selectKey(element, "role", missing));
1776
+ return text && ROLE_LITERALS[text.toLowerCase()] || "unknown";
1367
1777
  }
1368
1778
  if (element.kind === "array") {
1369
1779
  const text = singleLiteral(element.children?.[0]);
@@ -1415,6 +1825,7 @@ function splitMessages(tree) {
1415
1825
 
1416
1826
  // src/harness-extract/program.ts
1417
1827
  var import_typescript4 = __toESM(require_typescript(), 1);
1828
+ import { createHash } from "node:crypto";
1418
1829
  import { readdirSync, statSync } from "node:fs";
1419
1830
  import { dirname, join, relative as relative2, resolve } from "node:path";
1420
1831
  var SKIPPED_DIRS = new Set(["node_modules", "dist", "build", "out", "vendor", "coverage", "graphify-out", "__pycache__"]);
@@ -1489,6 +1900,39 @@ function walkRepository(rootDir, maxFiles, includeTestRunnerFiles) {
1489
1900
  walk(rootDir, "");
1490
1901
  return { files, configs, truncated };
1491
1902
  }
1903
+ function sourceFingerprint(rootDir, options = {}) {
1904
+ const walked = walkRepository(resolve(rootDir), options.maxFiles ?? DEFAULT_MAX_FILES, options.includeTestRunnerFiles ?? false);
1905
+ const hash = createHash("sha1");
1906
+ const inputs = new Set([...walked.configs, ...walked.files.map((file) => file.endsWith(SVELTE_VIRTUAL_SUFFIX) ? file.slice(0, -SVELTE_VIRTUAL_SUFFIX.length) : file)]);
1907
+ for (const config of walked.configs) {
1908
+ const host = { ...import_typescript4.default.sys, onUnRecoverableConfigFileDiagnostic: () => {}, readFile: (file) => {
1909
+ inputs.add(file);
1910
+ return import_typescript4.default.sys.readFile(file);
1911
+ } };
1912
+ import_typescript4.default.getParsedCommandLineOfConfigFile(config, {}, host);
1913
+ }
1914
+ const metadata = (directory) => {
1915
+ let entries;
1916
+ try {
1917
+ entries = readdirSync(directory, { withFileTypes: true });
1918
+ } catch {
1919
+ return;
1920
+ }
1921
+ for (const entry of entries) {
1922
+ if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIPPED_DIRS.has(entry.name))
1923
+ metadata(join(directory, entry.name));
1924
+ else if (entry.isFile() && (entry.name === "package.json" || /^wrangler(?:\.[\w.-]+)?\.(?:json|jsonc|toml)$/.test(entry.name)))
1925
+ inputs.add(join(directory, entry.name));
1926
+ }
1927
+ };
1928
+ metadata(resolve(rootDir));
1929
+ for (const file of [...inputs].sort()) {
1930
+ const text = import_typescript4.default.sys.readFile(file);
1931
+ hash.update(`${file}\x00${text ?? "missing"}
1932
+ `);
1933
+ }
1934
+ return hash.digest("hex");
1935
+ }
1492
1936
  function extractorOptions(base) {
1493
1937
  const options = {
1494
1938
  ...base,
@@ -1548,6 +1992,7 @@ function sharedHost(options, cache) {
1548
1992
  host.writeFile = () => {};
1549
1993
  return host;
1550
1994
  }
1995
+ var NO_INPUTS_FOUND = 18003;
1551
1996
  function parseConfig(configPath, warnings, rootDir) {
1552
1997
  const rel = relative2(rootDir, configPath).split("\\").join("/");
1553
1998
  const read = import_typescript4.default.readConfigFile(configPath, import_typescript4.default.sys.readFile);
@@ -1557,6 +2002,11 @@ function parseConfig(configPath, warnings, rootDir) {
1557
2002
  }
1558
2003
  const raw = read.config;
1559
2004
  const parsed = import_typescript4.default.parseJsonConfigFileContent(read.config, import_typescript4.default.sys, dirname(configPath), undefined, configPath);
2005
+ const errors = parsed.errors.filter((error) => error.code !== NO_INPUTS_FOUND);
2006
+ if (errors.length > 0) {
2007
+ const messages = errors.slice(0, 3).map((error) => import_typescript4.default.flattenDiagnosticMessageText(error.messageText, " "));
2008
+ warnings.push(`tsconfig ${rel}: ${messages.join("; ")}${errors.length > 3 ? ` (+${errors.length - 3} more)` : ""}; extraction continues with the options that parsed`);
2009
+ }
1560
2010
  const explicit = raw.include !== undefined || raw.files !== undefined || parsed.raw && (parsed.raw.include !== undefined || parsed.raw.files !== undefined);
1561
2011
  return { options: parsed.options, fileNames: explicit ? parsed.fileNames.map((name) => resolve(name)) : undefined };
1562
2012
  }
@@ -1604,7 +2054,8 @@ function buildPrograms(rootDir, options = {}) {
1604
2054
  continue;
1605
2055
  const dir = dirname(configPath);
1606
2056
  const nested = configDirs.filter((other) => other !== dir && isUnder(other, dir));
1607
- const included = parsed.fileNames ? parsed.fileNames.filter((file) => known.has(file)) : walked.files.filter((file) => isUnder(file, dir) && !nested.some((other) => isUnder(file, other)));
2057
+ const underDir = (file) => isUnder(file, dir) && !nested.some((other) => isUnder(file, other));
2058
+ const included = parsed.fileNames ? [...parsed.fileNames.filter((file) => known.has(file)), ...walked.files.filter((file) => file.endsWith(SVELTE_VIRTUAL_SUFFIX) && underDir(file))] : walked.files.filter(underDir);
1608
2059
  const owned = included.filter((file) => !assigned.has(file));
1609
2060
  if (owned.length === 0)
1610
2061
  continue;
@@ -1620,6 +2071,7 @@ function buildPrograms(rootDir, options = {}) {
1620
2071
 
1621
2072
  // src/harness-extract/sinks.ts
1622
2073
  var import_typescript5 = __toESM(require_typescript(), 1);
2074
+ var URL_MODEL_KEY = "?model";
1623
2075
  var ANTHROPIC_MODULE = /^@anthropic-ai\//;
1624
2076
  var OPENAI_MODULE = /^openai(?:\/|$)/;
1625
2077
  var OPENAI_COMPATIBLE_MODULE = /^(?:groq-sdk|together-ai|@cerebras\/cerebras_cloud_sdk|fireworks-ai|portkey-ai|@portkey-ai\/|@azure\/openai|deepseek|@helicone\/|cohere-ai)/;
@@ -1656,9 +2108,9 @@ function requestRoles(argument, keys, ctx) {
1656
2108
  if (object && unwrap(argument) === object) {
1657
2109
  for (const [key, role] of Object.entries(keys)) {
1658
2110
  const value = propertyValue(object, key);
1659
- if (value)
2111
+ if (value && !spreadAfter(object, key))
1660
2112
  roles.push({ role, node: value });
1661
- else if (!hasProperty(object, key) && object.properties.some((property) => import_typescript5.default.isSpreadAssignment(property))) {
2113
+ else if ((value || !hasProperty(object, key)) && object.properties.some((property) => import_typescript5.default.isSpreadAssignment(property))) {
1662
2114
  roles.push({ role, node: argument, key });
1663
2115
  }
1664
2116
  }
@@ -1671,6 +2123,14 @@ function requestRoles(argument, keys, ctx) {
1671
2123
  }
1672
2124
  return dedupeRoles(roles);
1673
2125
  }
2126
+ function spreadAfter(object, key) {
2127
+ let last = -1;
2128
+ object.properties.forEach((property, index) => {
2129
+ if (!import_typescript5.default.isSpreadAssignment(property) && property.name && propertyNameText(property.name) === key)
2130
+ last = index;
2131
+ });
2132
+ return object.properties.some((property, index) => index > last && import_typescript5.default.isSpreadAssignment(property));
2133
+ }
1674
2134
  function dedupeRoles(roles) {
1675
2135
  const seen = new Set;
1676
2136
  return roles.filter((role) => {
@@ -1980,13 +2440,14 @@ function urlTemplate(expression, checker, depth = 0) {
1980
2440
  }
1981
2441
  return;
1982
2442
  }
2443
+ var MODEL_SOCKET = /^wss:\/\/(?:api\.openai\.com\/v1\/realtime|[\w.-]+\.openai\.azure\.com\/openai\/realtime|generativelanguage\.googleapis\.com\/ws\/[\w.]+BidiGenerateContent|gateway\.ai\.cloudflare\.com\/)/;
1983
2444
  function urlModel(url, checker) {
1984
2445
  const template = urlTemplate(url, checker);
1985
2446
  if (!template)
1986
2447
  return;
1987
2448
  let before = template.head.text;
1988
2449
  for (const span of template.templateSpans) {
1989
- if (/\/models\/$|[?&]model=$/.test(before))
2450
+ if (/\/models\/$/.test(before) && /^[:/?#]/.test(span.literal.text) || /[?&]model=$/.test(before) && /^(?:[&#]|$)/.test(span.literal.text))
1990
2451
  return span.expression;
1991
2452
  before = span.literal.text;
1992
2453
  }
@@ -2111,7 +2572,7 @@ function receiverClasses(expression, checker, depth = 0) {
2111
2572
  }
2112
2573
  return [];
2113
2574
  }
2114
- function firstParamType(declaration) {
2575
+ function firstParamType(declaration, checker) {
2115
2576
  if (!declaration)
2116
2577
  return;
2117
2578
  let parameters;
@@ -2120,7 +2581,13 @@ function firstParamType(declaration) {
2120
2581
  else if ((import_typescript5.default.isPropertySignature(declaration) || import_typescript5.default.isPropertyDeclaration(declaration)) && declaration.type && import_typescript5.default.isFunctionTypeNode(declaration.type))
2121
2582
  parameters = declaration.type.parameters;
2122
2583
  const type = parameters?.[0]?.type;
2123
- return type && import_typescript5.default.isTypeReferenceNode(type) ? type.typeName.getText() : undefined;
2584
+ if (!type || !import_typescript5.default.isTypeReferenceNode(type))
2585
+ return;
2586
+ const name = import_typescript5.default.isQualifiedName(type.typeName) ? type.typeName.right : type.typeName;
2587
+ return valueDeclarationOrType(resolvedSymbol(name, checker));
2588
+ }
2589
+ function valueDeclarationOrType(symbol) {
2590
+ return symbol?.declarations?.[0];
2124
2591
  }
2125
2592
  function structuralAgent(receiver, method, ctx) {
2126
2593
  let declaration;
@@ -2136,10 +2603,10 @@ function structuralAgent(receiver, method, ctx) {
2136
2603
  const base = ctx.frameworkBase(owner).base;
2137
2604
  return base && AGENT_BASES.has(base) ? owner : undefined;
2138
2605
  }
2139
- const wanted = firstParamType(declaration);
2606
+ const wanted = firstParamType(declaration, ctx.checker);
2140
2607
  if (!wanted)
2141
2608
  return;
2142
- return ctx.agentClasses().find((cls) => cls.members.some((member) => import_typescript5.default.isMethodDeclaration(member) && propertyNameText(member.name) === method && firstParamType(member) === wanted));
2609
+ return ctx.agentClasses().find((cls) => cls.members.some((member) => import_typescript5.default.isMethodDeclaration(member) && propertyNameText(member.name) === method && firstParamType(member, ctx.checker) === wanted));
2143
2610
  }
2144
2611
  var agentEntrySink = {
2145
2612
  id: "agent-entry",
@@ -2228,7 +2695,9 @@ var clientChatSink = {
2228
2695
  if (!import_typescript5.default.isCallExpression(call) || call.arguments.length === 0)
2229
2696
  return;
2230
2697
  const callee = unwrap(call.expression);
2231
- const name = import_typescript5.default.isIdentifier(callee) ? callee.text : import_typescript5.default.isPropertyAccessExpression(callee) ? callee.name.text : undefined;
2698
+ const binding = import_typescript5.default.isIdentifier(callee) ? valueDeclaration(resolvedSymbol(callee, ctx.checker)) : undefined;
2699
+ const bound = binding && import_typescript5.default.isBindingElement(binding) && binding.propertyName ? propertyNameText(binding.propertyName) : undefined;
2700
+ const name = bound ?? (import_typescript5.default.isIdentifier(callee) ? callee.text : import_typescript5.default.isPropertyAccessExpression(callee) ? callee.name.text : undefined);
2232
2701
  if (!name || !CHAT_SEND.has(name))
2233
2702
  return;
2234
2703
  const argument = call.arguments[0];
@@ -2240,6 +2709,27 @@ var clientChatSink = {
2240
2709
  return { api: `chat.${name}`, roles: [{ role: "user", node: argument }] };
2241
2710
  }
2242
2711
  };
2712
+ var websocketSink = {
2713
+ id: "websocket",
2714
+ sdk: "websocket",
2715
+ match(call, ctx) {
2716
+ if (!import_typescript5.default.isNewExpression(call) || !call.arguments?.length)
2717
+ return;
2718
+ const callee = unwrap(call.expression);
2719
+ if (!import_typescript5.default.isIdentifier(callee) || callee.text !== "WebSocket")
2720
+ return;
2721
+ const url = ctx.urlTexts(call.arguments[0]).find((candidate) => MODEL_SOCKET.test(candidate));
2722
+ if (!url)
2723
+ return;
2724
+ const modelInUrl = urlModel(call.arguments[0], ctx.checker);
2725
+ const roles = modelInUrl ? [{ role: "model", node: modelInUrl }] : /[?&]model=/.test(url) ? [{ role: "model", node: call.arguments[0], key: URL_MODEL_KEY }] : [];
2726
+ return {
2727
+ api: "websocket",
2728
+ provider: /openai/.test(url) ? "openai" : /generativelanguage/.test(url) ? "google" : undefined,
2729
+ roles
2730
+ };
2731
+ }
2732
+ };
2243
2733
  function frameworkName(identifier, checker, member) {
2244
2734
  if (!identifier)
2245
2735
  return;
@@ -2325,7 +2815,7 @@ function hookSink(memo) {
2325
2815
  const roles = [];
2326
2816
  for (const member of cls.members) {
2327
2817
  const name = propertyNameText(member.name);
2328
- if (!name)
2818
+ if (!name || import_typescript5.default.getCombinedModifierFlags(member) & import_typescript5.default.ModifierFlags.Static)
2329
2819
  continue;
2330
2820
  if ((import_typescript5.default.isMethodDeclaration(member) || import_typescript5.default.isGetAccessorDeclaration(member)) && HOOK_METHODS[name] && member.body) {
2331
2821
  roles.push({ role: HOOK_METHODS[name], node: member, values: returnExpressions(member) });
@@ -2339,7 +2829,7 @@ function hookSink(memo) {
2339
2829
  roles.push({ role, node: member, values: returns, key });
2340
2830
  }
2341
2831
  }
2342
- } else if (import_typescript5.default.isPropertyDeclaration(member) && HOOK_PROPERTIES[name] && member.initializer && !member.modifiers?.some((m) => m.kind === import_typescript5.default.SyntaxKind.StaticKeyword)) {
2832
+ } else if (import_typescript5.default.isPropertyDeclaration(member) && HOOK_PROPERTIES[name] && member.initializer) {
2343
2833
  roles.push({ role: HOOK_PROPERTIES[name], node: member.initializer });
2344
2834
  }
2345
2835
  }
@@ -2352,7 +2842,7 @@ function hookSink(memo) {
2352
2842
  function createFrameworkMemo() {
2353
2843
  return new Map;
2354
2844
  }
2355
- var CALL_SINKS = [anthropicSink, openaiSink, aiSdkSink, workersAiSink, fetchSink, providerFactorySink, agentEntrySink, clientChatSink];
2845
+ var CALL_SINKS = [anthropicSink, openaiSink, aiSdkSink, workersAiSink, fetchSink, websocketSink, providerFactorySink, agentEntrySink, clientChatSink];
2356
2846
  function registerCallSink(recognizer) {
2357
2847
  CALL_SINKS.push(recognizer);
2358
2848
  }
@@ -2412,43 +2902,52 @@ function definitionName(tree) {
2412
2902
  }
2413
2903
  function toolNamesOf(tree) {
2414
2904
  const out = { names: [], unresolved: [] };
2415
- const visit = (node, inArray) => {
2416
- if (node.kind === "array") {
2905
+ const visit = (node, inArray, names) => {
2906
+ if (node.kind === "array" || node.kind === "assembled" && node.text === "array") {
2417
2907
  for (const child of node.children ?? [])
2418
- visit(child, true);
2908
+ visit(child, true, names);
2419
2909
  return;
2420
2910
  }
2421
2911
  if (node.kind === "object") {
2422
2912
  const definition = definitionName(node);
2423
2913
  if (definition) {
2424
- out.names.push(definition);
2914
+ names.push(definition);
2425
2915
  return;
2426
2916
  }
2427
2917
  if (inArray) {
2428
2918
  out.unresolved.push({ ...node, note: "tool object without a literal name" });
2429
2919
  return;
2430
2920
  }
2921
+ const record = new Map;
2922
+ const set = (hit) => {
2923
+ record.delete(hit.name);
2924
+ record.set(hit.name, hit);
2925
+ };
2431
2926
  for (const child of node.children ?? []) {
2432
- if (child.key === "...")
2433
- visit(child, false);
2434
- else if (child.key)
2435
- out.names.push({ name: child.key, path: child.path, line: child.line, via: "key" });
2927
+ if (child.key === "...") {
2928
+ const spread = [];
2929
+ visit(child, false, spread);
2930
+ spread.forEach(set);
2931
+ } else if (child.key) {
2932
+ set({ name: child.key, path: child.path, line: child.line, via: "key" });
2933
+ }
2436
2934
  }
2935
+ names.push(...record.values());
2437
2936
  return;
2438
2937
  }
2439
2938
  if (PASS_THROUGH2.has(node.kind) && valueChildren(node).length > 0) {
2440
2939
  for (const child of valueChildren(node))
2441
- visit(child, inArray);
2940
+ visit(child, inArray, names);
2442
2941
  return;
2443
2942
  }
2444
2943
  out.unresolved.push(node);
2445
2944
  };
2446
- visit(tree, false);
2945
+ visit(tree, false, out.names);
2447
2946
  return out;
2448
2947
  }
2449
2948
 
2450
2949
  // src/harness-extract/index.ts
2451
- var PROMPT_ROLES = new Set(["system", "messages", "prompt", "instructions"]);
2950
+ var PROMPT_ROLES = new Set(["system", "user", "messages", "prompt", "instructions", "questions"]);
2452
2951
  var MAX_HELPER_CALLERS = 200;
2453
2952
  var MAX_DERIVED = 5000;
2454
2953
  var MAX_CALLER_CHAIN = 3;
@@ -2463,11 +2962,12 @@ function extractHarnessWithContext(rootDir, options = {}) {
2463
2962
  const root = resolve2(rootDir);
2464
2963
  const { fresh, ...rest } = options;
2465
2964
  const key = `${root}\x00${JSON.stringify(rest)}`;
2965
+ const fingerprint = sourceFingerprint(root, rest);
2466
2966
  const cached = fresh ? undefined : cache.get(key);
2467
- if (cached)
2468
- return cached;
2967
+ if (cached && cached.fingerprint === fingerprint)
2968
+ return cached.output;
2469
2969
  const output = runExtract(root, options);
2470
- cache.set(key, output);
2970
+ cache.set(key, { output, fingerprint });
2471
2971
  return output;
2472
2972
  }
2473
2973
  function argRef(rootDir, node, key) {
@@ -2492,7 +2992,19 @@ function qualifiedName(fn, name) {
2492
2992
  return owner ? `${owner}.${name}` : name;
2493
2993
  }
2494
2994
  function functionScope(rootDir, fn, name) {
2495
- return `${relPath(rootDir, fn.getSourceFile().fileName)}#${qualifiedName(fn, name)}`;
2995
+ const parts = [qualifiedName(fn, name)];
2996
+ const object = objectLiteralOf2(fn);
2997
+ if (object) {
2998
+ const holder = object.parent && import_typescript6.default.isVariableDeclaration(object.parent) && import_typescript6.default.isIdentifier(object.parent.name) ? object.parent.name.text : `L${lineOf(object)}`;
2999
+ parts.unshift(holder);
3000
+ }
3001
+ for (let outer = namedEnclosingFunction(object ?? fn);outer; outer = namedEnclosingFunction(outer.fn))
3002
+ parts.unshift(qualifiedName(outer.fn, outer.name));
3003
+ return `${relPath(rootDir, fn.getSourceFile().fileName)}#${parts.join(".")}`;
3004
+ }
3005
+ function objectLiteralOf2(fn) {
3006
+ const member2 = import_typescript6.default.isMethodDeclaration(fn) || import_typescript6.default.isGetAccessorDeclaration(fn) || import_typescript6.default.isSetAccessorDeclaration(fn) ? fn : import_typescript6.default.isPropertyAssignment(fn.parent) ? fn.parent : undefined;
3007
+ return member2 && import_typescript6.default.isObjectLiteralExpression(member2.parent) ? member2.parent : undefined;
2496
3008
  }
2497
3009
  function scopeOf(rootDir, node) {
2498
3010
  const named = namedEnclosingFunction(node);
@@ -2606,6 +3118,15 @@ function paramFlow(node, fn, checker, hops = 0) {
2606
3118
  }
2607
3119
  return;
2608
3120
  }
3121
+ function roleParamFlow(arg, fn, checker) {
3122
+ const object = unwrap(arg.node);
3123
+ if (arg.key !== undefined && !arg.key.includes(".") && import_typescript6.default.isObjectLiteralExpression(object)) {
3124
+ const value = propertyValue(object, arg.key);
3125
+ const flow = value ? paramFlow(value, fn, checker) : undefined;
3126
+ return flow ? { ...flow, viaProperty: true } : undefined;
3127
+ }
3128
+ return paramFlow(arg.node, fn, checker);
3129
+ }
2609
3130
  function descendArg(argument, path) {
2610
3131
  let node = argument;
2611
3132
  let index = 0;
@@ -2647,24 +3168,23 @@ function runExtract(rootDir, options) {
2647
3168
  const memo = createFrameworkMemo();
2648
3169
  const chainMemo = new Map;
2649
3170
  const wrapperSinks = {};
3171
+ const calls = new RepoCallIndex(repo?.entries ?? []);
2650
3172
  const tracers = new Map;
2651
- const toolsFor = (entry) => {
2652
- let tools = tracers.get(entry);
2653
- if (!tools) {
3173
+ const tracerFor = (entry) => {
3174
+ let tracer = tracers.get(entry);
3175
+ if (!tracer) {
2654
3176
  const checker = entry.checker();
2655
- const calls = new CallIndex(entry.program, checker);
2656
3177
  const modelArgOf = (call) => providerModelCall(call, checker)?.model;
2657
- tools = { tracer: new ValueTracer(rootDir, checker, calls, options, modelArgOf), calls };
2658
- tracers.set(entry, tools);
3178
+ tracer = new ValueTracer(rootDir, checker, calls, options, modelArgOf, entry.program);
3179
+ tracers.set(entry, tracer);
2659
3180
  }
2660
- return tools;
3181
+ return tracer;
2661
3182
  };
2662
3183
  for (const entry of repo?.entries ?? []) {
2663
3184
  try {
2664
3185
  const checker = entry.checker();
2665
- const { calls } = toolsFor(entry);
2666
3186
  let agentClasses;
2667
- const urlTracer = new ValueTracer(rootDir, checker, calls, { maxDepth: 6, maxNodes: 300, maxCallers: 2 });
3187
+ const urlTracer = new ValueTracer(rootDir, checker, calls, { maxDepth: 6, maxNodes: 300, maxCallers: 2 }, undefined, entry.program);
2668
3188
  const ctx2 = { checker, urlTexts: (expression) => renderings(urlTracer.trace(expression)), frameworkBase: (cls) => frameworkBaseOf(cls, checker, memo), agentClasses: () => agentClasses ??= findAgentClasses(entry.program, checker, memo) };
2669
3189
  const classRecognizers = classSinks(memo);
2670
3190
  for (const file of entry.owned) {
@@ -2728,7 +3248,9 @@ function runExtract(rootDir, options) {
2728
3248
  clock = mark("sinks", clock);
2729
3249
  for (const [id, ms] of Object.entries(recognizerMs))
2730
3250
  timings[`sinks.${id}`] = Math.round(ms);
2731
- const seenIds = new Set(sites.map((entry) => entry.site.id));
3251
+ const directIds = new Set(sites.map((entry) => entry.site.id));
3252
+ const seenIds = new Set(directIds);
3253
+ const seenDerived = new Set;
2732
3254
  const rounds = options.helperDepth ?? 8;
2733
3255
  let frontier = sites.filter((entry) => entry.sources.every((source) => !source.values));
2734
3256
  let derivedCount = 0;
@@ -2741,8 +3263,7 @@ function runExtract(rootDir, options) {
2741
3263
  const named = namedEnclosingFunction(inner.anchor);
2742
3264
  if (!named)
2743
3265
  continue;
2744
- const checker = inner.entry.checker();
2745
- const flowing = inner.sources.map((source) => ({ source, flow: paramFlow(source.arg.node, named.fn, checker) })).filter((item) => Boolean(item.flow));
3266
+ const flowing = inner.sources.map((source) => ({ source, flow: roleParamFlow(source.arg, named.fn, inner.entry.checker()) })).filter((item) => Boolean(item.flow));
2746
3267
  if (flowing.length === 0)
2747
3268
  continue;
2748
3269
  const fnSource = named.fn.getSourceFile();
@@ -2754,14 +3275,15 @@ function runExtract(rootDir, options) {
2754
3275
  wrapper.roles.push({ role: source.role, param, index: named.fn.parameters.indexOf(flow.param), ...flow.key ? { key: flow.key } : {} });
2755
3276
  }
2756
3277
  }
2757
- const { calls } = toolsFor(inner.entry);
2758
- for (const call of calls.callersOf(named.fn, named.name, MAX_HELPER_CALLERS)) {
3278
+ const origin = inner.site.viaHelper ?? inner.site.id;
3279
+ for (const { call, entry: callerEntry } of calls.indexedCallersOf(named.fn, named.name, MAX_HELPER_CALLERS)) {
2759
3280
  if (enclosingFunction(call) === named.fn)
2760
3281
  continue;
2761
3282
  const source = call.getSourceFile();
2762
3283
  const rel = relPath(rootDir, source.fileName);
2763
- const id = `${rel}:${lineOf(call)}:${colOf(call)}`;
2764
- if (seenIds.has(id))
3284
+ const location = `${rel}:${lineOf(call)}:${colOf(call)}`;
3285
+ const derivedKey = `${location}\x00${origin}`;
3286
+ if (directIds.has(location) || seenDerived.has(derivedKey))
2765
3287
  continue;
2766
3288
  const callerBindings = named.fn.parameters.map((param, index) => ({ param, expr: call.arguments[index] }));
2767
3289
  const sources = [];
@@ -2769,12 +3291,18 @@ function runExtract(rootDir, options) {
2769
3291
  const argument = call.arguments[named.fn.parameters.indexOf(flow.param)];
2770
3292
  if (!argument || import_typescript6.default.isSpreadElement(argument))
2771
3293
  continue;
2772
- const arg = descendArg(argument, [flow.key, role.arg.key].filter((part) => Boolean(part)));
3294
+ const arg = descendArg(argument, [flow.key, flow.viaProperty ? undefined : role.arg.key].filter((part) => Boolean(part)));
2773
3295
  sources.push({ ...role, bindings: extendBindings(role.bindings, callerBindings), arg });
2774
3296
  }
2775
3297
  if (sources.length === 0)
2776
3298
  continue;
3299
+ seenDerived.add(derivedKey);
3300
+ let id = location;
3301
+ for (let n = 2;seenIds.has(id); n += 1)
3302
+ id = `${location}#${n}`;
2777
3303
  seenIds.add(id);
3304
+ const entry = callerEntry.program.getSourceFile(fnSource.fileName) === fnSource ? callerEntry : inner.entry;
3305
+ const checker = entry.checker();
2778
3306
  const site = {
2779
3307
  id,
2780
3308
  path: rel,
@@ -2792,7 +3320,7 @@ function runExtract(rootDir, options) {
2792
3320
  isTest: isTestPath(rel),
2793
3321
  isEval: isEvalPath(rel)
2794
3322
  };
2795
- const derived = { site, entry: inner.entry, anchor: call, sources, level };
3323
+ const derived = { site, entry, anchor: call, sources, level };
2796
3324
  sites.push(derived);
2797
3325
  next.push(derived);
2798
3326
  derivedCount += 1;
@@ -2811,14 +3339,16 @@ function runExtract(rootDir, options) {
2811
3339
  const roles = [];
2812
3340
  const tracePrompts = options.tracePrompts ?? true;
2813
3341
  for (const entry of sites) {
2814
- const { tracer } = toolsFor(entry.entry);
3342
+ const tracer = tracerFor(entry.entry);
2815
3343
  for (const source of entry.sources) {
2816
3344
  if (!tracePrompts && PROMPT_ROLES.has(source.role))
2817
3345
  continue;
2818
3346
  let tree;
2819
3347
  try {
2820
3348
  tree = source.values ? tracer.traceAll(source.values, source.node) : tracer.trace(source.node, source.bindings);
2821
- if (source.key !== undefined) {
3349
+ if (source.key === URL_MODEL_KEY) {
3350
+ tree = queryParamValue(tree, "model");
3351
+ } else if (source.key !== undefined) {
2822
3352
  for (const segment of source.key.split("."))
2823
3353
  tree = selectKey(tree, segment, tracer.missing(source.arg.node, `property ${source.key} not found`));
2824
3354
  }