@suss/adapter-typescript 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,26 +32,24 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ADAPTER_VERSION: () => ADAPTER_VERSION,
34
34
  ResolutionStore: () => ResolutionStore,
35
- WhySession: () => WhySession,
35
+ TypeScriptWhySession: () => TypeScriptWhySession,
36
36
  accessContextFor: () => accessContextFor,
37
37
  buildProjectHelperIndex: () => buildProjectHelperIndex,
38
38
  callOpsFor: () => callOpsFor,
39
39
  collectAncestorBranches: () => collectAncestorBranches,
40
40
  collectClientFieldAccesses: () => collectClientFieldAccesses,
41
41
  computeAdapterPacksDigest: () => computeAdapterPacksDigest,
42
- computeContentHash: () => computeContentHash,
42
+ computeContentHash: () => import_extractor10.computeContentHash,
43
43
  createProjectWithoutTsconfig: () => createProjectWithoutTsconfig,
44
44
  createTsDiscoveryContext: () => createTsDiscoveryContext,
45
45
  createTsSubUnitContext: () => createTsSubUnitContext,
46
46
  createTypeScriptAdapter: () => createTypeScriptAdapter,
47
47
  discoverUnits: () => discoverUnits,
48
- evaluatePackHealth: () => evaluatePackHealth,
49
48
  exportedDeclarationsOf: () => exportedDeclarationsOf,
50
49
  extractCodeStructure: () => extractCodeStructure,
51
50
  extractRawBranches: () => extractRawBranches,
52
51
  findNearestTsconfig: () => findNearestTsconfig,
53
52
  findTerminals: () => findTerminals,
54
- formatPackHealth: () => formatPackHealth,
55
53
  functionTargetOf: () => functionTargetOf,
56
54
  importDeclarationsOf: () => importDeclarationsOf,
57
55
  importedNamesOf: () => importedNamesOf,
@@ -75,15 +73,15 @@ __export(index_exports, {
75
73
  module.exports = __toCommonJS(index_exports);
76
74
 
77
75
  // src/adapter.ts
78
- var import_node_path11 = __toESM(require("path"), 1);
76
+ var import_node_path10 = __toESM(require("path"), 1);
79
77
  var import_ts_morph66 = require("ts-morph");
80
- var import_behavioral_ir8 = require("@suss/behavioral-ir");
78
+ var import_behavioral_ir9 = require("@suss/behavioral-ir");
81
79
  var import_datalog6 = require("@suss/datalog");
82
- var import_extractor7 = require("@suss/extractor");
80
+ var import_extractor11 = require("@suss/extractor");
83
81
 
84
82
  // src/assembly.ts
85
83
  var import_ts_morph54 = require("ts-morph");
86
- var import_extractor4 = require("@suss/extractor");
84
+ var import_extractor5 = require("@suss/extractor");
87
85
 
88
86
  // src/conditions.ts
89
87
  var import_ts_morph6 = require("ts-morph");
@@ -95,6 +93,7 @@ var import_ts_morph5 = require("ts-morph");
95
93
  var import_ts_morph2 = require("ts-morph");
96
94
 
97
95
  // src/walk/nodeKeys.ts
96
+ var import_behavioral_ir = require("@suss/behavioral-ir");
98
97
  function offsetKeyOf(node) {
99
98
  return offsetKeyFor(node.getSourceFile().getFilePath(), {
100
99
  start: node.getStart(),
@@ -102,7 +101,7 @@ function offsetKeyOf(node) {
102
101
  });
103
102
  }
104
103
  function offsetKeyFor(file, span) {
105
- return `${file}:${span.start}-${span.end}`;
104
+ return (0, import_behavioral_ir.declarationKey)(file, span);
106
105
  }
107
106
  function lineRangeKey(file, startLine, endLine) {
108
107
  return `${file}:${startLine}-${endLine}`;
@@ -1542,7 +1541,7 @@ function degradedResult(func, terminalNodes, reason) {
1542
1541
 
1543
1542
  // src/resolve/invocationEffects.ts
1544
1543
  var import_ts_morph43 = require("ts-morph");
1545
- var import_extractor3 = require("@suss/extractor");
1544
+ var import_extractor4 = require("@suss/extractor");
1546
1545
 
1547
1546
  // src/lines.ts
1548
1547
  function startLineOf(node) {
@@ -1910,8 +1909,8 @@ function loadImportGraphsDepthFirstFromPaths(project, rootPaths) {
1910
1909
  stack.pop();
1911
1910
  loadOrder.push(top.path);
1912
1911
  let deepest = 0;
1913
- for (const path15 of top.deps) {
1914
- deepest = Math.max(deepest, chainDepth.get(path15) ?? 0);
1912
+ for (const path14 of top.deps) {
1913
+ deepest = Math.max(deepest, chainDepth.get(path14) ?? 0);
1915
1914
  }
1916
1915
  chainDepth.set(top.path, deepest + 1);
1917
1916
  }
@@ -2263,11 +2262,11 @@ function walkAndWarm(root, reach) {
2263
2262
  if (warmedFiles.get(target) !== void 0) {
2264
2263
  continue;
2265
2264
  }
2266
- const path15 = target.getFilePath();
2267
- if (visited.has(path15)) {
2265
+ const path14 = target.getFilePath();
2266
+ if (visited.has(path14)) {
2268
2267
  continue;
2269
2268
  }
2270
- visited.add(path15);
2269
+ visited.add(path14);
2271
2270
  stack.push(frameOf(target, reach));
2272
2271
  continue;
2273
2272
  }
@@ -2284,7 +2283,7 @@ function sitsOnWarmChains(file) {
2284
2283
  return false;
2285
2284
  }
2286
2285
  const imported = importedFilePathsOf(project, file.getFilePath());
2287
- return imported.length > 0 && imported.every((path15) => warmed.has(path15));
2286
+ return imported.length > 0 && imported.every((path14) => warmed.has(path14));
2288
2287
  }
2289
2288
  function markWarmed(file) {
2290
2289
  warmedFiles.set(file, true);
@@ -2579,6 +2578,32 @@ function writesToBindingUncached(declaration) {
2579
2578
  }
2580
2579
  return { values, inOrder: writesRunInOrder(declaration, writes) };
2581
2580
  }
2581
+ function sameConstructionAcrossWrites(values) {
2582
+ const candidates = values.filter((value) => !isNotYetPlaceholder(value));
2583
+ if (candidates.length === 0) {
2584
+ return null;
2585
+ }
2586
+ let construction = null;
2587
+ for (const value of candidates) {
2588
+ if (!isConstruction(value)) {
2589
+ return null;
2590
+ }
2591
+ if (construction !== null && sourceOf(value) !== sourceOf(construction)) {
2592
+ return null;
2593
+ }
2594
+ construction = value;
2595
+ }
2596
+ return construction;
2597
+ }
2598
+ function isNotYetPlaceholder(value) {
2599
+ return value.getKind() === import_ts_morph14.SyntaxKind.NullKeyword || import_ts_morph14.Node.isIdentifier(value) && value.getText() === "undefined";
2600
+ }
2601
+ function isConstruction(value) {
2602
+ return import_ts_morph14.Node.isCallExpression(value) || import_ts_morph14.Node.isNewExpression(value);
2603
+ }
2604
+ function sourceOf(node) {
2605
+ return node.getText().replace(/\s+/g, " ").trim();
2606
+ }
2582
2607
  function writesToField(declaration) {
2583
2608
  const remembered = byField.get(declaration);
2584
2609
  if (remembered !== void 0) {
@@ -2669,6 +2694,9 @@ function assignmentsInFile(sourceFile) {
2669
2694
  function isAssignmentOperator(kind) {
2670
2695
  return kind >= import_ts_morph14.SyntaxKind.FirstAssignment && kind <= import_ts_morph14.SyntaxKind.LastAssignment;
2671
2696
  }
2697
+ function writesItsWholeValue(kind) {
2698
+ return kind === import_ts_morph14.SyntaxKind.EqualsToken || kind === import_ts_morph14.SyntaxKind.QuestionQuestionEqualsToken || kind === import_ts_morph14.SyntaxKind.BarBarEqualsToken;
2699
+ }
2672
2700
  function writeAt(node) {
2673
2701
  if (import_ts_morph14.Node.isBinaryExpression(node)) {
2674
2702
  const operator = node.getOperatorToken().getKind();
@@ -2676,7 +2704,7 @@ function writeAt(node) {
2676
2704
  if (!isAssignmentOperator(operator) || !(import_ts_morph14.Node.isIdentifier(target) || import_ts_morph14.Node.isPropertyAccessExpression(target))) {
2677
2705
  return null;
2678
2706
  }
2679
- const value = operator === import_ts_morph14.SyntaxKind.EqualsToken ? node.getRight() : null;
2707
+ const value = writesItsWholeValue(operator) ? node.getRight() : null;
2680
2708
  return { target, write: { value, node } };
2681
2709
  }
2682
2710
  if (import_ts_morph14.Node.isPrefixUnaryExpression(node) || import_ts_morph14.Node.isPostfixUnaryExpression(node)) {
@@ -3067,11 +3095,16 @@ function emitBindingValues(db, table, declaration) {
3067
3095
  }
3068
3096
  const { values, inOrder } = writesToBinding(declaration);
3069
3097
  const last = values[values.length - 1];
3070
- if (!inOrder || last === void 0) {
3071
- reassignedUnstated.add(declarationId);
3098
+ if (inOrder && last !== void 0) {
3099
+ fact(db, "endsHolding", declarationId, emitValue(db, table, last));
3100
+ return;
3101
+ }
3102
+ const construction = sameConstructionAcrossWrites(values);
3103
+ if (construction !== null) {
3104
+ fact(db, "endsHolding", declarationId, emitValue(db, table, construction));
3072
3105
  return;
3073
3106
  }
3074
- fact(db, "endsHolding", declarationId, emitValue(db, table, last));
3107
+ reassignedUnstated.add(declarationId);
3075
3108
  }
3076
3109
  var reassignedUnstated = /* @__PURE__ */ new Set();
3077
3110
  function reassignedNamesUnstated() {
@@ -3083,23 +3116,31 @@ function forgetReassignedNamesUnstated() {
3083
3116
  function emitFieldValues(db, table, declaration) {
3084
3117
  const { values, inOrder } = writesToField(declaration);
3085
3118
  const last = values[values.length - 1];
3086
- if (!inOrder || last === void 0 || !import_ts_morph15.Node.isExpression(last)) {
3119
+ if (inOrder && last !== void 0 && import_ts_morph15.Node.isExpression(last)) {
3120
+ fact(db, "binds", nodeId(declaration), emitValue(db, table, last));
3087
3121
  return;
3088
3122
  }
3089
- fact(db, "binds", nodeId(declaration), emitValue(db, table, last));
3123
+ const construction = sameConstructionAcrossWrites(values);
3124
+ if (construction !== null) {
3125
+ fact(db, "binds", nodeId(declaration), emitValue(db, table, construction));
3126
+ }
3090
3127
  }
3091
3128
  function emitParameterPropertyRead(db, table, referenceId, declaration) {
3092
3129
  const { values, inOrder } = writesToField(declaration);
3093
3130
  const last = values[values.length - 1];
3094
- if (!inOrder || last === void 0) {
3131
+ if (inOrder && last !== void 0 && import_ts_morph15.Node.isExpression(last)) {
3132
+ fact(db, "binds", referenceId, emitValue(db, table, last));
3095
3133
  return;
3096
3134
  }
3097
- if (!import_ts_morph15.Node.isExpression(last)) {
3135
+ if (inOrder && last !== void 0) {
3098
3136
  table.byId.set(nodeId(last), last);
3099
3137
  fact(db, "binds", referenceId, nodeId(last));
3100
3138
  return;
3101
3139
  }
3102
- fact(db, "binds", referenceId, emitValue(db, table, last));
3140
+ const construction = sameConstructionAcrossWrites(values);
3141
+ if (construction !== null) {
3142
+ fact(db, "binds", referenceId, emitValue(db, table, construction));
3143
+ }
3103
3144
  }
3104
3145
  function emitBindingElementFacts(db, table, element) {
3105
3146
  const id = nodeId(element);
@@ -3563,7 +3604,8 @@ var JS_RULES = [
3563
3604
  ];
3564
3605
  var RESOLUTION_PROGRAM = process.env.SUSS_RESOLUTION_ON_DEMAND === "0" ? {
3565
3606
  rules: [...import_resolution2.RESOLUTION_RULES, ...JS_RULES, ...import_resolution2.RESOLUTION_QUESTIONS],
3566
- demandDriven: []
3607
+ demandDriven: [],
3608
+ demands: []
3567
3609
  } : (0, import_datalog2.deriveOnDemand)(
3568
3610
  [...import_resolution2.RESOLUTION_RULES, ...JS_RULES, ...import_resolution2.RESOLUTION_QUESTIONS],
3569
3611
  import_resolution2.ANSWER_RELATIONS
@@ -3590,7 +3632,9 @@ var QUERY_FACTS = RESOLUTION_PROGRAM.demandDriven.length === 0 ? [] : [
3590
3632
  "wantedAnchor",
3591
3633
  "wantedSubject"
3592
3634
  ];
3593
- var MAX_MODULE_HOPS = 6;
3635
+ var MODULE_DEMANDS = RESOLUTION_PROGRAM.demands.filter(
3636
+ (one) => (one.relation === "moduleExport" || one.relation === "moduleForwards") && one.bound[0] === true
3637
+ ).map((one) => one.demand);
3594
3638
  var ResolutionStore = class {
3595
3639
  db = new import_datalog2.Database();
3596
3640
  table = createNodeTable();
@@ -3606,7 +3650,7 @@ var ResolutionStore = class {
3606
3650
  * extraction elsewhere cannot change an entry.
3607
3651
  */
3608
3652
  exportTables = /* @__PURE__ */ new Map();
3609
- /** Files the most recent wave walk entered, for the memo to keep. */
3653
+ /** Files the most recent query read, for the memo to keep. */
3610
3654
  lastQueryWalked = [];
3611
3655
  declarations = /* @__PURE__ */ new Map();
3612
3656
  graph = new ModuleGraph();
@@ -3637,19 +3681,19 @@ var ResolutionStore = class {
3637
3681
  */
3638
3682
  resolveCallableSources(value, alsoFrom) {
3639
3683
  const target = factKeyOf(value);
3640
- return this.resolveByWaves(
3684
+ return this.askAbout(
3641
3685
  target,
3642
3686
  "wanted",
3643
3687
  () => this.lookupSources(target),
3644
3688
  alsoFrom
3645
- ) ?? [];
3689
+ );
3646
3690
  }
3647
3691
  /**
3648
3692
  * The witness proof behind `resolveCallable`'s answer. Resolves the
3649
- * value first, which walks files and extracts facts in the usual
3650
- * waves, then re-evaluates the rules over those base facts under the
3651
- * witness algebra and rebuilds the proof of the answer. Null when
3652
- * the value does not resolve at all.
3693
+ * value first, which extracts the files the question demands, then
3694
+ * re-evaluates the rules over those base facts under the witness
3695
+ * algebra and rebuilds the proof of the answer. Null when the value
3696
+ * does not resolve at all.
3653
3697
  */
3654
3698
  explainCallable(value, options = {}) {
3655
3699
  const resolved = this.resolveCallable(value, options.alsoFrom);
@@ -3686,11 +3730,7 @@ var ResolutionStore = class {
3686
3730
  }
3687
3731
  resolveObject(value) {
3688
3732
  const target = factKeyOf(value);
3689
- return this.resolveByWaves(
3690
- target,
3691
- "wanted",
3692
- () => this.lookupObject(target)
3693
- );
3733
+ return this.askAbout(target, "wanted", () => this.lookupObject(target));
3694
3734
  }
3695
3735
  /**
3696
3736
  * For a value that is neither a function nor an object, such as a
@@ -3706,17 +3746,17 @@ var ResolutionStore = class {
3706
3746
  }
3707
3747
  return cached.written;
3708
3748
  }
3709
- const settled2 = this.resolveByWaves(
3749
+ const written = this.askAbout(
3710
3750
  target,
3711
3751
  "wanted",
3712
3752
  () => this.lookupWritten(target)
3713
3753
  );
3714
3754
  this.writtenValues.set(key, {
3715
- written: settled2?.written ?? null,
3755
+ written,
3716
3756
  walked: [...this.lastQueryWalked],
3717
3757
  extractedAt: this.fullyExtracted.size
3718
3758
  });
3719
- return settled2?.written ?? null;
3759
+ return written;
3720
3760
  }
3721
3761
  /**
3722
3762
  * The construction behind a registration subject: the call or `new`
@@ -3736,18 +3776,29 @@ var ResolutionStore = class {
3736
3776
  }
3737
3777
  return cached.construction;
3738
3778
  }
3739
- const settled2 = this.resolveByWaves(
3779
+ const settled2 = this.askAbout(
3740
3780
  target,
3741
3781
  "wantedSubject",
3742
3782
  () => this.lookupSubjectConstruction(target, importModule, importName)
3743
3783
  );
3784
+ const walked = [...this.lastQueryWalked];
3785
+ let resolved = settled2;
3786
+ if (settled2.construction === null && settled2.candidate !== null && import_ts_morph16.Node.isCallExpression(settled2.candidate) && this.resolveCallable(settled2.candidate.getExpression()) !== null) {
3787
+ const call = settled2.candidate;
3788
+ resolved = this.askAbout(
3789
+ call,
3790
+ "wantedSubject",
3791
+ () => this.lookupSubjectConstruction(call, importModule, importName)
3792
+ );
3793
+ walked.push(...this.lastQueryWalked);
3794
+ }
3744
3795
  this.subjectConstructions.set(key, {
3745
- construction: settled2?.construction ?? null,
3746
- candidates: settled2?.candidates ?? 0,
3747
- walked: [...this.lastQueryWalked],
3796
+ construction: resolved.construction,
3797
+ candidates: resolved.candidates,
3798
+ walked,
3748
3799
  extractedAt: this.fullyExtracted.size
3749
3800
  });
3750
- return settled2?.construction ?? null;
3801
+ return resolved.construction;
3751
3802
  }
3752
3803
  /**
3753
3804
  * How many constructions this value was written as, which is what
@@ -3761,10 +3812,8 @@ var ResolutionStore = class {
3761
3812
  )?.candidates ?? 0;
3762
3813
  }
3763
3814
  /**
3764
- * The single-answer policy over the written-value walk, then the
3765
- * origin check on the one candidate. A candidate failing the check
3766
- * is settled refusal rather than a reason to widen: its own file's
3767
- * import facts are already in, and more files only add candidates.
3815
+ * The single-answer policy over the written-value chain, then the
3816
+ * origin check on the one candidate.
3768
3817
  */
3769
3818
  lookupSubjectConstruction(value, importModule, importName) {
3770
3819
  this.derive();
@@ -3780,6 +3829,7 @@ var ResolutionStore = class {
3780
3829
  if (candidates.size > 1) {
3781
3830
  return {
3782
3831
  construction: null,
3832
+ candidate: null,
3783
3833
  candidates: [...candidates].filter(
3784
3834
  (one) => this.constructionComesFrom(valueId, one, importModule, importName)
3785
3835
  ).length
@@ -3787,7 +3837,7 @@ var ResolutionStore = class {
3787
3837
  }
3788
3838
  const single = [...candidates][0];
3789
3839
  if (single === void 0) {
3790
- return neverWritable(value) ? { construction: null, candidates: 0 } : null;
3840
+ return { construction: null, candidate: null, candidates: 0 };
3791
3841
  }
3792
3842
  return {
3793
3843
  construction: this.constructionComesFrom(
@@ -3796,6 +3846,7 @@ var ResolutionStore = class {
3796
3846
  importModule,
3797
3847
  importName
3798
3848
  ) ? single : null,
3849
+ candidate: single,
3799
3850
  candidates: 1
3800
3851
  };
3801
3852
  }
@@ -3855,11 +3906,11 @@ var ResolutionStore = class {
3855
3906
  return cached.names;
3856
3907
  }
3857
3908
  const target = factKeyOf(value);
3858
- const found = this.resolveByWaves(
3909
+ const found = this.askAbout(
3859
3910
  target,
3860
3911
  "wantedOrigin",
3861
3912
  () => this.lookupImportedNames(target, modules)
3862
- ) ?? [];
3913
+ );
3863
3914
  this.importedNames.set(declaration, {
3864
3915
  names: found,
3865
3916
  walked: [...this.lastQueryWalked]
@@ -3878,8 +3929,8 @@ var ResolutionStore = class {
3878
3929
  return this.importOriginsOfMany([value], modules).get(value) ?? [];
3879
3930
  }
3880
3931
  /**
3881
- * The batched form: one demand set, one derivation, one widening
3882
- * walk shared by every value. A discovery pass asks about every
3932
+ * The batched form: one demand set, one derivation, one round of
3933
+ * extraction shared by every value. A discovery pass asks about every
3883
3934
  * callee in a file, and per-value queries would re-pay demand
3884
3935
  * clearing and re-derivation once per call site.
3885
3936
  */
@@ -3907,49 +3958,12 @@ var ResolutionStore = class {
3907
3958
  this.wantValue("wantedCallOrigin", one.target);
3908
3959
  this.seedValue(one.target);
3909
3960
  }
3910
- const walked = /* @__PURE__ */ new Set();
3911
- this.lastQueryWalked = [];
3912
- let frontier = [];
3961
+ this.extractDemanded(pending.map((one) => one.target.getSourceFile()));
3913
3962
  for (const one of pending) {
3914
- const file = one.target.getSourceFile();
3915
- if (!walked.has(file.getFilePath()) && !frontier.includes(file)) {
3916
- frontier.push(file);
3917
- }
3918
- }
3919
- const open = new Set(pending);
3920
- for (let hop = 0; hop <= MAX_MODULE_HOPS && open.size > 0; hop++) {
3921
- const next = [];
3922
- for (const sourceFile of frontier) {
3923
- if (walked.has(sourceFile.getFilePath())) {
3924
- continue;
3925
- }
3926
- walked.add(sourceFile.getFilePath());
3927
- this.lastQueryWalked.push(sourceFile.getFilePath());
3928
- recordFileDependency(sourceFile.getFilePath());
3929
- this.extractFile(sourceFile);
3930
- next.push(...this.graph.importedFilesOf(sourceFile));
3931
- }
3932
- for (const one of [...open]) {
3933
- const found = this.lookupImportOrigins(one.target, modules);
3934
- if (found !== null) {
3935
- results.set(one.value, found);
3936
- this.importOrigins.set(one.key, {
3937
- origins: found,
3938
- walked: [...this.lastQueryWalked],
3939
- extractedAt: this.fullyExtracted.size
3940
- });
3941
- open.delete(one);
3942
- }
3943
- }
3944
- if (next.length === 0) {
3945
- break;
3946
- }
3947
- frontier = next;
3948
- }
3949
- for (const one of open) {
3950
- results.set(one.value, []);
3963
+ const origins = this.lookupImportOrigins(one.target, modules);
3964
+ results.set(one.value, origins);
3951
3965
  this.importOrigins.set(one.key, {
3952
- origins: [],
3966
+ origins,
3953
3967
  walked: [...this.lastQueryWalked],
3954
3968
  extractedAt: this.fullyExtracted.size
3955
3969
  });
@@ -4008,67 +4022,89 @@ var ResolutionStore = class {
4008
4022
  const refined = new Set(
4009
4023
  [...byPath.values()].filter((one) => one.path.length > 1).map((one) => (0, import_datalog2.tupleKey)(one.path.slice(0, -1)))
4010
4024
  );
4011
- const origins = [...byPath.values()].filter((one) => !refined.has((0, import_datalog2.tupleKey)(one.path))).sort((a, b) => a.path.join(".").localeCompare(b.path.join(".")));
4012
- if (origins.length > 0) {
4013
- return origins;
4014
- }
4015
- return all.some((one) => namesAPackage(one.module)) ? [] : null;
4025
+ return [...byPath.values()].filter((one) => !refined.has((0, import_datalog2.tupleKey)(one.path))).sort((a, b) => a.path.join(".").localeCompare(b.path.join(".")));
4016
4026
  }
4017
- /**
4018
- * Null tells the walk to keep widening. A value that landed in a
4019
- * package returns empty instead, because a library's own body is not
4020
- * here to read and widening would never find anything.
4021
- */
4022
4027
  lookupImportedNames(value, modules) {
4023
4028
  this.derive();
4024
- const origins = this.answerPairsFor("wantedComesFrom", nodeId(value));
4025
- const reached = new Set(origins);
4029
+ const reached = new Set(
4030
+ this.answerPairsFor("wantedComesFrom", nodeId(value))
4031
+ );
4026
4032
  for (const target of this.answersFor("wantedComesTo", nodeId(value))) {
4027
4033
  for (const entry of this.answerPairsFor("wantedCallsInto", target)) {
4028
4034
  reached.add(entry);
4029
4035
  }
4030
4036
  }
4031
- const names = namesFrom([...reached], modules);
4032
- if (names.length > 0) {
4033
- return names;
4034
- }
4035
- return origins.some((pair) => namesAPackage(pairHalves(pair).module)) ? [] : null;
4037
+ return namesFrom([...reached], modules);
4036
4038
  }
4037
- resolveByWaves(value, question, ask, alsoFrom) {
4039
+ askAbout(value, question, read2, alsoFrom) {
4038
4040
  try {
4039
- return this.walkForAnswer(value, question, ask, alsoFrom);
4041
+ this.wantValue(question, value);
4042
+ this.seedValue(value);
4043
+ this.extractDemanded(
4044
+ alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom]
4045
+ );
4046
+ return read2();
4040
4047
  } finally {
4041
4048
  this.forgetQuery();
4042
4049
  }
4043
4050
  }
4044
- walkForAnswer(value, question, ask, alsoFrom) {
4045
- this.wantValue(question, value);
4046
- this.seedValue(value);
4047
- const walked = /* @__PURE__ */ new Set();
4051
+ /**
4052
+ * Extract the seed files, then every module the rules ask for, until
4053
+ * they ask for nothing new. The files a question reads are then fixed
4054
+ * by the question alone, so its answer does not depend on what an
4055
+ * earlier question left in the store.
4056
+ */
4057
+ extractDemanded(seeds) {
4058
+ const project = seeds[0]?.getProject();
4059
+ if (project === void 0) {
4060
+ return;
4061
+ }
4062
+ const read2 = /* @__PURE__ */ new Set();
4063
+ const considered = /* @__PURE__ */ new Set();
4048
4064
  this.lastQueryWalked = [];
4049
- let frontier = alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom];
4050
- for (let hop = 0; hop <= MAX_MODULE_HOPS; hop++) {
4051
- const next = [];
4052
- for (const sourceFile of frontier) {
4053
- if (walked.has(sourceFile.getFilePath())) {
4065
+ let pending = [...seeds];
4066
+ while (pending.length > 0) {
4067
+ const readThisRound = [];
4068
+ for (const sourceFile of pending) {
4069
+ const filePath = sourceFile.getFilePath();
4070
+ if (read2.has(filePath)) {
4054
4071
  continue;
4055
4072
  }
4056
- walked.add(sourceFile.getFilePath());
4057
- this.lastQueryWalked.push(sourceFile.getFilePath());
4058
- recordFileDependency(sourceFile.getFilePath());
4073
+ read2.add(filePath);
4074
+ readThisRound.push(filePath);
4075
+ this.lastQueryWalked.push(filePath);
4076
+ recordFileDependency(filePath);
4059
4077
  this.extractFile(sourceFile);
4060
- next.push(...this.graph.importedFilesOf(sourceFile));
4061
- }
4062
- const found = ask();
4063
- if (found !== null) {
4064
- return found;
4065
4078
  }
4066
- if (next.length === 0) {
4067
- return null;
4079
+ this.derive();
4080
+ pending = [];
4081
+ for (const moduleKey of this.demandedModules(readThisRound)) {
4082
+ if (considered.has(moduleKey)) {
4083
+ continue;
4084
+ }
4085
+ considered.add(moduleKey);
4086
+ const sourceFile = sourceFileFor(project, moduleKey);
4087
+ if (sourceFile !== void 0 && !read2.has(sourceFile.getFilePath())) {
4088
+ pending.push(sourceFile);
4089
+ }
4068
4090
  }
4069
- frontier = next;
4070
4091
  }
4071
- return null;
4092
+ }
4093
+ /**
4094
+ * Module keys the rules are waiting on. Demand facts are cleared
4095
+ * between queries, so the demand relations contain this query's
4096
+ * alone; the unrestricted program has none, and follows the imports
4097
+ * of the files read this round instead.
4098
+ */
4099
+ demandedModules(readThisRound) {
4100
+ if (RESOLUTION_PROGRAM.demands.length === 0) {
4101
+ return readThisRound.flatMap(
4102
+ (filePath) => this.db.lookup("importsModule", 0, filePath).map((tuple) => String(tuple[1]))
4103
+ );
4104
+ }
4105
+ return MODULE_DEMANDS.flatMap(
4106
+ (relation) => this.db.facts(relation).map((tuple) => String(tuple[0]))
4107
+ );
4072
4108
  }
4073
4109
  /**
4074
4110
  * The question and the chain that settled it are dropped. The files
@@ -4087,6 +4123,27 @@ var ResolutionStore = class {
4087
4123
  this.stale = true;
4088
4124
  }
4089
4125
  }
4126
+ wantExportsOf(filePath) {
4127
+ if (this.db.add("wantedExportsOf", [filePath]) === "added") {
4128
+ this.stale = true;
4129
+ }
4130
+ }
4131
+ /**
4132
+ * Discovery walks the table in order, and the rounds derive it in
4133
+ * the order files arrived. Deriving it again from the demand alone,
4134
+ * over the loaded closure, orders it by the file's own statements.
4135
+ */
4136
+ rederiveExportsOf(filePath) {
4137
+ if (QUERY_FACTS.length === 0) {
4138
+ return;
4139
+ }
4140
+ this.db.retract("wantedModuleExport", [
4141
+ ...this.db.lookup("wantedModuleExport", 0, filePath)
4142
+ ]);
4143
+ this.forgetQuery();
4144
+ this.wantExportsOf(filePath);
4145
+ this.derive();
4146
+ }
4090
4147
  /**
4091
4148
  * File extraction only reaches values hanging off exports, and a query
4092
4149
  * can be rooted anywhere, so the queried value gets its own facts.
@@ -4136,24 +4193,18 @@ var ResolutionStore = class {
4136
4193
  }
4137
4194
  candidates.add(node);
4138
4195
  }
4139
- if (candidates.size === 1) {
4140
- return { written: [...candidates][0] };
4141
- }
4142
- if (candidates.size > 1) {
4143
- return { written: null };
4144
- }
4145
- if (neverWritable(value)) {
4146
- return { written: null };
4196
+ if (candidates.size !== 1) {
4197
+ return null;
4147
4198
  }
4148
- return null;
4199
+ return [...candidates][0];
4149
4200
  }
4150
4201
  /**
4151
4202
  * Read these files' facts now, before anything asks a question.
4152
4203
  *
4153
- * A query widens along the imports of the file it starts in, which
4154
- * is where a value's definition is. What a caller passed is the other
4204
+ * A query follows the imports of the file it starts in, which is
4205
+ * where a value's definition is. What a caller passed is the other
4155
4206
  * direction, so a question about a parameter is answered only by
4156
- * files the walk never reaches on its own.
4207
+ * files the query never reaches on its own.
4157
4208
  */
4158
4209
  extractFiles(sourceFiles) {
4159
4210
  for (const sourceFile of sourceFiles) {
@@ -4169,15 +4220,14 @@ var ResolutionStore = class {
4169
4220
  return this.graph.filesReachingAnyPackage(fileSets);
4170
4221
  }
4171
4222
  /**
4172
- * The calls behind a receiver that `matches` accepts, walked out in
4173
- * waves the way every question is. The wave ends as soon as one file
4174
- * yields a match, and the caller applies the single-answer policy to
4175
- * the set; the resolution README's anchor section says why the rules
4176
- * cannot rank a nearer call above a farther one.
4223
+ * The calls behind a receiver that `matches` accepts. The caller
4224
+ * applies the single-answer policy to the set; the resolution
4225
+ * README's anchor section says why the rules cannot rank a nearer
4226
+ * call above a farther one.
4177
4227
  */
4178
4228
  anchorCallsOf(value, matches) {
4179
4229
  const target = factKeyOf(value);
4180
- return this.resolveByWaves(target, "wantedAnchor", () => {
4230
+ return this.askAbout(target, "wantedAnchor", () => {
4181
4231
  this.derive();
4182
4232
  const found = [];
4183
4233
  for (const id of this.answersFor("wantedAnchorCall", nodeId(target))) {
@@ -4186,12 +4236,12 @@ var ResolutionStore = class {
4186
4236
  found.push(node);
4187
4237
  }
4188
4238
  }
4189
- return found.length === 0 ? null : found;
4190
- }) ?? [];
4239
+ return found;
4240
+ });
4191
4241
  }
4192
4242
  /**
4193
4243
  * Every name a module exports and the values behind each, with
4194
- * re-export chains flattened by the rules. The frontier follows
4244
+ * re-export chains flattened by the rules. The demand follows
4195
4245
  * re-export targets only, so a barrel of barrels extracts its own
4196
4246
  * chain and nothing beside it, to any depth.
4197
4247
  */
@@ -4211,33 +4261,10 @@ var ResolutionStore = class {
4211
4261
  }
4212
4262
  }
4213
4263
  collectExports(sourceFile) {
4214
- const project = sourceFile.getProject();
4215
- const walked = /* @__PURE__ */ new Set();
4216
- let frontier = [sourceFile];
4217
- while (frontier.length > 0) {
4218
- const next = [];
4219
- for (const file of frontier) {
4220
- const filePath2 = file.getFilePath();
4221
- if (walked.has(filePath2)) {
4222
- continue;
4223
- }
4224
- walked.add(filePath2);
4225
- recordFileDependency(filePath2);
4226
- this.extractFile(file);
4227
- for (const target of this.reExportTargetsOf(filePath2)) {
4228
- const targetFile = sourceFileFor(project, target);
4229
- if (targetFile !== void 0) {
4230
- next.push(targetFile);
4231
- }
4232
- }
4233
- }
4234
- frontier = next;
4235
- }
4236
4264
  const filePath = sourceFile.getFilePath();
4237
- if (this.db.add("wantedExportsOf", [filePath]) === "added") {
4238
- this.stale = true;
4239
- }
4240
- this.derive();
4265
+ this.wantExportsOf(filePath);
4266
+ this.extractDemanded([sourceFile]);
4267
+ this.rederiveExportsOf(filePath);
4241
4268
  const exports2 = /* @__PURE__ */ new Map();
4242
4269
  for (const tuple of this.db.lookup("wantedModuleExport", 0, filePath)) {
4243
4270
  const node = this.table.byId.get(String(tuple[2]));
@@ -4254,22 +4281,9 @@ var ResolutionStore = class {
4254
4281
  }
4255
4282
  return exports2;
4256
4283
  }
4257
- /** Module keys this file re-exports from, read off its own facts. */
4258
- reExportTargetsOf(filePath) {
4259
- const targets = /* @__PURE__ */ new Set();
4260
- for (const tuple of this.db.lookup("reExports", 0, filePath)) {
4261
- targets.add(String(tuple[2]));
4262
- }
4263
- for (const tuple of this.db.lookup("reExportsAll", 0, filePath)) {
4264
- targets.add(String(tuple[1]));
4265
- }
4266
- return [...targets];
4267
- }
4268
4284
  /**
4269
- * Null keeps the wave walk widening; any candidate at all ends it,
4270
- * since more files can only add candidates, never take one away. The
4271
- * single-answer policy stays with the callers: picking one of two
4272
- * would make the result depend on the order the facts came in.
4285
+ * The single-answer policy stays with the callers: a value with two
4286
+ * sources is something to say at the site, not something to pick from.
4273
4287
  */
4274
4288
  lookupSources(value) {
4275
4289
  this.derive();
@@ -4283,7 +4297,7 @@ var ResolutionStore = class {
4283
4297
  candidates.add(resolved);
4284
4298
  }
4285
4299
  }
4286
- return candidates.size === 0 ? null : [...candidates];
4300
+ return [...candidates];
4287
4301
  }
4288
4302
  derive() {
4289
4303
  if (!this.stale) {
@@ -4342,18 +4356,6 @@ function pairHalves(pair) {
4342
4356
  const [module2 = "", name = ""] = (0, import_datalog2.tupleKeyParts)(pair);
4343
4357
  return { module: module2, name };
4344
4358
  }
4345
- function namesAPackage(moduleKey) {
4346
- return !moduleKey.startsWith("/") || moduleKey.includes("/node_modules/");
4347
- }
4348
- function neverWritable(value) {
4349
- if (!import_ts_morph16.Node.isIdentifier(value)) {
4350
- return false;
4351
- }
4352
- const declarations = value.getSymbol()?.getDeclarations() ?? [];
4353
- return declarations.some(
4354
- (declaration) => declaration.getSourceFile().getFilePath().includes("/node_modules/")
4355
- );
4356
- }
4357
4359
  function namesPackage2(moduleKey, packages) {
4358
4360
  return namesAnyPackage(
4359
4361
  [moduleKey, ...packagesDeclaring(moduleKey)],
@@ -4374,35 +4376,7 @@ var import_node_path3 = __toESM(require("path"), 1);
4374
4376
  // src/diagnostics.ts
4375
4377
  var import_node_path2 = __toESM(require("path"), 1);
4376
4378
  var import_ts_morph17 = require("ts-morph");
4377
- var import_behavioral_ir = require("@suss/behavioral-ir");
4378
- var emptyTally = () => ({
4379
- candidateFiles: 0,
4380
- unitsDiscovered: 0,
4381
- unitsInGatedFiles: 0,
4382
- effectsRecognized: 0,
4383
- unitsClaimed: 0,
4384
- selfCollisions: 0,
4385
- summariesProduced: 0,
4386
- failures: [],
4387
- helpersMatched: /* @__PURE__ */ new Set()
4388
- });
4389
- function recordPackFailure(tally, failure) {
4390
- const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
4391
- tally?.failures.push({
4392
- hook: failure.hook,
4393
- file: failure.file,
4394
- message
4395
- });
4396
- return `[suss] pack "${failure.pack}" threw from ${failure.hook} while reading ${failure.file}: ${message}
4397
- `;
4398
- }
4399
- function createPackTallies(packs) {
4400
- const tallies = /* @__PURE__ */ new Map();
4401
- for (const pack of packs) {
4402
- tallies.set(pack.name, emptyTally());
4403
- }
4404
- return tallies;
4405
- }
4379
+ var import_extractor3 = require("@suss/extractor");
4406
4380
  function commonDirectoryOf(files) {
4407
4381
  const absolute = files.filter((f) => import_node_path2.default.isAbsolute(f));
4408
4382
  const first = absolute[0];
@@ -4479,30 +4453,6 @@ function unresolvedGatesFor(gates, args) {
4479
4453
  }
4480
4454
  return unresolved;
4481
4455
  }
4482
- var emptyCounts = () => ({
4483
- bound: 0,
4484
- providers: 0,
4485
- withBehavior: 0
4486
- });
4487
- function summaryCountsByPack(summaries) {
4488
- const counts = /* @__PURE__ */ new Map();
4489
- for (const summary of summaries) {
4490
- const recognition = summary.identity.boundaryBinding?.recognition;
4491
- if (recognition === void 0 || recognition === null) {
4492
- continue;
4493
- }
4494
- const entry = counts.get(recognition) ?? emptyCounts();
4495
- entry.bound += 1;
4496
- if (import_behavioral_ir.BOUNDARY_ROLE[summary.kind] === "provider") {
4497
- entry.providers += 1;
4498
- }
4499
- if (summary.transitions.length > 0) {
4500
- entry.withBehavior += 1;
4501
- }
4502
- counts.set(recognition, entry);
4503
- }
4504
- return counts;
4505
- }
4506
4456
  function helpersWithNoCall(patterns, matched, projectRoot) {
4507
4457
  const missing = [];
4508
4458
  for (const pattern of patterns) {
@@ -4523,11 +4473,15 @@ function shortModule(module2, projectRoot) {
4523
4473
  return import_node_path2.default.relative(projectRoot, module2);
4524
4474
  }
4525
4475
  function buildExtractionReport(args) {
4526
- const bySummary = summaryCountsByPack(args.summaries);
4476
+ const bySummary = (0, import_extractor3.summaryCountsByPack)(args.summaries);
4527
4477
  const packFunnels = args.packs.map((pack) => {
4528
4478
  const gates = packIsUngated(pack) ? [] : collectPackGates(pack);
4529
- const tally = args.tallies.get(pack.name) ?? emptyTally();
4530
- const counted = bySummary.get(pack.name) ?? emptyCounts();
4479
+ const tally = args.tallies.get(pack.name) ?? (0, import_extractor3.emptyTally)();
4480
+ const counted = bySummary.get(pack.name) ?? {
4481
+ bound: 0,
4482
+ providers: 0,
4483
+ withBehavior: 0
4484
+ };
4531
4485
  return {
4532
4486
  pack: pack.name,
4533
4487
  version: pack.version ?? null,
@@ -4756,13 +4710,26 @@ function propertiesReached(object, resolution, seen) {
4756
4710
  }
4757
4711
  function writtenNodeOf(value, resolution) {
4758
4712
  const written = factKeyOf(value);
4759
- if (import_ts_morph18.Node.isCallExpression(written) || import_ts_morph18.Node.isNewExpression(written)) {
4713
+ if (import_ts_morph18.Node.isNewExpression(written)) {
4760
4714
  return written;
4761
4715
  }
4716
+ if (import_ts_morph18.Node.isCallExpression(written)) {
4717
+ return writtenThroughCall(written, resolution);
4718
+ }
4762
4719
  if (resolution === void 0 || !couldNameAValue(written)) {
4763
4720
  return null;
4764
4721
  }
4765
- return resolution.resolveWrittenValue(written);
4722
+ const resolved = resolution.resolveWrittenValue(written);
4723
+ return resolved !== null && import_ts_morph18.Node.isCallExpression(resolved) ? writtenThroughCall(resolved, resolution) : resolved;
4724
+ }
4725
+ function writtenThroughCall(call, resolution) {
4726
+ if (resolution !== void 0 && resolution.resolveCallable(call.getExpression()) !== null) {
4727
+ const resolved = resolution.resolveWrittenValue(call);
4728
+ if (resolved !== null) {
4729
+ return resolved;
4730
+ }
4731
+ }
4732
+ return call;
4766
4733
  }
4767
4734
  function stringValueOf(value, resolution) {
4768
4735
  const written = factKeyOf(value);
@@ -4824,7 +4791,7 @@ function discoverClientCalls(sourceFile, match, kind, resolution) {
4824
4791
  }
4825
4792
  } else if (import_ts_morph19.Node.isPropertyAccessExpression(callee)) {
4826
4793
  const subject = callee.getExpression();
4827
- if (import_ts_morph19.Node.isIdentifier(subject) && (isClientImport(subject, match, resolution, true) || clientVarNames.has(subject.getText()) || resolvesToKnownInstance(subject, match, resolution))) {
4794
+ if (import_ts_morph19.Node.isIdentifier(subject) && (isClientImport(subject, match, resolution, true) || clientVarNames.has(subject.getText())) || resolvesToKnownInstance(subject, match, resolution)) {
4828
4795
  methodName = callee.getName();
4829
4796
  if (methodFilter === null || methodFilter.has(methodName)) {
4830
4797
  matched = true;
@@ -6428,14 +6395,14 @@ function unitForJsxRoute(node, match, routeNames, kind, resolution) {
6428
6395
  }
6429
6396
  return routeUnit(node, kind, match.method, resolved.path, target, resolution);
6430
6397
  }
6431
- function routeUnit(at, kind, method, path15, target, resolution) {
6432
- const routeInfo = { method, path: path15 };
6398
+ function routeUnit(at, kind, method, path14, target, resolution) {
6399
+ const routeInfo = { method, path: path14 };
6433
6400
  if (target.kind === "unreadable") {
6434
6401
  return {
6435
6402
  func: null,
6436
6403
  announcedAt: at,
6437
6404
  kind,
6438
- name: path15,
6405
+ name: path14,
6439
6406
  nameKind: "label",
6440
6407
  routeInfo
6441
6408
  };
@@ -6634,7 +6601,6 @@ function labelOfExpression(node) {
6634
6601
  }
6635
6602
 
6636
6603
  // src/discovery/packageExports.ts
6637
- var import_node_fs3 = __toESM(require("fs"), 1);
6638
6604
  var import_ts_morph31 = require("ts-morph");
6639
6605
 
6640
6606
  // src/packageExports.ts
@@ -6663,7 +6629,8 @@ function resolvePackageExports(packageJsonPath, opts = {}) {
6663
6629
  packageName,
6664
6630
  subPath,
6665
6631
  exportPathPrefix: subPathToPrefix(subPath),
6666
- sourceFile: src
6632
+ sourceFile: src,
6633
+ publishedFile: import_node_path5.default.resolve(pkgDir, distPath)
6667
6634
  });
6668
6635
  };
6669
6636
  const exportsField = pkg.exports;
@@ -6683,6 +6650,53 @@ function resolvePackageExports(packageJsonPath, opts = {}) {
6683
6650
  }
6684
6651
  return { packageName, entries, warnings };
6685
6652
  }
6653
+ var packageExportsCache = /* @__PURE__ */ new Map();
6654
+ var PATH_STAMP_SEPARATOR = "";
6655
+ function packageJsonStamp(packageJsonPath) {
6656
+ try {
6657
+ const stat = import_node_fs2.default.statSync(packageJsonPath);
6658
+ return `${stat.mtimeMs}:${stat.size}`;
6659
+ } catch {
6660
+ return "unreadable";
6661
+ }
6662
+ }
6663
+ function resolvePackageExportsCached(packageJsonPath) {
6664
+ const stamp2 = packageJsonStamp(packageJsonPath);
6665
+ const key = `${packageJsonPath}${PATH_STAMP_SEPARATOR}${stamp2}`;
6666
+ const cached = packageExportsCache.get(key);
6667
+ if (cached !== void 0) {
6668
+ return cached;
6669
+ }
6670
+ const fresh = resolvePackageExports(packageJsonPath);
6671
+ packageExportsCache.set(key, fresh);
6672
+ return fresh;
6673
+ }
6674
+ function namedPackageDirAbove(start) {
6675
+ let at = import_node_path5.default.resolve(start);
6676
+ for (let up = 0; up < 12; up += 1) {
6677
+ if (packageNameAt(at) !== null) {
6678
+ return at;
6679
+ }
6680
+ const parent = import_node_path5.default.dirname(at);
6681
+ if (parent === at) {
6682
+ break;
6683
+ }
6684
+ at = parent;
6685
+ }
6686
+ return null;
6687
+ }
6688
+ function packageNameAt(dir) {
6689
+ try {
6690
+ const manifest = JSON.parse(
6691
+ import_node_fs2.default.readFileSync(import_node_path5.default.join(dir, "package.json"), "utf8")
6692
+ );
6693
+ if (typeof manifest.name === "string" && manifest.name.length > 0) {
6694
+ return manifest.name;
6695
+ }
6696
+ } catch {
6697
+ }
6698
+ return null;
6699
+ }
6686
6700
  function pickConditional(value, warnings, key) {
6687
6701
  if (value === void 0 || value === null) {
6688
6702
  if (value === null) {
@@ -6732,8 +6746,55 @@ function resolveSourceFor(pkgDir, distPath, srcRoot) {
6732
6746
  return null;
6733
6747
  }
6734
6748
 
6749
+ // src/resolve/sourceDeclaration.ts
6750
+ var import_node_path6 = __toESM(require("path"), 1);
6751
+ function sourceDeclarationsBehind(decl, resolution, visited = /* @__PURE__ */ new Set()) {
6752
+ const file = decl.getSourceFile();
6753
+ if (!file.isDeclarationFile() || file.isInNodeModules()) {
6754
+ return [decl];
6755
+ }
6756
+ const packageDir = namedPackageDirAbove(import_node_path6.default.dirname(file.getFilePath()));
6757
+ if (packageDir === null || visited.has(packageDir)) {
6758
+ return [decl];
6759
+ }
6760
+ visited.add(packageDir);
6761
+ const project = file.getProject();
6762
+ const { entries } = resolvePackageExportsCached(
6763
+ import_node_path6.default.join(packageDir, "package.json")
6764
+ );
6765
+ const found = [];
6766
+ for (const entry of entries) {
6767
+ const published = sourceFileFor(project, entry.publishedFile);
6768
+ const source = sourceFileFor(project, entry.sourceFile);
6769
+ if (published === void 0 || source === void 0) {
6770
+ continue;
6771
+ }
6772
+ for (const [name, nodes] of resolution.exportsOf(published)) {
6773
+ if (!nodes.some((node) => sameDeclaration(node, decl))) {
6774
+ continue;
6775
+ }
6776
+ for (const behind of resolution.exportsOf(source).get(name) ?? []) {
6777
+ for (const candidate of sourceDeclarationsBehind(
6778
+ behind,
6779
+ resolution,
6780
+ visited
6781
+ )) {
6782
+ if (!found.includes(candidate)) {
6783
+ found.push(candidate);
6784
+ }
6785
+ }
6786
+ }
6787
+ }
6788
+ }
6789
+ return found.length > 0 ? found : [decl];
6790
+ }
6791
+ function sameDeclaration(a, b) {
6792
+ return a === b || a.getSourceFile().getFilePath() === b.getSourceFile().getFilePath() && a.getStart() === b.getStart() && a.getEnd() === b.getEnd();
6793
+ }
6794
+
6735
6795
  // src/discovery/factorySurface.ts
6736
6796
  var import_ts_morph30 = require("ts-morph");
6797
+ var MAX_BUILDER_HOPS = 2;
6737
6798
  function surfaceMethods(decl) {
6738
6799
  if (import_ts_morph30.Node.isClassDeclaration(decl)) {
6739
6800
  return surfaceClassMethods(decl);
@@ -6773,16 +6834,20 @@ function surfaceClassMethods(cls) {
6773
6834
  function surfaceFactoryReturnMethods(fn) {
6774
6835
  const out = [];
6775
6836
  const seen = /* @__PURE__ */ new Set();
6837
+ collectFromFunctionReturns(fn, out, seen, 0);
6838
+ return out;
6839
+ }
6840
+ function collectFromFunctionReturns(fn, out, seen, depth) {
6776
6841
  if (import_ts_morph30.Node.isArrowFunction(fn)) {
6777
6842
  const body2 = fn.getBody();
6778
6843
  if (import_ts_morph30.Node.isExpression(body2)) {
6779
- collectFromObjectLiteral(body2, out, seen);
6780
- return out;
6844
+ collectFromReturnValue(body2, out, seen, depth);
6845
+ return;
6781
6846
  }
6782
6847
  }
6783
6848
  const body = fn.getBody?.();
6784
6849
  if (body === void 0) {
6785
- return out;
6850
+ return;
6786
6851
  }
6787
6852
  const returns = [];
6788
6853
  body.forEachDescendant((node, traversal) => {
@@ -6799,9 +6864,53 @@ function surfaceFactoryReturnMethods(fn) {
6799
6864
  if (expr === void 0) {
6800
6865
  continue;
6801
6866
  }
6867
+ collectFromReturnValue(expr, out, seen, depth);
6868
+ }
6869
+ }
6870
+ function collectFromReturnValue(node, out, seen, depth) {
6871
+ const expr = peelParens(node);
6872
+ if (import_ts_morph30.Node.isObjectLiteralExpression(expr)) {
6802
6873
  collectFromObjectLiteral(expr, out, seen);
6874
+ return;
6803
6875
  }
6804
- return out;
6876
+ if (depth >= MAX_BUILDER_HOPS || !import_ts_morph30.Node.isCallExpression(expr)) {
6877
+ return;
6878
+ }
6879
+ const callee = expr.getExpression();
6880
+ if (!import_ts_morph30.Node.isIdentifier(callee)) {
6881
+ return;
6882
+ }
6883
+ const helper = sameFileFunctionBehind(callee);
6884
+ if (helper !== null) {
6885
+ collectFromFunctionReturns(helper, out, seen, depth + 1);
6886
+ }
6887
+ }
6888
+ function sameFileFunctionBehind(id) {
6889
+ const symbol = id.getSymbol();
6890
+ if (symbol === void 0) {
6891
+ return null;
6892
+ }
6893
+ return functionAmong(
6894
+ symbol.getDeclarations(),
6895
+ id.getSourceFile().getFilePath()
6896
+ );
6897
+ }
6898
+ function functionAmong(declarations, filePath) {
6899
+ for (const decl of declarations) {
6900
+ if (decl.getSourceFile().getFilePath() !== filePath) {
6901
+ continue;
6902
+ }
6903
+ if (import_ts_morph30.Node.isFunctionDeclaration(decl)) {
6904
+ return decl;
6905
+ }
6906
+ if (import_ts_morph30.Node.isVariableDeclaration(decl)) {
6907
+ const init = decl.getInitializer();
6908
+ if (init !== void 0 && (import_ts_morph30.Node.isArrowFunction(init) || import_ts_morph30.Node.isFunctionExpression(init))) {
6909
+ return init;
6910
+ }
6911
+ }
6912
+ }
6913
+ return null;
6805
6914
  }
6806
6915
  function collectFromObjectLiteral(node, out, seen) {
6807
6916
  const e = peelParens(node);
@@ -6836,31 +6945,31 @@ function collectFromObjectLiteral(node, out, seen) {
6836
6945
  out.push({ func: v3, name });
6837
6946
  }
6838
6947
  }
6948
+ if (import_ts_morph30.Node.isShorthandPropertyAssignment(prop)) {
6949
+ collectShorthandFunction(prop, out, seen);
6950
+ }
6839
6951
  }
6840
6952
  }
6841
-
6842
- // src/discovery/packageExports.ts
6843
- var packageExportsCache = /* @__PURE__ */ new Map();
6844
- var PATH_STAMP_SEPARATOR = "";
6845
- function packageJsonStamp(packageJsonPath) {
6846
- try {
6847
- const stat = import_node_fs3.default.statSync(packageJsonPath);
6848
- return `${stat.mtimeMs}:${stat.size}`;
6849
- } catch {
6850
- return "unreadable";
6953
+ function collectShorthandFunction(prop, out, seen) {
6954
+ const name = prop.getName();
6955
+ if (seen.has(name)) {
6956
+ return;
6851
6957
  }
6852
- }
6853
- function resolvePackageExportsCached(packageJsonPath) {
6854
- const stamp = packageJsonStamp(packageJsonPath);
6855
- const key = `${packageJsonPath}${PATH_STAMP_SEPARATOR}${stamp}`;
6856
- const cached = packageExportsCache.get(key);
6857
- if (cached !== void 0) {
6858
- return cached;
6958
+ const symbol = prop.getValueSymbol();
6959
+ if (symbol === void 0) {
6960
+ return;
6961
+ }
6962
+ const fn = functionAmong(
6963
+ symbol.getDeclarations(),
6964
+ prop.getSourceFile().getFilePath()
6965
+ );
6966
+ if (fn !== null) {
6967
+ seen.add(name);
6968
+ out.push({ func: fn, name });
6859
6969
  }
6860
- const fresh = resolvePackageExports(packageJsonPath);
6861
- packageExportsCache.set(key, fresh);
6862
- return fresh;
6863
6970
  }
6971
+
6972
+ // src/discovery/packageExports.ts
6864
6973
  function discoverPackageExports(sourceFile, match, kind, resolution) {
6865
6974
  if (match.packageJsonPath === void 0) {
6866
6975
  return [];
@@ -6895,7 +7004,10 @@ function discoverPackageExports(sourceFile, match, kind, resolution) {
6895
7004
  if (seenNames.has(key)) {
6896
7005
  continue;
6897
7006
  }
6898
- for (const decl of decls) {
7007
+ const candidates = decls.flatMap(
7008
+ (decl) => sourceDeclarationsBehind(decl, resolution)
7009
+ );
7010
+ for (const decl of candidates) {
6899
7011
  if (import_ts_morph31.Node.isVariableDeclaration(decl)) {
6900
7012
  const init = decl.getInitializer();
6901
7013
  if (init !== void 0 && (import_ts_morph31.Node.isArrowFunction(init) || import_ts_morph31.Node.isFunctionExpression(init))) {
@@ -7048,7 +7160,7 @@ function attributedCalls(sourceFile, packages, resolution) {
7048
7160
  return null;
7049
7161
  }
7050
7162
  if (!methodComesFromSource(callee)) {
7051
- return subject;
7163
+ return null;
7052
7164
  }
7053
7165
  return {
7054
7166
  packageName: subject.packageName,
@@ -7162,12 +7274,12 @@ function originEndOf(flattened) {
7162
7274
  const slash = flattened.indexOf("/", opener[0].length);
7163
7275
  return slash === -1 ? flattened.length : slash;
7164
7276
  }
7165
- function appendPathText(path15, text) {
7277
+ function appendPathText(path14, text) {
7166
7278
  const idx = text.search(/[?#]/);
7167
7279
  if (idx === -1) {
7168
- return { path: path15 + text, stop: false };
7280
+ return { path: path14 + text, stop: false };
7169
7281
  }
7170
- return { path: path15 + text.slice(0, idx), stop: true };
7282
+ return { path: path14 + text.slice(0, idx), stop: true };
7171
7283
  }
7172
7284
  function parseAbsoluteUrl(text) {
7173
7285
  try {
@@ -7191,8 +7303,8 @@ function pathnameOfAbsoluteLiteral(text) {
7191
7303
  return stripQueryAndFragment(stripOriginManually(text));
7192
7304
  }
7193
7305
  function pathFromLiteralUrl(text) {
7194
- const path15 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
7195
- return path15 === "" ? void 0 : path15;
7306
+ const path14 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
7307
+ return path14 === "" ? void 0 : path14;
7196
7308
  }
7197
7309
  function pathFromTemplateUrl(arg, resolution) {
7198
7310
  const headText = arg.getHead().getLiteralText();
@@ -7200,7 +7312,7 @@ function pathFromTemplateUrl(arg, resolution) {
7200
7312
  const tails = spans.map((span) => span.getLiteral().getLiteralText());
7201
7313
  const originEnd = originEndOf([headText, ...tails].join(SUBSTITUTION));
7202
7314
  const head = appendPathText("", headText.slice(originEnd));
7203
- let path15 = head.path;
7315
+ let path14 = head.path;
7204
7316
  let stop = head.stop;
7205
7317
  let at = headText.length;
7206
7318
  for (let i = 0; i < spans.length && !stop; i++) {
@@ -7210,18 +7322,18 @@ function pathFromTemplateUrl(arg, resolution) {
7210
7322
  continue;
7211
7323
  }
7212
7324
  if (at >= originEnd) {
7213
- path15 += holeText(span.getExpression(), resolution);
7325
+ path14 += holeText(span.getExpression(), resolution);
7214
7326
  }
7215
7327
  at += SUBSTITUTION.length;
7216
7328
  const appended = appendPathText(
7217
- path15,
7329
+ path14,
7218
7330
  tailText.slice(Math.max(0, originEnd - at))
7219
7331
  );
7220
- path15 = appended.path;
7332
+ path14 = appended.path;
7221
7333
  stop = appended.stop;
7222
7334
  at += tailText.length;
7223
7335
  }
7224
- return path15 === "" ? void 0 : path15;
7336
+ return path14 === "" ? void 0 : path14;
7225
7337
  }
7226
7338
  function pathFromUrlNode(node, resolution) {
7227
7339
  if (import_ts_morph33.Node.isStringLiteral(node)) {
@@ -7291,12 +7403,12 @@ function discoverRegistrationCalls(sourceFile, match, kind, bindingExtraction, r
7291
7403
  match.importName,
7292
7404
  resolution
7293
7405
  );
7294
- if (registrationSubjects.size === 0 && !storeCanFindSubjects(sourceFile, match, resolution)) {
7295
- return results;
7296
- }
7297
7406
  const registrationMethods = match.registrationChain.map(
7298
7407
  (c) => c.startsWith(".") ? c.slice(1) : c
7299
7408
  );
7409
+ if (registrationSubjects.size === 0 && !storeCanFindSubjects(sourceFile, match, resolution, registrationMethods)) {
7410
+ return results;
7411
+ }
7300
7412
  sourceFile.forEachDescendant((node) => {
7301
7413
  if (!import_ts_morph34.Node.isCallExpression(node)) {
7302
7414
  return;
@@ -7490,8 +7602,54 @@ function receiverOf(subject, subjects, match, resolution) {
7490
7602
  );
7491
7603
  return candidates > 1 ? { kind: "several", candidates } : NOT_A_ROUTABLE;
7492
7604
  }
7493
- function storeCanFindSubjects(sourceFile, match, resolution) {
7494
- return resolution !== void 0 && importDeclarationsOf(sourceFile, [match.importModule]).length > 0;
7605
+ function storeCanFindSubjects(sourceFile, match, resolution, methods = []) {
7606
+ if (resolution === void 0) {
7607
+ return false;
7608
+ }
7609
+ if (importDeclarationsOf(sourceFile, [match.importModule]).length > 0) {
7610
+ return true;
7611
+ }
7612
+ return methods.length > 0 && callsOnCallResult(sourceFile, methods);
7613
+ }
7614
+ function callsOnCallResult(sourceFile, methods) {
7615
+ let called = methodsCalledOnCallResults.get(sourceFile);
7616
+ if (called === void 0) {
7617
+ called = collectMethodsCalledOnCallResults(sourceFile);
7618
+ methodsCalledOnCallResults.set(sourceFile, called);
7619
+ }
7620
+ return methods.some((method) => called.has(method));
7621
+ }
7622
+ var methodsCalledOnCallResults = /* @__PURE__ */ new WeakMap();
7623
+ function collectMethodsCalledOnCallResults(sourceFile) {
7624
+ const setToCalls = /* @__PURE__ */ new Set();
7625
+ const calledOn = /* @__PURE__ */ new Map();
7626
+ sourceFile.forEachDescendant((node) => {
7627
+ if (import_ts_morph34.Node.isVariableDeclaration(node)) {
7628
+ const init = node.getInitializer();
7629
+ if (init !== void 0 && import_ts_morph34.Node.isCallExpression(init)) {
7630
+ setToCalls.add(node.getName());
7631
+ }
7632
+ return;
7633
+ }
7634
+ if (!import_ts_morph34.Node.isCallExpression(node)) {
7635
+ return;
7636
+ }
7637
+ const callee = node.getExpression();
7638
+ if (!import_ts_morph34.Node.isPropertyAccessExpression(callee) || !import_ts_morph34.Node.isIdentifier(callee.getExpression())) {
7639
+ return;
7640
+ }
7641
+ const receiver = callee.getExpression().getText();
7642
+ const methods = calledOn.get(receiver) ?? /* @__PURE__ */ new Set();
7643
+ methods.add(callee.getName());
7644
+ calledOn.set(receiver, methods);
7645
+ });
7646
+ const called = /* @__PURE__ */ new Set();
7647
+ for (const name of setToCalls) {
7648
+ for (const method of calledOn.get(name) ?? []) {
7649
+ called.add(method);
7650
+ }
7651
+ }
7652
+ return called;
7495
7653
  }
7496
7654
  function registrationSubjectsOf(sourceFile, importModule, importName, resolution) {
7497
7655
  const importedLocalName = resolveImportedLocalName(
@@ -7556,9 +7714,33 @@ function registrationSubjectIdsOf(sourceFile, matches, resolution) {
7556
7714
  ).values()) {
7557
7715
  ids.add(nodeId(node));
7558
7716
  }
7717
+ for (const node of unboundConstructionsOf(
7718
+ sourceFile,
7719
+ match.importModule,
7720
+ match.importName
7721
+ )) {
7722
+ ids.add(nodeId(node));
7723
+ }
7559
7724
  }
7560
7725
  return ids;
7561
7726
  }
7727
+ function unboundConstructionsOf(sourceFile, importModule, importName) {
7728
+ const importedLocalName = resolveImportedLocalName(
7729
+ sourceFile,
7730
+ importModule,
7731
+ importName
7732
+ );
7733
+ if (importedLocalName === null) {
7734
+ return [];
7735
+ }
7736
+ const constructions = [];
7737
+ sourceFile.forEachDescendant((node) => {
7738
+ if ((import_ts_morph34.Node.isCallExpression(node) || import_ts_morph34.Node.isNewExpression(node)) && node.getExpression().getText() === importedLocalName && !import_ts_morph34.Node.isVariableDeclaration(node.getParent())) {
7739
+ constructions.push(node);
7740
+ }
7741
+ });
7742
+ return constructions;
7743
+ }
7562
7744
  function discoverMountEdges(sourceFile, match, mount, knownSubjectIds, resolution) {
7563
7745
  const subjects = registrationSubjectsOf(
7564
7746
  sourceFile,
@@ -7566,7 +7748,7 @@ function discoverMountEdges(sourceFile, match, mount, knownSubjectIds, resolutio
7566
7748
  match.importName,
7567
7749
  resolution
7568
7750
  );
7569
- if (subjects.size === 0 && !storeCanFindSubjects(sourceFile, match, resolution)) {
7751
+ if (subjects.size === 0 && !storeCanFindSubjects(sourceFile, match, resolution, [mount.method])) {
7570
7752
  return [];
7571
7753
  }
7572
7754
  const edges = [];
@@ -7647,11 +7829,11 @@ function extractRouteInfoFromBinding(call, methodName, binding, resolution) {
7647
7829
  return null;
7648
7830
  }
7649
7831
  const method2 = stringProperty(routeObject, binding.method.property);
7650
- const path16 = stringProperty(routeObject, binding.path.property);
7651
- if (method2 === null || path16 === null) {
7832
+ const path15 = stringProperty(routeObject, binding.path.property);
7833
+ if (method2 === null || path15 === null) {
7652
7834
  return null;
7653
7835
  }
7654
- return { method: method2.toUpperCase(), path: path16 };
7836
+ return { method: method2.toUpperCase(), path: path15 };
7655
7837
  }
7656
7838
  if (binding.method.type !== "fromRegistration" || binding.path.type !== "fromArgument") {
7657
7839
  return null;
@@ -7668,8 +7850,8 @@ function extractRouteInfoFromBinding(call, methodName, binding, resolution) {
7668
7850
  method = arg.getLiteralValue().toUpperCase();
7669
7851
  }
7670
7852
  const pathArg = call.getArguments()[binding.path.position];
7671
- const path15 = pathArg === void 0 ? void 0 : pathFromArgument(pathArg, resolution);
7672
- return path15 === void 0 ? null : { method, path: path15 };
7853
+ const path14 = pathArg === void 0 ? void 0 : pathFromArgument(pathArg, resolution);
7854
+ return path14 === void 0 ? null : { method, path: path14 };
7673
7855
  }
7674
7856
 
7675
7857
  // src/discovery/registrationLoop.ts
@@ -7801,7 +7983,7 @@ function bodyCallsOnRoutable(loop, guard, resolution) {
7801
7983
  }
7802
7984
  function readRouteSpec(element, shape, resolution) {
7803
7985
  let method = null;
7804
- let path15 = null;
7986
+ let path14 = null;
7805
7987
  let handler = null;
7806
7988
  for (const prop of element.getProperties()) {
7807
7989
  if (import_ts_morph35.Node.isMethodDeclaration(prop) && prop.getName() === shape.handlerKey) {
@@ -7819,16 +8001,16 @@ function readRouteSpec(element, shape, resolution) {
7819
8001
  if (name === shape.methodKey) {
7820
8002
  method = readStringLiteralValue(init);
7821
8003
  } else if (name === shape.pathKey) {
7822
- path15 = readStringLiteralValue(init);
8004
+ path14 = readStringLiteralValue(init);
7823
8005
  } else if (name === shape.handlerKey) {
7824
8006
  const func = functionValueOf(init, resolution);
7825
8007
  handler = func === null ? null : { func, name: handlerName(init) };
7826
8008
  }
7827
8009
  }
7828
- if (method === null || path15 === null || handler === null) {
8010
+ if (method === null || path14 === null || handler === null) {
7829
8011
  return null;
7830
8012
  }
7831
- return { method, path: path15, handler: handler.func, handlerName: handler.name };
8013
+ return { method, path: path14, handler: handler.func, handlerName: handler.name };
7832
8014
  }
7833
8015
  function readStringLiteralValue(node) {
7834
8016
  if (import_ts_morph35.Node.isStringLiteral(node) || import_ts_morph35.Node.isNoSubstitutionTemplateLiteral(node)) {
@@ -7841,12 +8023,13 @@ function handlerName(value) {
7841
8023
  }
7842
8024
 
7843
8025
  // src/discovery/registrationTemplate.ts
7844
- var import_node_path6 = __toESM(require("path"), 1);
8026
+ var import_node_path7 = __toESM(require("path"), 1);
7845
8027
  var import_ts_morph37 = require("ts-morph");
7846
8028
  var import_resolution4 = require("@suss/resolution");
7847
8029
 
7848
8030
  // src/resolve/unfollowedCall.ts
7849
8031
  var import_ts_morph36 = require("ts-morph");
8032
+ var import_behavioral_ir3 = require("@suss/behavioral-ir");
7850
8033
  function isInExternalCode(sourceFile) {
7851
8034
  if (sourceFile.isDeclarationFile()) {
7852
8035
  return true;
@@ -7900,6 +8083,19 @@ function declarationsBehind(symbol) {
7900
8083
  function isParameterOf(declaration, scanning) {
7901
8084
  return import_ts_morph36.Node.isParameterDeclaration(declaration) && declaration.getParent() === scanning;
7902
8085
  }
8086
+ function parameterIndexOf(declarations, scanning) {
8087
+ if (scanning === void 0) {
8088
+ return void 0;
8089
+ }
8090
+ const params = scanning.getParameters();
8091
+ for (const declaration of declarations) {
8092
+ const index = params.findIndex((param) => param === declaration);
8093
+ if (index !== -1) {
8094
+ return index;
8095
+ }
8096
+ }
8097
+ return void 0;
8098
+ }
7903
8099
  function classifyStop(declarations, scanning) {
7904
8100
  if (declarations.length === 0) {
7905
8101
  return "noDeclaration";
@@ -7920,36 +8116,6 @@ function classifyStop(declarations, scanning) {
7920
8116
  }
7921
8117
  return "unsettledValue";
7922
8118
  }
7923
- var RECORDED = {
7924
- noBody: true,
7925
- unsettledValue: true,
7926
- multipleSources: true,
7927
- outsideRun: false,
7928
- noDeclaration: false,
7929
- callerSupplied: false,
7930
- multipleReceivers: true
7931
- };
7932
- function worthRecording(reason) {
7933
- return RECORDED[reason];
7934
- }
7935
- var STOP_SENTENCE = {
7936
- noBody: ({ callee }) => `The call to ${callee} lands on a declaration with no body, so whatever runs there is missing from this summary`,
7937
- unsettledValue: ({ callee }) => `The call to ${callee} goes through a value this run could not settle, so whatever runs there is missing from this summary`,
7938
- multipleSources: ({ callee }) => `The call to ${callee} reaches a value with more than one possible source, so whatever runs there is missing from this summary`,
7939
- outsideRun: ({ callee }) => `The call to ${callee} lands in a package whose source is not in this run, so whatever runs there is missing from this summary`,
7940
- noDeclaration: ({ callee }) => `The call to ${callee} has no declaration this run could find, so whatever runs there is missing from this summary`,
7941
- callerSupplied: ({ callee }) => `The call to ${callee} runs the function this unit's caller passed in, so what happens there is decided at the call site`,
7942
- multipleReceivers: ({ callee, candidates }) => `The call to ${callee} is made on a receiver this run reads as ${candidates ?? "several"} different values, so nothing says which one it registers on and the registration is left out`
7943
- };
7944
- function unfollowedCallGap(stop) {
7945
- return {
7946
- type: "unfollowedCall",
7947
- conditions: [],
7948
- consequence: "unknown",
7949
- description: STOP_SENTENCE[stop.reason](stop),
7950
- callee: stop.callee
7951
- };
7952
- }
7953
8119
 
7954
8120
  // src/discovery/registrationTemplate.ts
7955
8121
  function discoverRegistrationTemplates(sourceFile, match, kind, resolution, mountPrefixes) {
@@ -7959,8 +8125,8 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution, moun
7959
8125
  const subject = subjectOfCall(sourceFile, args, match, resolution);
7960
8126
  const prefix = subject === void 0 ? "" : mountPrefixes?.effectivePrefixFor(subject) ?? "";
7961
8127
  for (const reg of match.registrations) {
7962
- const path15 = substitutePath(reg.pathTemplate, args);
7963
- if (path15 === null) {
8128
+ const path14 = substitutePath(reg.pathTemplate, args);
8129
+ if (path14 === null) {
7964
8130
  continue;
7965
8131
  }
7966
8132
  const handler = resolveHandler(reg.handlerArg, args, resolution);
@@ -7974,7 +8140,7 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution, moun
7974
8140
  ...subject === void 0 ? {} : { registrationSubjectId: nodeId(subject) },
7975
8141
  routeInfo: {
7976
8142
  method: reg.method.toUpperCase(),
7977
- path: prefix === "" ? path15 : (0, import_resolution4.joinMountedPath)(prefix, path15)
8143
+ path: prefix === "" ? path14 : (0, import_resolution4.joinMountedPath)(prefix, path14)
7978
8144
  }
7979
8145
  });
7980
8146
  }
@@ -8007,7 +8173,7 @@ function subjectOfCall(sourceFile, args, match, resolution) {
8007
8173
  }
8008
8174
  function helperCallsOf(sourceFile, match, resolution) {
8009
8175
  const module2 = match.importModule;
8010
- if (module2 !== void 0 && !import_node_path6.default.isAbsolute(module2)) {
8176
+ if (module2 !== void 0 && !import_node_path7.default.isAbsolute(module2)) {
8011
8177
  const store = resolution ?? new ResolutionStore();
8012
8178
  return callsResolvingTo(sourceFile, store, {
8013
8179
  module: module2,
@@ -8453,24 +8619,24 @@ function declName(decl) {
8453
8619
 
8454
8620
  // src/parameterReads.ts
8455
8621
  function pathAbove(reference) {
8456
- const path15 = [];
8622
+ const path14 = [];
8457
8623
  let current2 = reference;
8458
8624
  for (; ; ) {
8459
8625
  const parent = current2.getParent();
8460
8626
  if (parent !== void 0 && import_ts_morph40.Node.isPropertyAccessExpression(parent) && parent.getExpression() === current2) {
8461
- path15.push(parent.getName());
8627
+ path14.push(parent.getName());
8462
8628
  current2 = parent;
8463
8629
  continue;
8464
8630
  }
8465
8631
  if (parent !== void 0 && import_ts_morph40.Node.isElementAccessExpression(parent) && parent.getExpression() === current2) {
8466
8632
  const argument = parent.getArgumentExpression();
8467
8633
  if (argument !== void 0 && import_ts_morph40.Node.isStringLiteral(argument)) {
8468
- path15.push(argument.getLiteralValue());
8634
+ path14.push(argument.getLiteralValue());
8469
8635
  current2 = parent;
8470
8636
  continue;
8471
8637
  }
8472
8638
  }
8473
- return path15;
8639
+ return path14;
8474
8640
  }
8475
8641
  }
8476
8642
  function declaresParameterOf(node, func) {
@@ -8518,7 +8684,7 @@ function parameterReads(func, parameterNames2, barriers = NO_BARRIERS) {
8518
8684
 
8519
8685
  // src/resolve/readName.ts
8520
8686
  var import_ts_morph41 = require("ts-morph");
8521
- var import_behavioral_ir3 = require("@suss/behavioral-ir");
8687
+ var import_behavioral_ir4 = require("@suss/behavioral-ir");
8522
8688
  var MAX_HELPER_HOPS = 2;
8523
8689
  var UNNAMED_HOLE = "param";
8524
8690
  function readName(expr, options = {}) {
@@ -8533,7 +8699,7 @@ function readName(expr, options = {}) {
8533
8699
  return name;
8534
8700
  }
8535
8701
  const reference = referenceIn(expr);
8536
- return reference === null ? null : (0, import_behavioral_ir3.referenceName)(reference);
8702
+ return reference === null ? null : (0, import_behavioral_ir4.referenceName)(reference);
8537
8703
  }
8538
8704
  function referenceIn(expr) {
8539
8705
  const fromParameter = parameterPath(expr);
@@ -8782,7 +8948,7 @@ function boundOnce(expr, ctx) {
8782
8948
  }
8783
8949
  function readOrHole(expr, ctx) {
8784
8950
  const name = read(expr, ctx);
8785
- return name ?? (0, import_behavioral_ir3.patternHole)(holeName(expr, ctx));
8951
+ return name ?? (0, import_behavioral_ir4.patternHole)(holeName(expr, ctx));
8786
8952
  }
8787
8953
  function holeName(expr, ctx) {
8788
8954
  const bound = boundOnce(expr, ctx);
@@ -9169,142 +9335,40 @@ function importSpecifierMatches(decl, expectedModule) {
9169
9335
  return import_ts_morph43.Node.isImportDeclaration(importDecl) && importDecl.getModuleSpecifierValue() === expectedModule;
9170
9336
  }
9171
9337
  function extractInvocationEffects(func, barriers = NO_BARRIERS) {
9172
- const results = [];
9338
+ const calls = [];
9173
9339
  func.forEachDescendant((node, traversal) => {
9174
- if (isDescentStop(node, func, barriers)) {
9340
+ if (isDescentStop(node, func, barriers) || import_ts_morph43.Node.isDecorator(node)) {
9175
9341
  traversal.skip();
9176
9342
  return;
9177
9343
  }
9178
- if (import_ts_morph43.Node.isExpressionStatement(node)) {
9179
- const { call, async } = unwrapCall(node.getExpression());
9180
- if (call !== null) {
9181
- const preconditions = collectPreconditions(node, func);
9182
- results.push({
9183
- effect: {
9184
- type: "invocation",
9185
- callee: call.getExpression().getText(),
9186
- args: extractArgs(call),
9187
- async,
9188
- ...preconditions.length > 0 ? { preconditions } : {}
9189
- },
9190
- line: startLineOf(node),
9191
- neverTerminal: false,
9192
- alwaysRuns: runsOnEveryPath(node, func)
9193
- });
9194
- }
9195
- return;
9196
- }
9197
- if (import_ts_morph43.Node.isSpreadElement(node)) {
9198
- const parent = node.getParent();
9199
- if (parent !== void 0 && (import_ts_morph43.Node.isArrayLiteralExpression(parent) || import_ts_morph43.Node.isObjectLiteralExpression(parent))) {
9200
- const { call, async } = unwrapCall(node.getExpression());
9201
- if (call !== null) {
9202
- const preconditions = collectPreconditions(node, func);
9203
- results.push({
9204
- effect: {
9205
- type: "invocation",
9206
- callee: call.getExpression().getText(),
9207
- args: extractArgs(call),
9208
- async,
9209
- ...preconditions.length > 0 ? { preconditions } : {}
9210
- },
9211
- line: enclosingStatementLine(node),
9212
- neverTerminal: true,
9213
- alwaysRuns: runsOnEveryPath(node, func)
9214
- });
9215
- }
9216
- }
9217
- return;
9218
- }
9219
- if (import_ts_morph43.Node.isVariableDeclaration(node)) {
9220
- const initializer = node.getInitializer();
9221
- if (initializer !== void 0) {
9222
- const { call, async } = unwrapCall(initializer);
9223
- if (call !== null) {
9224
- const preconditions = collectPreconditions(node, func);
9225
- results.push({
9226
- effect: {
9227
- type: "invocation",
9228
- callee: call.getExpression().getText(),
9229
- args: extractArgs(call),
9230
- async,
9231
- ...preconditions.length > 0 ? { preconditions } : {}
9232
- },
9233
- line: enclosingStatementLine(node),
9234
- neverTerminal: true,
9235
- alwaysRuns: runsOnEveryPath(node, func)
9236
- });
9237
- }
9238
- }
9239
- return;
9240
- }
9241
- if (import_ts_morph43.Node.isReturnStatement(node)) {
9242
- const returned = node.getExpression();
9243
- const { call, async } = returned === void 0 ? { call: null, async: false } : unwrapCall(returned);
9244
- if (call !== null) {
9245
- const preconditions = collectPreconditions(node, func);
9246
- results.push({
9247
- effect: {
9248
- type: "invocation",
9249
- callee: call.getExpression().getText(),
9250
- args: extractArgs(call),
9251
- async,
9252
- ...preconditions.length > 0 ? { preconditions } : {}
9253
- },
9254
- line: startLineOf(node),
9255
- neverTerminal: false,
9256
- alwaysRuns: runsOnEveryPath(node, func),
9257
- node: call
9258
- });
9259
- }
9260
- return;
9261
- }
9262
- if (import_ts_morph43.Node.isArrowFunction(node)) {
9263
- const body = node.getBody();
9264
- const { call, async } = import_ts_morph43.Node.isExpression(body) ? unwrapCall(body) : { call: null, async: false };
9265
- if (call !== null) {
9266
- const preconditions = collectPreconditions(node, func);
9267
- results.push({
9268
- effect: {
9269
- type: "invocation",
9270
- callee: call.getExpression().getText(),
9271
- args: extractArgs(call),
9272
- async: async || node.isAsync(),
9273
- ...preconditions.length > 0 ? { preconditions } : {}
9274
- },
9275
- line: startLineOf(node),
9276
- neverTerminal: false,
9277
- alwaysRuns: runsOnEveryPath(node, func),
9278
- node: call
9279
- });
9280
- }
9281
- return;
9282
- }
9283
9344
  if (import_ts_morph43.Node.isCallExpression(node)) {
9284
- const parent = node.getParent();
9285
- if (parent === void 0) {
9286
- return;
9287
- }
9288
- const isArrayElement = import_ts_morph43.Node.isArrayLiteralExpression(parent);
9289
- const isPropertyValue = import_ts_morph43.Node.isPropertyAssignment(parent) && parent.getInitializer() === node;
9290
- if (isArrayElement || isPropertyValue) {
9291
- const preconditions = collectPreconditions(node, func);
9292
- results.push({
9293
- effect: {
9294
- type: "invocation",
9295
- callee: node.getExpression().getText(),
9296
- args: extractArgs(node),
9297
- async: false,
9298
- ...preconditions.length > 0 ? { preconditions } : {}
9299
- },
9300
- line: enclosingStatementLine(node),
9301
- neverTerminal: true,
9302
- alwaysRuns: runsOnEveryPath(node, func)
9303
- });
9304
- }
9345
+ calls.push(node);
9305
9346
  }
9306
9347
  });
9307
- return results;
9348
+ calls.sort((a, b) => a.getEnd() - b.getEnd());
9349
+ return calls.map((call) => invocationAt(call, func));
9350
+ }
9351
+ function invocationAt(call, func) {
9352
+ const preconditions = collectPreconditions(call, func);
9353
+ return {
9354
+ effect: {
9355
+ type: "invocation",
9356
+ callee: call.getExpression().getText(),
9357
+ args: extractArgs(call),
9358
+ async: isAwaited(call),
9359
+ ...preconditions.length > 0 ? { preconditions } : {}
9360
+ },
9361
+ node: call,
9362
+ line: enclosingStatementLine(call),
9363
+ alwaysRuns: runsOnEveryPath(call, func)
9364
+ };
9365
+ }
9366
+ function isAwaited(call) {
9367
+ let current2 = call.getParent();
9368
+ while (current2 !== void 0 && import_ts_morph43.Node.isParenthesizedExpression(current2)) {
9369
+ current2 = current2.getParent();
9370
+ }
9371
+ return current2 !== void 0 && import_ts_morph43.Node.isAwaitExpression(current2);
9308
9372
  }
9309
9373
  function runInvocationRecognizers(func, recognizers, barriers = NO_BARRIERS, resolveWrittenValue, originatesFrom, anchorCallsOf) {
9310
9374
  if (recognizers.length === 0) {
@@ -9590,7 +9654,7 @@ function withPreconditions(effect, preconditions) {
9590
9654
  }
9591
9655
  return {
9592
9656
  ...effect,
9593
- preconditions: preconditions.map(import_extractor3.rawConditionToPredicate)
9657
+ preconditions: preconditions.map(import_extractor4.rawConditionToPredicate)
9594
9658
  };
9595
9659
  }
9596
9660
  function inlineModuleBinding(ident, depth) {
@@ -9647,22 +9711,6 @@ function enclosingStatementLine(node) {
9647
9711
  }
9648
9712
  return startLineOf(node);
9649
9713
  }
9650
- function unwrapCall(node) {
9651
- if (import_ts_morph43.Node.isAwaitExpression(node)) {
9652
- const inner = node.getExpression();
9653
- if (import_ts_morph43.Node.isCallExpression(inner)) {
9654
- return { call: inner, async: true };
9655
- }
9656
- return { call: null, async: false };
9657
- }
9658
- if (import_ts_morph43.Node.isParenthesizedExpression(node)) {
9659
- return unwrapCall(node.getExpression());
9660
- }
9661
- if (import_ts_morph43.Node.isCallExpression(node)) {
9662
- return { call: node, async: false };
9663
- }
9664
- return { call: null, async: false };
9665
- }
9666
9714
 
9667
9715
  // src/terminals/index.ts
9668
9716
  var import_ts_morph53 = require("ts-morph");
@@ -11692,10 +11740,26 @@ function makeFallthroughTerminal(func) {
11692
11740
  // src/assembly.ts
11693
11741
  var preconditionsOf = (effect) => effect.type === "invocation" ? effect.preconditions : void 0;
11694
11742
  function firesOn(site, branch) {
11695
- if (!(0, import_extractor4.guardsHoldOn)(site.preconditions, branch.conditions)) {
11743
+ if (!(0, import_extractor5.guardsHoldOn)(site.preconditions, branch.conditions)) {
11696
11744
  return false;
11697
11745
  }
11698
- return site.alwaysRuns || (0, import_extractor4.runsBefore)(site.line, branch.terminal.location.end);
11746
+ return site.alwaysRuns || (0, import_extractor5.runsBefore)(site.line, branch.terminal.location.end);
11747
+ }
11748
+ function writesTerminal(call, terminalNodes) {
11749
+ let current2 = call;
11750
+ for (; ; ) {
11751
+ if (terminalNodes.has(current2)) {
11752
+ return true;
11753
+ }
11754
+ const parent = current2.getParent();
11755
+ if (parent === void 0 || !isReceiverOf(current2, parent)) {
11756
+ return false;
11757
+ }
11758
+ current2 = parent;
11759
+ }
11760
+ }
11761
+ function isReceiverOf(node, parent) {
11762
+ return (import_ts_morph54.Node.isPropertyAccessExpression(parent) || import_ts_morph54.Node.isElementAccessExpression(parent) || import_ts_morph54.Node.isCallExpression(parent)) && parent.getExpression() === node;
11699
11763
  }
11700
11764
  var isDefaultConditionList = (conditions) => conditions.length === 0 || conditions.every(
11701
11765
  (c) => c.source === "earlyReturn" || c.source === "earlyThrow"
@@ -11835,11 +11899,8 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
11835
11899
  });
11836
11900
  if (invocations.length > 0) {
11837
11901
  const terminalNodes = new Set(terminals.map(({ node }) => node));
11838
- const terminalLines = new Set(
11839
- distinctBranches.map((b) => b.terminal.location.start)
11840
- );
11841
11902
  const sideEffects = invocations.filter(
11842
- (i) => i.node !== void 0 ? !terminalNodes.has(i.node) : i.neverTerminal || !terminalLines.has(i.line)
11903
+ (i) => !writesTerminal(i.node, terminalNodes)
11843
11904
  );
11844
11905
  for (const branch of distinctBranches) {
11845
11906
  branch.effects = sideEffects.filter(
@@ -11885,9 +11946,9 @@ function createSourceFileLookup(project) {
11885
11946
  if (sf === null) {
11886
11947
  return null;
11887
11948
  }
11888
- const path15 = sf.getFilePath();
11889
- const index = functionsByFile.get(path15) ?? indexFunctions(sf);
11890
- functionsByFile.set(path15, index);
11949
+ const path14 = sf.getFilePath();
11950
+ const index = functionsByFile.get(path14) ?? indexFunctions(sf);
11951
+ functionsByFile.set(path14, index);
11891
11952
  if (location.span !== void 0) {
11892
11953
  const bySpan = index.get(spanKey(location.span));
11893
11954
  if (bySpan !== void 0) {
@@ -11932,397 +11993,47 @@ function indexFunctions(sf) {
11932
11993
  return index;
11933
11994
  }
11934
11995
 
11935
- // src/cache.ts
11936
- var import_node_crypto2 = require("crypto");
11937
- var import_promises2 = __toESM(require("fs/promises"), 1);
11938
- var import_node_path7 = __toESM(require("path"), 1);
11939
- var SCHEMA_VERSION = "6";
11940
- var MAX_ENTRIES = 2;
11941
- var ENTRY_PREFIX = "key-";
11942
- var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
11943
- function createCacheLayer(cacheDir) {
11944
- if (cacheDir === null) {
11945
- return {
11946
- tryHit: async () => null,
11947
- lookup: async () => ({
11948
- kind: "miss",
11949
- diagnostic: { kind: "miss", missReason: "no-manifest" }
11950
- }),
11951
- plan: async () => null,
11952
- write: async () => {
11953
- }
11954
- };
11955
- }
11956
- return {
11957
- async tryHit(input) {
11958
- const result = await this.lookup(input);
11959
- return result.kind === "hit" ? result.summaries : null;
11960
- },
11961
- async lookup(input) {
11962
- const entryDir = entryDirFor(cacheDir, input);
11963
- const manifest = await readManifest(import_node_path7.default.join(entryDir, "manifest.json"));
11964
- if (manifest === null) {
11965
- return missDiag(await describeAbsentEntry(cacheDir));
11966
- }
11967
- const currentTsconfigStamp = await stampTsconfig(input.tsconfigPath);
11968
- if (!fileStampEquals(manifest.tsconfigStamp, currentTsconfigStamp)) {
11969
- return missDiag("tsconfig-changed");
11970
- }
11971
- const currentFiles = await resolveFileStamps(input);
11972
- if (!fileStampsEqual(manifest.files, currentFiles)) {
11973
- return missDiag("files-changed");
11974
- }
11975
- await markUsed(entryDir);
11976
- return {
11977
- kind: "hit",
11978
- summaries: manifest.summaries,
11979
- diagnostic: { kind: "hit" }
11980
- };
11981
- },
11982
- async plan(input) {
11983
- const entryDir = entryDirFor(cacheDir, input);
11984
- const manifest = await readManifest(import_node_path7.default.join(entryDir, "manifest.json"));
11985
- if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
11986
- return null;
11987
- }
11988
- const currentTsconfigStamp = await stampTsconfig(input.tsconfigPath);
11989
- if (!fileStampEquals(manifest.tsconfigStamp, currentTsconfigStamp)) {
11990
- return null;
11991
- }
11992
- await markUsed(entryDir);
11993
- return buildPlan(manifest, await resolveFileStamps(input));
11994
- },
11995
- async write(input, summaries, attribution) {
11996
- const entryDir = entryDirFor(cacheDir, input);
11997
- const previous = await readManifest(import_node_path7.default.join(entryDir, "manifest.json"));
11998
- const tsconfigStamp = await stampTsconfig(input.tsconfigPath);
11999
- const files = await hashStamps(await resolveFileStamps(input), previous);
12000
- const manifest = {
12001
- schemaVersion: SCHEMA_VERSION,
12002
- adapterPacksDigest: input.adapterPacksDigest,
12003
- tsconfigStamp,
12004
- files,
12005
- summaries,
12006
- ...attribution === void 0 ? {} : encodeAttribution(attribution)
12007
- };
12008
- await import_promises2.default.mkdir(entryDir, { recursive: true });
12009
- await import_promises2.default.writeFile(
12010
- import_node_path7.default.join(entryDir, "manifest.json"),
12011
- JSON.stringify(manifest)
12012
- );
12013
- await evictOldEntries(cacheDir, entryDir);
12014
- }
12015
- };
12016
- }
12017
- async function buildPlan(manifest, currentStamps) {
12018
- const stored = new Map(manifest.files.map((f) => [f.path, f]));
12019
- const current2 = new Map(currentStamps.map((f) => [f.path, f]));
12020
- const changed = /* @__PURE__ */ new Set();
12021
- const removed = /* @__PURE__ */ new Set();
12022
- for (const p of stored.keys()) {
12023
- if (!current2.has(p)) {
12024
- removed.add(p);
12025
- }
12026
- }
12027
- const toVerify = [];
12028
- for (const [p, stamp] of current2) {
12029
- const before = stored.get(p);
12030
- if (before === void 0) {
12031
- changed.add(p);
12032
- } else if (!fileStampEquals(before, stamp)) {
12033
- if (before.contentHash === void 0) {
12034
- changed.add(p);
12035
- } else {
12036
- toVerify.push(p);
12037
- }
12038
- }
11996
+ // src/contract.ts
11997
+ var import_ts_morph56 = require("ts-morph");
11998
+ var import_behavioral_ir5 = require("@suss/behavioral-ir");
11999
+ function extractDeclaredBody(node) {
12000
+ if (node === void 0) {
12001
+ return null;
12039
12002
  }
12040
- await Promise.all(
12041
- toVerify.map(async (p) => {
12042
- const hash = await hashFile(p);
12043
- if (hash === null || hash !== stored.get(p)?.contentHash) {
12044
- changed.add(p);
12045
- }
12046
- })
12047
- );
12048
- const roots = /* @__PURE__ */ new Map();
12049
- const rootNames = manifest.roots ?? [];
12050
- const depPaths = manifest.depPaths ?? [];
12051
- let rootsDeclined = 0;
12052
- rootNames.forEach((rootPath, i) => {
12053
- const meta = manifest.rootMeta?.[i];
12054
- if (meta === void 0) {
12055
- return;
12056
- }
12057
- if (!meta.cacheable) {
12058
- rootsDeclined += 1;
12059
- }
12060
- roots.set(rootPath, {
12061
- path: rootPath,
12062
- cacheable: meta.cacheable,
12063
- deps: meta.deps.flatMap((d) => {
12064
- const p = depPaths[d];
12065
- return p === void 0 ? [] : [p];
12066
- }),
12067
- claims: meta.claims,
12068
- mountPrefixes: meta.mountPrefixes,
12069
- packs: meta.packs
12070
- });
12071
- });
12072
- const validRoots = /* @__PURE__ */ new Set();
12073
- for (const [rootPath, record2] of roots) {
12074
- if (!record2.cacheable || !current2.has(rootPath)) {
12075
- continue;
12076
- }
12077
- if (changed.has(rootPath)) {
12078
- continue;
12079
- }
12080
- const depMoved = record2.deps.some((d) => changed.has(d) || removed.has(d));
12081
- if (!depMoved) {
12082
- validRoots.add(rootPath);
12083
- }
12003
+ if (!import_ts_morph56.Node.isCallExpression(node)) {
12004
+ return null;
12084
12005
  }
12085
- const owners = manifest.owners ?? [];
12086
- return {
12087
- changed,
12088
- removed,
12089
- roots,
12090
- validRoots,
12091
- rootsDeclined,
12092
- reuse(valid) {
12093
- const summaries = [];
12094
- const reusedOwners = [];
12095
- manifest.summaries.forEach((summary, i) => {
12096
- const ownerPaths = (owners[i] ?? []).flatMap((o) => {
12097
- const p = rootNames[o];
12098
- return p === void 0 ? [] : [p];
12099
- });
12100
- if (ownerPaths.some((p) => valid.has(p))) {
12101
- summaries.push(summary);
12102
- reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
12103
- }
12104
- });
12105
- return { summaries, owners: reusedOwners };
12106
- },
12107
- allSummaries() {
12108
- return manifest.summaries;
12109
- },
12110
- attribution() {
12111
- return {
12112
- roots: [...roots.values()],
12113
- owners: owners.map(
12114
- (ownerIds) => ownerIds.flatMap((o) => {
12115
- const p = rootNames[o];
12116
- return p === void 0 ? [] : [p];
12117
- })
12118
- )
12119
- };
12120
- }
12121
- };
12122
- }
12123
- function encodeAttribution(attribution) {
12124
- const roots = attribution.roots.map((r) => r.path);
12125
- const rootIndex = new Map(roots.map((p, i) => [p, i]));
12126
- const depIndex = /* @__PURE__ */ new Map();
12127
- const depPaths = [];
12128
- const depIdOf = (p) => {
12129
- const existing = depIndex.get(p);
12130
- if (existing !== void 0) {
12131
- return existing;
12132
- }
12133
- const id = depPaths.length;
12134
- depPaths.push(p);
12135
- depIndex.set(p, id);
12136
- return id;
12137
- };
12138
- const rootMeta = attribution.roots.map((r) => ({
12139
- cacheable: r.cacheable,
12140
- deps: r.deps.map(depIdOf),
12141
- claims: r.claims,
12142
- mountPrefixes: r.mountPrefixes,
12143
- packs: r.packs
12144
- }));
12145
- const owners = attribution.owners.map(
12146
- (ownerPaths) => ownerPaths.flatMap((p) => {
12147
- const i = rootIndex.get(p);
12148
- return i === void 0 ? [] : [i];
12149
- })
12150
- );
12151
- return { roots, rootMeta, depPaths, owners };
12152
- }
12153
- async function hashStamps(stamps, previous) {
12154
- const before = new Map((previous?.files ?? []).map((f) => [f.path, f]));
12155
- return Promise.all(
12156
- stamps.map(async (stamp) => {
12157
- const prior = before.get(stamp.path);
12158
- if (prior !== void 0 && prior.contentHash !== void 0 && fileStampEquals(prior, stamp)) {
12159
- return { ...stamp, contentHash: prior.contentHash };
12160
- }
12161
- const contentHash = await hashFile(stamp.path);
12162
- return contentHash === null ? stamp : { ...stamp, contentHash };
12163
- })
12164
- );
12165
- }
12166
- async function hashFile(filePath) {
12167
- try {
12168
- const content = await import_promises2.default.readFile(filePath);
12169
- return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
12170
- } catch {
12006
+ const typeArgs = node.getTypeArguments();
12007
+ if (typeArgs.length === 0) {
12171
12008
  return null;
12172
12009
  }
12010
+ return shapeFromNodeType(typeArgs[0]);
12173
12011
  }
12174
- async function describeAbsentEntry(cacheDir) {
12175
- try {
12176
- const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
12177
- return entries.some((e) => e.isDirectory() && isEntryDir(e.name)) ? "key-changed" : "no-manifest";
12178
- } catch {
12179
- return "no-manifest";
12012
+ function resolveHandlerPropNode(func) {
12013
+ const parent = func.getParent();
12014
+ if (parent === void 0) {
12015
+ return null;
12180
12016
  }
12181
- }
12182
- function entryDirFor(cacheDir, input) {
12183
- const key = [
12184
- SCHEMA_VERSION,
12185
- input.adapterPacksDigest,
12186
- input.tsconfigPath ?? ""
12187
- ].join(" ");
12188
- const name = (0, import_node_crypto2.createHash)("sha256").update(key).digest("hex").slice(0, 16);
12189
- return import_node_path7.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
12190
- }
12191
- function isEntryDir(name) {
12192
- return ENTRY_DIR_NAME.test(name);
12193
- }
12194
- async function markUsed(entryDir) {
12195
- const now = /* @__PURE__ */ new Date();
12196
- try {
12197
- await import_promises2.default.utimes(entryDir, now, now);
12198
- } catch {
12017
+ if (import_ts_morph56.Node.isPropertyAssignment(parent)) {
12018
+ return parent;
12199
12019
  }
12200
- }
12201
- async function evictOldEntries(cacheDir, keepDir) {
12202
- try {
12203
- await import_promises2.default.rm(import_node_path7.default.join(cacheDir, "manifest.json"), { force: true });
12204
- const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
12205
- const dirs = await Promise.all(
12206
- entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
12207
- const dir = import_node_path7.default.join(cacheDir, e.name);
12208
- const stat = await import_promises2.default.stat(dir);
12209
- return { dir, mtimeMs: stat.mtimeMs };
12210
- })
12211
- );
12212
- const doomed = dirs.filter((d) => d.dir !== keepDir).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(MAX_ENTRIES - 1);
12213
- await Promise.all(
12214
- doomed.map((d) => import_promises2.default.rm(d.dir, { recursive: true, force: true }))
12215
- );
12216
- } catch {
12020
+ if (import_ts_morph56.Node.isMethodDeclaration(func)) {
12021
+ return func;
12217
12022
  }
12023
+ return null;
12218
12024
  }
12219
- function missDiag(reason) {
12220
- return {
12221
- kind: "miss",
12222
- diagnostic: { kind: "miss", missReason: reason }
12223
- };
12224
- }
12225
- async function readManifest(manifestPath) {
12226
- try {
12227
- const raw = await import_promises2.default.readFile(manifestPath, "utf-8");
12228
- return JSON.parse(raw);
12229
- } catch {
12025
+ function findRouterCall(unit) {
12026
+ const func = unit.func;
12027
+ const propNode = func === null ? null : resolveHandlerPropNode(func);
12028
+ if (propNode === null) {
12230
12029
  return null;
12231
12030
  }
12232
- }
12233
- async function stampTsconfig(tsconfigPath) {
12234
- if (tsconfigPath === void 0) {
12031
+ const handlersObj = propNode.getParent();
12032
+ if (handlersObj === void 0 || !import_ts_morph56.Node.isObjectLiteralExpression(handlersObj)) {
12235
12033
  return null;
12236
12034
  }
12237
- try {
12238
- const stat = await import_promises2.default.stat(tsconfigPath);
12239
- return { path: tsconfigPath, mtimeMs: stat.mtimeMs, size: stat.size };
12240
- } catch {
12241
- return null;
12242
- }
12243
- }
12244
- async function resolveFileStamps(input) {
12245
- if (input.files !== void 0) {
12246
- return stampPaths(input.files);
12247
- }
12248
- if (input.project !== void 0) {
12249
- return stampProjectFiles(input.project);
12250
- }
12251
- return [];
12252
- }
12253
- async function stampProjectFiles(project) {
12254
- const files = project.getSourceFiles().filter((sf) => !sf.isDeclarationFile()).map((sf) => sf.getFilePath());
12255
- return stampPaths(files);
12256
- }
12257
- async function stampPaths(paths) {
12258
- const files = paths;
12259
- const stamped = await Promise.all(
12260
- files.map(async (p) => {
12261
- try {
12262
- const stat = await import_promises2.default.stat(p);
12263
- return { path: p, mtimeMs: stat.mtimeMs, size: stat.size };
12264
- } catch {
12265
- return { path: p, mtimeMs: -1, size: -1 };
12266
- }
12267
- })
12268
- );
12269
- stamped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
12270
- return stamped;
12271
- }
12272
- function fileStampsEqual(a, b) {
12273
- return a.length === b.length && a.every((s, i) => fileStampEquals(s, b[i]));
12274
- }
12275
- function fileStampEquals(a, b) {
12276
- if (a === null && b === null) {
12277
- return true;
12278
- }
12279
- if (a === null || b === null) {
12280
- return false;
12281
- }
12282
- return a.path === b.path && a.mtimeMs === b.mtimeMs && a.size === b.size;
12283
- }
12284
-
12285
- // src/contract.ts
12286
- var import_ts_morph56 = require("ts-morph");
12287
- var import_behavioral_ir4 = require("@suss/behavioral-ir");
12288
- function extractDeclaredBody(node) {
12289
- if (node === void 0) {
12290
- return null;
12291
- }
12292
- if (!import_ts_morph56.Node.isCallExpression(node)) {
12293
- return null;
12294
- }
12295
- const typeArgs = node.getTypeArguments();
12296
- if (typeArgs.length === 0) {
12297
- return null;
12298
- }
12299
- return shapeFromNodeType(typeArgs[0]);
12300
- }
12301
- function resolveHandlerPropNode(func) {
12302
- const parent = func.getParent();
12303
- if (parent === void 0) {
12304
- return null;
12305
- }
12306
- if (import_ts_morph56.Node.isPropertyAssignment(parent)) {
12307
- return parent;
12308
- }
12309
- if (import_ts_morph56.Node.isMethodDeclaration(func)) {
12310
- return func;
12311
- }
12312
- return null;
12313
- }
12314
- function findRouterCall(unit) {
12315
- const func = unit.func;
12316
- const propNode = func === null ? null : resolveHandlerPropNode(func);
12317
- if (propNode === null) {
12318
- return null;
12319
- }
12320
- const handlersObj = propNode.getParent();
12321
- if (handlersObj === void 0 || !import_ts_morph56.Node.isObjectLiteralExpression(handlersObj)) {
12322
- return null;
12323
- }
12324
- const routerCall = handlersObj.getParent();
12325
- if (routerCall === void 0 || !import_ts_morph56.Node.isCallExpression(routerCall)) {
12035
+ const routerCall = handlersObj.getParent();
12036
+ if (routerCall === void 0 || !import_ts_morph56.Node.isCallExpression(routerCall)) {
12326
12037
  return null;
12327
12038
  }
12328
12039
  const args = routerCall.getArguments();
@@ -12424,7 +12135,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
12424
12135
  }
12425
12136
  const responses = [];
12426
12137
  let method;
12427
- let path15;
12138
+ let path14;
12428
12139
  for (const prop of endpointNode.getProperties()) {
12429
12140
  if (!import_ts_morph56.Node.isPropertyAssignment(prop)) {
12430
12141
  continue;
@@ -12439,7 +12150,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
12439
12150
  if (propName === pattern.pathProperty) {
12440
12151
  const val = prop.getInitializer();
12441
12152
  if (val !== void 0 && import_ts_morph56.Node.isStringLiteral(val)) {
12442
- path15 = val.getLiteralValue();
12153
+ path14 = val.getLiteralValue();
12443
12154
  }
12444
12155
  }
12445
12156
  if (propName === pattern.responseExtraction.property) {
@@ -12463,10 +12174,10 @@ function extractEndpointContract(endpointNode, pattern, framework) {
12463
12174
  if (responses.length === 0) {
12464
12175
  return null;
12465
12176
  }
12466
- const boundaryBinding = method !== void 0 || path15 !== void 0 ? (0, import_behavioral_ir4.restBinding)({
12177
+ const boundaryBinding = method !== void 0 || path14 !== void 0 ? (0, import_behavioral_ir5.restBinding)({
12467
12178
  transport: "http",
12468
12179
  method: method ?? null,
12469
- path: path15 ?? null,
12180
+ path: path14 ?? null,
12470
12181
  recognition: framework
12471
12182
  }) : null;
12472
12183
  return {
@@ -12664,7 +12375,7 @@ function parameterName(segment, convention) {
12664
12375
  // src/discovery/graphqlClientConstruction.ts
12665
12376
  var import_picomatch2 = __toESM(require("picomatch"), 1);
12666
12377
  var import_ts_morph57 = require("ts-morph");
12667
- var import_behavioral_ir5 = require("@suss/behavioral-ir");
12378
+ var import_behavioral_ir6 = require("@suss/behavioral-ir");
12668
12379
  function stampGraphqlClientRefs(summaries, sourceFiles, packs, resolution) {
12669
12380
  const scopes = compileOperationScopes(packs);
12670
12381
  const sole = soleGraphqlClientRef(
@@ -12674,10 +12385,10 @@ function stampGraphqlClientRefs(summaries, sourceFiles, packs, resolution) {
12674
12385
  const soleClient = sole !== null ? workspace !== null ? { ...sole, workspace } : sole : null;
12675
12386
  let registry = null;
12676
12387
  for (const summary of summaries) {
12677
- if (!(0, import_behavioral_ir5.isGraphqlOperationBinding)(summary.identity.boundaryBinding)) {
12388
+ if (!(0, import_behavioral_ir6.isGraphqlOperationBinding)(summary.identity.boundaryBinding)) {
12678
12389
  continue;
12679
12390
  }
12680
- const existing = (0, import_behavioral_ir5.readGraphqlMetadata)(summary) ?? {};
12391
+ const existing = (0, import_behavioral_ir6.readGraphqlMetadata)(summary) ?? {};
12681
12392
  const scoped = scopedWorkspaceFor(summary.location.file, scopes);
12682
12393
  const client = scoped !== null ? { uri: null, uriRef: null, workspace: scoped } : soleClient;
12683
12394
  const dangling = (existing.unresolvedFragments?.length ?? 0) > 0;
@@ -12687,7 +12398,7 @@ function stampGraphqlClientRefs(summaries, sourceFiles, packs, resolution) {
12687
12398
  if (dangling && registry === null) {
12688
12399
  registry = fragmentRegistryStatus(sourceFiles, packs);
12689
12400
  }
12690
- summary.metadata = (0, import_behavioral_ir5.withGraphqlMetadata)(summary.metadata, {
12401
+ summary.metadata = (0, import_behavioral_ir6.withGraphqlMetadata)(summary.metadata, {
12691
12402
  ...existing,
12692
12403
  ...client !== null ? { client } : {},
12693
12404
  ...dangling && registry !== null ? { fragmentRegistry: registry } : {}
@@ -13099,8 +12810,8 @@ function noteExpanded(expanded, readings, discovery) {
13099
12810
  const paths = new Set(match.registrations.map((one) => one.pathTemplate));
13100
12811
  const methods = expanded.get(reading.declarationId) ?? /* @__PURE__ */ new Set();
13101
12812
  for (const sink of reading.helper.sinks) {
13102
- const path15 = sink.arguments[0];
13103
- if (sink.method !== null && path15?.as === "text" && paths.has(path15.text)) {
12813
+ const path14 = sink.arguments[0];
12814
+ if (sink.method !== null && path14?.as === "text" && paths.has(path14.text)) {
13104
12815
  methods.add(sink.method);
13105
12816
  }
13106
12817
  }
@@ -13689,7 +13400,7 @@ function scalarOrRef(node) {
13689
13400
  }
13690
13401
 
13691
13402
  // src/moduleInit.ts
13692
- var import_extractor5 = require("@suss/extractor");
13403
+ var import_extractor6 = require("@suss/extractor");
13693
13404
  function moduleInitName(sourceFile) {
13694
13405
  return sourceFile.getBaseName();
13695
13406
  }
@@ -13697,44 +13408,15 @@ function moduleInitSummary(sourceFile, effects, options) {
13697
13408
  if (effects.length === 0) {
13698
13409
  return null;
13699
13410
  }
13700
- const end = sourceFile.getEnd();
13701
- const raw = {
13702
- identity: {
13411
+ return (0, import_extractor6.assembleSummary)(
13412
+ (0, import_extractor6.moduleInitStructure)({
13703
13413
  name: moduleInitName(sourceFile),
13704
- kind: "module-init",
13705
13414
  file: sourceFile.getFilePath(),
13706
- range: { start: 0, end },
13707
- exportName: null,
13708
- exportPath: null
13709
- },
13710
- boundaryBinding: null,
13711
- parameters: [],
13712
- branches: [
13713
- {
13714
- conditions: [],
13715
- terminal: {
13716
- kind: "void",
13717
- statusCode: null,
13718
- body: null,
13719
- exceptionType: null,
13720
- message: null,
13721
- component: null,
13722
- renderTree: null,
13723
- delegateTarget: null,
13724
- emitEvent: null,
13725
- location: { start: 0, end }
13726
- },
13727
- effects: [],
13728
- extraEffects: effects,
13729
- location: { start: 0, end },
13730
- isDefault: true
13731
- }
13732
- ],
13733
- unmatchedReturns: 0,
13734
- dependencyCalls: [],
13735
- declaredContract: null
13736
- };
13737
- return (0, import_extractor5.assembleSummary)(raw, options);
13415
+ range: { start: 0, end: sourceFile.getEnd() },
13416
+ effects
13417
+ }),
13418
+ options
13419
+ );
13738
13420
  }
13739
13421
 
13740
13422
  // src/referencedFiles.ts
@@ -13882,9 +13564,9 @@ function deriveBoundaryEffects(summaries, facts) {
13882
13564
 
13883
13565
  // src/resolve/reachableClosure.ts
13884
13566
  var import_ts_morph62 = require("ts-morph");
13885
- var import_behavioral_ir6 = require("@suss/behavioral-ir");
13567
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
13886
13568
  var import_datalog4 = require("@suss/datalog");
13887
- var import_extractor6 = require("@suss/extractor");
13569
+ var import_extractor7 = require("@suss/extractor");
13888
13570
  var reachablePack = {
13889
13571
  name: "reachable",
13890
13572
  languages: ["typescript"],
@@ -13906,6 +13588,12 @@ var reachablePack = {
13906
13588
  function nodeKey(func) {
13907
13589
  return offsetKeyOf(func);
13908
13590
  }
13591
+ function targetOf(node) {
13592
+ return {
13593
+ file: node.getSourceFile().getFilePath(),
13594
+ span: { start: node.getStart(), end: node.getEnd() }
13595
+ };
13596
+ }
13909
13597
  function resolveCallee(call, calleeName, scan) {
13910
13598
  if (!import_ts_morph62.Node.isCallExpression(call)) {
13911
13599
  return null;
@@ -13930,19 +13618,42 @@ function resolveCallee(call, calleeName, scan) {
13930
13618
  if (sources.length > 1) {
13931
13619
  return {
13932
13620
  kind: "stopped",
13933
- stop: { callee: calleeName, reason: "multipleSources" }
13621
+ stop: { callee: calleeName, reason: "multipleSources" },
13622
+ declaration: declarationToReport(declarations, calleeName, scan)
13934
13623
  };
13935
13624
  }
13936
13625
  }
13626
+ const reason = classifyStop(declarations, scan.scanning);
13627
+ const parameterIndex = reason === "callerSupplied" ? parameterIndexOf(declarations, scan.scanning) : void 0;
13937
13628
  return {
13938
13629
  kind: "stopped",
13939
- stop: {
13940
- callee: calleeName,
13941
- reason: classifyStop(declarationsBehind(symbol), scan.scanning)
13942
- }
13630
+ stop: { callee: calleeName, reason },
13631
+ declaration: declarationToReport(declarations, calleeName, scan),
13632
+ ...parameterIndex === void 0 ? {} : { parameterIndex }
13943
13633
  };
13944
13634
  }
13945
- function resolveJsxReference(node) {
13635
+ function declarationToReport(declarations, calleeName, scan) {
13636
+ const first = declarations[0];
13637
+ if (first === void 0) {
13638
+ return null;
13639
+ }
13640
+ const sources = scan.sourceDeclarationsBehind?.(first) ?? [first];
13641
+ const only = sources.length === 1 ? sources[0] : void 0;
13642
+ if (only === void 0 || only === first) {
13643
+ return first;
13644
+ }
13645
+ return resolveDecl(only, calleeName)?.func ?? only;
13646
+ }
13647
+ function declaredAtOf(outcome) {
13648
+ if (outcome.kind === "followed") {
13649
+ return targetOf(outcome.candidate.func);
13650
+ }
13651
+ return outcome.declaration === null ? null : targetOf(outcome.declaration);
13652
+ }
13653
+ function passedPositionKey(target, position) {
13654
+ return `${offsetKeyFor(target.file, target.span)}#${position}`;
13655
+ }
13656
+ function resolveJsxReference(node, scan) {
13946
13657
  if (!import_ts_morph62.Node.isJsxOpeningElement(node) && !import_ts_morph62.Node.isJsxSelfClosingElement(node)) {
13947
13658
  return null;
13948
13659
  }
@@ -13962,7 +13673,8 @@ function resolveJsxReference(node) {
13962
13673
  tag: name,
13963
13674
  outcome: {
13964
13675
  kind: "stopped",
13965
- stop: { callee: name, reason: classifyStop(declarations) }
13676
+ stop: { callee: name, reason: classifyStop(declarations) },
13677
+ declaration: declarationToReport(declarations, name, scan)
13966
13678
  }
13967
13679
  };
13968
13680
  }
@@ -13970,11 +13682,15 @@ function collectReachable(func, scan) {
13970
13682
  const inFunc = { ...scan, scanning: func };
13971
13683
  const candidates = [];
13972
13684
  const stops = [];
13685
+ const placements = new import_behavioral_ir7.TargetPlacements();
13686
+ const parameterCalls = [];
13687
+ const passedPositions = /* @__PURE__ */ new Set();
13973
13688
  const seen = /* @__PURE__ */ new Set();
13689
+ const parameterCallsSeen = /* @__PURE__ */ new Set();
13974
13690
  const record2 = (outcome) => {
13975
13691
  if (outcome.kind === "stopped") {
13976
13692
  const stopKey = `${outcome.stop.reason}:${outcome.stop.callee}`;
13977
- if (!seen.has(stopKey) && worthRecording(outcome.stop.reason)) {
13693
+ if (!seen.has(stopKey) && (0, import_behavioral_ir3.worthRecording)(outcome.stop.reason)) {
13978
13694
  seen.add(stopKey);
13979
13695
  stops.push(outcome.stop);
13980
13696
  }
@@ -13987,8 +13703,28 @@ function collectReachable(func, scan) {
13987
13703
  seen.add(key);
13988
13704
  candidates.push(outcome.candidate);
13989
13705
  };
13706
+ const recordPassedArgs = (call, calleeText, calleeOutcome) => {
13707
+ if (!import_ts_morph62.Node.isCallExpression(call)) {
13708
+ return;
13709
+ }
13710
+ const calleeTarget = declaredAtOf(calleeOutcome);
13711
+ call.getArguments().forEach((arg, position) => {
13712
+ if (!import_ts_morph62.Node.isIdentifier(arg)) {
13713
+ return;
13714
+ }
13715
+ const resolved = functionTargetOf(arg);
13716
+ if (resolved === null) {
13717
+ return;
13718
+ }
13719
+ record2({ kind: "followed", candidate: resolved });
13720
+ placements.placeArg(calleeText, position, targetOf(resolved.func));
13721
+ if (calleeTarget !== null) {
13722
+ passedPositions.add(passedPositionKey(calleeTarget, position));
13723
+ }
13724
+ });
13725
+ };
13990
13726
  func.forEachDescendant((node) => {
13991
- const jsx = resolveJsxReference(node);
13727
+ const jsx = resolveJsxReference(node, inFunc);
13992
13728
  if (jsx !== null) {
13993
13729
  record2(jsx.outcome);
13994
13730
  return;
@@ -14002,8 +13738,24 @@ function collectReachable(func, scan) {
14002
13738
  return;
14003
13739
  }
14004
13740
  record2(outcome);
13741
+ placements.place(calleeText, declaredAtOf(outcome));
13742
+ recordPassedArgs(node, calleeText, outcome);
13743
+ if (outcome.kind === "stopped" && outcome.stop.reason === "callerSupplied" && outcome.parameterIndex !== void 0 && !parameterCallsSeen.has(calleeText)) {
13744
+ parameterCallsSeen.add(calleeText);
13745
+ parameterCalls.push({
13746
+ callee: calleeText,
13747
+ parameterIndex: outcome.parameterIndex
13748
+ });
13749
+ }
14005
13750
  });
14006
- return { candidates, stops };
13751
+ return {
13752
+ candidates,
13753
+ stops,
13754
+ targets: placements.targets,
13755
+ argTargets: placements.argTargets,
13756
+ parameterCalls,
13757
+ passedPositions
13758
+ };
14007
13759
  }
14008
13760
  function normalizeCallee(text) {
14009
13761
  return text.replace(/\s+/g, " ").trim();
@@ -14022,11 +13774,11 @@ function extractReachableSummary(candidate, options, recognizers) {
14022
13774
  void 0,
14023
13775
  recognizers.resolveWrittenValue
14024
13776
  );
14025
- raw.boundaryBinding = (0, import_behavioral_ir6.functionCallBinding)({
13777
+ raw.boundaryBinding = (0, import_behavioral_ir7.functionCallBinding)({
14026
13778
  transport: "in-process",
14027
13779
  recognition: "reachable"
14028
13780
  });
14029
- return (0, import_extractor6.assembleSummary)(raw, options);
13781
+ return (0, import_extractor7.assembleSummary)(raw, options);
14030
13782
  }
14031
13783
  var REACHABLE_RULES = [
14032
13784
  (0, import_datalog4.rule)("reachable", [(0, import_datalog4.variable)("f")], [(0, import_datalog4.lit)("entry", (0, import_datalog4.variable)("f"))]),
@@ -14078,6 +13830,10 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
14078
13830
  const seedKeys = /* @__PURE__ */ new Set();
14079
13831
  const scanned = /* @__PURE__ */ new Set();
14080
13832
  const stopsByKey = /* @__PURE__ */ new Map();
13833
+ const targetsByKey = /* @__PURE__ */ new Map();
13834
+ const argTargetsByKey = /* @__PURE__ */ new Map();
13835
+ const parameterCallsByKey = /* @__PURE__ */ new Map();
13836
+ const passedPositions = /* @__PURE__ */ new Set();
14081
13837
  const summariesByKey = /* @__PURE__ */ new Map();
14082
13838
  const reachedFrom = /* @__PURE__ */ new Map();
14083
13839
  for (const seed of seeds) {
@@ -14122,9 +13878,17 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
14122
13878
  const cameFrom = reachedFrom.get(key);
14123
13879
  const scan = {
14124
13880
  ...recognizers.resolveCallableSources === void 0 ? {} : { resolveCallableSources: recognizers.resolveCallableSources },
13881
+ ...recognizers.sourceDeclarationsBehind === void 0 ? {} : { sourceDeclarationsBehind: recognizers.sourceDeclarationsBehind },
14125
13882
  ...cameFrom === void 0 ? {} : { reachedFrom: cameFrom }
14126
13883
  };
14127
- const { candidates, stops } = scanWithRecording(
13884
+ const {
13885
+ candidates,
13886
+ stops,
13887
+ targets,
13888
+ argTargets,
13889
+ parameterCalls,
13890
+ passedPositions: scanPassedPositions
13891
+ } = scanWithRecording(
14128
13892
  key,
14129
13893
  facts,
14130
13894
  () => collectReachable(source.func, scan)
@@ -14132,6 +13896,14 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
14132
13896
  if (stops.length > 0) {
14133
13897
  stopsByKey.set(key, stops);
14134
13898
  }
13899
+ targetsByKey.set(key, targets);
13900
+ argTargetsByKey.set(key, argTargets);
13901
+ if (parameterCalls.length > 0) {
13902
+ parameterCallsByKey.set(key, parameterCalls);
13903
+ }
13904
+ for (const position of scanPassedPositions) {
13905
+ passedPositions.add(position);
13906
+ }
14135
13907
  for (const candidate of candidates) {
14136
13908
  const calleeKey = nodeKey(candidate.func);
14137
13909
  if (!functionByKey.has(calleeKey)) {
@@ -14171,6 +13943,15 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
14171
13943
  reached.push(summary);
14172
13944
  }
14173
13945
  recordStops(stopsByKey, summariesByKey, options);
13946
+ recordTargets(targetsByKey, summariesByKey);
13947
+ recordArgTargets(argTargetsByKey, summariesByKey);
13948
+ recordCalleeParameters(parameterCallsByKey, summariesByKey);
13949
+ recordParameterGaps(
13950
+ parameterCallsByKey,
13951
+ summariesByKey,
13952
+ passedPositions,
13953
+ options
13954
+ );
14174
13955
  return [...seeds, ...reached];
14175
13956
  }
14176
13957
  function scanWithRecording(key, facts, fn) {
@@ -14198,7 +13979,91 @@ function recordStops(stopsByKey, summariesByKey, options) {
14198
13979
  }
14199
13980
  for (const [key, stops] of stopsByKey) {
14200
13981
  for (const summary of summariesByKey.get(key) ?? []) {
14201
- summary.gaps.push(...stops.map(unfollowedCallGap));
13982
+ summary.gaps.push(...stops.map(import_behavioral_ir3.unfollowedCallGap));
13983
+ }
13984
+ }
13985
+ }
13986
+ function recordTargets(targetsByKey, summariesByKey) {
13987
+ for (const [key, targets] of targetsByKey) {
13988
+ for (const summary of summariesByKey.get(key) ?? []) {
13989
+ for (const transition of summary.transitions) {
13990
+ for (const effect of transition.effects) {
13991
+ if (effect.type !== "invocation") {
13992
+ continue;
13993
+ }
13994
+ const target = targets.get(normalizeCallee(effect.callee));
13995
+ if (target !== void 0) {
13996
+ effect.declaredAt = target;
13997
+ }
13998
+ }
13999
+ }
14000
+ }
14001
+ }
14002
+ }
14003
+ function recordArgTargets(argTargetsByKey, summariesByKey) {
14004
+ for (const [key, argTargets] of argTargetsByKey) {
14005
+ for (const summary of summariesByKey.get(key) ?? []) {
14006
+ for (const transition of summary.transitions) {
14007
+ for (const effect of transition.effects) {
14008
+ if (effect.type !== "invocation") {
14009
+ continue;
14010
+ }
14011
+ const byPosition = argTargets.get(normalizeCallee(effect.callee));
14012
+ if (byPosition === void 0) {
14013
+ continue;
14014
+ }
14015
+ const argsDeclaredAt = {};
14016
+ for (const [position, target] of byPosition) {
14017
+ argsDeclaredAt[position] = target;
14018
+ }
14019
+ if (Object.keys(argsDeclaredAt).length > 0) {
14020
+ effect.argsDeclaredAt = argsDeclaredAt;
14021
+ }
14022
+ }
14023
+ }
14024
+ }
14025
+ }
14026
+ }
14027
+ function recordCalleeParameters(parameterCallsByKey, summariesByKey) {
14028
+ for (const [key, parameterCalls] of parameterCallsByKey) {
14029
+ const byCallee = new Map(
14030
+ parameterCalls.map(({ callee, parameterIndex }) => [
14031
+ callee,
14032
+ parameterIndex
14033
+ ])
14034
+ );
14035
+ for (const summary of summariesByKey.get(key) ?? []) {
14036
+ for (const transition of summary.transitions) {
14037
+ for (const effect of transition.effects) {
14038
+ if (effect.type !== "invocation") {
14039
+ continue;
14040
+ }
14041
+ const parameterIndex = byCallee.get(normalizeCallee(effect.callee));
14042
+ if (parameterIndex !== void 0) {
14043
+ effect.calleeParameter = parameterIndex;
14044
+ }
14045
+ }
14046
+ }
14047
+ }
14048
+ }
14049
+ }
14050
+ function recordParameterGaps(parameterCallsByKey, summariesByKey, passedPositions, options) {
14051
+ if (options?.gapHandling === "silent") {
14052
+ return;
14053
+ }
14054
+ for (const [key, parameterCalls] of parameterCallsByKey) {
14055
+ const unbound = parameterCalls.filter(
14056
+ ({ parameterIndex }) => !passedPositions.has(`${key}#${parameterIndex}`)
14057
+ );
14058
+ if (unbound.length === 0) {
14059
+ continue;
14060
+ }
14061
+ for (const summary of summariesByKey.get(key) ?? []) {
14062
+ summary.gaps.push(
14063
+ ...unbound.map(
14064
+ ({ callee }) => (0, import_behavioral_ir3.unfollowedCallGap)({ callee, reason: "unboundParameter" })
14065
+ )
14066
+ );
14202
14067
  }
14203
14068
  }
14204
14069
  }
@@ -14933,9 +14798,8 @@ function readArrayLiteralText(node) {
14933
14798
  }
14934
14799
 
14935
14800
  // src/summaryIdentity.ts
14936
- var import_node_fs4 = __toESM(require("fs"), 1);
14937
14801
  var import_node_path8 = __toESM(require("path"), 1);
14938
- var import_behavioral_ir7 = require("@suss/behavioral-ir");
14802
+ var import_behavioral_ir8 = require("@suss/behavioral-ir");
14939
14803
  function workspaceRootFor(anchor) {
14940
14804
  return namedPackageDirAbove(anchor) ?? import_node_path8.default.resolve(anchor);
14941
14805
  }
@@ -14950,32 +14814,6 @@ function workspaceNameFor(root) {
14950
14814
  const folder = import_node_path8.default.basename(import_node_path8.default.resolve(root));
14951
14815
  return folder.length > 0 ? folder : null;
14952
14816
  }
14953
- function namedPackageDirAbove(start) {
14954
- let at = import_node_path8.default.resolve(start);
14955
- for (let up = 0; up < 12; up += 1) {
14956
- if (packageNameAt(at) !== null) {
14957
- return at;
14958
- }
14959
- const parent = import_node_path8.default.dirname(at);
14960
- if (parent === at) {
14961
- break;
14962
- }
14963
- at = parent;
14964
- }
14965
- return null;
14966
- }
14967
- function packageNameAt(dir) {
14968
- try {
14969
- const manifest = JSON.parse(
14970
- import_node_fs4.default.readFileSync(import_node_path8.default.join(dir, "package.json"), "utf8")
14971
- );
14972
- if (typeof manifest.name === "string" && manifest.name.length > 0) {
14973
- return manifest.name;
14974
- }
14975
- } catch {
14976
- }
14977
- return null;
14978
- }
14979
14817
  function nameSummaries(summaries, args) {
14980
14818
  for (const summary of summaries) {
14981
14819
  const file = relativeFile(summary.location.file, args.projectRoot);
@@ -14984,8 +14822,8 @@ function nameSummaries(summaries, args) {
14984
14822
  }
14985
14823
  summary.identity.id = idFor(args.workspace, file, summary);
14986
14824
  }
14987
- (0, import_behavioral_ir7.disambiguateSummaryIds)(summaries);
14988
- nameWhatEachCallReaches(summaries);
14825
+ (0, import_behavioral_ir8.disambiguateSummaryIds)(summaries);
14826
+ (0, import_behavioral_ir8.linkCallsToSummaries)(summaries);
14989
14827
  }
14990
14828
  function relativeFile(file, projectRoot) {
14991
14829
  if (projectRoot === void 0 || !import_node_path8.default.isAbsolute(file)) {
@@ -14995,193 +14833,39 @@ function relativeFile(file, projectRoot) {
14995
14833
  return relative.startsWith("..") ? file : relative;
14996
14834
  }
14997
14835
  function idFor(workspace, file, summary) {
14998
- return (0, import_behavioral_ir7.summaryIdFromParts)({
14836
+ return (0, import_behavioral_ir8.summaryIdFromParts)({
14999
14837
  workspace: workspace ?? void 0,
15000
14838
  file,
15001
14839
  name: summary.identity.name,
15002
14840
  exportPath: summary.identity.exportPath
15003
14841
  });
15004
14842
  }
15005
- function nameWhatEachCallReaches(summaries) {
15006
- const byName = /* @__PURE__ */ new Map();
15007
- const byFileAndName = /* @__PURE__ */ new Map();
15008
- const remember = (index, key, summary) => {
15009
- const found = index.get(key);
15010
- if (found === void 0) {
15011
- index.set(key, [summary]);
15012
- return;
15013
- }
15014
- found.push(summary);
15015
- };
15016
- for (const summary of summaries) {
15017
- if (summary.identity.nameKind === "label") {
15018
- continue;
15019
- }
15020
- remember(byName, summary.identity.name, summary);
15021
- remember(
15022
- byFileAndName,
15023
- `${summary.location.file}::${summary.identity.name}`,
15024
- summary
15025
- );
15026
- }
15027
- const onlyAnswer = (index, key) => {
15028
- const found = index.get(key) ?? [];
15029
- return found.length === 1 ? found[0] : null;
15030
- };
15031
- for (const summary of summaries) {
15032
- for (const transition of summary.transitions) {
15033
- for (const effect of transition.effects) {
15034
- if (effect.type !== "invocation") {
15035
- continue;
15036
- }
15037
- const called = effect.callee.split(".").pop() ?? effect.callee;
15038
- const reached = onlyAnswer(byFileAndName, `${summary.location.file}::${called}`) ?? onlyAnswer(byName, called);
15039
- if (reached?.identity.id !== void 0) {
15040
- effect.summary = reached.identity.id;
15041
- }
15042
- }
15043
- }
15044
- }
15045
- }
15046
14843
 
15047
14844
  // src/timing.ts
15048
- function createTimer() {
15049
- const phases = /* @__PURE__ */ new Map();
15050
- const start = performance.now();
15051
- function record2(label, deltaMs) {
15052
- const existing = phases.get(label);
15053
- if (existing === void 0) {
15054
- phases.set(label, { durationMs: deltaMs, calls: 1 });
15055
- } else {
15056
- existing.durationMs += deltaMs;
15057
- existing.calls += 1;
15058
- }
15059
- }
15060
- return {
15061
- time(label, fn) {
15062
- const t0 = performance.now();
15063
- try {
15064
- return fn();
15065
- } finally {
15066
- record2(label, performance.now() - t0);
15067
- }
15068
- },
15069
- async timeAsync(label, fn) {
15070
- const t0 = performance.now();
15071
- try {
15072
- return await fn();
15073
- } finally {
15074
- record2(label, performance.now() - t0);
15075
- }
15076
- },
15077
- report() {
15078
- const totalMs = performance.now() - start;
15079
- const ordered = [...phases.entries()].map(([label, s]) => ({
15080
- label,
15081
- durationMs: s.durationMs,
15082
- calls: s.calls
15083
- })).sort((a, b) => b.durationMs - a.durationMs);
15084
- return { totalMs, phases: ordered };
15085
- }
15086
- };
15087
- }
14845
+ var import_extractor8 = require("@suss/extractor");
15088
14846
 
15089
14847
  // src/version.ts
15090
- var import_node_crypto3 = require("crypto");
15091
- var import_node_fs5 = __toESM(require("fs"), 1);
15092
- var import_node_module = require("module");
15093
- var import_node_path9 = __toESM(require("path"), 1);
15094
- var import_node_url = require("url");
14848
+ var import_extractor9 = require("@suss/extractor");
14849
+ var import_extractor10 = require("@suss/extractor");
15095
14850
  var import_meta = {};
15096
14851
  var ADAPTER_VERSION = "0.2.1";
15097
- var ANALYSIS_PACKAGES = [
15098
- "@suss/extractor",
15099
- "@suss/resolution",
15100
- "@suss/datalog",
15101
- "@suss/behavioral-ir"
15102
- ];
15103
- var SOURCE_STAMP = { kind: "source" };
15104
- var cachedCodeStamp = null;
15105
- function adapterCodeStamp() {
15106
- if (cachedCodeStamp !== null) {
15107
- return cachedCodeStamp;
15108
- }
15109
- cachedCodeStamp = readAdapterCodeStamp();
15110
- return cachedCodeStamp;
15111
- }
15112
- function readAdapterCodeStamp() {
15113
- try {
15114
- const selfPath = (0, import_node_url.fileURLToPath)(import_meta.url);
15115
- const hash = computeDistHashFrom(import_node_path9.default.dirname(selfPath));
15116
- return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
15117
- } catch {
15118
- return SOURCE_STAMP;
15119
- }
15120
- }
15121
- function computeDistHashFrom(dir) {
15122
- const candidates = [import_node_path9.default.join(dir, "index.js"), import_node_path9.default.join(dir, "index.cjs")];
15123
- for (const candidate of candidates) {
15124
- if (!import_node_fs5.default.existsSync(candidate)) {
15125
- continue;
15126
- }
15127
- return computeContentHash([candidate, ...analysisBundles()]);
15128
- }
15129
- return "";
15130
- }
15131
- function computeContentHash(paths) {
15132
- if (paths.length === 0) {
15133
- return "";
15134
- }
15135
- const hash = (0, import_node_crypto3.createHash)("sha256");
15136
- for (const file of paths) {
15137
- try {
15138
- hash.update(import_node_fs5.default.readFileSync(file));
15139
- } catch {
15140
- return "";
15141
- }
15142
- }
15143
- return hash.digest("hex").slice(0, 16);
15144
- }
15145
- function projectFileStamp(paths) {
15146
- if (paths.length === 0) {
15147
- return "none";
15148
- }
15149
- const hash = (0, import_node_crypto3.createHash)("sha256");
15150
- for (const file of [...paths].sort()) {
15151
- hash.update(file);
15152
- hash.update("\0");
15153
- try {
15154
- hash.update(import_node_fs5.default.readFileSync(file));
15155
- } catch {
15156
- hash.update("absent");
15157
- }
15158
- }
15159
- return hash.digest("hex").slice(0, 16);
15160
- }
15161
- function analysisBundles() {
15162
- const require2 = (0, import_node_module.createRequire)(import_meta.url);
15163
- const found = [];
15164
- for (const name of ANALYSIS_PACKAGES) {
15165
- try {
15166
- found.push(require2.resolve(name));
15167
- } catch {
15168
- }
15169
- }
15170
- return found;
15171
- }
14852
+ var stamp = (0, import_extractor9.createAdapterStamp)({
14853
+ moduleUrl: import_meta.url,
14854
+ version: ADAPTER_VERSION
14855
+ });
15172
14856
  function computeAdapterPacksDigest(packVersions) {
15173
- const sortedPacks = [...packVersions].map((p) => `${p.name}@${p.version ?? "unset"}`).sort();
15174
- const stamp = adapterCodeStamp();
15175
- const adapterStamp = stamp.kind === "bundle" ? `adapter@${ADAPTER_VERSION}+${stamp.hash}` : `adapter@${ADAPTER_VERSION}+source`;
15176
- return `${adapterStamp}|${sortedPacks.join(",")}`;
14857
+ return stamp.packsDigest(packVersions);
14858
+ }
14859
+ function declineWhenRunFromSource(cacheDir) {
14860
+ return stamp.declineWhenRunFromSource(cacheDir);
15177
14861
  }
15178
14862
 
15179
14863
  // src/workspacePatterns.ts
15180
- var import_node_fs6 = __toESM(require("fs"), 1);
15181
- var import_node_path10 = __toESM(require("path"), 1);
14864
+ var import_node_fs3 = __toESM(require("fs"), 1);
14865
+ var import_node_path9 = __toESM(require("path"), 1);
15182
14866
  function readJson(filePath) {
15183
14867
  try {
15184
- return JSON.parse(import_node_fs6.default.readFileSync(filePath, "utf8"));
14868
+ return JSON.parse(import_node_fs3.default.readFileSync(filePath, "utf8"));
15185
14869
  } catch {
15186
14870
  return void 0;
15187
14871
  }
@@ -15208,7 +14892,7 @@ function pnpmWorkspaceGlobs(yamlText) {
15208
14892
  return globs;
15209
14893
  }
15210
14894
  function workspaceGlobsAt(dir) {
15211
- const pkg = readJson(import_node_path10.default.join(dir, "package.json"));
14895
+ const pkg = readJson(import_node_path9.default.join(dir, "package.json"));
15212
14896
  if (typeof pkg === "object" && pkg !== null && "workspaces" in pkg) {
15213
14897
  const ws = pkg.workspaces;
15214
14898
  if (Array.isArray(ws)) {
@@ -15220,10 +14904,10 @@ function workspaceGlobsAt(dir) {
15220
14904
  );
15221
14905
  }
15222
14906
  }
15223
- const pnpmPath = import_node_path10.default.join(dir, "pnpm-workspace.yaml");
15224
- if (import_node_fs6.default.existsSync(pnpmPath)) {
14907
+ const pnpmPath = import_node_path9.default.join(dir, "pnpm-workspace.yaml");
14908
+ if (import_node_fs3.default.existsSync(pnpmPath)) {
15225
14909
  try {
15226
- return pnpmWorkspaceGlobs(import_node_fs6.default.readFileSync(pnpmPath, "utf8"));
14910
+ return pnpmWorkspaceGlobs(import_node_fs3.default.readFileSync(pnpmPath, "utf8"));
15227
14911
  } catch {
15228
14912
  return null;
15229
14913
  }
@@ -15232,14 +14916,14 @@ function workspaceGlobsAt(dir) {
15232
14916
  }
15233
14917
  function isDirectory(candidate) {
15234
14918
  try {
15235
- return import_node_fs6.default.statSync(candidate).isDirectory();
14919
+ return import_node_fs3.default.statSync(candidate).isDirectory();
15236
14920
  } catch {
15237
14921
  return false;
15238
14922
  }
15239
14923
  }
15240
14924
  function subdirectoriesOf(dir) {
15241
14925
  try {
15242
- return import_node_fs6.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => !name.startsWith(".") && name !== "node_modules");
14926
+ return import_node_fs3.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => !name.startsWith(".") && name !== "node_modules");
15243
14927
  } catch {
15244
14928
  return [];
15245
14929
  }
@@ -15257,13 +14941,13 @@ function directoriesMatchingGlob(root, glob) {
15257
14941
  for (const dir of current2) {
15258
14942
  for (const entry of subdirectoriesOf(dir)) {
15259
14943
  if (pattern.test(entry)) {
15260
- next.push(import_node_path10.default.join(dir, entry));
14944
+ next.push(import_node_path9.default.join(dir, entry));
15261
14945
  }
15262
14946
  }
15263
14947
  }
15264
14948
  } else {
15265
14949
  for (const dir of current2) {
15266
- next.push(import_node_path10.default.join(dir, segment));
14950
+ next.push(import_node_path9.default.join(dir, segment));
15267
14951
  }
15268
14952
  }
15269
14953
  current2 = next.filter(isDirectory);
@@ -15271,7 +14955,7 @@ function directoriesMatchingGlob(root, glob) {
15271
14955
  return current2;
15272
14956
  }
15273
14957
  function packageAt(dir) {
15274
- const packageJsonPath = import_node_path10.default.join(dir, "package.json");
14958
+ const packageJsonPath = import_node_path9.default.join(dir, "package.json");
15275
14959
  const pkg = readJson(packageJsonPath);
15276
14960
  if (typeof pkg === "object" && pkg !== null && typeof pkg.name === "string") {
15277
14961
  return { name: pkg.name, packageJsonPath };
@@ -15279,7 +14963,7 @@ function packageAt(dir) {
15279
14963
  return null;
15280
14964
  }
15281
14965
  function workspacePackagesFrom(anchor) {
15282
- let dir = import_node_path10.default.resolve(anchor);
14966
+ let dir = import_node_path9.default.resolve(anchor);
15283
14967
  for (; ; ) {
15284
14968
  const globs = workspaceGlobsAt(dir);
15285
14969
  if (globs !== null) {
@@ -15299,7 +14983,7 @@ function workspacePackagesFrom(anchor) {
15299
14983
  (a, b) => a.packageJsonPath.localeCompare(b.packageJsonPath)
15300
14984
  );
15301
14985
  }
15302
- const parent = import_node_path10.default.dirname(dir);
14986
+ const parent = import_node_path9.default.dirname(dir);
15303
14987
  if (parent === dir) {
15304
14988
  return [];
15305
14989
  }
@@ -15333,7 +15017,7 @@ function workspaceExpansionStamp(frameworks) {
15333
15017
  const paths = frameworks.flatMap((pack) => pack.discovery).flatMap(
15334
15018
  (pattern) => pattern.match.type === "packageExports" && pattern.match.packageJsonPath !== void 0 ? [pattern.match.packageJsonPath] : []
15335
15019
  );
15336
- return projectFileStamp(paths);
15020
+ return (0, import_extractor10.projectFileStamp)(paths);
15337
15021
  }
15338
15022
  function expandWorkspacePatterns(frameworks, anchor) {
15339
15023
  const marked = frameworks.some(
@@ -15841,11 +15525,11 @@ function extractConsumerBinding(unit, pattern, pack, resolution) {
15841
15525
  return null;
15842
15526
  }
15843
15527
  const method = extractBindingMethod(binding, callSite, pack, resolution);
15844
- const path15 = extractBindingPath(binding, callSite, pack, resolution);
15845
- return (0, import_behavioral_ir8.restBinding)({
15528
+ const path14 = extractBindingPath(binding, callSite, pack, resolution);
15529
+ return (0, import_behavioral_ir9.restBinding)({
15846
15530
  transport: pack.protocol,
15847
15531
  method: method ?? null,
15848
- path: path15 ?? null,
15532
+ path: path14 ?? null,
15849
15533
  recognition: pack.name
15850
15534
  });
15851
15535
  }
@@ -15861,7 +15545,7 @@ function fileRouteBinding(filePath, unit, binding, pack) {
15861
15545
  if (method === void 0) {
15862
15546
  return null;
15863
15547
  }
15864
- return (0, import_behavioral_ir8.restBinding)({
15548
+ return (0, import_behavioral_ir9.restBinding)({
15865
15549
  transport: pack.protocol,
15866
15550
  recognition: pack.name,
15867
15551
  method,
@@ -15991,7 +15675,7 @@ function computeSubUnitBarriers(unit, packs, ctx, tallies, file) {
15991
15675
  }
15992
15676
  } catch (err) {
15993
15677
  process.stderr.write(
15994
- recordPackFailure(tallies?.get(pack.name), {
15678
+ (0, import_extractor3.recordPackFailure)(tallies?.get(pack.name), {
15995
15679
  pack: pack.name,
15996
15680
  hook: "subUnits",
15997
15681
  file,
@@ -16053,7 +15737,7 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16053
15737
  }
16054
15738
  } catch (err) {
16055
15739
  process.stderr.write(
16056
- recordPackFailure(tally, {
15740
+ (0, import_extractor3.recordPackFailure)(tally, {
16057
15741
  pack: pack.name,
16058
15742
  hook: "discoverUnits",
16059
15743
  file: sourceFile.getFilePath(),
@@ -16114,7 +15798,7 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16114
15798
  stampWrappers(raw, unit, wrappers);
16115
15799
  const matchedPattern = unit.pattern ?? pack.discovery.find((d) => d.kind === unit.kind);
16116
15800
  if (unit.resolverInfo !== void 0) {
16117
- raw.boundaryBinding = (0, import_behavioral_ir8.graphqlResolverBinding)({
15801
+ raw.boundaryBinding = (0, import_behavioral_ir9.graphqlResolverBinding)({
16118
15802
  transport: pack.protocol,
16119
15803
  recognition: pack.name,
16120
15804
  typeName: unit.resolverInfo.typeName,
@@ -16141,26 +15825,26 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16141
15825
  }
16142
15826
  }
16143
15827
  } else if (unit.routeInfo !== void 0) {
16144
- raw.boundaryBinding = (0, import_behavioral_ir8.restBinding)({
15828
+ raw.boundaryBinding = (0, import_behavioral_ir9.restBinding)({
16145
15829
  transport: pack.protocol,
16146
15830
  recognition: pack.name,
16147
15831
  method: unit.routeInfo.method,
16148
15832
  path: unit.routeInfo.path
16149
15833
  });
16150
15834
  } else if (unit.channelInfo !== void 0) {
16151
- raw.boundaryBinding = (0, import_behavioral_ir8.messageBusBinding)({
15835
+ raw.boundaryBinding = (0, import_behavioral_ir9.messageBusBinding)({
16152
15836
  recognition: pack.name,
16153
15837
  messageBus: unit.channelInfo.messageBus,
16154
15838
  channel: unit.channelInfo.channel
16155
15839
  });
16156
15840
  } else if (unit.invocationInfo !== void 0) {
16157
- raw.boundaryBinding = (0, import_behavioral_ir8.unitInvocationBinding)({
15841
+ raw.boundaryBinding = (0, import_behavioral_ir9.unitInvocationBinding)({
16158
15842
  recognition: pack.name,
16159
15843
  deploymentTarget: unit.invocationInfo.deploymentTarget,
16160
15844
  instanceName: unit.invocationInfo.instanceName
16161
15845
  });
16162
15846
  } else if (unit.operationInfo !== void 0) {
16163
- raw.boundaryBinding = (0, import_behavioral_ir8.graphqlOperationBinding)({
15847
+ raw.boundaryBinding = (0, import_behavioral_ir9.graphqlOperationBinding)({
16164
15848
  transport: pack.protocol,
16165
15849
  recognition: pack.name,
16166
15850
  operationType: unit.operationInfo.operationType,
@@ -16249,14 +15933,14 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16249
15933
  }
16250
15934
  if (raw.boundaryBinding === null) {
16251
15935
  if (unit.packageExportInfo !== void 0) {
16252
- raw.boundaryBinding = (0, import_behavioral_ir8.packageExportBinding)({
15936
+ raw.boundaryBinding = (0, import_behavioral_ir9.packageExportBinding)({
16253
15937
  transport: pack.protocol,
16254
15938
  recognition: pack.name,
16255
15939
  packageName: unit.packageExportInfo.packageName,
16256
15940
  exportPath: unit.packageExportInfo.exportPath
16257
15941
  });
16258
15942
  } else {
16259
- raw.boundaryBinding = (0, import_behavioral_ir8.functionCallBinding)({
15943
+ raw.boundaryBinding = (0, import_behavioral_ir9.functionCallBinding)({
16260
15944
  transport: pack.protocol,
16261
15945
  recognition: pack.name,
16262
15946
  ...unit.functionCallInfo !== void 0 ? {
@@ -16266,9 +15950,9 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16266
15950
  });
16267
15951
  }
16268
15952
  }
16269
- const summary = (0, import_extractor7.assembleSummary)(raw, options);
15953
+ const summary = (0, import_extractor11.assembleSummary)(raw, options);
16270
15954
  if (unit.unfollowed !== void 0 && options?.gapHandling !== "silent") {
16271
- summary.gaps.push(unfollowedCallGap(unit.unfollowed));
15955
+ summary.gaps.push((0, import_behavioral_ir3.unfollowedCallGap)(unit.unfollowed));
16272
15956
  }
16273
15957
  if (unit.metadata !== void 0 && Object.keys(unit.metadata).length > 0) {
16274
15958
  summary.metadata = {
@@ -16321,24 +16005,6 @@ function stampWrappers(raw, unit, wrappers) {
16321
16005
  recordFileDependency(wrapper.file);
16322
16006
  }
16323
16007
  }
16324
- function stampModuleImports(summaries, project) {
16325
- const byFile2 = /* @__PURE__ */ new Map();
16326
- for (const summary of summaries) {
16327
- const file = summary.location.file;
16328
- let imports = byFile2.get(file);
16329
- if (imports === void 0) {
16330
- imports = internalImportsOf(project, file);
16331
- byFile2.set(file, imports);
16332
- }
16333
- if (imports.length === 0) {
16334
- continue;
16335
- }
16336
- summary.metadata = {
16337
- ...summary.metadata ?? {},
16338
- moduleImports: imports
16339
- };
16340
- }
16341
- }
16342
16008
  function emitLibraryEnvReadMarkers(summaries, project, frameworks) {
16343
16009
  for (const pack of frameworks) {
16344
16010
  for (const declared of pack.libraryEnvVars ?? []) {
@@ -16366,14 +16032,14 @@ function fileImporting(project, modulePrefix) {
16366
16032
  function liftSchemasOntoDocuments(summaries) {
16367
16033
  const sdlByDocument = /* @__PURE__ */ new Map();
16368
16034
  for (const summary of summaries) {
16369
- const label = (0, import_behavioral_ir8.readSourceDocumentMetadata)(summary)?.label;
16370
- const graphql = (0, import_behavioral_ir8.readGraphqlMetadata)(summary);
16035
+ const label = (0, import_behavioral_ir9.readSourceDocumentMetadata)(summary)?.label;
16036
+ const graphql = (0, import_behavioral_ir9.readGraphqlMetadata)(summary);
16371
16037
  if (label === void 0 || graphql?.schemaSdl === void 0) {
16372
16038
  continue;
16373
16039
  }
16374
16040
  sdlByDocument.set(label, graphql.schemaSdl);
16375
16041
  const { schemaSdl: _movedToTheDocument, ...rest } = graphql;
16376
- summary.metadata = (0, import_behavioral_ir8.withGraphqlMetadata)(summary.metadata, rest);
16042
+ summary.metadata = (0, import_behavioral_ir9.withGraphqlMetadata)(summary.metadata, rest);
16377
16043
  }
16378
16044
  for (const [label, schemaSdl] of sdlByDocument) {
16379
16045
  summaries.push(schemaDocumentSummary(label, schemaSdl));
@@ -16388,8 +16054,8 @@ function schemaDocumentSummary(label, schemaSdl) {
16388
16054
  transitions: [],
16389
16055
  gaps: [],
16390
16056
  confidence: { source: "declared", level: "high" },
16391
- metadata: (0, import_behavioral_ir8.withGraphqlMetadata)(
16392
- (0, import_behavioral_ir8.withSourceDocumentMetadata)(void 0, { label }),
16057
+ metadata: (0, import_behavioral_ir9.withGraphqlMetadata)(
16058
+ (0, import_behavioral_ir9.withSourceDocumentMetadata)(void 0, { label }),
16393
16059
  { schemaSdl }
16394
16060
  )
16395
16061
  };
@@ -16401,7 +16067,7 @@ function libraryEnvReadMarker(pack, declared, importer) {
16401
16067
  identity: {
16402
16068
  name: `${declared.module} env reads`,
16403
16069
  exportPath: null,
16404
- boundaryBinding: (0, import_behavioral_ir8.functionCallBinding)({
16070
+ boundaryBinding: (0, import_behavioral_ir9.functionCallBinding)({
16405
16071
  transport: "in-process",
16406
16072
  recognition: pack.name
16407
16073
  })
@@ -16513,8 +16179,8 @@ function synthesizeCallerSummaries(wrapper, _project, options, resolution) {
16513
16179
  if (pathArg === void 0) {
16514
16180
  continue;
16515
16181
  }
16516
- const path15 = pathFromArgument(pathArg, resolution);
16517
- if (path15 === void 0) {
16182
+ const path14 = pathFromArgument(pathArg, resolution);
16183
+ if (path14 === void 0) {
16518
16184
  continue;
16519
16185
  }
16520
16186
  const callerFunc = enclosingFunction(callExpr);
@@ -16526,7 +16192,7 @@ function synthesizeCallerSummaries(wrapper, _project, options, resolution) {
16526
16192
  continue;
16527
16193
  }
16528
16194
  seen.add(dedupKey);
16529
- out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path15, options));
16195
+ out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path14, options));
16530
16196
  }
16531
16197
  return out;
16532
16198
  }
@@ -16583,7 +16249,7 @@ function callerName(func) {
16583
16249
  }
16584
16250
  return "anonymous";
16585
16251
  }
16586
- function buildCallerSummary(wrapper, callerFunc, callExpr, path15, options) {
16252
+ function buildCallerSummary(wrapper, callerFunc, callExpr, path14, options) {
16587
16253
  const wrapperBinding = wrapper.summary.identity.boundaryBinding;
16588
16254
  const wrapperRest = wrapperBinding?.semantics.name === "rest" ? wrapperBinding.semantics : null;
16589
16255
  const syntheticPack = {
@@ -16612,13 +16278,13 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path15, options) {
16612
16278
  }
16613
16279
  };
16614
16280
  const raw = extractCodeStructure(unit, syntheticPack);
16615
- raw.boundaryBinding = (0, import_behavioral_ir8.restBinding)({
16281
+ raw.boundaryBinding = (0, import_behavioral_ir9.restBinding)({
16616
16282
  transport: wrapperBinding?.transport ?? "http",
16617
16283
  method: wrapperRest?.method ?? null,
16618
- path: path15,
16284
+ path: path14,
16619
16285
  recognition: wrapperBinding?.recognition ?? "unknown"
16620
16286
  });
16621
- const summary = (0, import_extractor7.assembleSummary)(raw, options);
16287
+ const summary = (0, import_extractor11.assembleSummary)(raw, options);
16622
16288
  summary.metadata = {
16623
16289
  ...summary.metadata ?? {},
16624
16290
  derivedFromWrapper: {
@@ -16637,25 +16303,12 @@ function named(summaries, workspace, runRoot) {
16637
16303
  });
16638
16304
  return summaries;
16639
16305
  }
16640
- var saidWhyNoCache = false;
16641
16306
  function extractionConfigStamp(config) {
16642
16307
  return [
16643
16308
  `includeReachable=${config.includeReachable !== false}`,
16644
16309
  `gapHandling=${config.extractorOptions?.gapHandling ?? "default"}`
16645
16310
  ].join(",");
16646
16311
  }
16647
- function declineWhenRunFromSource(cacheDir) {
16648
- if (cacheDir === null || adapterCodeStamp().kind === "bundle") {
16649
- return cacheDir;
16650
- }
16651
- if (!saidWhyNoCache) {
16652
- saidWhyNoCache = true;
16653
- process.stderr.write(
16654
- "[suss] extraction cache off: this process loaded the adapter from source, where nothing can tell one build of it from another. Run the built adapter to cache.\n"
16655
- );
16656
- }
16657
- return null;
16658
- }
16659
16312
  function loadRunFiles(project, candidatePaths, timer) {
16660
16313
  if (candidatePaths !== null) {
16661
16314
  const deep2 = timer.time(
@@ -16686,7 +16339,7 @@ function createTypeScriptAdapter(suppliedConfig) {
16686
16339
  ...suppliedConfig,
16687
16340
  frameworks: expandWorkspacePatterns(
16688
16341
  suppliedConfig.frameworks,
16689
- suppliedConfig.projectRoot ?? (suppliedConfig.tsConfigFilePath !== void 0 ? import_node_path11.default.dirname(suppliedConfig.tsConfigFilePath) : void 0)
16342
+ suppliedConfig.projectRoot ?? (suppliedConfig.tsConfigFilePath !== void 0 ? import_node_path10.default.dirname(suppliedConfig.tsConfigFilePath) : void 0)
16690
16343
  )
16691
16344
  };
16692
16345
  const project = config.project ?? new import_ts_morph66.Project(
@@ -16695,24 +16348,18 @@ function createTypeScriptAdapter(suppliedConfig) {
16695
16348
  skipAddingFilesFromTsConfig: true
16696
16349
  } : { skipAddingFilesFromTsConfig: true }
16697
16350
  );
16698
- const runRoot = config.projectRoot ?? (config.tsConfigFilePath !== void 0 ? workspaceRootFor(import_node_path11.default.dirname(config.tsConfigFilePath)) : void 0);
16351
+ const runRoot = config.projectRoot ?? (config.tsConfigFilePath !== void 0 ? workspaceRootFor(import_node_path10.default.dirname(config.tsConfigFilePath)) : void 0);
16699
16352
  let lazyBootstrapped = false;
16700
16353
  let projectFileSet;
16701
16354
  const cacheDir = declineWhenRunFromSource(
16702
- config.cacheDir === null ? null : config.cacheDir ?? (config.tsConfigFilePath !== void 0 ? import_node_path11.default.join(import_node_path11.default.dirname(config.tsConfigFilePath), ".suss", "cache") : null)
16355
+ config.cacheDir === null ? null : config.cacheDir ?? (config.tsConfigFilePath !== void 0 ? import_node_path10.default.join(import_node_path10.default.dirname(config.tsConfigFilePath), ".suss", "cache") : null)
16703
16356
  );
16704
- const cache = createCacheLayer(cacheDir);
16357
+ const cache = (0, import_extractor11.createCacheLayer)(cacheDir);
16705
16358
  const packsDigest = `${computeAdapterPacksDigest(
16706
16359
  config.frameworks.map(
16707
16360
  (p) => p.version !== void 0 ? { name: p.name, version: p.version } : { name: p.name }
16708
16361
  )
16709
16362
  )}|${extractionConfigStamp(config)}|ws:${workspaceExpansionStamp(config.frameworks)}`;
16710
- const digestFor = (files) => {
16711
- const inputs = config.frameworks.flatMap(
16712
- (pack) => pack.discoveryInputs?.(files) ?? []
16713
- );
16714
- return `${packsDigest}|reads:${projectFileStamp(inputs)}`;
16715
- };
16716
16363
  const packWrappers = config.frameworks.flatMap(
16717
16364
  (pack) => pack.transparentWrappers ?? []
16718
16365
  );
@@ -16745,8 +16392,8 @@ function createTypeScriptAdapter(suppliedConfig) {
16745
16392
  return named(summaries, config.workspace, runRoot);
16746
16393
  },
16747
16394
  async extractAll() {
16748
- const timer = createTimer();
16749
- const tallies = createPackTallies(config.frameworks);
16395
+ const timer = (0, import_extractor8.createTimer)();
16396
+ const tallies = (0, import_extractor3.createPackTallies)(config.frameworks);
16750
16397
  forgetUnreadableExportFiles();
16751
16398
  forgetReassignedNamesUnstated();
16752
16399
  const lazyEligible = !lazyBootstrapped && config.tsConfigFilePath !== void 0 && config.project === void 0;
@@ -16756,20 +16403,17 @@ function createTypeScriptAdapter(suppliedConfig) {
16756
16403
  config.tsConfigFilePath ?? raise("lazy bootstrap requires tsConfigFilePath")
16757
16404
  )
16758
16405
  ) : null;
16759
- const adapterPacksDigest = cacheDir === null ? packsDigest : timer.time(
16760
- "cache.digest",
16761
- () => digestFor(
16762
- tsconfigFileList ?? project.getSourceFiles().map((sf) => sf.getFilePath())
16763
- )
16764
- );
16765
- const cacheInput = tsconfigFileList !== null ? {
16766
- files: tsconfigFileList,
16767
- adapterPacksDigest,
16768
- tsconfigPath: config.tsConfigFilePath ?? raise("lazy bootstrap requires tsConfigFilePath")
16769
- } : {
16770
- project,
16406
+ let cacheFiles = null;
16407
+ const adapterPacksDigest = cacheDir === null ? packsDigest : timer.time("cache.digest", () => {
16408
+ cacheFiles = tsconfigFileList ?? project.getSourceFiles().map((sf) => sf.getFilePath());
16409
+ return (0, import_extractor11.runDigest)(packsDigest, config.frameworks, cacheFiles);
16410
+ });
16411
+ const cacheInput = {
16412
+ // Empty when cacheDir is null: a no-op cache layer never reads
16413
+ // the file list, so there is nothing worth resolving early.
16414
+ files: cacheFiles ?? [],
16771
16415
  adapterPacksDigest,
16772
- ...config.tsConfigFilePath !== void 0 ? { tsconfigPath: config.tsConfigFilePath } : {}
16416
+ ...config.tsConfigFilePath !== void 0 ? { configPath: config.tsConfigFilePath } : {}
16773
16417
  };
16774
16418
  const lookup = await timer.timeAsync(
16775
16419
  "cache.lookup",
@@ -16975,7 +16619,8 @@ function createTypeScriptAdapter(suppliedConfig) {
16975
16619
  ],
16976
16620
  access: collectAccessRecognizers(config.frameworks, tallies),
16977
16621
  resolveWrittenValue: (value) => resolution.resolveWrittenValue(value),
16978
- resolveCallableSources: (value, alsoFrom) => resolution.resolveCallableSources(value, alsoFrom)
16622
+ resolveCallableSources: (value, alsoFrom) => resolution.resolveCallableSources(value, alsoFrom),
16623
+ sourceDeclarationsBehind: (declaration) => sourceDeclarationsBehind(declaration, resolution)
16979
16624
  },
16980
16625
  // Reached units the cache already serves emit nothing,
16981
16626
  // the way a cold run's seeds do not.
@@ -17012,7 +16657,10 @@ function createTypeScriptAdapter(suppliedConfig) {
17012
16657
  }
17013
16658
  timer.time(
17014
16659
  "stampModuleImports",
17015
- () => stampModuleImports(enriched, project)
16660
+ () => (0, import_extractor11.stampModuleImports)(enriched, (file) => {
16661
+ const imports = internalImportsOf(project, file);
16662
+ return imports.length === 0 ? void 0 : imports;
16663
+ })
17016
16664
  );
17017
16665
  timer.time(
17018
16666
  "emitLibraryEnvReadMarkers",
@@ -17071,7 +16719,7 @@ function createTypeScriptAdapter(suppliedConfig) {
17071
16719
  });
17072
16720
  const composed = timer.time(
17073
16721
  "composeWrappers",
17074
- () => (0, import_extractor7.composeWrappers)(enriched)
16722
+ () => (0, import_extractor11.composeWrappers)(enriched)
17075
16723
  );
17076
16724
  if (config.onTiming !== void 0) {
17077
16725
  config.onTiming(timer.report());
@@ -17103,7 +16751,7 @@ function createTypeScriptAdapter(suppliedConfig) {
17103
16751
  };
17104
16752
  }
17105
16753
  function mountAssumptionsAgree(record2, index) {
17106
- return Object.entries(record2.mountPrefixes).every(
16754
+ return Object.entries(record2.meta.mountPrefixes).every(
17107
16755
  ([childId, prefix]) => (index.prefixForId?.(childId) ?? "") === prefix
17108
16756
  );
17109
16757
  }
@@ -17203,10 +16851,10 @@ function mergeWithReused(reused, fresh, ownersBySummary) {
17203
16851
  return { summaries: [...reusedKept, ...fresh], reusedKept, reusedOwners };
17204
16852
  }
17205
16853
  function readsRunLevelJoins(summary) {
17206
- if ((0, import_behavioral_ir8.readSourceDocumentMetadata)(summary) !== void 0) {
16854
+ if ((0, import_behavioral_ir9.readSourceDocumentMetadata)(summary) !== void 0) {
17207
16855
  return true;
17208
16856
  }
17209
- if ((0, import_behavioral_ir8.readGraphqlMetadata)(summary)?.client !== void 0) {
16857
+ if ((0, import_behavioral_ir9.readGraphqlMetadata)(summary)?.client !== void 0) {
17210
16858
  return true;
17211
16859
  }
17212
16860
  const semantics = summary.identity.boundaryBinding?.semantics;
@@ -17263,7 +16911,7 @@ function buildCacheAttribution(args) {
17263
16911
  cacheable,
17264
16912
  deps: [...deps].sort(),
17265
16913
  claims: sink.claims,
17266
- mountPrefixes: Object.fromEntries(sink.mountPrefixes),
16914
+ meta: { mountPrefixes: Object.fromEntries(sink.mountPrefixes) },
17267
16915
  packs: packNamesByPath.get(rootPath) ?? []
17268
16916
  });
17269
16917
  }
@@ -17396,7 +17044,7 @@ function buildSubUnitSummary(subUnit, parent, invocationRecognizers, accessRecog
17396
17044
  if (parent.identity.boundaryBinding !== null) {
17397
17045
  raw.boundaryBinding = parent.identity.boundaryBinding;
17398
17046
  }
17399
- const summary = (0, import_extractor7.assembleSummary)(raw, options);
17047
+ const summary = (0, import_extractor11.assembleSummary)(raw, options);
17400
17048
  if (subUnit.metadata !== void 0 && Object.keys(subUnit.metadata).length > 0) {
17401
17049
  summary.metadata = {
17402
17050
  ...summary.metadata ?? {},
@@ -17408,8 +17056,8 @@ function buildSubUnitSummary(subUnit, parent, invocationRecognizers, accessRecog
17408
17056
  }
17409
17057
 
17410
17058
  // src/bootstrap/noTsconfigProject.ts
17411
- var import_node_fs7 = __toESM(require("fs"), 1);
17412
- var import_node_path12 = __toESM(require("path"), 1);
17059
+ var import_node_fs4 = __toESM(require("fs"), 1);
17060
+ var import_node_path11 = __toESM(require("path"), 1);
17413
17061
  var import_ts_morph67 = require("ts-morph");
17414
17062
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
17415
17063
  ".ts",
@@ -17440,7 +17088,7 @@ function findSourceFiles(rootDir, depth = 0) {
17440
17088
  const found = [];
17441
17089
  let entries;
17442
17090
  try {
17443
- entries = import_node_fs7.default.readdirSync(rootDir, { withFileTypes: true });
17091
+ entries = import_node_fs4.default.readdirSync(rootDir, { withFileTypes: true });
17444
17092
  } catch {
17445
17093
  return [];
17446
17094
  }
@@ -17448,10 +17096,10 @@ function findSourceFiles(rootDir, depth = 0) {
17448
17096
  if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name)) {
17449
17097
  continue;
17450
17098
  }
17451
- const full = import_node_path12.default.join(rootDir, entry.name);
17099
+ const full = import_node_path11.default.join(rootDir, entry.name);
17452
17100
  if (entry.isDirectory()) {
17453
17101
  found.push(...findSourceFiles(full, depth + 1));
17454
- } else if (SOURCE_EXTENSIONS.has(import_node_path12.default.extname(entry.name)) && !entry.name.endsWith(".d.ts")) {
17102
+ } else if (SOURCE_EXTENSIONS.has(import_node_path11.default.extname(entry.name)) && !entry.name.endsWith(".d.ts")) {
17455
17103
  found.push(full);
17456
17104
  }
17457
17105
  }
@@ -17486,15 +17134,15 @@ function createProjectWithoutTsconfig(rootDir) {
17486
17134
  return { project, files };
17487
17135
  }
17488
17136
  function findNearestTsconfig(startDir) {
17489
- let dir = import_node_path12.default.resolve(startDir);
17137
+ let dir = import_node_path11.default.resolve(startDir);
17490
17138
  while (true) {
17491
17139
  for (const name of ["tsconfig.json", "jsconfig.json"]) {
17492
- const candidate = import_node_path12.default.join(dir, name);
17493
- if (import_node_fs7.default.existsSync(candidate)) {
17140
+ const candidate = import_node_path11.default.join(dir, name);
17141
+ if (import_node_fs4.default.existsSync(candidate)) {
17494
17142
  return candidate;
17495
17143
  }
17496
17144
  }
17497
- const parent = import_node_path12.default.dirname(dir);
17145
+ const parent = import_node_path11.default.dirname(dir);
17498
17146
  if (parent === dir) {
17499
17147
  return null;
17500
17148
  }
@@ -17503,7 +17151,7 @@ function findNearestTsconfig(startDir) {
17503
17151
  }
17504
17152
 
17505
17153
  // src/facts/why.ts
17506
- var import_node_path13 = __toESM(require("path"), 1);
17154
+ var import_node_path12 = __toESM(require("path"), 1);
17507
17155
  var import_ts_morph68 = require("ts-morph");
17508
17156
  var import_resolution7 = require("@suss/resolution");
17509
17157
  var JS_PHRASES = {
@@ -17511,12 +17159,12 @@ var JS_PHRASES = {
17511
17159
  reason: `${describe(tuple[0])} is a .bind of ${describe(tuple[1])}, which runs the same function`
17512
17160
  })
17513
17161
  };
17514
- var WhySession = class {
17162
+ var TypeScriptWhySession = class {
17515
17163
  project;
17516
17164
  store;
17517
17165
  root;
17518
17166
  constructor(options) {
17519
- this.root = import_node_path13.default.resolve(options.dir);
17167
+ this.root = import_node_path12.default.resolve(options.dir);
17520
17168
  const tsconfig = findNearestTsconfig(this.root);
17521
17169
  this.project = tsconfig === null ? createProjectWithoutTsconfig(this.root).project : new import_ts_morph68.Project({ tsConfigFilePath: tsconfig });
17522
17170
  this.store = new ResolutionStore();
@@ -17605,7 +17253,7 @@ var WhySession = class {
17605
17253
  };
17606
17254
  }
17607
17255
  sourceFileAt(file) {
17608
- const absolute = import_node_path13.default.resolve(this.root, file);
17256
+ const absolute = import_node_path12.default.resolve(this.root, file);
17609
17257
  const loaded = this.project.getSourceFile(absolute);
17610
17258
  if (loaded !== void 0) {
17611
17259
  return loaded;
@@ -17619,7 +17267,7 @@ var WhySession = class {
17619
17267
  /** A module key: a file path said relative to the root, or a package name. */
17620
17268
  displayPath(key) {
17621
17269
  if (key.startsWith(this.root)) {
17622
- return import_node_path13.default.relative(this.root, key);
17270
+ return import_node_path12.default.relative(this.root, key);
17623
17271
  }
17624
17272
  return key;
17625
17273
  }
@@ -17647,228 +17295,8 @@ function displayNameOf(node) {
17647
17295
  return text.length > 40 ? `${text.slice(0, 40)}...` : text;
17648
17296
  }
17649
17297
 
17650
- // src/packHealth.ts
17651
- function stagesOf(funnel) {
17652
- const stages = [];
17653
- const gateSaysSomething = funnel.gates.length > 0 && funnel.unresolvedGates.length === 0;
17654
- if (funnel.recognizes && !funnel.discovers && gateSaysSomething) {
17655
- stages.push({
17656
- from: { count: funnel.unitsInGatedFiles, name: "unit bodies" },
17657
- to: { count: funnel.effectsRecognized, name: "effects" }
17658
- });
17659
- }
17660
- if (funnel.discovers && gateSaysSomething) {
17661
- stages.push({
17662
- from: { count: funnel.candidateFiles, name: "source files" },
17663
- to: { count: funnel.unitsDiscovered, name: "units" }
17664
- });
17665
- }
17666
- stages.push(
17667
- {
17668
- from: { count: funnel.unitsClaimed, name: "units" },
17669
- to: { count: funnel.summariesBound, name: "summaries" }
17670
- },
17671
- {
17672
- from: { count: funnel.summariesBound, name: "summaries" },
17673
- to: { count: funnel.summariesWithBehavior, name: "transitions" }
17674
- }
17675
- );
17676
- return stages;
17677
- }
17678
- function funnelDrops(packs) {
17679
- const violations = [];
17680
- for (const funnel of packs) {
17681
- for (const stage of stagesOf(funnel)) {
17682
- if (stage.from.count === 0 || stage.to.count > 0) {
17683
- continue;
17684
- }
17685
- violations.push({
17686
- label: funnel.pack,
17687
- detail: `${stage.from.count} ${stage.from.name} -> 0 ${stage.to.name}`
17688
- });
17689
- }
17690
- }
17691
- return violations;
17692
- }
17693
- function recognizersWithNoUnits(packs) {
17694
- return packs.filter(
17695
- (funnel) => funnel.recognizes && !funnel.discovers && funnel.gates.length > 0 && funnel.unresolvedGates.length === 0 && funnel.candidateFiles > 0 && funnel.unitsInGatedFiles === 0 && // The closure walks a recognizer-only pack's gated exports as
17696
- // roots, so effects recognized there mean the run worked and
17697
- // the missing framework pack costs attribution, not existence.
17698
- funnel.effectsRecognized === 0
17699
- ).map((funnel) => ({
17700
- label: funnel.pack,
17701
- detail: `${funnel.candidateFiles} gated files, and no pack in this run discovered a unit in them. ${funnel.pack} reads calls inside units another pack finds, so add the pack that finds this project's handlers (-f express, -f fastify, ...), or run suss init to work out which.`
17702
- }));
17703
- }
17704
- function helpersThatMatchedNothing(packs) {
17705
- return packs.flatMap(
17706
- (funnel) => funnel.helpersUnmatched.map((helper) => ({
17707
- label: funnel.pack,
17708
- detail: `${helper} matched no call in this run, so whatever it registers is missing. suss read that helper out of the project and then failed to match a call to it, which is a bug in suss rather than in your code.`
17709
- }))
17710
- );
17711
- }
17712
- function unversionedPacks(packs) {
17713
- return packs.filter((funnel) => funnel.version === null).map((funnel) => ({
17714
- label: funnel.pack,
17715
- detail: "no version declared"
17716
- }));
17717
- }
17718
- function selfCollisions(packs) {
17719
- return packs.filter((funnel) => funnel.selfCollisions > 0).map((funnel) => ({
17720
- label: funnel.pack,
17721
- detail: `${funnel.selfCollisions} ${funnel.selfCollisions === 1 ? "unit" : "units"} matched twice, second dropped`
17722
- }));
17723
- }
17724
- function threwWhileReading(packs) {
17725
- return packs.filter((funnel) => funnel.failures.length > 0).map((funnel) => {
17726
- const first = funnel.failures[0];
17727
- const rest = funnel.failures.length - 1;
17728
- const alsoIn = rest > 0 ? ` (+${rest} more)` : "";
17729
- return {
17730
- label: funnel.pack,
17731
- detail: `${first.hook} on ${first.file}${alsoIn}: ${first.message}`
17732
- };
17733
- });
17734
- }
17735
- function packGradients(report) {
17736
- const gradients = [];
17737
- for (const funnel of report.packs) {
17738
- if (funnel.declarations === null) {
17739
- continue;
17740
- }
17741
- gradients.push(gradientOf(funnel.pack, funnel.declarations.declarations));
17742
- }
17743
- return gradients;
17744
- }
17745
- function gradientOf(pack, declarations) {
17746
- const gradient = {
17747
- pack,
17748
- dataLinks: 0,
17749
- functionLinks: [],
17750
- astLinks: [],
17751
- withoutExample: []
17752
- };
17753
- for (const declaration of declarations) {
17754
- gradient.dataLinks += declaration.dataLinks;
17755
- for (const question of declaration.functionLinks) {
17756
- gradient.functionLinks.push(`${declaration.name}.${question}`);
17757
- }
17758
- for (const question of declaration.astLinks) {
17759
- gradient.astLinks.push(`${declaration.name}.${question}`);
17760
- }
17761
- if (declaration.example === null) {
17762
- gradient.withoutExample.push(declaration.name);
17763
- }
17764
- }
17765
- return gradient;
17766
- }
17767
- function opaqueLinks(gradients) {
17768
- return gradients.filter((gradient) => gradient.functionLinks.length > 0).map((gradient) => ({
17769
- label: gradient.pack,
17770
- detail: `${gradient.functionLinks.join(", ")} (${gradient.dataLinks} data)`
17771
- }));
17772
- }
17773
- function reachesTheSyntaxTree(gradients) {
17774
- return gradients.filter((gradient) => gradient.astLinks.length > 0).map((gradient) => ({
17775
- label: gradient.pack,
17776
- detail: gradient.astLinks.join(", ")
17777
- }));
17778
- }
17779
- function undocumentedDeclarations(gradients) {
17780
- return gradients.filter((gradient) => gradient.withoutExample.length > 0).map((gradient) => ({
17781
- label: gradient.pack,
17782
- detail: gradient.withoutExample.join(", ")
17783
- }));
17784
- }
17785
- function evaluatePackHealth(report) {
17786
- const gradients = packGradients(report);
17787
- return [
17788
- {
17789
- name: "no pack throws while it reads",
17790
- code: "threw",
17791
- audience: "run",
17792
- violations: threwWhileReading(report.packs)
17793
- },
17794
- {
17795
- name: "no pack finds something and records nothing",
17796
- code: "no-output",
17797
- audience: "run",
17798
- violations: funnelDrops(report.packs)
17799
- },
17800
- {
17801
- name: "no pack collides with itself",
17802
- code: "double-match",
17803
- audience: "run",
17804
- violations: selfCollisions(report.packs)
17805
- },
17806
- {
17807
- name: "every recognizer had units to look inside",
17808
- code: "no-units",
17809
- audience: "run",
17810
- violations: recognizersWithNoUnits(report.packs)
17811
- },
17812
- {
17813
- name: "every registration helper the run read matched a call",
17814
- code: "no-helper",
17815
- audience: "run",
17816
- violations: helpersThatMatchedNothing(report.packs)
17817
- },
17818
- {
17819
- name: "every pack declares a version",
17820
- code: "no-version",
17821
- audience: "pack",
17822
- violations: unversionedPacks(report.packs)
17823
- },
17824
- {
17825
- name: "every link a declared pack states is data",
17826
- code: "fn-link",
17827
- audience: "pack",
17828
- violations: opaqueLinks(gradients)
17829
- },
17830
- {
17831
- name: "no declared pack reads the syntax tree",
17832
- code: "ast-link",
17833
- audience: "pack",
17834
- violations: reachesTheSyntaxTree(gradients)
17835
- },
17836
- {
17837
- name: "every declaration states an example",
17838
- code: "no-example",
17839
- audience: "pack",
17840
- violations: undocumentedDeclarations(gradients)
17841
- }
17842
- ];
17843
- }
17844
- function formatPackHealth(checks, audiences) {
17845
- const fired = checks.filter(
17846
- (check) => check.violations.length > 0 && audiences.includes(check.audience)
17847
- );
17848
- if (fired.length === 0) {
17849
- return "";
17850
- }
17851
- const rows = fired.flatMap(
17852
- (check) => check.violations.map((violation) => ({
17853
- code: check.code,
17854
- pack: violation.label,
17855
- detail: violation.detail
17856
- }))
17857
- );
17858
- const codeWidth = Math.max(...rows.map((row) => row.code.length));
17859
- const packWidth = Math.max(...rows.map((row) => row.pack.length));
17860
- const lines = ["", `Pack health (${rows.length}):`];
17861
- for (const row of rows) {
17862
- lines.push(
17863
- ` ${row.code.padEnd(codeWidth)} ${row.pack.padEnd(packWidth)} ${row.detail}`
17864
- );
17865
- }
17866
- return `${lines.join("\n")}
17867
- `;
17868
- }
17869
-
17870
17298
  // src/stubEvidence.ts
17871
- var import_node_path14 = __toESM(require("path"), 1);
17299
+ var import_node_path13 = __toESM(require("path"), 1);
17872
17300
  var import_ts_morph69 = require("ts-morph");
17873
17301
  function stubEvidenceIn(project, packageName, root) {
17874
17302
  const resolution = new ResolutionStore();
@@ -17884,7 +17312,7 @@ function stubEvidenceIn(project, packageName, root) {
17884
17312
  calls: []
17885
17313
  };
17886
17314
  entry.calls.push({
17887
- file: import_node_path14.default.relative(root, sourceFile.getFilePath()).split(import_node_path14.default.sep).join("/"),
17315
+ file: import_node_path13.default.relative(root, sourceFile.getFilePath()).split(import_node_path13.default.sep).join("/"),
17888
17316
  line: one.call.getStartLineNumber(),
17889
17317
  args: one.call.getArguments().map((arg) => effectArgOf(arg))
17890
17318
  });
@@ -17899,7 +17327,7 @@ function stubEvidence(options) {
17899
17327
  const project = new import_ts_morph69.Project(
17900
17328
  options.tsConfigFilePath !== void 0 ? { tsConfigFilePath: options.tsConfigFilePath } : { compilerOptions: { allowJs: true } }
17901
17329
  );
17902
- const root = options.tsConfigFilePath !== void 0 ? import_node_path14.default.dirname(options.tsConfigFilePath) : import_node_path14.default.resolve(options.directory ?? process.cwd());
17330
+ const root = options.tsConfigFilePath !== void 0 ? import_node_path13.default.dirname(options.tsConfigFilePath) : import_node_path13.default.resolve(options.directory ?? process.cwd());
17903
17331
  if (options.tsConfigFilePath === void 0) {
17904
17332
  project.addSourceFilesAtPaths([
17905
17333
  `${root}/**/*.{ts,tsx,js,jsx}`,
@@ -17912,7 +17340,7 @@ function stubEvidence(options) {
17912
17340
  0 && (module.exports = {
17913
17341
  ADAPTER_VERSION,
17914
17342
  ResolutionStore,
17915
- WhySession,
17343
+ TypeScriptWhySession,
17916
17344
  accessContextFor,
17917
17345
  buildProjectHelperIndex,
17918
17346
  callOpsFor,
@@ -17925,13 +17353,11 @@ function stubEvidence(options) {
17925
17353
  createTsSubUnitContext,
17926
17354
  createTypeScriptAdapter,
17927
17355
  discoverUnits,
17928
- evaluatePackHealth,
17929
17356
  exportedDeclarationsOf,
17930
17357
  extractCodeStructure,
17931
17358
  extractRawBranches,
17932
17359
  findNearestTsconfig,
17933
17360
  findTerminals,
17934
- formatPackHealth,
17935
17361
  functionTargetOf,
17936
17362
  importDeclarationsOf,
17937
17363
  importedNamesOf,