@suss/extractor 0.34.1 → 0.35.1

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
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ CACHE_DIR_ENV: () => CACHE_DIR_ENV,
33
34
  EnclosingNodes: () => EnclosingNodes,
34
35
  IdMap: () => IdMap,
35
36
  IdSet: () => IdSet,
@@ -47,6 +48,7 @@ __export(index_exports, {
47
48
  assembleSummary: () => assembleSummary,
48
49
  assessConfidence: () => assessConfidence,
49
50
  buildUngatedExtractionReport: () => buildUngatedExtractionReport,
51
+ chosenCacheRoot: () => chosenCacheRoot,
50
52
  commonDirectoryOf: () => commonDirectoryOf,
51
53
  composeWrappers: () => composeWrappers,
52
54
  computeContentHash: () => computeContentHash,
@@ -54,6 +56,7 @@ __export(index_exports, {
54
56
  configuredCallOption: () => configuredCallOption,
55
57
  createAdapterStamp: () => createAdapterStamp,
56
58
  createCacheLayer: () => createCacheLayer,
59
+ createCodeHasher: () => createCodeHasher,
57
60
  createPackTallies: () => createPackTallies,
58
61
  createTimer: () => createTimer,
59
62
  detectGaps: () => detectGaps,
@@ -62,6 +65,7 @@ __export(index_exports, {
62
65
  enumerateOrDegrade: () => enumerateOrDegrade,
63
66
  enumerateStructuredPaths: () => enumerateStructuredPaths,
64
67
  evaluatePackHealth: () => evaluatePackHealth,
68
+ extractionCacheDirFor: () => extractionCacheDirFor,
65
69
  extractionConfigStamp: () => extractionConfigStamp,
66
70
  firstWrittenReading: () => firstWrittenReading,
67
71
  formatPackHealth: () => formatPackHealth,
@@ -69,6 +73,7 @@ __export(index_exports, {
69
73
  helperPathsOnVariables: () => helperPathsOnVariables,
70
74
  httpRouteDiscovery: () => httpRouteDiscovery,
71
75
  isListedTestFile: () => isListedTestFile,
76
+ liveRecordFor: () => liveRecordFor,
72
77
  makeIgnoredDirectory: () => makeIgnoredDirectory,
73
78
  makeTransitionId: () => makeTransitionId,
74
79
  mapReading: () => mapReading,
@@ -78,8 +83,11 @@ __export(index_exports, {
78
83
  moduleInitStructure: () => moduleInitStructure,
79
84
  moduleOfFile: () => moduleOfFile,
80
85
  noopTimer: () => noopTimer,
86
+ ownersWhereWritten: () => ownersWhereWritten,
87
+ packConfigCheck: () => packConfigCheck,
81
88
  packGradients: () => packGradients,
82
89
  paramToInput: () => paramToInput,
90
+ processCodeHasher: () => processCodeHasher,
83
91
  projectFileStamp: () => projectFileStamp,
84
92
  rawConditionToPredicate: () => rawConditionToPredicate,
85
93
  recordPackFailure: () => recordPackFailure,
@@ -109,8 +117,8 @@ __export(index_exports, {
109
117
  writtenReading: () => writtenReading
110
118
  });
111
119
  module.exports = __toCommonJS(index_exports);
112
- var import_node_crypto4 = require("crypto");
113
- var import_behavioral_ir7 = require("@suss/behavioral-ir");
120
+ var import_node_crypto7 = require("crypto");
121
+ var import_behavioral_ir8 = require("@suss/behavioral-ir");
114
122
 
115
123
  // src/contractStatusGaps.ts
116
124
  var import_behavioral_ir = require("@suss/behavioral-ir");
@@ -661,7 +669,7 @@ function splitAtHandOffs(branch, sites, from = 0) {
661
669
  continue;
662
670
  }
663
671
  return throughHelper(branch, site.at, site.helper).flatMap(
664
- (path7) => path7.terminal === branch.terminal ? splitAtHandOffs(path7, sites, index + 1) : [path7]
672
+ (path10) => path10.terminal === branch.terminal ? splitAtHandOffs(path10, sites, index + 1) : [path10]
665
673
  );
666
674
  }
667
675
  return [branch];
@@ -709,12 +717,12 @@ function rootName(ref) {
709
717
  }
710
718
 
711
719
  // src/moduleImports.ts
712
- function stampModuleImports(summaries, importsOf) {
720
+ function stampModuleImports(summaries, importsOf2) {
713
721
  const byFile = /* @__PURE__ */ new Map();
714
722
  for (const summary of summaries) {
715
723
  const file = summary.location.file;
716
724
  if (!byFile.has(file)) {
717
- const found = importsOf(file);
725
+ const found = importsOf2(file);
718
726
  byFile.set(
719
727
  file,
720
728
  found === void 0 ? void 0 : [...new Set(found)].sort()
@@ -1009,8 +1017,8 @@ var catchEntryCond = () => fixedCond(
1009
1017
  "positive",
1010
1018
  "catchBlock"
1011
1019
  );
1012
- function classify(path7, home, isAncestorOrSelf) {
1013
- return path7.map((cond) => {
1020
+ function classify(path10, home, isAncestorOrSelf) {
1021
+ return path10.map((cond) => {
1014
1022
  if (cond.branchStmt === null) {
1015
1023
  return cond.info;
1016
1024
  }
@@ -1082,9 +1090,9 @@ function limitOpenPaths(paths) {
1082
1090
  );
1083
1091
  }
1084
1092
  }
1085
- function recordTerminal(ctx, terminal, home, path7) {
1093
+ function recordTerminal(ctx, terminal, home, path10) {
1086
1094
  chargeBudget(ctx.state);
1087
- const info = classify(path7, home, ctx.isAncestorOrSelf);
1095
+ const info = classify(path10, home, ctx.isAncestorOrSelf);
1088
1096
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
1089
1097
  existing.push(info);
1090
1098
  ctx.state.byTerminal.set(terminal, existing);
@@ -1095,15 +1103,15 @@ function containsTerminal(ctx, stmt) {
1095
1103
  }
1096
1104
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
1097
1105
  }
1098
- function stepIf(stmt, path7, ctx) {
1106
+ function stepIf(stmt, path10, ctx) {
1099
1107
  const thenExit = exitKindOfList(stmt.thenBody);
1100
1108
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
1101
1109
  const thenPath = [
1102
- ...path7,
1110
+ ...path10,
1103
1111
  branchCond(stmt, stmt.condition, "positive", elseExit)
1104
1112
  ];
1105
1113
  const elsePath = [
1106
- ...path7,
1114
+ ...path10,
1107
1115
  branchCond(
1108
1116
  stmt,
1109
1117
  stmt.condition,
@@ -1120,7 +1128,7 @@ function stepIf(stmt, path7, ctx) {
1120
1128
  }
1121
1129
  return out;
1122
1130
  }
1123
- function stepSwitch(stmt, path7, ctx) {
1131
+ function stepSwitch(stmt, path10, ctx) {
1124
1132
  const out = [];
1125
1133
  const negations = [];
1126
1134
  let defaultRanBody = false;
@@ -1130,7 +1138,7 @@ function stepSwitch(stmt, path7, ctx) {
1130
1138
  if (hasStrayBreak(group.body)) {
1131
1139
  throw new UnmodeledFlow("non-trailing break in switch clause");
1132
1140
  }
1133
- const groupPath = group.condition === null ? [...path7, ...negations] : [...path7, fixedCond(group.condition, "positive", "explicit")];
1141
+ const groupPath = group.condition === null ? [...path10, ...negations] : [...path10, fixedCond(group.condition, "positive", "explicit")];
1134
1142
  const conts = enumerate(ctx, group.body, groupPath);
1135
1143
  if (conts.length > 0) {
1136
1144
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -1147,24 +1155,24 @@ function stepSwitch(stmt, path7, ctx) {
1147
1155
  }
1148
1156
  }
1149
1157
  if (!defaultRanBody) {
1150
- out.push([...path7, ...negations]);
1158
+ out.push([...path10, ...negations]);
1151
1159
  }
1152
1160
  return out;
1153
1161
  }
1154
- function stepLoop(stmt, path7, ctx) {
1162
+ function stepLoop(stmt, path10, ctx) {
1155
1163
  const bodyExit = exitKindOfList(stmt.body);
1156
1164
  if (containsTerminal(ctx, stmt)) {
1157
1165
  enumerate(ctx, stmt.body, [
1158
- ...path7,
1166
+ ...path10,
1159
1167
  loopIterationCond(stmt.condition.sourceText)
1160
1168
  ]);
1161
1169
  }
1162
1170
  if (bodyExit !== null) {
1163
- return [[...path7, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1171
+ return [[...path10, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1164
1172
  }
1165
- return [path7];
1173
+ return [path10];
1166
1174
  }
1167
- function stepTry(stmt, path7, ctx) {
1175
+ function stepTry(stmt, path10, ctx) {
1168
1176
  if (stmt.finallyBody !== null) {
1169
1177
  const finallyHasTerminal = stmt.finallyBody.some(
1170
1178
  (s) => containsTerminal(ctx, s)
@@ -1174,22 +1182,22 @@ function stepTry(stmt, path7, ctx) {
1174
1182
  }
1175
1183
  }
1176
1184
  const out = [];
1177
- out.push(...enumerate(ctx, stmt.tryBody, path7));
1185
+ out.push(...enumerate(ctx, stmt.tryBody, path10));
1178
1186
  if (stmt.catchBody !== null) {
1179
- const catchPath = [...path7, catchEntryCond()];
1187
+ const catchPath = [...path10, catchEntryCond()];
1180
1188
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
1181
1189
  }
1182
1190
  return out;
1183
1191
  }
1184
- function stepExit(stmt, path7, ctx) {
1192
+ function stepExit(stmt, path10, ctx) {
1185
1193
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
1186
1194
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
1187
- collector.push(path7);
1195
+ collector.push(path10);
1188
1196
  }
1189
1197
  return [];
1190
1198
  }
1191
- function stepOpaque(_stmt, path7, _ctx) {
1192
- return [path7];
1199
+ function stepOpaque(_stmt, path10, _ctx) {
1200
+ return [path10];
1193
1201
  }
1194
1202
  var stepHandlers = {
1195
1203
  if: stepIf,
@@ -1199,20 +1207,20 @@ var stepHandlers = {
1199
1207
  exit: stepExit,
1200
1208
  opaque: stepOpaque
1201
1209
  };
1202
- function stepCallback(ctx, body, path7) {
1210
+ function stepCallback(ctx, body, path10) {
1203
1211
  const exits = [];
1204
1212
  ctx.state.callbackExits.push(exits);
1205
1213
  let fellThrough;
1206
1214
  try {
1207
- fellThrough = enumerate(ctx, body, path7);
1215
+ fellThrough = enumerate(ctx, body, path10);
1208
1216
  } finally {
1209
1217
  ctx.state.callbackExits.pop();
1210
1218
  }
1211
1219
  const out = [...fellThrough, ...exits];
1212
- return out.length === 0 ? [path7] : out;
1220
+ return out.length === 0 ? [path10] : out;
1213
1221
  }
1214
- function stepStatement(ctx, stmt, path7) {
1215
- let paths = [path7];
1222
+ function stepStatement(ctx, stmt, path10) {
1223
+ let paths = [path10];
1216
1224
  for (const body of stmt.callbacks ?? []) {
1217
1225
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
1218
1226
  limitOpenPaths(paths);
@@ -1299,8 +1307,8 @@ function enumerate(ctx, stmts, prefix) {
1299
1307
  let frontiers = [[...prefix]];
1300
1308
  for (const stmt of stmts) {
1301
1309
  const nextFrontiers = [];
1302
- for (const path7 of mergeRejoined(frontiers, ctx.keepsArms)) {
1303
- nextFrontiers.push(...stepStatement(ctx, stmt, path7));
1310
+ for (const path10 of mergeRejoined(frontiers, ctx.keepsArms)) {
1311
+ nextFrontiers.push(...stepStatement(ctx, stmt, path10));
1304
1312
  }
1305
1313
  frontiers = nextFrontiers;
1306
1314
  limitOpenPaths(frontiers);
@@ -1349,9 +1357,9 @@ function enumerateStructuredPaths(input) {
1349
1357
  isAncestorOrSelf
1350
1358
  };
1351
1359
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
1352
- for (const path7 of continuations) {
1360
+ for (const path10 of continuations) {
1353
1361
  chargeBudget(state);
1354
- state.fallthrough.push(classify(path7, null, isAncestorOrSelf));
1362
+ state.fallthrough.push(classify(path10, null, isAncestorOrSelf));
1355
1363
  }
1356
1364
  return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
1357
1365
  }
@@ -1394,8 +1402,8 @@ function sharedGatingConditions(paths, readPredicate) {
1394
1402
  return [];
1395
1403
  }
1396
1404
  const shared = new Set(first.map(keyOf2));
1397
- for (const path7 of rest) {
1398
- const here = new Set(path7.map(keyOf2));
1405
+ for (const path10 of rest) {
1406
+ const here = new Set(path10.map(keyOf2));
1399
1407
  for (const value of shared) {
1400
1408
  if (!here.has(value)) {
1401
1409
  shared.delete(value);
@@ -1661,18 +1669,179 @@ function quoted(text, key) {
1661
1669
  }
1662
1670
 
1663
1671
  // src/adapterStamp.ts
1672
+ var import_node_crypto2 = require("crypto");
1673
+ var import_node_fs3 = __toESM(require("fs"), 1);
1674
+ var import_node_path5 = __toESM(require("path"), 1);
1675
+ var import_node_url = require("url");
1676
+
1677
+ // src/codeHash.ts
1664
1678
  var import_node_crypto = require("crypto");
1665
1679
  var import_node_fs2 = __toESM(require("fs"), 1);
1666
1680
  var import_node_module = require("module");
1667
1681
  var import_node_path4 = __toESM(require("path"), 1);
1668
- var import_node_url = require("url");
1669
- var import_meta = {};
1670
- var ANALYSIS_PACKAGES = [
1671
- "@suss/extractor",
1672
- "@suss/resolution",
1673
- "@suss/datalog",
1674
- "@suss/behavioral-ir"
1675
- ];
1682
+ var SUSS_SCOPE = "@suss/";
1683
+ var IMPORT_FORMS = /\b(?:from|import)\s*\(?\s*["']([^"'\s]+)["']|require\s*\(\s*["']([^"'\s]+)["']/g;
1684
+ var CONDITIONS = {
1685
+ import: /* @__PURE__ */ new Set(["import", "node", "default"]),
1686
+ require: /* @__PURE__ */ new Set(["require", "node", "default"])
1687
+ };
1688
+ function createCodeHasher() {
1689
+ const read = /* @__PURE__ */ new Map();
1690
+ function readOnce(file) {
1691
+ const known = read.get(file);
1692
+ if (known !== void 0) {
1693
+ return known;
1694
+ }
1695
+ const found = readCodeFile(file);
1696
+ read.set(file, found);
1697
+ return found;
1698
+ }
1699
+ function codeFiles(entryFile) {
1700
+ const entry = realFile(entryFile);
1701
+ if (entry === null || readOnce(entry) === null) {
1702
+ return [];
1703
+ }
1704
+ const found = [entry];
1705
+ const seen = new Set(found);
1706
+ for (let at = 0; at < found.length; at++) {
1707
+ for (const next of readOnce(found[at])?.imports ?? []) {
1708
+ if (seen.has(next)) {
1709
+ continue;
1710
+ }
1711
+ seen.add(next);
1712
+ if (readOnce(next) !== null) {
1713
+ found.push(next);
1714
+ }
1715
+ }
1716
+ }
1717
+ return found;
1718
+ }
1719
+ function codeHash(entryFile) {
1720
+ const files = codeFiles(entryFile);
1721
+ if (files.length === 0) {
1722
+ return "";
1723
+ }
1724
+ const hash = (0, import_node_crypto.createHash)("sha256");
1725
+ for (const file of files) {
1726
+ hash.update(read.get(file)?.digest ?? "");
1727
+ }
1728
+ return hash.digest("hex").slice(0, 16);
1729
+ }
1730
+ return { codeFiles, codeHash };
1731
+ }
1732
+ var processCodeHasher = createCodeHasher();
1733
+ function readCodeFile(file) {
1734
+ let text;
1735
+ try {
1736
+ text = import_node_fs2.default.readFileSync(file);
1737
+ } catch {
1738
+ return null;
1739
+ }
1740
+ return {
1741
+ digest: (0, import_node_crypto.createHash)("sha256").update(text).digest("hex"),
1742
+ imports: importsOf(file, text.toString("utf8"))
1743
+ };
1744
+ }
1745
+ function importsOf(file, text) {
1746
+ const asked = /* @__PURE__ */ new Set();
1747
+ const found = [];
1748
+ for (const match of text.matchAll(IMPORT_FORMS)) {
1749
+ const kind = match[1] === void 0 ? "require" : "import";
1750
+ const specifier = match[1] ?? match[2] ?? "";
1751
+ if (asked.has(`${kind} ${specifier}`)) {
1752
+ continue;
1753
+ }
1754
+ asked.add(`${kind} ${specifier}`);
1755
+ const target = resolveImport(specifier, file, kind);
1756
+ if (target !== null) {
1757
+ found.push(target);
1758
+ }
1759
+ }
1760
+ return found;
1761
+ }
1762
+ function resolveImport(specifier, fromFile, kind) {
1763
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
1764
+ return realFile(import_node_path4.default.resolve(import_node_path4.default.dirname(fromFile), specifier));
1765
+ }
1766
+ if (specifier.startsWith(SUSS_SCOPE)) {
1767
+ return resolvePackageImport(specifier, fromFile, kind);
1768
+ }
1769
+ return null;
1770
+ }
1771
+ function resolvePackageImport(specifier, fromFile, kind) {
1772
+ const [scope = "", name = "", ...rest] = specifier.split("/");
1773
+ const packageName = `${scope}/${name}`;
1774
+ const subpath = rest.length === 0 ? "." : `./${rest.join("/")}`;
1775
+ const lookIn = (0, import_node_module.createRequire)(fromFile).resolve.paths(packageName) ?? [];
1776
+ for (const dir of lookIn) {
1777
+ const packageDir = import_node_path4.default.join(dir, packageName);
1778
+ const manifest = readManifest(packageDir);
1779
+ if (manifest === null) {
1780
+ continue;
1781
+ }
1782
+ const target = exportTarget(manifest, subpath, CONDITIONS[kind]);
1783
+ return target === null ? null : realFile(import_node_path4.default.join(packageDir, target));
1784
+ }
1785
+ return null;
1786
+ }
1787
+ function readManifest(packageDir) {
1788
+ try {
1789
+ return JSON.parse(
1790
+ import_node_fs2.default.readFileSync(import_node_path4.default.join(packageDir, "package.json"), "utf8")
1791
+ );
1792
+ } catch {
1793
+ return null;
1794
+ }
1795
+ }
1796
+ function exportTarget(manifest, subpath, conditions) {
1797
+ if (manifest.exports === void 0) {
1798
+ return subpath === "." ? manifest.main ?? "index.js" : subpath;
1799
+ }
1800
+ return conditionalTarget(subpathEntry(manifest.exports, subpath), conditions);
1801
+ }
1802
+ function subpathEntry(exportsField, subpath) {
1803
+ const isSubpathMap = typeof exportsField === "object" && exportsField !== null && !Array.isArray(exportsField) && Object.keys(exportsField).some((key) => key.startsWith("."));
1804
+ if (isSubpathMap) {
1805
+ return exportsField[subpath];
1806
+ }
1807
+ return subpath === "." ? exportsField : void 0;
1808
+ }
1809
+ function conditionalTarget(value, conditions) {
1810
+ if (typeof value === "string") {
1811
+ return value;
1812
+ }
1813
+ if (Array.isArray(value)) {
1814
+ for (const each of value) {
1815
+ const found = conditionalTarget(each, conditions);
1816
+ if (found !== null) {
1817
+ return found;
1818
+ }
1819
+ }
1820
+ return null;
1821
+ }
1822
+ if (value === null || typeof value !== "object") {
1823
+ return null;
1824
+ }
1825
+ for (const [condition, target] of Object.entries(value)) {
1826
+ if (!conditions.has(condition)) {
1827
+ continue;
1828
+ }
1829
+ const found = conditionalTarget(target, conditions);
1830
+ if (found !== null) {
1831
+ return found;
1832
+ }
1833
+ }
1834
+ return null;
1835
+ }
1836
+ function realFile(file) {
1837
+ try {
1838
+ return import_node_fs2.default.realpathSync(file);
1839
+ } catch {
1840
+ return null;
1841
+ }
1842
+ }
1843
+
1844
+ // src/adapterStamp.ts
1676
1845
  var SOURCE_STAMP = { kind: "source" };
1677
1846
  function createAdapterStamp(config) {
1678
1847
  let cachedCodeStamp = null;
@@ -1707,19 +1876,19 @@ function createAdapterStamp(config) {
1707
1876
  function readAdapterCodeStamp(moduleUrl) {
1708
1877
  try {
1709
1878
  const selfPath = (0, import_node_url.fileURLToPath)(moduleUrl);
1710
- const hash = computeDistHashFrom(import_node_path4.default.dirname(selfPath));
1879
+ const hash = computeDistHashFrom(import_node_path5.default.dirname(selfPath), processCodeHasher);
1711
1880
  return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
1712
1881
  } catch {
1713
1882
  return SOURCE_STAMP;
1714
1883
  }
1715
1884
  }
1716
- function computeDistHashFrom(dir) {
1717
- const candidates = [import_node_path4.default.join(dir, "index.js"), import_node_path4.default.join(dir, "index.cjs")];
1885
+ function computeDistHashFrom(dir, hasher = createCodeHasher()) {
1886
+ const candidates = [import_node_path5.default.join(dir, "index.js"), import_node_path5.default.join(dir, "index.cjs")];
1718
1887
  for (const candidate of candidates) {
1719
- if (!import_node_fs2.default.existsSync(candidate)) {
1888
+ if (!import_node_fs3.default.existsSync(candidate)) {
1720
1889
  continue;
1721
1890
  }
1722
- return computeContentHash([candidate, ...analysisBundles()]);
1891
+ return hasher.codeHash(candidate);
1723
1892
  }
1724
1893
  return "";
1725
1894
  }
@@ -1727,10 +1896,10 @@ function computeContentHash(paths) {
1727
1896
  if (paths.length === 0) {
1728
1897
  return "";
1729
1898
  }
1730
- const hash = (0, import_node_crypto.createHash)("sha256");
1899
+ const hash = (0, import_node_crypto2.createHash)("sha256");
1731
1900
  for (const file of paths) {
1732
1901
  try {
1733
- hash.update(import_node_fs2.default.readFileSync(file));
1902
+ hash.update(import_node_fs3.default.readFileSync(file));
1734
1903
  } catch {
1735
1904
  return "";
1736
1905
  }
@@ -1741,12 +1910,12 @@ function projectFileStamp(paths) {
1741
1910
  if (paths.length === 0) {
1742
1911
  return "none";
1743
1912
  }
1744
- const hash = (0, import_node_crypto.createHash)("sha256");
1913
+ const hash = (0, import_node_crypto2.createHash)("sha256");
1745
1914
  for (const file of [...paths].sort()) {
1746
1915
  hash.update(file);
1747
1916
  hash.update("\0");
1748
1917
  try {
1749
- hash.update(import_node_fs2.default.readFileSync(file));
1918
+ hash.update(import_node_fs3.default.readFileSync(file));
1750
1919
  } catch {
1751
1920
  hash.update("absent");
1752
1921
  }
@@ -1759,7 +1928,7 @@ function extractionConfigStamp(config) {
1759
1928
  ["includeReachable", optionalText(config.includeReachable)],
1760
1929
  ["workspaceRoot", config.workspaceRoot],
1761
1930
  ["projectRoot", config.projectRoot],
1762
- ["importRoots", config.importRoots?.join(import_node_path4.default.delimiter)],
1931
+ ["importRoots", config.importRoots?.join(import_node_path5.default.delimiter)],
1763
1932
  ["modules", modulesStamp(config.modules)],
1764
1933
  ["walkedDirectory", config.walkedDirectory]
1765
1934
  ];
@@ -1774,26 +1943,283 @@ function runDigest(packsDigest, packs, files) {
1774
1943
  const inputs = packs.flatMap((pack) => pack.discoveryInputs?.(files) ?? []);
1775
1944
  return `${packsDigest}|reads:${projectFileStamp(inputs)}`;
1776
1945
  }
1777
- function analysisBundles() {
1778
- const require2 = (0, import_node_module.createRequire)(import_meta.url);
1779
- const found = [];
1780
- for (const name of ANALYSIS_PACKAGES) {
1781
- try {
1782
- found.push(require2.resolve(name));
1783
- } catch {
1946
+
1947
+ // src/cache.ts
1948
+ var import_node_crypto5 = require("crypto");
1949
+ var import_promises2 = __toESM(require("fs/promises"), 1);
1950
+ var import_node_path8 = __toESM(require("path"), 1);
1951
+ var import_files3 = require("@suss/behavioral-ir/files");
1952
+
1953
+ // src/cacheIndex.ts
1954
+ var import_behavioral_ir6 = require("@suss/behavioral-ir");
1955
+ var import_files = require("@suss/behavioral-ir/files");
1956
+ function entriesOf(units) {
1957
+ const boundaries = [];
1958
+ const callees = [];
1959
+ units.forEach((summary, unit) => {
1960
+ const seen = /* @__PURE__ */ new Set();
1961
+ const add = (key, role) => {
1962
+ if (key !== null && !seen.has(`${role} ${key}`)) {
1963
+ seen.add(`${role} ${key}`);
1964
+ boundaries.push([key, unit, role]);
1965
+ }
1966
+ };
1967
+ const own = summary.identity?.boundaryBinding ?? null;
1968
+ if (own !== null) {
1969
+ add(
1970
+ keyOf3(own),
1971
+ import_behavioral_ir6.BOUNDARY_ROLE[summary.kind] === "provider" ? "serves" : "calls"
1972
+ );
1973
+ }
1974
+ const spellings = /* @__PURE__ */ new Set();
1975
+ for (const transition of summary.transitions ?? []) {
1976
+ for (const effect of transition.effects ?? []) {
1977
+ if (effect.type === "interaction") {
1978
+ add(keyOf3(effect.binding), "calls");
1979
+ }
1980
+ if (effect.type === "invocation") {
1981
+ spellings.add(effect.callee);
1982
+ }
1983
+ }
1984
+ }
1985
+ for (const spelling of spellings) {
1986
+ callees.push([spelling, unit]);
1987
+ }
1988
+ });
1989
+ return { boundaries, callees };
1990
+ }
1991
+ function indexOf(shards) {
1992
+ const boundaries = {};
1993
+ const callees = {};
1994
+ shards.forEach(({ entries }, file) => {
1995
+ for (const [key, unit, role] of entries.boundaries) {
1996
+ listAt(boundaries, key).push([file, unit, role]);
1997
+ }
1998
+ for (const [spelling, unit] of entries.callees) {
1999
+ listAt(callees, spelling).push([file, unit]);
2000
+ }
2001
+ });
2002
+ return {
2003
+ files: shards.map(({ file, id }) => [file, id]),
2004
+ boundaries,
2005
+ callees
2006
+ };
2007
+ }
2008
+ function listAt(record, key) {
2009
+ const existing = Object.hasOwn(record, key) ? record[key] : void 0;
2010
+ if (existing !== void 0) {
2011
+ return existing;
2012
+ }
2013
+ const created = [];
2014
+ record[key] = created;
2015
+ return created;
2016
+ }
2017
+ function entriesByShard(index) {
2018
+ const byShard = /* @__PURE__ */ new Map();
2019
+ const at = index.files.map(([, id]) => {
2020
+ const entries = { boundaries: [], callees: [] };
2021
+ byShard.set(id, entries);
2022
+ return entries;
2023
+ });
2024
+ for (const [key, units] of Object.entries(index.boundaries)) {
2025
+ for (const [file, unit, role] of units) {
2026
+ at[file]?.boundaries.push([key, unit, role]);
1784
2027
  }
1785
2028
  }
1786
- return found;
2029
+ for (const [spelling, units] of Object.entries(index.callees)) {
2030
+ for (const [file, unit] of units) {
2031
+ at[file]?.callees.push([spelling, unit]);
2032
+ }
2033
+ }
2034
+ return byShard;
2035
+ }
2036
+ function readStoreIndex(file) {
2037
+ try {
2038
+ const index = (0, import_files.readJsonFile)(file);
2039
+ return Array.isArray(index.files) ? index : null;
2040
+ } catch {
2041
+ return null;
2042
+ }
2043
+ }
2044
+ function writeStoreIndex(file, index) {
2045
+ (0, import_files.writeDocumentFile)(file, index);
2046
+ }
2047
+ function keyOf3(binding) {
2048
+ try {
2049
+ return (0, import_behavioral_ir6.displayLabel)(binding);
2050
+ } catch {
2051
+ return null;
2052
+ }
1787
2053
  }
1788
2054
 
1789
- // src/cache.ts
2055
+ // src/cacheShards.ts
1790
2056
  var import_node_crypto3 = require("crypto");
1791
- var import_promises2 = __toESM(require("fs/promises"), 1);
2057
+ var import_node_fs4 = __toESM(require("fs"), 1);
1792
2058
  var import_node_path6 = __toESM(require("path"), 1);
1793
- var import_files = require("@suss/behavioral-ir/files");
2059
+ var import_files2 = require("@suss/behavioral-ir/files");
2060
+ var SHARDS_DIR = "shards";
2061
+ var RECORDS_DIR = "records";
2062
+ var STORED_ID = /^[0-9a-f]{32}\.json$/;
2063
+ function writeShard(entryDir, content) {
2064
+ return writeOnce(import_node_path6.default.join(entryDir, SHARDS_DIR), interned(content));
2065
+ }
2066
+ function readShard(entryDir, id, file) {
2067
+ const stored = readStored(import_node_path6.default.join(entryDir, SHARDS_DIR), id);
2068
+ if (stored?.file !== file || !Array.isArray(stored.units)) {
2069
+ return null;
2070
+ }
2071
+ const take = takerFor(stored.intern ?? []);
2072
+ return {
2073
+ file: stored.file,
2074
+ units: stored.units.map((unit) => expanded(unit, take))
2075
+ };
2076
+ }
2077
+ function writeRecord(entryDir, meta) {
2078
+ return writeOnce(import_node_path6.default.join(entryDir, RECORDS_DIR), { meta });
2079
+ }
2080
+ function readRecord(entryDir, id) {
2081
+ const stored = readStored(import_node_path6.default.join(entryDir, RECORDS_DIR), id);
2082
+ return typeof stored === "object" && stored !== null ? stored : null;
2083
+ }
2084
+ function recordReader(entryDir) {
2085
+ const opened = /* @__PURE__ */ new Map();
2086
+ return (id) => {
2087
+ if (!opened.has(id)) {
2088
+ const record2 = readRecord(entryDir, id);
2089
+ opened.set(id, record2);
2090
+ return record2;
2091
+ }
2092
+ const record = opened.get(id) ?? null;
2093
+ return record === null ? null : copyOfJson(record);
2094
+ };
2095
+ }
2096
+ function removeStoredOutside(entryDir, keep) {
2097
+ for (const kind of [SHARDS_DIR, RECORDS_DIR]) {
2098
+ const root = import_node_path6.default.join(entryDir, kind);
2099
+ for (const prefix of namesIn(root)) {
2100
+ const dir = import_node_path6.default.join(root, prefix);
2101
+ for (const name of namesIn(dir)) {
2102
+ if (STORED_ID.test(name) && !keep.has(name.slice(0, -".json".length))) {
2103
+ import_node_fs4.default.rmSync(import_node_path6.default.join(dir, name), { force: true });
2104
+ }
2105
+ }
2106
+ }
2107
+ }
2108
+ }
2109
+ function namesIn(dir) {
2110
+ try {
2111
+ return import_node_fs4.default.readdirSync(dir);
2112
+ } catch {
2113
+ return [];
2114
+ }
2115
+ }
2116
+ function writeOnce(root, value) {
2117
+ const pieces = [...(0, import_files2.documentPieces)(value)];
2118
+ const hash = (0, import_node_crypto3.createHash)("sha256");
2119
+ for (const piece of pieces) {
2120
+ hash.update(piece);
2121
+ }
2122
+ const id = hash.digest("hex").slice(0, 32);
2123
+ const file = storedPath(root, id);
2124
+ if (!import_node_fs4.default.existsSync(file)) {
2125
+ (0, import_files2.writeFileAtomically)(file, pieces);
2126
+ }
2127
+ return id;
2128
+ }
2129
+ function readStored(root, id) {
2130
+ try {
2131
+ return (0, import_files2.readJsonFile)(storedPath(root, id));
2132
+ } catch {
2133
+ return void 0;
2134
+ }
2135
+ }
2136
+ function storedPath(root, id) {
2137
+ return import_node_path6.default.join(root, id.slice(0, 2), `${id}.json`);
2138
+ }
2139
+ function interned(content) {
2140
+ const intern = [];
2141
+ const byText = /* @__PURE__ */ new Map();
2142
+ const byObject = /* @__PURE__ */ new Map();
2143
+ const positionOf = (value) => {
2144
+ const known = byObject.get(value);
2145
+ if (known !== void 0) {
2146
+ return known;
2147
+ }
2148
+ const text = JSON.stringify(value) ?? "null";
2149
+ let position = byText.get(text);
2150
+ if (position === void 0) {
2151
+ position = intern.length;
2152
+ intern.push(value);
2153
+ byText.set(text, position);
2154
+ }
2155
+ if (typeof value === "object" && value !== null) {
2156
+ byObject.set(value, position);
2157
+ }
2158
+ return position;
2159
+ };
2160
+ const positions = (values) => Array.isArray(values) ? values.map(positionOf) : values;
2161
+ const units = content.units.map((summary) => ({
2162
+ ...summary,
2163
+ transitions: summary.transitions.map((transition) => ({
2164
+ ...transition,
2165
+ conditions: positions(transition.conditions),
2166
+ effects: positions(transition.effects)
2167
+ })),
2168
+ gaps: summary.gaps.map((gap) => ({
2169
+ ...gap,
2170
+ conditions: positions(gap.conditions)
2171
+ }))
2172
+ }));
2173
+ return {
2174
+ file: content.file,
2175
+ intern,
2176
+ units
2177
+ };
2178
+ }
2179
+ function expanded(unit, take) {
2180
+ const values = (positions) => Array.isArray(positions) ? positions.map((at) => take(at)) : positions;
2181
+ const transitions = Array.isArray(unit.transitions) ? unit.transitions : [];
2182
+ const gaps = Array.isArray(unit.gaps) ? unit.gaps : [];
2183
+ return {
2184
+ ...unit,
2185
+ transitions: transitions.map((transition) => ({
2186
+ ...transition,
2187
+ conditions: values(transition.conditions),
2188
+ effects: values(transition.effects)
2189
+ })),
2190
+ gaps: gaps.map((gap) => ({
2191
+ ...gap,
2192
+ conditions: values(gap.conditions)
2193
+ }))
2194
+ };
2195
+ }
2196
+ function takerFor(intern) {
2197
+ const handedOut = /* @__PURE__ */ new Set();
2198
+ return (position) => {
2199
+ const value = intern[position];
2200
+ if (!handedOut.has(position)) {
2201
+ handedOut.add(position);
2202
+ return value;
2203
+ }
2204
+ return copyOfJson(value);
2205
+ };
2206
+ }
2207
+ function copyOfJson(value) {
2208
+ if (Array.isArray(value)) {
2209
+ return value.map(copyOfJson);
2210
+ }
2211
+ if (typeof value === "object" && value !== null) {
2212
+ const copy = {};
2213
+ for (const key of Object.keys(value)) {
2214
+ copy[key] = copyOfJson(value[key]);
2215
+ }
2216
+ return copy;
2217
+ }
2218
+ return value;
2219
+ }
1794
2220
 
1795
2221
  // src/cacheStamps.ts
1796
- var import_node_crypto2 = require("crypto");
2222
+ var import_node_crypto4 = require("crypto");
1797
2223
  var import_promises = __toESM(require("fs/promises"), 1);
1798
2224
  var RECENT_CHANGE_MS = 2e3;
1799
2225
  function stampMayMissAWrite(stat, statAt) {
@@ -1883,7 +2309,7 @@ function reusableHash(prior, stamp) {
1883
2309
  async function hashFile(filePath) {
1884
2310
  try {
1885
2311
  const content = await import_promises.default.readFile(filePath);
1886
- return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
2312
+ return (0, import_node_crypto4.createHash)("sha256").update(content).digest("hex").slice(0, 16);
1887
2313
  } catch {
1888
2314
  return null;
1889
2315
  }
@@ -1899,21 +2325,29 @@ function fileStampEquals(a, b) {
1899
2325
  }
1900
2326
 
1901
2327
  // src/ignoredDirectory.ts
1902
- var import_node_fs3 = __toESM(require("fs"), 1);
1903
- var import_node_path5 = __toESM(require("path"), 1);
2328
+ var import_node_fs5 = __toESM(require("fs"), 1);
2329
+ var import_node_path7 = __toESM(require("path"), 1);
1904
2330
  function makeIgnoredDirectory(dir) {
1905
- import_node_fs3.default.mkdirSync(dir, { recursive: true });
1906
- const ignore = import_node_path5.default.join(dir, ".gitignore");
1907
- if (!import_node_fs3.default.existsSync(ignore)) {
1908
- import_node_fs3.default.writeFileSync(ignore, "*\n");
2331
+ import_node_fs5.default.mkdirSync(dir, { recursive: true });
2332
+ const ignore = import_node_path7.default.join(dir, ".gitignore");
2333
+ if (!import_node_fs5.default.existsSync(ignore)) {
2334
+ import_node_fs5.default.writeFileSync(ignore, "*\n");
1909
2335
  }
1910
2336
  }
1911
2337
 
1912
2338
  // src/cache.ts
1913
- var SCHEMA_VERSION = "9";
2339
+ var SCHEMA_VERSION = "10";
2340
+ var MANIFEST = "manifest.json";
2341
+ var INDEX = "index.json";
1914
2342
  var MAX_ENTRIES = 2;
1915
2343
  var ENTRY_PREFIX = "key-";
1916
2344
  var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
2345
+ function ownersWhereWritten(summaries, walkedFileOf) {
2346
+ return summaries.map((summary) => {
2347
+ const walked = walkedFileOf(summary.location.file);
2348
+ return walked === void 0 ? [] : [walked];
2349
+ });
2350
+ }
1917
2351
  function createCacheLayer(cacheDir) {
1918
2352
  if (cacheDir === null) {
1919
2353
  return {
@@ -1933,7 +2367,7 @@ function createCacheLayer(cacheDir) {
1933
2367
  const lookupWith = async (input, run) => {
1934
2368
  const entryDir = entryDirFor(cacheDir, input);
1935
2369
  const manifest = await manifests.readAtRunStart(
1936
- import_node_path6.default.join(entryDir, "manifest.json")
2370
+ import_node_path8.default.join(entryDir, MANIFEST)
1937
2371
  );
1938
2372
  if (manifest === null) {
1939
2373
  return missDiag(await describeAbsentEntry(cacheDir));
@@ -1948,11 +2382,15 @@ function createCacheLayer(cacheDir) {
1948
2382
  if (!await filesStillMatch(manifest.files, run)) {
1949
2383
  return missDiag("files-changed");
1950
2384
  }
2385
+ const summaries = storedSummaries(entryDir, manifest);
2386
+ if (summaries === null) {
2387
+ return missDiag("no-manifest");
2388
+ }
1951
2389
  await markUsed(entryDir);
1952
2390
  manifests.forget();
1953
2391
  return {
1954
2392
  kind: "hit",
1955
- summaries: manifest.summaries,
2393
+ summaries,
1956
2394
  diagnostic: { kind: "hit" }
1957
2395
  };
1958
2396
  };
@@ -1972,10 +2410,8 @@ function createCacheLayer(cacheDir) {
1972
2410
  },
1973
2411
  async plan(input) {
1974
2412
  const entryDir = entryDirFor(cacheDir, input);
1975
- const manifest = await manifests.readAgain(
1976
- import_node_path6.default.join(entryDir, "manifest.json")
1977
- );
1978
- if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
2413
+ const manifest = await manifests.readAgain(import_node_path8.default.join(entryDir, MANIFEST));
2414
+ if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0) {
1979
2415
  return null;
1980
2416
  }
1981
2417
  const run = await runFor(input);
@@ -1986,14 +2422,17 @@ function createCacheLayer(cacheDir) {
1986
2422
  )) {
1987
2423
  return null;
1988
2424
  }
2425
+ const summaries = storedSummaries(entryDir, manifest);
2426
+ const records = storedRecords(entryDir, manifest);
2427
+ if (summaries === null || records === null) {
2428
+ return null;
2429
+ }
1989
2430
  await markUsed(entryDir);
1990
- return buildPlan(manifest, run);
2431
+ return buildPlan(manifest, records, summaries, run);
1991
2432
  },
1992
2433
  async write(input, summaries, attribution) {
1993
2434
  const entryDir = entryDirFor(cacheDir, input);
1994
- const previous = await manifests.readAgain(
1995
- import_node_path6.default.join(entryDir, "manifest.json")
1996
- );
2435
+ const previous = await manifests.readAgain(import_node_path8.default.join(entryDir, MANIFEST));
1997
2436
  manifests.forget();
1998
2437
  const known = runs.get(input);
1999
2438
  const run = known ?? await runFor(input);
@@ -2009,55 +2448,119 @@ function createCacheLayer(cacheDir) {
2009
2448
  )
2010
2449
  );
2011
2450
  const files = recorded.filter((stamp) => stamp !== null);
2451
+ const encoded = attribution === void 0 ? null : encodeAttribution(
2452
+ attribution,
2453
+ (meta) => writeRecord(entryDir, meta)
2454
+ );
2455
+ makeIgnoredDirectory(cacheDir);
2456
+ const shards = writeShards(entryDir, summaries, encoded);
2012
2457
  const manifest = {
2013
2458
  schemaVersion: SCHEMA_VERSION,
2014
2459
  adapterPacksDigest: input.adapterPacksDigest,
2015
2460
  configStamp,
2016
2461
  files,
2017
- summaries,
2018
- ...attribution === void 0 ? {} : encodeAttribution(attribution)
2462
+ summaryCount: summaries.length,
2463
+ shards,
2464
+ ...encoded === null ? {} : encoded.stored
2019
2465
  };
2020
- makeIgnoredDirectory(cacheDir);
2021
- (0, import_files.writeDocumentFile)(import_node_path6.default.join(entryDir, "manifest.json"), manifest);
2466
+ (0, import_files3.writeDocumentFile)(import_node_path8.default.join(entryDir, MANIFEST), manifest);
2467
+ removeStoredOutside(
2468
+ entryDir,
2469
+ /* @__PURE__ */ new Set([...storedIds(manifest), ...storedIds(previous)])
2470
+ );
2022
2471
  await evictOldEntries(cacheDir, entryDir);
2023
2472
  }
2024
2473
  };
2025
2474
  }
2026
- async function buildPlan(manifest, run) {
2027
- const stored = new Map(manifest.files.map((f) => [f.path, f]));
2028
- const current = new Map(run.files.map((f) => [f.stamp.path, f.stamp]));
2029
- const changed = /* @__PURE__ */ new Set();
2030
- const added = /* @__PURE__ */ new Set();
2031
- const removed = /* @__PURE__ */ new Set();
2032
- for (const p of stored.keys()) {
2033
- if (!current.has(p)) {
2034
- removed.add(p);
2475
+ function storedIds(manifest) {
2476
+ return [
2477
+ ...(manifest?.shards ?? []).map((shard) => shard.id),
2478
+ ...(manifest?.rootMeta ?? []).flatMap(
2479
+ (root) => root.record === void 0 ? [] : [root.record]
2480
+ )
2481
+ ];
2482
+ }
2483
+ function writeShards(entryDir, summaries, encoded) {
2484
+ const byFile = /* @__PURE__ */ new Map();
2485
+ summaries.forEach((summary, position) => {
2486
+ const file = summary.location?.file ?? "";
2487
+ const shard = byFile.get(file) ?? {
2488
+ content: { file, units: [] },
2489
+ order: [],
2490
+ owners: []
2491
+ };
2492
+ byFile.set(file, shard);
2493
+ shard.content.units.push(summary);
2494
+ shard.order.push(position);
2495
+ shard.owners.push(encoded?.owners[position] ?? []);
2496
+ });
2497
+ const indexFile = import_node_path8.default.join(entryDir, INDEX);
2498
+ const before = readStoreIndex(indexFile);
2499
+ const known = before === null ? /* @__PURE__ */ new Map() : entriesByShard(before);
2500
+ const sorted = [...byFile].sort(([a], [b]) => a < b ? -1 : 1);
2501
+ const written = sorted.map(([file, { content, order, owners }]) => {
2502
+ const id = writeShard(entryDir, content);
2503
+ return {
2504
+ shard: {
2505
+ file,
2506
+ id,
2507
+ order,
2508
+ ...encoded === null ? {} : { owners }
2509
+ },
2510
+ entries: known.get(id) ?? entriesOf(content.units)
2511
+ };
2512
+ });
2513
+ writeStoreIndex(
2514
+ indexFile,
2515
+ indexOf(
2516
+ written.map(({ shard, entries }) => ({
2517
+ file: shard.file,
2518
+ id: shard.id,
2519
+ entries
2520
+ }))
2521
+ )
2522
+ );
2523
+ return written.map(({ shard }) => shard);
2524
+ }
2525
+ function storedSummaries(entryDir, manifest) {
2526
+ const summaries = new Array(
2527
+ manifest.summaryCount
2528
+ );
2529
+ for (const shard of manifest.shards) {
2530
+ const content = readShard(entryDir, shard.id, shard.file);
2531
+ if (content === null || content.units.length !== shard.order.length) {
2532
+ return null;
2035
2533
  }
2534
+ shard.order.forEach((position, i) => {
2535
+ summaries[position] = content.units[i];
2536
+ });
2036
2537
  }
2037
- const toVerify = [];
2038
- for (const [p, stamp] of current) {
2039
- const before = stored.get(p);
2040
- if (before === void 0) {
2041
- changed.add(p);
2042
- added.add(p);
2043
- } else if (!fileStampEquals(before, stamp)) {
2044
- if (before.contentHash === void 0) {
2045
- changed.add(p);
2046
- } else {
2047
- toVerify.push(p);
2048
- }
2049
- } else if (before.verifyHash === true) {
2050
- toVerify.push(p);
2538
+ for (let position = 0; position < summaries.length; position++) {
2539
+ if (summaries[position] === void 0) {
2540
+ return null;
2051
2541
  }
2052
2542
  }
2053
- await Promise.all(
2054
- toVerify.map(async (p) => {
2055
- const hash = await hashOf(run, p);
2056
- if (hash === null || hash !== stored.get(p)?.contentHash) {
2057
- changed.add(p);
2058
- }
2059
- })
2060
- );
2543
+ return summaries;
2544
+ }
2545
+ function storedRecords(entryDir, manifest) {
2546
+ const records = /* @__PURE__ */ new Map();
2547
+ const roots = manifest.roots ?? [];
2548
+ const readRecord2 = recordReader(entryDir);
2549
+ for (const [i, root] of (manifest.rootMeta ?? []).entries()) {
2550
+ const rootPath = roots[i];
2551
+ if (rootPath === void 0 || root.record === void 0) {
2552
+ continue;
2553
+ }
2554
+ const record = readRecord2(root.record);
2555
+ if (record === null) {
2556
+ return null;
2557
+ }
2558
+ records.set(rootPath, record.meta);
2559
+ }
2560
+ return records;
2561
+ }
2562
+ async function buildPlan(manifest, records, stored, run) {
2563
+ const { changed, added, removed } = await changedSince(manifest, run);
2061
2564
  const roots = /* @__PURE__ */ new Map();
2062
2565
  const rootNames = manifest.roots ?? [];
2063
2566
  const depPaths = manifest.depPaths ?? [];
@@ -2067,23 +2570,24 @@ async function buildPlan(manifest, run) {
2067
2570
  });
2068
2571
  let rootsDeclined = 0;
2069
2572
  rootNames.forEach((rootPath, i) => {
2070
- const meta = manifest.rootMeta?.[i];
2071
- if (meta === void 0) {
2573
+ const record = manifest.rootMeta?.[i];
2574
+ if (record === void 0) {
2072
2575
  return;
2073
2576
  }
2074
- if (!meta.cacheable) {
2577
+ if (!record.cacheable) {
2075
2578
  rootsDeclined += 1;
2076
2579
  }
2077
2580
  roots.set(rootPath, {
2078
2581
  path: rootPath,
2079
- cacheable: meta.cacheable,
2080
- deps: pathsOf(meta.deps),
2081
- claims: meta.claims,
2082
- meta: meta.meta,
2083
- packs: meta.packs,
2084
- ...meta.imports === void 0 ? {} : { imports: pathsOf(meta.imports) }
2582
+ cacheable: record.cacheable,
2583
+ deps: pathsOf(record.deps),
2584
+ claims: record.claims,
2585
+ meta: records.get(rootPath),
2586
+ packs: record.packs,
2587
+ ...record.imports === void 0 ? {} : { imports: pathsOf(record.imports) }
2085
2588
  });
2086
2589
  });
2590
+ const current = new Set(run.files.map((f) => f.stamp.path));
2087
2591
  const unchanged = (filePath, deps) => current.has(filePath) && !changed.has(filePath) && !deps.some((d) => changed.has(d) || removed.has(d));
2088
2592
  const validRoots = /* @__PURE__ */ new Set();
2089
2593
  for (const [rootPath, record] of roots) {
@@ -2091,14 +2595,14 @@ async function buildPlan(manifest, run) {
2091
2595
  validRoots.add(rootPath);
2092
2596
  }
2093
2597
  }
2094
- const units = (manifest.units ?? []).flatMap((stored2) => {
2095
- const file = depPaths[stored2.file];
2598
+ const units = (manifest.units ?? []).flatMap((unit) => {
2599
+ const file = depPaths[unit.file];
2096
2600
  return file === void 0 ? [] : [
2097
2601
  {
2098
- key: stored2.key,
2602
+ key: unit.key,
2099
2603
  file,
2100
- deps: pathsOf(stored2.deps),
2101
- data: stored2.data
2604
+ deps: pathsOf(unit.deps),
2605
+ data: unit.data
2102
2606
  }
2103
2607
  ];
2104
2608
  });
@@ -2116,7 +2620,7 @@ async function buildPlan(manifest, run) {
2116
2620
  }
2117
2621
  }
2118
2622
  }
2119
- const owners = manifest.owners ?? [];
2623
+ const ownerPaths = ownersInRunOrder(manifest);
2120
2624
  return {
2121
2625
  changed,
2122
2626
  removed,
@@ -2128,36 +2632,81 @@ async function buildPlan(manifest, run) {
2128
2632
  reuse(valid) {
2129
2633
  const summaries = [];
2130
2634
  const reusedOwners = [];
2131
- manifest.summaries.forEach((summary, i) => {
2132
- const ownerPaths = (owners[i] ?? []).flatMap((o) => {
2133
- const p = rootNames[o];
2134
- return p === void 0 ? [] : [p];
2135
- });
2136
- if (ownerPaths.some((p) => valid.has(p))) {
2635
+ stored.forEach((summary, i) => {
2636
+ const owners = ownerPaths[i] ?? [];
2637
+ if (owners.some((p) => valid.has(p))) {
2137
2638
  summaries.push(summary);
2138
- reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
2639
+ reusedOwners.push(owners.filter((p) => valid.has(p)));
2139
2640
  }
2140
2641
  });
2141
2642
  return { summaries, owners: reusedOwners };
2142
2643
  },
2143
2644
  allSummaries() {
2144
- return manifest.summaries;
2645
+ return stored;
2145
2646
  },
2146
2647
  attribution() {
2147
2648
  return {
2148
2649
  roots: [...roots.values()],
2149
- owners: owners.map(
2150
- (ownerIds) => ownerIds.flatMap((o) => {
2151
- const p = rootNames[o];
2152
- return p === void 0 ? [] : [p];
2153
- })
2154
- ),
2650
+ owners: ownerPaths,
2155
2651
  units
2156
2652
  };
2157
2653
  }
2158
2654
  };
2159
2655
  }
2160
- function encodeAttribution(attribution) {
2656
+ function ownersInRunOrder(manifest) {
2657
+ const rootNames = manifest.roots ?? [];
2658
+ const owners = Array.from(
2659
+ { length: manifest.summaryCount },
2660
+ () => []
2661
+ );
2662
+ for (const shard of manifest.shards ?? []) {
2663
+ shard.order.forEach((position, i) => {
2664
+ owners[position] = (shard.owners?.[i] ?? []).flatMap((o) => {
2665
+ const p = rootNames[o];
2666
+ return p === void 0 ? [] : [p];
2667
+ });
2668
+ });
2669
+ }
2670
+ return owners;
2671
+ }
2672
+ async function changedSince(manifest, run) {
2673
+ const stored = new Map(manifest.files.map((f) => [f.path, f]));
2674
+ const current = new Map(run.files.map((f) => [f.stamp.path, f.stamp]));
2675
+ const changed = /* @__PURE__ */ new Set();
2676
+ const added = /* @__PURE__ */ new Set();
2677
+ const removed = /* @__PURE__ */ new Set();
2678
+ for (const p of stored.keys()) {
2679
+ if (!current.has(p)) {
2680
+ removed.add(p);
2681
+ }
2682
+ }
2683
+ const toVerify = [];
2684
+ for (const [p, stamp] of current) {
2685
+ const before = stored.get(p);
2686
+ if (before === void 0) {
2687
+ changed.add(p);
2688
+ added.add(p);
2689
+ } else if (!fileStampEquals(before, stamp)) {
2690
+ if (before.contentHash === void 0) {
2691
+ changed.add(p);
2692
+ } else {
2693
+ toVerify.push(p);
2694
+ }
2695
+ } else if (before.verifyHash === true) {
2696
+ toVerify.push(p);
2697
+ }
2698
+ }
2699
+ await Promise.all(
2700
+ toVerify.map(async (p) => {
2701
+ const hash = await hashOf(run, p);
2702
+ if (hash === null || hash !== stored.get(p)?.contentHash) {
2703
+ changed.add(p);
2704
+ }
2705
+ })
2706
+ );
2707
+ return { changed, added, removed };
2708
+ }
2709
+ function encodeAttribution(attribution, storeRecord) {
2161
2710
  const roots = attribution.roots.map((r) => r.path);
2162
2711
  const rootIndex = new Map(roots.map((p, i) => [p, i]));
2163
2712
  const depIndex = /* @__PURE__ */ new Map();
@@ -2173,10 +2722,10 @@ function encodeAttribution(attribution) {
2173
2722
  return id;
2174
2723
  };
2175
2724
  const rootMeta = attribution.roots.map((r) => ({
2725
+ ...r.meta === void 0 ? {} : { record: storeRecord(r.meta) },
2176
2726
  cacheable: r.cacheable,
2177
2727
  deps: r.deps.map(depIdOf),
2178
2728
  claims: r.claims,
2179
- meta: r.meta,
2180
2729
  packs: r.packs,
2181
2730
  ...r.imports === void 0 ? {} : { imports: r.imports.map(depIdOf) }
2182
2731
  }));
@@ -2193,11 +2742,13 @@ function encodeAttribution(attribution) {
2193
2742
  data: unit.data
2194
2743
  }));
2195
2744
  return {
2196
- roots,
2197
- rootMeta,
2198
- depPaths,
2199
- owners,
2200
- ...units === void 0 ? {} : { units }
2745
+ stored: {
2746
+ roots,
2747
+ rootMeta,
2748
+ depPaths,
2749
+ ...units === void 0 ? {} : { units }
2750
+ },
2751
+ owners
2201
2752
  };
2202
2753
  }
2203
2754
  async function describeAbsentEntry(cacheDir) {
@@ -2214,8 +2765,8 @@ function entryDirFor(cacheDir, input) {
2214
2765
  input.adapterPacksDigest,
2215
2766
  input.configPath ?? ""
2216
2767
  ].join(" ");
2217
- const name = (0, import_node_crypto3.createHash)("sha256").update(key).digest("hex").slice(0, 16);
2218
- return import_node_path6.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
2768
+ const name = (0, import_node_crypto5.createHash)("sha256").update(key).digest("hex").slice(0, 16);
2769
+ return import_node_path8.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
2219
2770
  }
2220
2771
  function isEntryDir(name) {
2221
2772
  return ENTRY_DIR_NAME.test(name);
@@ -2229,11 +2780,11 @@ async function markUsed(entryDir) {
2229
2780
  }
2230
2781
  async function evictOldEntries(cacheDir, keepDir) {
2231
2782
  try {
2232
- await import_promises2.default.rm(import_node_path6.default.join(cacheDir, "manifest.json"), { force: true });
2783
+ await import_promises2.default.rm(import_node_path8.default.join(cacheDir, "manifest.json"), { force: true });
2233
2784
  const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
2234
2785
  const dirs = await Promise.all(
2235
2786
  entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
2236
- const dir = import_node_path6.default.join(cacheDir, e.name);
2787
+ const dir = import_node_path8.default.join(cacheDir, e.name);
2237
2788
  const stat = await import_promises2.default.stat(dir);
2238
2789
  return { dir, mtimeMs: stat.mtimeMs };
2239
2790
  })
@@ -2255,7 +2806,7 @@ var ManifestReads = class {
2255
2806
  last = null;
2256
2807
  async readAtRunStart(manifestPath) {
2257
2808
  const stamp = await manifestStamp(manifestPath);
2258
- const manifest = await readManifest(manifestPath);
2809
+ const manifest = await readManifest2(manifestPath);
2259
2810
  this.last = { path: manifestPath, stamp, manifest };
2260
2811
  return manifest;
2261
2812
  }
@@ -2278,16 +2829,54 @@ async function manifestStamp(manifestPath) {
2278
2829
  return null;
2279
2830
  }
2280
2831
  }
2281
- async function readManifest(manifestPath) {
2832
+ async function readManifest2(manifestPath) {
2282
2833
  try {
2283
- return (0, import_files.readJsonFile)(manifestPath);
2834
+ const manifest = (0, import_files3.readJsonFile)(manifestPath);
2835
+ return Array.isArray(manifest.shards) ? manifest : null;
2284
2836
  } catch {
2285
2837
  return null;
2286
2838
  }
2287
2839
  }
2288
2840
 
2841
+ // src/cacheLocation.ts
2842
+ var import_node_crypto6 = require("crypto");
2843
+ var import_node_fs6 = __toESM(require("fs"), 1);
2844
+ var import_node_path9 = __toESM(require("path"), 1);
2845
+ var CACHE_DIR_ENV = "SUSS_CACHE_DIR";
2846
+ function chosenCacheRoot() {
2847
+ const chosen = process.env[CACHE_DIR_ENV];
2848
+ if (chosen === void 0 || chosen === "") {
2849
+ return null;
2850
+ }
2851
+ return import_node_path9.default.resolve(chosen);
2852
+ }
2853
+ function realPathOrSelf(dir) {
2854
+ try {
2855
+ return import_node_fs6.default.realpathSync(dir);
2856
+ } catch {
2857
+ return dir;
2858
+ }
2859
+ }
2860
+ function projectKey(projectDir) {
2861
+ return (0, import_node_crypto6.createHash)("sha256").update(realPathOrSelf(import_node_path9.default.resolve(projectDir))).digest("hex").slice(0, 16);
2862
+ }
2863
+ function extractionCacheDirFor(projectDir) {
2864
+ const root = chosenCacheRoot();
2865
+ if (root === null) {
2866
+ return import_node_path9.default.join(projectDir, ".suss", "cache");
2867
+ }
2868
+ return import_node_path9.default.join(root, "cache", projectKey(projectDir));
2869
+ }
2870
+ function liveRecordFor(projectDir) {
2871
+ const root = chosenCacheRoot();
2872
+ if (root === null) {
2873
+ return import_node_path9.default.join(projectDir, ".suss", "live", "server.json");
2874
+ }
2875
+ return import_node_path9.default.join(root, "live", `${projectKey(projectDir)}.json`);
2876
+ }
2877
+
2289
2878
  // src/extractionReport.ts
2290
- var import_behavioral_ir6 = require("@suss/behavioral-ir");
2879
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
2291
2880
  var emptyTally = () => ({
2292
2881
  candidateFiles: 0,
2293
2882
  unitsDiscovered: 0,
@@ -2337,7 +2926,7 @@ function summaryCountsByPack(summaries) {
2337
2926
  }
2338
2927
  const entry = counts.get(recognition) ?? emptyCounts();
2339
2928
  entry.bound += 1;
2340
- if (import_behavioral_ir6.BOUNDARY_ROLE[summary.kind] === "provider") {
2929
+ if (import_behavioral_ir7.BOUNDARY_ROLE[summary.kind] === "provider") {
2341
2930
  entry.providers += 1;
2342
2931
  }
2343
2932
  if (summary.transitions.length > 0) {
@@ -2562,6 +3151,17 @@ function undocumentedDeclarations(gradients) {
2562
3151
  detail: gradient.withoutExample.join(", ")
2563
3152
  }));
2564
3153
  }
3154
+ function packConfigCheck(needs) {
3155
+ return {
3156
+ name: "every pack that needs an option was given it",
3157
+ code: "no-config",
3158
+ audience: "run",
3159
+ violations: needs.map((need) => ({
3160
+ label: need.pack,
3161
+ detail: `reads nothing until ${need.option} is set, for example ${need.example}. Write that to a JSON file and pass -f ${need.pack}=<config.json>.`
3162
+ }))
3163
+ };
3164
+ }
2565
3165
  function evaluatePackHealth(report) {
2566
3166
  const gradients = packGradients(report);
2567
3167
  return [
@@ -2714,7 +3314,7 @@ function makeTransitionId(functionName, branch) {
2714
3314
  const { terminal } = branch;
2715
3315
  const statusKey = terminal.statusCode === null ? "none" : terminal.statusCode.type === "literal" ? String(terminal.statusCode.value) : `dyn:${terminal.statusCode.sourceText}`;
2716
3316
  const conditionSig = branch.conditions.map((c) => `${c.polarity}:${c.sourceText}`).join(";");
2717
- const conditionHash = (0, import_node_crypto4.createHash)("sha1").update(conditionSig).digest("hex").slice(0, 7);
3317
+ const conditionHash = (0, import_node_crypto7.createHash)("sha1").update(conditionSig).digest("hex").slice(0, 7);
2718
3318
  return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
2719
3319
  }
2720
3320
  function collapseReading(table, reading) {
@@ -2783,17 +3383,17 @@ function effectsOfBranch(branch) {
2783
3383
  ...branch.effects.map(effectToIR),
2784
3384
  ...branch.extraEffects ?? []
2785
3385
  ];
2786
- return (0, import_behavioral_ir7.foldRepeatedEffects)(effects.map(withNormalizedCallee));
3386
+ return (0, import_behavioral_ir8.foldRepeatedEffects)(effects.map(withNormalizedCallee));
2787
3387
  }
2788
3388
  function provenanceOf(raw, effects) {
2789
3389
  if (raw.length === 0) {
2790
3390
  return [];
2791
3391
  }
2792
- const positionOf = new Map(effects.map((one, at) => [(0, import_behavioral_ir7.effectKey)(one), at]));
3392
+ const positionOf = new Map(effects.map((one, at) => [(0, import_behavioral_ir8.effectKey)(one), at]));
2793
3393
  const bySlot = /* @__PURE__ */ new Map();
2794
3394
  const said = /* @__PURE__ */ new Map();
2795
3395
  for (const one of raw) {
2796
- const effect = positionOf.get((0, import_behavioral_ir7.effectKey)(withNormalizedCallee(one.effect)));
3396
+ const effect = positionOf.get((0, import_behavioral_ir8.effectKey)(withNormalizedCallee(one.effect)));
2797
3397
  if (effect === void 0) {
2798
3398
  continue;
2799
3399
  }
@@ -2825,7 +3425,7 @@ function withNormalizedCallee(effect) {
2825
3425
  if (effect.callee === void 0) {
2826
3426
  return effect;
2827
3427
  }
2828
- return { ...effect, callee: (0, import_behavioral_ir7.normalizeCalleeText)(effect.callee) };
3428
+ return { ...effect, callee: (0, import_behavioral_ir8.normalizeCalleeText)(effect.callee) };
2829
3429
  }
2830
3430
  function assembleSummary(raw, options = DEFAULT_OPTIONS) {
2831
3431
  const transitions = raw.branches.map((rawBranch) => {
@@ -2890,28 +3490,28 @@ function buildMetadata(raw) {
2890
3490
  let metadata = {};
2891
3491
  const http = buildHttpMetadataValue(raw);
2892
3492
  if (http !== null) {
2893
- metadata = (0, import_behavioral_ir7.withHttpMetadata)(metadata, http);
3493
+ metadata = (0, import_behavioral_ir8.withHttpMetadata)(metadata, http);
2894
3494
  }
2895
3495
  const graphql = buildGraphqlMetadataValue(raw);
2896
3496
  if (graphql !== null) {
2897
- metadata = (0, import_behavioral_ir7.withGraphqlMetadata)(metadata, graphql);
3497
+ metadata = (0, import_behavioral_ir8.withGraphqlMetadata)(metadata, graphql);
2898
3498
  }
2899
3499
  if (raw.sourceDocumentLabel !== void 0) {
2900
- metadata = (0, import_behavioral_ir7.withSourceDocumentMetadata)(metadata, {
3500
+ metadata = (0, import_behavioral_ir8.withSourceDocumentMetadata)(metadata, {
2901
3501
  label: raw.sourceDocumentLabel
2902
3502
  });
2903
3503
  }
2904
3504
  if (raw.mount !== void 0) {
2905
- metadata = (0, import_behavioral_ir7.withMountMetadata)(metadata, raw.mount);
3505
+ metadata = (0, import_behavioral_ir8.withMountMetadata)(metadata, raw.mount);
2906
3506
  }
2907
3507
  if (raw.wrappers !== void 0 && raw.wrappers.length > 0) {
2908
- metadata = (0, import_behavioral_ir7.withWrapperMetadata)(metadata, { applied: raw.wrappers });
3508
+ metadata = (0, import_behavioral_ir8.withWrapperMetadata)(metadata, { applied: raw.wrappers });
2909
3509
  }
2910
3510
  if (raw.requestSpelling !== void 0) {
2911
- metadata = (0, import_behavioral_ir7.withRequestSpellingMetadata)(metadata, raw.requestSpelling);
3511
+ metadata = (0, import_behavioral_ir8.withRequestSpellingMetadata)(metadata, raw.requestSpelling);
2912
3512
  }
2913
3513
  if (raw.test !== void 0) {
2914
- metadata = (0, import_behavioral_ir7.withTestMetadata)(metadata, raw.test);
3514
+ metadata = (0, import_behavioral_ir8.withTestMetadata)(metadata, raw.test);
2915
3515
  }
2916
3516
  return Object.keys(metadata).length > 0 ? metadata : null;
2917
3517
  }
@@ -2970,7 +3570,7 @@ function answersWithHttpResponses(raw) {
2970
3570
  if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
2971
3571
  return true;
2972
3572
  }
2973
- return (0, import_behavioral_ir7.exchangesHttpResponses)(raw.boundaryBinding);
3573
+ return (0, import_behavioral_ir8.exchangesHttpResponses)(raw.boundaryBinding);
2974
3574
  }
2975
3575
  function detectGaps(raw, transitions, options) {
2976
3576
  if (options.gapHandling === "silent") {
@@ -3169,6 +3769,7 @@ function paramToInput(param) {
3169
3769
  }
3170
3770
  // Annotate the CommonJS export names for ESM import in node:
3171
3771
  0 && (module.exports = {
3772
+ CACHE_DIR_ENV,
3172
3773
  EnclosingNodes,
3173
3774
  IdMap,
3174
3775
  IdSet,
@@ -3186,6 +3787,7 @@ function paramToInput(param) {
3186
3787
  assembleSummary,
3187
3788
  assessConfidence,
3188
3789
  buildUngatedExtractionReport,
3790
+ chosenCacheRoot,
3189
3791
  commonDirectoryOf,
3190
3792
  composeWrappers,
3191
3793
  computeContentHash,
@@ -3193,6 +3795,7 @@ function paramToInput(param) {
3193
3795
  configuredCallOption,
3194
3796
  createAdapterStamp,
3195
3797
  createCacheLayer,
3798
+ createCodeHasher,
3196
3799
  createPackTallies,
3197
3800
  createTimer,
3198
3801
  detectGaps,
@@ -3201,6 +3804,7 @@ function paramToInput(param) {
3201
3804
  enumerateOrDegrade,
3202
3805
  enumerateStructuredPaths,
3203
3806
  evaluatePackHealth,
3807
+ extractionCacheDirFor,
3204
3808
  extractionConfigStamp,
3205
3809
  firstWrittenReading,
3206
3810
  formatPackHealth,
@@ -3208,6 +3812,7 @@ function paramToInput(param) {
3208
3812
  helperPathsOnVariables,
3209
3813
  httpRouteDiscovery,
3210
3814
  isListedTestFile,
3815
+ liveRecordFor,
3211
3816
  makeIgnoredDirectory,
3212
3817
  makeTransitionId,
3213
3818
  mapReading,
@@ -3217,8 +3822,11 @@ function paramToInput(param) {
3217
3822
  moduleInitStructure,
3218
3823
  moduleOfFile,
3219
3824
  noopTimer,
3825
+ ownersWhereWritten,
3826
+ packConfigCheck,
3220
3827
  packGradients,
3221
3828
  paramToInput,
3829
+ processCodeHasher,
3222
3830
  projectFileStamp,
3223
3831
  rawConditionToPredicate,
3224
3832
  recordPackFailure,