@suss/extractor 0.35.0 → 0.36.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.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // src/index.ts
2
- import { createHash as createHash7 } from "crypto";
2
+ import { createHash as createHash8 } from "crypto";
3
3
  import {
4
- effectKey,
4
+ effectKey as effectKey2,
5
5
  exchangesHttpResponses as exchangesHttpResponses2,
6
6
  foldRepeatedEffects,
7
- normalizeCalleeText,
7
+ normalizeCalleeText as normalizeCalleeText2,
8
8
  withGraphqlMetadata,
9
9
  withHttpMetadata,
10
10
  withMountMetadata,
@@ -165,6 +165,45 @@ function mergeInputReads(derived, syntactic) {
165
165
  return [...found.values()];
166
166
  }
167
167
 
168
+ // src/callSiteGroup.ts
169
+ import { createHash } from "crypto";
170
+ import { effectKey, normalizeCalleeText } from "@suss/behavioral-ir";
171
+ function callSiteKey(effect) {
172
+ const callee = calleeOf(effect);
173
+ const written = callee === void 0 ? effect : { ...effect, callee: normalizeCalleeText(callee) };
174
+ const hash = createHash("sha1").update(effectKey(written)).digest("hex").slice(0, 7);
175
+ return callee === void 0 ? hash : `${normalizeCalleeText(callee)}:${hash}`;
176
+ }
177
+ function calleeOf(effect) {
178
+ return effect.type === "interaction" || effect.type === "invocation" ? effect.callee : void 0;
179
+ }
180
+ function numberRepeatedCallSites(sites) {
181
+ const taken = /* @__PURE__ */ new Map();
182
+ return sites.map((entries) => {
183
+ const ids = /* @__PURE__ */ new Map();
184
+ for (const key of new Set(
185
+ entries.flatMap((entry) => groupIdOf(entry.effect))
186
+ )) {
187
+ const count = (taken.get(key) ?? 0) + 1;
188
+ taken.set(key, count);
189
+ ids.set(key, count === 1 ? key : `${key}#${count}`);
190
+ }
191
+ return entries.map((entry) => ({
192
+ ...entry,
193
+ effect: withGroupId(entry.effect, ids)
194
+ }));
195
+ });
196
+ }
197
+ function groupIdOf(effect) {
198
+ return effect.type === "interaction" && effect.groupId !== void 0 ? [effect.groupId] : [];
199
+ }
200
+ function withGroupId(effect, ids) {
201
+ if (effect.type !== "interaction" || effect.groupId === void 0) {
202
+ return effect;
203
+ }
204
+ return { ...effect, groupId: ids.get(effect.groupId) ?? effect.groupId };
205
+ }
206
+
168
207
  // src/composeWrappers.ts
169
208
  import {
170
209
  exchangesHttpResponses,
@@ -472,6 +511,22 @@ function modulesStamp(modules) {
472
511
  // src/effectGuards.ts
473
512
  var keyOf = (condition) => `${condition.polarity}:${condition.sourceText}`;
474
513
  var opposite = (condition) => `${condition.polarity === "positive" ? "negative" : "positive"}:${condition.sourceText}`;
514
+ function firesOn(site, branch) {
515
+ const loop = branch.loopAround;
516
+ if (loop !== void 0 && site.line >= loop.start && site.line <= loop.end) {
517
+ return true;
518
+ }
519
+ if (!guardsHoldOn(site.preconditions, branch.conditions)) {
520
+ return false;
521
+ }
522
+ return runsBefore(
523
+ site.cleansUpFrom ?? site.line,
524
+ branch.terminal.location.end
525
+ );
526
+ }
527
+ function effectsFiringOn(placed, branch) {
528
+ return placed.filter((one) => firesOn(one.site, branch)).map((one) => one.effect);
529
+ }
475
530
  function guardsHoldOn(preconditions, conditions) {
476
531
  if (preconditions === void 0 || preconditions.length === 0) {
477
532
  return true;
@@ -539,16 +594,21 @@ function throughHelper(branch, at, helperBranches) {
539
594
  isDefault: branch.isDefault && helper.isDefault
540
595
  };
541
596
  }
597
+ const { loopAround, ...rest } = branch;
542
598
  return {
543
- ...branch,
599
+ ...rest,
544
600
  ...reads,
545
601
  conditions,
546
602
  terminal: { ...helper.terminal, location: at },
547
603
  location: at,
604
+ ...loopAround !== void 0 && within(at, loopAround) ? { loopAround } : {},
548
605
  isDefault: false
549
606
  };
550
607
  });
551
608
  }
609
+ function within(inner, outer) {
610
+ return inner.start >= outer.start && inner.end <= outer.end;
611
+ }
552
612
  function mergeReadShapes(a, b) {
553
613
  if (a === null || b === null) {
554
614
  return a ?? b;
@@ -566,12 +626,11 @@ function mergeReadShapes(a, b) {
566
626
  function splitAtHandOffs(branch, sites, from = 0) {
567
627
  for (let index = from; index < sites.length; index++) {
568
628
  const site = sites[index];
569
- const reached = site.alwaysRuns === true || runsBefore(site.line, branch.terminal.location.end);
570
- if (!reached || !guardsHoldOn(site.preconditions, branch.conditions)) {
629
+ if (!firesOn(site, branch)) {
571
630
  continue;
572
631
  }
573
632
  return throughHelper(branch, site.at, site.helper).flatMap(
574
- (path10) => path10.terminal === branch.terminal ? splitAtHandOffs(path10, sites, index + 1) : [path10]
633
+ (path11) => path11.terminal === branch.terminal ? splitAtHandOffs(path11, sites, index + 1) : [path11]
575
634
  );
576
635
  }
577
636
  return [branch];
@@ -618,6 +677,34 @@ function rootName(ref) {
618
677
  return ref.type === "dependency" ? ref.name : null;
619
678
  }
620
679
 
680
+ // src/libraryEnvReads.ts
681
+ import { functionCallBinding } from "@suss/behavioral-ir";
682
+ function libraryEnvReadMarker(recognition, declared, importer) {
683
+ return {
684
+ kind: "library",
685
+ location: { file: importer, range: { start: 0, end: 0 }, exportName: null },
686
+ identity: {
687
+ name: `${declared.module} env reads`,
688
+ exportPath: null,
689
+ boundaryBinding: functionCallBinding({
690
+ transport: "in-process",
691
+ recognition
692
+ })
693
+ },
694
+ inputs: [],
695
+ transitions: [],
696
+ gaps: [],
697
+ confidence: { source: "declared", level: "high" },
698
+ metadata: {
699
+ libraryEnvReads: {
700
+ module: declared.module,
701
+ ...declared.prefixes !== void 0 ? { prefixes: declared.prefixes } : {},
702
+ ...declared.names !== void 0 ? { names: declared.names } : {}
703
+ }
704
+ }
705
+ };
706
+ }
707
+
621
708
  // src/moduleImports.ts
622
709
  function stampModuleImports(summaries, importsOf2) {
623
710
  const byFile = /* @__PURE__ */ new Map();
@@ -919,8 +1006,8 @@ var catchEntryCond = () => fixedCond(
919
1006
  "positive",
920
1007
  "catchBlock"
921
1008
  );
922
- function classify(path10, home, isAncestorOrSelf) {
923
- return path10.map((cond) => {
1009
+ function classify(path11, home, isAncestorOrSelf) {
1010
+ return path11.map((cond) => {
924
1011
  if (cond.branchStmt === null) {
925
1012
  return cond.info;
926
1013
  }
@@ -992,9 +1079,9 @@ function limitOpenPaths(paths) {
992
1079
  );
993
1080
  }
994
1081
  }
995
- function recordTerminal(ctx, terminal, home, path10) {
1082
+ function recordTerminal(ctx, terminal, home, path11) {
996
1083
  chargeBudget(ctx.state);
997
- const info = classify(path10, home, ctx.isAncestorOrSelf);
1084
+ const info = classify(path11, home, ctx.isAncestorOrSelf);
998
1085
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
999
1086
  existing.push(info);
1000
1087
  ctx.state.byTerminal.set(terminal, existing);
@@ -1005,15 +1092,15 @@ function containsTerminal(ctx, stmt) {
1005
1092
  }
1006
1093
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
1007
1094
  }
1008
- function stepIf(stmt, path10, ctx) {
1095
+ function stepIf(stmt, path11, ctx) {
1009
1096
  const thenExit = exitKindOfList(stmt.thenBody);
1010
1097
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
1011
1098
  const thenPath = [
1012
- ...path10,
1099
+ ...path11,
1013
1100
  branchCond(stmt, stmt.condition, "positive", elseExit)
1014
1101
  ];
1015
1102
  const elsePath = [
1016
- ...path10,
1103
+ ...path11,
1017
1104
  branchCond(
1018
1105
  stmt,
1019
1106
  stmt.condition,
@@ -1030,7 +1117,7 @@ function stepIf(stmt, path10, ctx) {
1030
1117
  }
1031
1118
  return out;
1032
1119
  }
1033
- function stepSwitch(stmt, path10, ctx) {
1120
+ function stepSwitch(stmt, path11, ctx) {
1034
1121
  const out = [];
1035
1122
  const negations = [];
1036
1123
  let defaultRanBody = false;
@@ -1040,7 +1127,7 @@ function stepSwitch(stmt, path10, ctx) {
1040
1127
  if (hasStrayBreak(group.body)) {
1041
1128
  throw new UnmodeledFlow("non-trailing break in switch clause");
1042
1129
  }
1043
- const groupPath = group.condition === null ? [...path10, ...negations] : [...path10, fixedCond(group.condition, "positive", "explicit")];
1130
+ const groupPath = group.condition === null ? [...path11, ...negations] : [...path11, fixedCond(group.condition, "positive", "explicit")];
1044
1131
  const conts = enumerate(ctx, group.body, groupPath);
1045
1132
  if (conts.length > 0) {
1046
1133
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -1057,24 +1144,24 @@ function stepSwitch(stmt, path10, ctx) {
1057
1144
  }
1058
1145
  }
1059
1146
  if (!defaultRanBody) {
1060
- out.push([...path10, ...negations]);
1147
+ out.push([...path11, ...negations]);
1061
1148
  }
1062
1149
  return out;
1063
1150
  }
1064
- function stepLoop(stmt, path10, ctx) {
1151
+ function stepLoop(stmt, path11, ctx) {
1065
1152
  const bodyExit = exitKindOfList(stmt.body);
1066
1153
  if (containsTerminal(ctx, stmt)) {
1067
1154
  enumerate(ctx, stmt.body, [
1068
- ...path10,
1155
+ ...path11,
1069
1156
  loopIterationCond(stmt.condition.sourceText)
1070
1157
  ]);
1071
1158
  }
1072
1159
  if (bodyExit !== null) {
1073
- return [[...path10, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1160
+ return [[...path11, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1074
1161
  }
1075
- return [path10];
1162
+ return [path11];
1076
1163
  }
1077
- function stepTry(stmt, path10, ctx) {
1164
+ function stepTry(stmt, path11, ctx) {
1078
1165
  if (stmt.finallyBody !== null) {
1079
1166
  const finallyHasTerminal = stmt.finallyBody.some(
1080
1167
  (s) => containsTerminal(ctx, s)
@@ -1084,22 +1171,22 @@ function stepTry(stmt, path10, ctx) {
1084
1171
  }
1085
1172
  }
1086
1173
  const out = [];
1087
- out.push(...enumerate(ctx, stmt.tryBody, path10));
1174
+ out.push(...enumerate(ctx, stmt.tryBody, path11));
1088
1175
  if (stmt.catchBody !== null) {
1089
- const catchPath = [...path10, catchEntryCond()];
1176
+ const catchPath = [...path11, catchEntryCond()];
1090
1177
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
1091
1178
  }
1092
1179
  return out;
1093
1180
  }
1094
- function stepExit(stmt, path10, ctx) {
1181
+ function stepExit(stmt, path11, ctx) {
1095
1182
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
1096
1183
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
1097
- collector.push(path10);
1184
+ collector.push(path11);
1098
1185
  }
1099
1186
  return [];
1100
1187
  }
1101
- function stepOpaque(_stmt, path10, _ctx) {
1102
- return [path10];
1188
+ function stepOpaque(_stmt, path11, _ctx) {
1189
+ return [path11];
1103
1190
  }
1104
1191
  var stepHandlers = {
1105
1192
  if: stepIf,
@@ -1109,20 +1196,20 @@ var stepHandlers = {
1109
1196
  exit: stepExit,
1110
1197
  opaque: stepOpaque
1111
1198
  };
1112
- function stepCallback(ctx, body, path10) {
1199
+ function stepCallback(ctx, body, path11) {
1113
1200
  const exits = [];
1114
1201
  ctx.state.callbackExits.push(exits);
1115
1202
  let fellThrough;
1116
1203
  try {
1117
- fellThrough = enumerate(ctx, body, path10);
1204
+ fellThrough = enumerate(ctx, body, path11);
1118
1205
  } finally {
1119
1206
  ctx.state.callbackExits.pop();
1120
1207
  }
1121
1208
  const out = [...fellThrough, ...exits];
1122
- return out.length === 0 ? [path10] : out;
1209
+ return out.length === 0 ? [path11] : out;
1123
1210
  }
1124
- function stepStatement(ctx, stmt, path10) {
1125
- let paths = [path10];
1211
+ function stepStatement(ctx, stmt, path11) {
1212
+ let paths = [path11];
1126
1213
  for (const body of stmt.callbacks ?? []) {
1127
1214
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
1128
1215
  limitOpenPaths(paths);
@@ -1209,8 +1296,8 @@ function enumerate(ctx, stmts, prefix) {
1209
1296
  let frontiers = [[...prefix]];
1210
1297
  for (const stmt of stmts) {
1211
1298
  const nextFrontiers = [];
1212
- for (const path10 of mergeRejoined(frontiers, ctx.keepsArms)) {
1213
- nextFrontiers.push(...stepStatement(ctx, stmt, path10));
1299
+ for (const path11 of mergeRejoined(frontiers, ctx.keepsArms)) {
1300
+ nextFrontiers.push(...stepStatement(ctx, stmt, path11));
1214
1301
  }
1215
1302
  frontiers = nextFrontiers;
1216
1303
  limitOpenPaths(frontiers);
@@ -1234,6 +1321,39 @@ function buildParentMap(statements) {
1234
1321
  }
1235
1322
  return parentOf;
1236
1323
  }
1324
+ function placeTerminals(input, parentOf = buildParentMap(input.statements)) {
1325
+ const placement = /* @__PURE__ */ new Map();
1326
+ for (const [home, terminals] of input.terminalsByStmt) {
1327
+ const placed = placementOf(home, parentOf);
1328
+ if (placed.loopAround === void 0 && placed.cleansUpFrom === void 0) {
1329
+ continue;
1330
+ }
1331
+ for (const terminal of terminals) {
1332
+ placement.set(terminal, placed);
1333
+ }
1334
+ }
1335
+ return placement;
1336
+ }
1337
+ function placementOf(home, parentOf) {
1338
+ let loopAround = home.kind === "loop" ? home.lines : void 0;
1339
+ let cleansUpFrom;
1340
+ let child = home;
1341
+ let parent = parentOf.get(home) ?? null;
1342
+ while (parent !== null) {
1343
+ if (parent.kind === "loop" && parent.lines !== void 0) {
1344
+ loopAround = parent.lines;
1345
+ }
1346
+ if (parent.kind === "try" && parent.lines !== void 0 && (parent.finallyBody ?? []).includes(child)) {
1347
+ cleansUpFrom = parent.lines.start;
1348
+ }
1349
+ child = parent;
1350
+ parent = parentOf.get(parent) ?? null;
1351
+ }
1352
+ return {
1353
+ ...loopAround === void 0 ? {} : { loopAround: { start: loopAround.start, end: loopAround.end } },
1354
+ ...cleansUpFrom === void 0 ? {} : { cleansUpFrom }
1355
+ };
1356
+ }
1237
1357
  function enumerateStructuredPaths(input) {
1238
1358
  const parentOf = buildParentMap(input.statements);
1239
1359
  const isAncestorOrSelf = (a, b) => {
@@ -1259,11 +1379,15 @@ function enumerateStructuredPaths(input) {
1259
1379
  isAncestorOrSelf
1260
1380
  };
1261
1381
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
1262
- for (const path10 of continuations) {
1382
+ for (const path11 of continuations) {
1263
1383
  chargeBudget(state);
1264
- state.fallthrough.push(classify(path10, null, isAncestorOrSelf));
1384
+ state.fallthrough.push(classify(path11, null, isAncestorOrSelf));
1265
1385
  }
1266
- return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
1386
+ return {
1387
+ byTerminal: state.byTerminal,
1388
+ fallthrough: state.fallthrough,
1389
+ placement: placeTerminals(input, parentOf)
1390
+ };
1267
1391
  }
1268
1392
  function enumerateOrDegrade(input, terminals) {
1269
1393
  try {
@@ -1283,9 +1407,28 @@ function enumerateOrDegrade(input, terminals) {
1283
1407
  for (const terminal of terminals) {
1284
1408
  byTerminal.set(terminal, [[marker]]);
1285
1409
  }
1286
- return { byTerminal, fallthrough: [[marker]], degraded: reason };
1410
+ return {
1411
+ byTerminal,
1412
+ fallthrough: [[marker]],
1413
+ placement: placeTerminals(input),
1414
+ degraded: reason
1415
+ };
1287
1416
  }
1288
1417
  }
1418
+ function enumerateCallStatements(input, terminals) {
1419
+ const placement = placeTerminals(input);
1420
+ const enumerable = new Map(
1421
+ [...input.terminalsByStmt].filter(
1422
+ ([, found]) => found.every(
1423
+ (terminal) => placement.get(terminal)?.cleansUpFrom === void 0
1424
+ )
1425
+ )
1426
+ );
1427
+ return {
1428
+ ...enumerateOrDegrade({ ...input, terminalsByStmt: enumerable }, terminals),
1429
+ placement
1430
+ };
1431
+ }
1289
1432
  function reasonToDegrade(error) {
1290
1433
  if (error instanceof PathBudgetExceeded) {
1291
1434
  return error.message === "" ? "path budget exceeded" : error.message;
@@ -1304,8 +1447,8 @@ function sharedGatingConditions(paths, readPredicate) {
1304
1447
  return [];
1305
1448
  }
1306
1449
  const shared = new Set(first.map(keyOf2));
1307
- for (const path10 of rest) {
1308
- const here = new Set(path10.map(keyOf2));
1450
+ for (const path11 of rest) {
1451
+ const here = new Set(path11.map(keyOf2));
1309
1452
  for (const value of shared) {
1310
1453
  if (!here.has(value)) {
1311
1454
  shared.delete(value);
@@ -1423,6 +1566,43 @@ function walkDescendants(root, carried, childrenOf2, visitor) {
1423
1566
  }
1424
1567
  }
1425
1568
 
1569
+ // src/paths/statementSites.ts
1570
+ function statementSites(nodes, reading) {
1571
+ const statements = new IdMap();
1572
+ const statementOfNode = new IdMap();
1573
+ for (const node of nodes) {
1574
+ const written = reading.statementOf(node);
1575
+ if (written === null) {
1576
+ continue;
1577
+ }
1578
+ const statement = statements.get(written) ?? written;
1579
+ statements.set(statement, statement);
1580
+ statementOfNode.set(node, statement);
1581
+ }
1582
+ const terminals = [...statements].map(([statement]) => statement);
1583
+ const enumerated = enumerateCallStatements(
1584
+ reading.lower(terminals),
1585
+ terminals
1586
+ );
1587
+ const pathsTo = (node) => {
1588
+ const statement = statementOfNode.get(node);
1589
+ return statement === void 0 ? void 0 : enumerated.byTerminal.get(statement);
1590
+ };
1591
+ const siteOf = (node, preconditions) => {
1592
+ const statement = statementOfNode.get(node);
1593
+ return {
1594
+ line: reading.firstLine(statement ?? node),
1595
+ preconditions,
1596
+ cleansUpFrom: statement === void 0 ? void 0 : enumerated.placement.get(statement)?.cleansUpFrom
1597
+ };
1598
+ };
1599
+ return {
1600
+ pathsTo,
1601
+ siteOf,
1602
+ plainSiteOf: (node) => siteOf(node, sharedGatingConditions(pathsTo(node)))
1603
+ };
1604
+ }
1605
+
1426
1606
  // src/projectRoot.ts
1427
1607
  import path3 from "path";
1428
1608
  function commonDirectoryOf(files) {
@@ -1571,13 +1751,13 @@ function quoted(text, key) {
1571
1751
  }
1572
1752
 
1573
1753
  // src/adapterStamp.ts
1574
- import { createHash as createHash2 } from "crypto";
1754
+ import { createHash as createHash3 } from "crypto";
1575
1755
  import fs3 from "fs";
1576
1756
  import path5 from "path";
1577
1757
  import { fileURLToPath } from "url";
1578
1758
 
1579
1759
  // src/codeHash.ts
1580
- import { createHash } from "crypto";
1760
+ import { createHash as createHash2 } from "crypto";
1581
1761
  import fs2 from "fs";
1582
1762
  import { createRequire } from "module";
1583
1763
  import path4 from "path";
@@ -1623,7 +1803,7 @@ function createCodeHasher() {
1623
1803
  if (files.length === 0) {
1624
1804
  return "";
1625
1805
  }
1626
- const hash = createHash("sha256");
1806
+ const hash = createHash2("sha256");
1627
1807
  for (const file of files) {
1628
1808
  hash.update(read.get(file)?.digest ?? "");
1629
1809
  }
@@ -1640,7 +1820,7 @@ function readCodeFile(file) {
1640
1820
  return null;
1641
1821
  }
1642
1822
  return {
1643
- digest: createHash("sha256").update(text).digest("hex"),
1823
+ digest: createHash2("sha256").update(text).digest("hex"),
1644
1824
  imports: importsOf(file, text.toString("utf8"))
1645
1825
  };
1646
1826
  }
@@ -1798,7 +1978,7 @@ function computeContentHash(paths) {
1798
1978
  if (paths.length === 0) {
1799
1979
  return "";
1800
1980
  }
1801
- const hash = createHash2("sha256");
1981
+ const hash = createHash3("sha256");
1802
1982
  for (const file of paths) {
1803
1983
  try {
1804
1984
  hash.update(fs3.readFileSync(file));
@@ -1812,7 +1992,7 @@ function projectFileStamp(paths) {
1812
1992
  if (paths.length === 0) {
1813
1993
  return "none";
1814
1994
  }
1815
- const hash = createHash2("sha256");
1995
+ const hash = createHash3("sha256");
1816
1996
  for (const file of [...paths].sort()) {
1817
1997
  hash.update(file);
1818
1998
  hash.update("\0");
@@ -1847,7 +2027,7 @@ function runDigest(packsDigest, packs, files) {
1847
2027
  }
1848
2028
 
1849
2029
  // src/cache.ts
1850
- import { createHash as createHash5 } from "crypto";
2030
+ import { createHash as createHash6 } from "crypto";
1851
2031
  import fs7 from "fs/promises";
1852
2032
  import path8 from "path";
1853
2033
  import { readJsonFile as readJsonFile3, writeDocumentFile as writeDocumentFile2 } from "@suss/behavioral-ir/files";
@@ -1955,7 +2135,7 @@ function keyOf3(binding) {
1955
2135
  }
1956
2136
 
1957
2137
  // src/cacheShards.ts
1958
- import { createHash as createHash3 } from "crypto";
2138
+ import { createHash as createHash4 } from "crypto";
1959
2139
  import fs4 from "fs";
1960
2140
  import path6 from "path";
1961
2141
  import {
@@ -2021,7 +2201,7 @@ function namesIn(dir) {
2021
2201
  }
2022
2202
  function writeOnce(root, value) {
2023
2203
  const pieces = [...documentPieces(value)];
2024
- const hash = createHash3("sha256");
2204
+ const hash = createHash4("sha256");
2025
2205
  for (const piece of pieces) {
2026
2206
  hash.update(piece);
2027
2207
  }
@@ -2125,7 +2305,7 @@ function copyOfJson(value) {
2125
2305
  }
2126
2306
 
2127
2307
  // src/cacheStamps.ts
2128
- import { createHash as createHash4 } from "crypto";
2308
+ import { createHash as createHash5 } from "crypto";
2129
2309
  import fs5 from "fs/promises";
2130
2310
  var RECENT_CHANGE_MS = 2e3;
2131
2311
  function stampMayMissAWrite(stat, statAt) {
@@ -2215,7 +2395,7 @@ function reusableHash(prior, stamp) {
2215
2395
  async function hashFile(filePath) {
2216
2396
  try {
2217
2397
  const content = await fs5.readFile(filePath);
2218
- return createHash4("sha256").update(content).digest("hex").slice(0, 16);
2398
+ return createHash5("sha256").update(content).digest("hex").slice(0, 16);
2219
2399
  } catch {
2220
2400
  return null;
2221
2401
  }
@@ -2671,7 +2851,7 @@ function entryDirFor(cacheDir, input) {
2671
2851
  input.adapterPacksDigest,
2672
2852
  input.configPath ?? ""
2673
2853
  ].join(" ");
2674
- const name = createHash5("sha256").update(key).digest("hex").slice(0, 16);
2854
+ const name = createHash6("sha256").update(key).digest("hex").slice(0, 16);
2675
2855
  return path8.join(cacheDir, `${ENTRY_PREFIX}${name}`);
2676
2856
  }
2677
2857
  function isEntryDir(name) {
@@ -2745,7 +2925,7 @@ async function readManifest2(manifestPath) {
2745
2925
  }
2746
2926
 
2747
2927
  // src/cacheLocation.ts
2748
- import { createHash as createHash6 } from "crypto";
2928
+ import { createHash as createHash7 } from "crypto";
2749
2929
  import fs8 from "fs";
2750
2930
  import path9 from "path";
2751
2931
  var CACHE_DIR_ENV = "SUSS_CACHE_DIR";
@@ -2764,7 +2944,7 @@ function realPathOrSelf(dir) {
2764
2944
  }
2765
2945
  }
2766
2946
  function projectKey(projectDir) {
2767
- return createHash6("sha256").update(realPathOrSelf(path9.resolve(projectDir))).digest("hex").slice(0, 16);
2947
+ return createHash7("sha256").update(realPathOrSelf(path9.resolve(projectDir))).digest("hex").slice(0, 16);
2768
2948
  }
2769
2949
  function extractionCacheDirFor(projectDir) {
2770
2950
  const root = chosenCacheRoot();
@@ -2773,6 +2953,13 @@ function extractionCacheDirFor(projectDir) {
2773
2953
  }
2774
2954
  return path9.join(root, "cache", projectKey(projectDir));
2775
2955
  }
2956
+ function mapFolderFor(projectDir) {
2957
+ const root = chosenCacheRoot();
2958
+ if (root === null) {
2959
+ return path9.join(projectDir, ".suss");
2960
+ }
2961
+ return path9.join(root, "map", projectKey(projectDir));
2962
+ }
2776
2963
  function liveRecordFor(projectDir) {
2777
2964
  const root = chosenCacheRoot();
2778
2965
  if (root === null) {
@@ -2921,6 +3108,7 @@ var KeptParses = class {
2921
3108
  };
2922
3109
 
2923
3110
  // src/packHealth.ts
3111
+ import path10 from "path";
2924
3112
  function stagesOf(funnel) {
2925
3113
  const stages = [];
2926
3114
  const gateSaysSomething = funnel.gates.length > 0 && funnel.unresolvedGates.length === 0;
@@ -3068,6 +3256,30 @@ function packConfigCheck(needs) {
3068
3256
  }))
3069
3257
  };
3070
3258
  }
3259
+ function describeMissingPath(missing) {
3260
+ const where = `${missing.option} is ${missing.value} in ${missing.configFile}`;
3261
+ if (path10.isAbsolute(missing.value)) {
3262
+ return {
3263
+ problem: `${where}, and nothing exists there.`,
3264
+ fix: `Set ${missing.option} to a path that exists.`
3265
+ };
3266
+ }
3267
+ return {
3268
+ problem: `${where}, and nothing exists at ${missing.resolved}. A relative path is read against the config file, not the project.`,
3269
+ fix: missing.inProject === void 0 ? `Set ${missing.option} to an absolute path, or move the config file into the project.` : `Set ${missing.option} to ${missing.inProject} in the config file, or move the config file into the project.`
3270
+ };
3271
+ }
3272
+ function packPathCheck(missing) {
3273
+ return {
3274
+ name: "every path a pack config gives exists",
3275
+ code: "no-path",
3276
+ audience: "run",
3277
+ violations: missing.map((one) => {
3278
+ const { problem, fix } = describeMissingPath(one);
3279
+ return { label: one.pack, detail: `${problem} ${fix}` };
3280
+ })
3281
+ };
3282
+ }
3071
3283
  function evaluatePackHealth(report) {
3072
3284
  const gradients = packGradients(report);
3073
3285
  return [
@@ -3220,7 +3432,7 @@ function makeTransitionId(functionName, branch) {
3220
3432
  const { terminal } = branch;
3221
3433
  const statusKey = terminal.statusCode === null ? "none" : terminal.statusCode.type === "literal" ? String(terminal.statusCode.value) : `dyn:${terminal.statusCode.sourceText}`;
3222
3434
  const conditionSig = branch.conditions.map((c) => `${c.polarity}:${c.sourceText}`).join(";");
3223
- const conditionHash = createHash7("sha1").update(conditionSig).digest("hex").slice(0, 7);
3435
+ const conditionHash = createHash8("sha1").update(conditionSig).digest("hex").slice(0, 7);
3224
3436
  return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
3225
3437
  }
3226
3438
  function collapseReading(table, reading) {
@@ -3295,11 +3507,11 @@ function provenanceOf(raw, effects) {
3295
3507
  if (raw.length === 0) {
3296
3508
  return [];
3297
3509
  }
3298
- const positionOf = new Map(effects.map((one, at) => [effectKey(one), at]));
3510
+ const positionOf = new Map(effects.map((one, at) => [effectKey2(one), at]));
3299
3511
  const bySlot = /* @__PURE__ */ new Map();
3300
3512
  const said = /* @__PURE__ */ new Map();
3301
3513
  for (const one of raw) {
3302
- const effect = positionOf.get(effectKey(withNormalizedCallee(one.effect)));
3514
+ const effect = positionOf.get(effectKey2(withNormalizedCallee(one.effect)));
3303
3515
  if (effect === void 0) {
3304
3516
  continue;
3305
3517
  }
@@ -3331,7 +3543,7 @@ function withNormalizedCallee(effect) {
3331
3543
  if (effect.callee === void 0) {
3332
3544
  return effect;
3333
3545
  }
3334
- return { ...effect, callee: normalizeCalleeText(effect.callee) };
3546
+ return { ...effect, callee: normalizeCalleeText2(effect.callee) };
3335
3547
  }
3336
3548
  function assembleSummary(raw, options = DEFAULT_OPTIONS) {
3337
3549
  const transitions = raw.branches.map((rawBranch) => {
@@ -3419,6 +3631,9 @@ function buildMetadata(raw) {
3419
3631
  if (raw.test !== void 0) {
3420
3632
  metadata = withTestMetadata(metadata, raw.test);
3421
3633
  }
3634
+ if (raw.packMetadata !== void 0) {
3635
+ metadata = { ...metadata, ...raw.packMetadata };
3636
+ }
3422
3637
  return Object.keys(metadata).length > 0 ? metadata : null;
3423
3638
  }
3424
3639
  function buildHttpMetadataValue(raw) {
@@ -3470,6 +3685,12 @@ function buildGraphqlMetadataValue(raw) {
3470
3685
  if (raw.graphqlUnresolvedDocument !== void 0) {
3471
3686
  graphql.unresolvedDocument = raw.graphqlUnresolvedDocument;
3472
3687
  }
3688
+ if (raw.graphqlClient !== void 0) {
3689
+ graphql.client = raw.graphqlClient;
3690
+ }
3691
+ if (raw.graphqlUnresolvedClient !== void 0) {
3692
+ graphql.unresolvedClient = raw.graphqlUnresolvedClient;
3693
+ }
3473
3694
  return Object.keys(graphql).length > 0 ? graphql : null;
3474
3695
  }
3475
3696
  function answersWithHttpResponses(raw) {
@@ -3661,7 +3882,10 @@ var effectConverters = {
3661
3882
  stateChange: (e) => ({ type: "stateChange", variable: e.variable })
3662
3883
  };
3663
3884
  function effectToIR(effect) {
3664
- return effectConverters[effect.type](effect);
3885
+ const converted = effectConverters[effect.type](
3886
+ effect
3887
+ );
3888
+ return effect.location === void 0 ? converted : { ...converted, location: effect.location };
3665
3889
  }
3666
3890
  function paramToInput(param) {
3667
3891
  return {
@@ -3692,6 +3916,7 @@ export {
3692
3916
  assembleSummary,
3693
3917
  assessConfidence,
3694
3918
  buildUngatedExtractionReport,
3919
+ callSiteKey,
3695
3920
  chosenCacheRoot,
3696
3921
  commonDirectoryOf,
3697
3922
  composeWrappers,
@@ -3703,23 +3928,28 @@ export {
3703
3928
  createCodeHasher,
3704
3929
  createPackTallies,
3705
3930
  createTimer,
3931
+ describeMissingPath,
3706
3932
  detectGaps,
3707
3933
  effectToIR,
3934
+ effectsFiringOn,
3708
3935
  emptyTally,
3709
3936
  enumerateOrDegrade,
3710
3937
  enumerateStructuredPaths,
3711
3938
  evaluatePackHealth,
3712
3939
  extractionCacheDirFor,
3713
3940
  extractionConfigStamp,
3941
+ firesOn,
3714
3942
  firstWrittenReading,
3715
3943
  formatPackHealth,
3716
3944
  guardsHoldOn,
3717
3945
  helperPathsOnVariables,
3718
3946
  httpRouteDiscovery,
3719
3947
  isListedTestFile,
3948
+ libraryEnvReadMarker,
3720
3949
  liveRecordFor,
3721
3950
  makeIgnoredDirectory,
3722
3951
  makeTransitionId,
3952
+ mapFolderFor,
3723
3953
  mapReading,
3724
3954
  markReturnsAsExitCode,
3725
3955
  matchesTestFileName,
@@ -3727,10 +3957,13 @@ export {
3727
3957
  moduleInitStructure,
3728
3958
  moduleOfFile,
3729
3959
  noopTimer,
3960
+ numberRepeatedCallSites,
3730
3961
  ownersWhereWritten,
3731
3962
  packConfigCheck,
3732
3963
  packGradients,
3964
+ packPathCheck,
3733
3965
  paramToInput,
3966
+ placeTerminals,
3734
3967
  processCodeHasher,
3735
3968
  projectFileStamp,
3736
3969
  rawConditionToPredicate,
@@ -3748,6 +3981,7 @@ export {
3748
3981
  stampMayMissAWrite,
3749
3982
  stampModuleImports,
3750
3983
  stampModules,
3984
+ statementSites,
3751
3985
  storageSystemOption,
3752
3986
  summaryCountsByPack,
3753
3987
  tallyUnit,