@moda-ai/cli 1.55.64 → 1.55.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hn9z5mtw.js → agent-ybekhjk0.js} +3 -3
- package/dist/{cli-rec661n7.js → cli-17v88cy7.js} +9 -5
- package/dist/{cli-vqhaj5tv.js → cli-28mf9mhw.js} +632 -102
- package/dist/{cli-7rq27nht.js → cli-bw10eqsm.js} +2 -2
- package/dist/{cli-7mdzvenp.js → cli-my8zmezy.js} +3 -3
- package/dist/cli.js +8 -8
- package/dist/{doctor-6bccjmx8.js → doctor-9meb05d8.js} +2 -2
- package/dist/{harness-github-actions-vt8tt6n9.js → harness-github-actions-cceykdhf.js} +2 -2
- package/dist/{harness-462pdr5e.js → harness-h6zwk7g8.js} +2 -2
- package/dist/{index-1cjavfpa.js → index-0pca9xsq.js} +4 -4
- package/dist/{index-xkgawks9.js → index-5y8h731g.js} +1 -1
- package/dist/{prompt-attribution-nha0sccy.js → prompt-attribution-fhe22taz.js} +2 -2
- package/dist/prompt-source-mcp.js +2 -2
- package/dist/{prompts-nhsbq9me.js → prompts-jgjfz7az.js} +1 -1
- package/dist/{sync-tvf4cggm.js → sync-wnf62ny6.js} +3 -3
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/references/output-protocol.md +1 -1
|
@@ -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
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
916
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
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
|
|
1108
|
-
return tree
|
|
1253
|
+
function hasWhere(tree, match) {
|
|
1254
|
+
return match(tree) || valueChildren(tree).some((child) => hasWhere(child, match));
|
|
1109
1255
|
}
|
|
1110
|
-
function
|
|
1111
|
-
if (tree
|
|
1112
|
-
return replacement;
|
|
1113
|
-
if (!
|
|
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) =>
|
|
1117
|
-
return { ...tree, children: (tree.children ?? []).map((child) =>
|
|
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
|
|
1218
|
-
|
|
1219
|
-
list.
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
|
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)
|
|
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
|
|
1360
|
-
|
|
1361
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
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
|
-
|
|
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
|
|
2652
|
-
let
|
|
2653
|
-
if (!
|
|
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
|
-
|
|
2658
|
-
tracers.set(entry,
|
|
3178
|
+
tracer = new ValueTracer(rootDir, checker, calls, options, modelArgOf, entry.program);
|
|
3179
|
+
tracers.set(entry, tracer);
|
|
2659
3180
|
}
|
|
2660
|
-
return
|
|
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
|
|
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
|
|
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
|
|
2758
|
-
for (const call of calls.
|
|
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
|
|
2764
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|