@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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { createHash as createHash4 } from "crypto";
2
+ import { createHash as createHash7 } from "crypto";
3
3
  import {
4
4
  effectKey,
5
5
  exchangesHttpResponses as exchangesHttpResponses2,
@@ -571,7 +571,7 @@ function splitAtHandOffs(branch, sites, from = 0) {
571
571
  continue;
572
572
  }
573
573
  return throughHelper(branch, site.at, site.helper).flatMap(
574
- (path7) => path7.terminal === branch.terminal ? splitAtHandOffs(path7, sites, index + 1) : [path7]
574
+ (path10) => path10.terminal === branch.terminal ? splitAtHandOffs(path10, sites, index + 1) : [path10]
575
575
  );
576
576
  }
577
577
  return [branch];
@@ -619,12 +619,12 @@ function rootName(ref) {
619
619
  }
620
620
 
621
621
  // src/moduleImports.ts
622
- function stampModuleImports(summaries, importsOf) {
622
+ function stampModuleImports(summaries, importsOf2) {
623
623
  const byFile = /* @__PURE__ */ new Map();
624
624
  for (const summary of summaries) {
625
625
  const file = summary.location.file;
626
626
  if (!byFile.has(file)) {
627
- const found = importsOf(file);
627
+ const found = importsOf2(file);
628
628
  byFile.set(
629
629
  file,
630
630
  found === void 0 ? void 0 : [...new Set(found)].sort()
@@ -919,8 +919,8 @@ var catchEntryCond = () => fixedCond(
919
919
  "positive",
920
920
  "catchBlock"
921
921
  );
922
- function classify(path7, home, isAncestorOrSelf) {
923
- return path7.map((cond) => {
922
+ function classify(path10, home, isAncestorOrSelf) {
923
+ return path10.map((cond) => {
924
924
  if (cond.branchStmt === null) {
925
925
  return cond.info;
926
926
  }
@@ -992,9 +992,9 @@ function limitOpenPaths(paths) {
992
992
  );
993
993
  }
994
994
  }
995
- function recordTerminal(ctx, terminal, home, path7) {
995
+ function recordTerminal(ctx, terminal, home, path10) {
996
996
  chargeBudget(ctx.state);
997
- const info = classify(path7, home, ctx.isAncestorOrSelf);
997
+ const info = classify(path10, home, ctx.isAncestorOrSelf);
998
998
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
999
999
  existing.push(info);
1000
1000
  ctx.state.byTerminal.set(terminal, existing);
@@ -1005,15 +1005,15 @@ function containsTerminal(ctx, stmt) {
1005
1005
  }
1006
1006
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
1007
1007
  }
1008
- function stepIf(stmt, path7, ctx) {
1008
+ function stepIf(stmt, path10, ctx) {
1009
1009
  const thenExit = exitKindOfList(stmt.thenBody);
1010
1010
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
1011
1011
  const thenPath = [
1012
- ...path7,
1012
+ ...path10,
1013
1013
  branchCond(stmt, stmt.condition, "positive", elseExit)
1014
1014
  ];
1015
1015
  const elsePath = [
1016
- ...path7,
1016
+ ...path10,
1017
1017
  branchCond(
1018
1018
  stmt,
1019
1019
  stmt.condition,
@@ -1030,7 +1030,7 @@ function stepIf(stmt, path7, ctx) {
1030
1030
  }
1031
1031
  return out;
1032
1032
  }
1033
- function stepSwitch(stmt, path7, ctx) {
1033
+ function stepSwitch(stmt, path10, ctx) {
1034
1034
  const out = [];
1035
1035
  const negations = [];
1036
1036
  let defaultRanBody = false;
@@ -1040,7 +1040,7 @@ function stepSwitch(stmt, path7, ctx) {
1040
1040
  if (hasStrayBreak(group.body)) {
1041
1041
  throw new UnmodeledFlow("non-trailing break in switch clause");
1042
1042
  }
1043
- const groupPath = group.condition === null ? [...path7, ...negations] : [...path7, fixedCond(group.condition, "positive", "explicit")];
1043
+ const groupPath = group.condition === null ? [...path10, ...negations] : [...path10, fixedCond(group.condition, "positive", "explicit")];
1044
1044
  const conts = enumerate(ctx, group.body, groupPath);
1045
1045
  if (conts.length > 0) {
1046
1046
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -1057,24 +1057,24 @@ function stepSwitch(stmt, path7, ctx) {
1057
1057
  }
1058
1058
  }
1059
1059
  if (!defaultRanBody) {
1060
- out.push([...path7, ...negations]);
1060
+ out.push([...path10, ...negations]);
1061
1061
  }
1062
1062
  return out;
1063
1063
  }
1064
- function stepLoop(stmt, path7, ctx) {
1064
+ function stepLoop(stmt, path10, ctx) {
1065
1065
  const bodyExit = exitKindOfList(stmt.body);
1066
1066
  if (containsTerminal(ctx, stmt)) {
1067
1067
  enumerate(ctx, stmt.body, [
1068
- ...path7,
1068
+ ...path10,
1069
1069
  loopIterationCond(stmt.condition.sourceText)
1070
1070
  ]);
1071
1071
  }
1072
1072
  if (bodyExit !== null) {
1073
- return [[...path7, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1073
+ return [[...path10, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1074
1074
  }
1075
- return [path7];
1075
+ return [path10];
1076
1076
  }
1077
- function stepTry(stmt, path7, ctx) {
1077
+ function stepTry(stmt, path10, ctx) {
1078
1078
  if (stmt.finallyBody !== null) {
1079
1079
  const finallyHasTerminal = stmt.finallyBody.some(
1080
1080
  (s) => containsTerminal(ctx, s)
@@ -1084,22 +1084,22 @@ function stepTry(stmt, path7, ctx) {
1084
1084
  }
1085
1085
  }
1086
1086
  const out = [];
1087
- out.push(...enumerate(ctx, stmt.tryBody, path7));
1087
+ out.push(...enumerate(ctx, stmt.tryBody, path10));
1088
1088
  if (stmt.catchBody !== null) {
1089
- const catchPath = [...path7, catchEntryCond()];
1089
+ const catchPath = [...path10, catchEntryCond()];
1090
1090
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
1091
1091
  }
1092
1092
  return out;
1093
1093
  }
1094
- function stepExit(stmt, path7, ctx) {
1094
+ function stepExit(stmt, path10, ctx) {
1095
1095
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
1096
1096
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
1097
- collector.push(path7);
1097
+ collector.push(path10);
1098
1098
  }
1099
1099
  return [];
1100
1100
  }
1101
- function stepOpaque(_stmt, path7, _ctx) {
1102
- return [path7];
1101
+ function stepOpaque(_stmt, path10, _ctx) {
1102
+ return [path10];
1103
1103
  }
1104
1104
  var stepHandlers = {
1105
1105
  if: stepIf,
@@ -1109,20 +1109,20 @@ var stepHandlers = {
1109
1109
  exit: stepExit,
1110
1110
  opaque: stepOpaque
1111
1111
  };
1112
- function stepCallback(ctx, body, path7) {
1112
+ function stepCallback(ctx, body, path10) {
1113
1113
  const exits = [];
1114
1114
  ctx.state.callbackExits.push(exits);
1115
1115
  let fellThrough;
1116
1116
  try {
1117
- fellThrough = enumerate(ctx, body, path7);
1117
+ fellThrough = enumerate(ctx, body, path10);
1118
1118
  } finally {
1119
1119
  ctx.state.callbackExits.pop();
1120
1120
  }
1121
1121
  const out = [...fellThrough, ...exits];
1122
- return out.length === 0 ? [path7] : out;
1122
+ return out.length === 0 ? [path10] : out;
1123
1123
  }
1124
- function stepStatement(ctx, stmt, path7) {
1125
- let paths = [path7];
1124
+ function stepStatement(ctx, stmt, path10) {
1125
+ let paths = [path10];
1126
1126
  for (const body of stmt.callbacks ?? []) {
1127
1127
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
1128
1128
  limitOpenPaths(paths);
@@ -1209,8 +1209,8 @@ function enumerate(ctx, stmts, prefix) {
1209
1209
  let frontiers = [[...prefix]];
1210
1210
  for (const stmt of stmts) {
1211
1211
  const nextFrontiers = [];
1212
- for (const path7 of mergeRejoined(frontiers, ctx.keepsArms)) {
1213
- nextFrontiers.push(...stepStatement(ctx, stmt, path7));
1212
+ for (const path10 of mergeRejoined(frontiers, ctx.keepsArms)) {
1213
+ nextFrontiers.push(...stepStatement(ctx, stmt, path10));
1214
1214
  }
1215
1215
  frontiers = nextFrontiers;
1216
1216
  limitOpenPaths(frontiers);
@@ -1259,9 +1259,9 @@ function enumerateStructuredPaths(input) {
1259
1259
  isAncestorOrSelf
1260
1260
  };
1261
1261
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
1262
- for (const path7 of continuations) {
1262
+ for (const path10 of continuations) {
1263
1263
  chargeBudget(state);
1264
- state.fallthrough.push(classify(path7, null, isAncestorOrSelf));
1264
+ state.fallthrough.push(classify(path10, null, isAncestorOrSelf));
1265
1265
  }
1266
1266
  return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
1267
1267
  }
@@ -1304,8 +1304,8 @@ function sharedGatingConditions(paths, readPredicate) {
1304
1304
  return [];
1305
1305
  }
1306
1306
  const shared = new Set(first.map(keyOf2));
1307
- for (const path7 of rest) {
1308
- const here = new Set(path7.map(keyOf2));
1307
+ for (const path10 of rest) {
1308
+ const here = new Set(path10.map(keyOf2));
1309
1309
  for (const value of shared) {
1310
1310
  if (!here.has(value)) {
1311
1311
  shared.delete(value);
@@ -1571,17 +1571,179 @@ function quoted(text, key) {
1571
1571
  }
1572
1572
 
1573
1573
  // src/adapterStamp.ts
1574
+ import { createHash as createHash2 } from "crypto";
1575
+ import fs3 from "fs";
1576
+ import path5 from "path";
1577
+ import { fileURLToPath } from "url";
1578
+
1579
+ // src/codeHash.ts
1574
1580
  import { createHash } from "crypto";
1575
1581
  import fs2 from "fs";
1576
1582
  import { createRequire } from "module";
1577
1583
  import path4 from "path";
1578
- import { fileURLToPath } from "url";
1579
- var ANALYSIS_PACKAGES = [
1580
- "@suss/extractor",
1581
- "@suss/resolution",
1582
- "@suss/datalog",
1583
- "@suss/behavioral-ir"
1584
- ];
1584
+ var SUSS_SCOPE = "@suss/";
1585
+ var IMPORT_FORMS = /\b(?:from|import)\s*\(?\s*["']([^"'\s]+)["']|require\s*\(\s*["']([^"'\s]+)["']/g;
1586
+ var CONDITIONS = {
1587
+ import: /* @__PURE__ */ new Set(["import", "node", "default"]),
1588
+ require: /* @__PURE__ */ new Set(["require", "node", "default"])
1589
+ };
1590
+ function createCodeHasher() {
1591
+ const read = /* @__PURE__ */ new Map();
1592
+ function readOnce(file) {
1593
+ const known = read.get(file);
1594
+ if (known !== void 0) {
1595
+ return known;
1596
+ }
1597
+ const found = readCodeFile(file);
1598
+ read.set(file, found);
1599
+ return found;
1600
+ }
1601
+ function codeFiles(entryFile) {
1602
+ const entry = realFile(entryFile);
1603
+ if (entry === null || readOnce(entry) === null) {
1604
+ return [];
1605
+ }
1606
+ const found = [entry];
1607
+ const seen = new Set(found);
1608
+ for (let at = 0; at < found.length; at++) {
1609
+ for (const next of readOnce(found[at])?.imports ?? []) {
1610
+ if (seen.has(next)) {
1611
+ continue;
1612
+ }
1613
+ seen.add(next);
1614
+ if (readOnce(next) !== null) {
1615
+ found.push(next);
1616
+ }
1617
+ }
1618
+ }
1619
+ return found;
1620
+ }
1621
+ function codeHash(entryFile) {
1622
+ const files = codeFiles(entryFile);
1623
+ if (files.length === 0) {
1624
+ return "";
1625
+ }
1626
+ const hash = createHash("sha256");
1627
+ for (const file of files) {
1628
+ hash.update(read.get(file)?.digest ?? "");
1629
+ }
1630
+ return hash.digest("hex").slice(0, 16);
1631
+ }
1632
+ return { codeFiles, codeHash };
1633
+ }
1634
+ var processCodeHasher = createCodeHasher();
1635
+ function readCodeFile(file) {
1636
+ let text;
1637
+ try {
1638
+ text = fs2.readFileSync(file);
1639
+ } catch {
1640
+ return null;
1641
+ }
1642
+ return {
1643
+ digest: createHash("sha256").update(text).digest("hex"),
1644
+ imports: importsOf(file, text.toString("utf8"))
1645
+ };
1646
+ }
1647
+ function importsOf(file, text) {
1648
+ const asked = /* @__PURE__ */ new Set();
1649
+ const found = [];
1650
+ for (const match of text.matchAll(IMPORT_FORMS)) {
1651
+ const kind = match[1] === void 0 ? "require" : "import";
1652
+ const specifier = match[1] ?? match[2] ?? "";
1653
+ if (asked.has(`${kind} ${specifier}`)) {
1654
+ continue;
1655
+ }
1656
+ asked.add(`${kind} ${specifier}`);
1657
+ const target = resolveImport(specifier, file, kind);
1658
+ if (target !== null) {
1659
+ found.push(target);
1660
+ }
1661
+ }
1662
+ return found;
1663
+ }
1664
+ function resolveImport(specifier, fromFile, kind) {
1665
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
1666
+ return realFile(path4.resolve(path4.dirname(fromFile), specifier));
1667
+ }
1668
+ if (specifier.startsWith(SUSS_SCOPE)) {
1669
+ return resolvePackageImport(specifier, fromFile, kind);
1670
+ }
1671
+ return null;
1672
+ }
1673
+ function resolvePackageImport(specifier, fromFile, kind) {
1674
+ const [scope = "", name = "", ...rest] = specifier.split("/");
1675
+ const packageName = `${scope}/${name}`;
1676
+ const subpath = rest.length === 0 ? "." : `./${rest.join("/")}`;
1677
+ const lookIn = createRequire(fromFile).resolve.paths(packageName) ?? [];
1678
+ for (const dir of lookIn) {
1679
+ const packageDir = path4.join(dir, packageName);
1680
+ const manifest = readManifest(packageDir);
1681
+ if (manifest === null) {
1682
+ continue;
1683
+ }
1684
+ const target = exportTarget(manifest, subpath, CONDITIONS[kind]);
1685
+ return target === null ? null : realFile(path4.join(packageDir, target));
1686
+ }
1687
+ return null;
1688
+ }
1689
+ function readManifest(packageDir) {
1690
+ try {
1691
+ return JSON.parse(
1692
+ fs2.readFileSync(path4.join(packageDir, "package.json"), "utf8")
1693
+ );
1694
+ } catch {
1695
+ return null;
1696
+ }
1697
+ }
1698
+ function exportTarget(manifest, subpath, conditions) {
1699
+ if (manifest.exports === void 0) {
1700
+ return subpath === "." ? manifest.main ?? "index.js" : subpath;
1701
+ }
1702
+ return conditionalTarget(subpathEntry(manifest.exports, subpath), conditions);
1703
+ }
1704
+ function subpathEntry(exportsField, subpath) {
1705
+ const isSubpathMap = typeof exportsField === "object" && exportsField !== null && !Array.isArray(exportsField) && Object.keys(exportsField).some((key) => key.startsWith("."));
1706
+ if (isSubpathMap) {
1707
+ return exportsField[subpath];
1708
+ }
1709
+ return subpath === "." ? exportsField : void 0;
1710
+ }
1711
+ function conditionalTarget(value, conditions) {
1712
+ if (typeof value === "string") {
1713
+ return value;
1714
+ }
1715
+ if (Array.isArray(value)) {
1716
+ for (const each of value) {
1717
+ const found = conditionalTarget(each, conditions);
1718
+ if (found !== null) {
1719
+ return found;
1720
+ }
1721
+ }
1722
+ return null;
1723
+ }
1724
+ if (value === null || typeof value !== "object") {
1725
+ return null;
1726
+ }
1727
+ for (const [condition, target] of Object.entries(value)) {
1728
+ if (!conditions.has(condition)) {
1729
+ continue;
1730
+ }
1731
+ const found = conditionalTarget(target, conditions);
1732
+ if (found !== null) {
1733
+ return found;
1734
+ }
1735
+ }
1736
+ return null;
1737
+ }
1738
+ function realFile(file) {
1739
+ try {
1740
+ return fs2.realpathSync(file);
1741
+ } catch {
1742
+ return null;
1743
+ }
1744
+ }
1745
+
1746
+ // src/adapterStamp.ts
1585
1747
  var SOURCE_STAMP = { kind: "source" };
1586
1748
  function createAdapterStamp(config) {
1587
1749
  let cachedCodeStamp = null;
@@ -1616,19 +1778,19 @@ function createAdapterStamp(config) {
1616
1778
  function readAdapterCodeStamp(moduleUrl) {
1617
1779
  try {
1618
1780
  const selfPath = fileURLToPath(moduleUrl);
1619
- const hash = computeDistHashFrom(path4.dirname(selfPath));
1781
+ const hash = computeDistHashFrom(path5.dirname(selfPath), processCodeHasher);
1620
1782
  return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
1621
1783
  } catch {
1622
1784
  return SOURCE_STAMP;
1623
1785
  }
1624
1786
  }
1625
- function computeDistHashFrom(dir) {
1626
- const candidates = [path4.join(dir, "index.js"), path4.join(dir, "index.cjs")];
1787
+ function computeDistHashFrom(dir, hasher = createCodeHasher()) {
1788
+ const candidates = [path5.join(dir, "index.js"), path5.join(dir, "index.cjs")];
1627
1789
  for (const candidate of candidates) {
1628
- if (!fs2.existsSync(candidate)) {
1790
+ if (!fs3.existsSync(candidate)) {
1629
1791
  continue;
1630
1792
  }
1631
- return computeContentHash([candidate, ...analysisBundles()]);
1793
+ return hasher.codeHash(candidate);
1632
1794
  }
1633
1795
  return "";
1634
1796
  }
@@ -1636,10 +1798,10 @@ function computeContentHash(paths) {
1636
1798
  if (paths.length === 0) {
1637
1799
  return "";
1638
1800
  }
1639
- const hash = createHash("sha256");
1801
+ const hash = createHash2("sha256");
1640
1802
  for (const file of paths) {
1641
1803
  try {
1642
- hash.update(fs2.readFileSync(file));
1804
+ hash.update(fs3.readFileSync(file));
1643
1805
  } catch {
1644
1806
  return "";
1645
1807
  }
@@ -1650,12 +1812,12 @@ function projectFileStamp(paths) {
1650
1812
  if (paths.length === 0) {
1651
1813
  return "none";
1652
1814
  }
1653
- const hash = createHash("sha256");
1815
+ const hash = createHash2("sha256");
1654
1816
  for (const file of [...paths].sort()) {
1655
1817
  hash.update(file);
1656
1818
  hash.update("\0");
1657
1819
  try {
1658
- hash.update(fs2.readFileSync(file));
1820
+ hash.update(fs3.readFileSync(file));
1659
1821
  } catch {
1660
1822
  hash.update("absent");
1661
1823
  }
@@ -1668,7 +1830,7 @@ function extractionConfigStamp(config) {
1668
1830
  ["includeReachable", optionalText(config.includeReachable)],
1669
1831
  ["workspaceRoot", config.workspaceRoot],
1670
1832
  ["projectRoot", config.projectRoot],
1671
- ["importRoots", config.importRoots?.join(path4.delimiter)],
1833
+ ["importRoots", config.importRoots?.join(path5.delimiter)],
1672
1834
  ["modules", modulesStamp(config.modules)],
1673
1835
  ["walkedDirectory", config.walkedDirectory]
1674
1836
  ];
@@ -1683,27 +1845,288 @@ function runDigest(packsDigest, packs, files) {
1683
1845
  const inputs = packs.flatMap((pack) => pack.discoveryInputs?.(files) ?? []);
1684
1846
  return `${packsDigest}|reads:${projectFileStamp(inputs)}`;
1685
1847
  }
1686
- function analysisBundles() {
1687
- const require2 = createRequire(import.meta.url);
1688
- const found = [];
1689
- for (const name of ANALYSIS_PACKAGES) {
1690
- try {
1691
- found.push(require2.resolve(name));
1692
- } catch {
1848
+
1849
+ // src/cache.ts
1850
+ import { createHash as createHash5 } from "crypto";
1851
+ import fs7 from "fs/promises";
1852
+ import path8 from "path";
1853
+ import { readJsonFile as readJsonFile3, writeDocumentFile as writeDocumentFile2 } from "@suss/behavioral-ir/files";
1854
+
1855
+ // src/cacheIndex.ts
1856
+ import { BOUNDARY_ROLE, displayLabel } from "@suss/behavioral-ir";
1857
+ import { readJsonFile, writeDocumentFile } from "@suss/behavioral-ir/files";
1858
+ function entriesOf(units) {
1859
+ const boundaries = [];
1860
+ const callees = [];
1861
+ units.forEach((summary, unit) => {
1862
+ const seen = /* @__PURE__ */ new Set();
1863
+ const add = (key, role) => {
1864
+ if (key !== null && !seen.has(`${role} ${key}`)) {
1865
+ seen.add(`${role} ${key}`);
1866
+ boundaries.push([key, unit, role]);
1867
+ }
1868
+ };
1869
+ const own = summary.identity?.boundaryBinding ?? null;
1870
+ if (own !== null) {
1871
+ add(
1872
+ keyOf3(own),
1873
+ BOUNDARY_ROLE[summary.kind] === "provider" ? "serves" : "calls"
1874
+ );
1875
+ }
1876
+ const spellings = /* @__PURE__ */ new Set();
1877
+ for (const transition of summary.transitions ?? []) {
1878
+ for (const effect of transition.effects ?? []) {
1879
+ if (effect.type === "interaction") {
1880
+ add(keyOf3(effect.binding), "calls");
1881
+ }
1882
+ if (effect.type === "invocation") {
1883
+ spellings.add(effect.callee);
1884
+ }
1885
+ }
1886
+ }
1887
+ for (const spelling of spellings) {
1888
+ callees.push([spelling, unit]);
1889
+ }
1890
+ });
1891
+ return { boundaries, callees };
1892
+ }
1893
+ function indexOf(shards) {
1894
+ const boundaries = {};
1895
+ const callees = {};
1896
+ shards.forEach(({ entries }, file) => {
1897
+ for (const [key, unit, role] of entries.boundaries) {
1898
+ listAt(boundaries, key).push([file, unit, role]);
1899
+ }
1900
+ for (const [spelling, unit] of entries.callees) {
1901
+ listAt(callees, spelling).push([file, unit]);
1902
+ }
1903
+ });
1904
+ return {
1905
+ files: shards.map(({ file, id }) => [file, id]),
1906
+ boundaries,
1907
+ callees
1908
+ };
1909
+ }
1910
+ function listAt(record, key) {
1911
+ const existing = Object.hasOwn(record, key) ? record[key] : void 0;
1912
+ if (existing !== void 0) {
1913
+ return existing;
1914
+ }
1915
+ const created = [];
1916
+ record[key] = created;
1917
+ return created;
1918
+ }
1919
+ function entriesByShard(index) {
1920
+ const byShard = /* @__PURE__ */ new Map();
1921
+ const at = index.files.map(([, id]) => {
1922
+ const entries = { boundaries: [], callees: [] };
1923
+ byShard.set(id, entries);
1924
+ return entries;
1925
+ });
1926
+ for (const [key, units] of Object.entries(index.boundaries)) {
1927
+ for (const [file, unit, role] of units) {
1928
+ at[file]?.boundaries.push([key, unit, role]);
1693
1929
  }
1694
1930
  }
1695
- return found;
1931
+ for (const [spelling, units] of Object.entries(index.callees)) {
1932
+ for (const [file, unit] of units) {
1933
+ at[file]?.callees.push([spelling, unit]);
1934
+ }
1935
+ }
1936
+ return byShard;
1937
+ }
1938
+ function readStoreIndex(file) {
1939
+ try {
1940
+ const index = readJsonFile(file);
1941
+ return Array.isArray(index.files) ? index : null;
1942
+ } catch {
1943
+ return null;
1944
+ }
1945
+ }
1946
+ function writeStoreIndex(file, index) {
1947
+ writeDocumentFile(file, index);
1948
+ }
1949
+ function keyOf3(binding) {
1950
+ try {
1951
+ return displayLabel(binding);
1952
+ } catch {
1953
+ return null;
1954
+ }
1696
1955
  }
1697
1956
 
1698
- // src/cache.ts
1957
+ // src/cacheShards.ts
1699
1958
  import { createHash as createHash3 } from "crypto";
1700
- import fs5 from "fs/promises";
1959
+ import fs4 from "fs";
1701
1960
  import path6 from "path";
1702
- import { readJsonFile, writeDocumentFile } from "@suss/behavioral-ir/files";
1961
+ import {
1962
+ documentPieces,
1963
+ readJsonFile as readJsonFile2,
1964
+ writeFileAtomically
1965
+ } from "@suss/behavioral-ir/files";
1966
+ var SHARDS_DIR = "shards";
1967
+ var RECORDS_DIR = "records";
1968
+ var STORED_ID = /^[0-9a-f]{32}\.json$/;
1969
+ function writeShard(entryDir, content) {
1970
+ return writeOnce(path6.join(entryDir, SHARDS_DIR), interned(content));
1971
+ }
1972
+ function readShard(entryDir, id, file) {
1973
+ const stored = readStored(path6.join(entryDir, SHARDS_DIR), id);
1974
+ if (stored?.file !== file || !Array.isArray(stored.units)) {
1975
+ return null;
1976
+ }
1977
+ const take = takerFor(stored.intern ?? []);
1978
+ return {
1979
+ file: stored.file,
1980
+ units: stored.units.map((unit) => expanded(unit, take))
1981
+ };
1982
+ }
1983
+ function writeRecord(entryDir, meta) {
1984
+ return writeOnce(path6.join(entryDir, RECORDS_DIR), { meta });
1985
+ }
1986
+ function readRecord(entryDir, id) {
1987
+ const stored = readStored(path6.join(entryDir, RECORDS_DIR), id);
1988
+ return typeof stored === "object" && stored !== null ? stored : null;
1989
+ }
1990
+ function recordReader(entryDir) {
1991
+ const opened = /* @__PURE__ */ new Map();
1992
+ return (id) => {
1993
+ if (!opened.has(id)) {
1994
+ const record2 = readRecord(entryDir, id);
1995
+ opened.set(id, record2);
1996
+ return record2;
1997
+ }
1998
+ const record = opened.get(id) ?? null;
1999
+ return record === null ? null : copyOfJson(record);
2000
+ };
2001
+ }
2002
+ function removeStoredOutside(entryDir, keep) {
2003
+ for (const kind of [SHARDS_DIR, RECORDS_DIR]) {
2004
+ const root = path6.join(entryDir, kind);
2005
+ for (const prefix of namesIn(root)) {
2006
+ const dir = path6.join(root, prefix);
2007
+ for (const name of namesIn(dir)) {
2008
+ if (STORED_ID.test(name) && !keep.has(name.slice(0, -".json".length))) {
2009
+ fs4.rmSync(path6.join(dir, name), { force: true });
2010
+ }
2011
+ }
2012
+ }
2013
+ }
2014
+ }
2015
+ function namesIn(dir) {
2016
+ try {
2017
+ return fs4.readdirSync(dir);
2018
+ } catch {
2019
+ return [];
2020
+ }
2021
+ }
2022
+ function writeOnce(root, value) {
2023
+ const pieces = [...documentPieces(value)];
2024
+ const hash = createHash3("sha256");
2025
+ for (const piece of pieces) {
2026
+ hash.update(piece);
2027
+ }
2028
+ const id = hash.digest("hex").slice(0, 32);
2029
+ const file = storedPath(root, id);
2030
+ if (!fs4.existsSync(file)) {
2031
+ writeFileAtomically(file, pieces);
2032
+ }
2033
+ return id;
2034
+ }
2035
+ function readStored(root, id) {
2036
+ try {
2037
+ return readJsonFile2(storedPath(root, id));
2038
+ } catch {
2039
+ return void 0;
2040
+ }
2041
+ }
2042
+ function storedPath(root, id) {
2043
+ return path6.join(root, id.slice(0, 2), `${id}.json`);
2044
+ }
2045
+ function interned(content) {
2046
+ const intern = [];
2047
+ const byText = /* @__PURE__ */ new Map();
2048
+ const byObject = /* @__PURE__ */ new Map();
2049
+ const positionOf = (value) => {
2050
+ const known = byObject.get(value);
2051
+ if (known !== void 0) {
2052
+ return known;
2053
+ }
2054
+ const text = JSON.stringify(value) ?? "null";
2055
+ let position = byText.get(text);
2056
+ if (position === void 0) {
2057
+ position = intern.length;
2058
+ intern.push(value);
2059
+ byText.set(text, position);
2060
+ }
2061
+ if (typeof value === "object" && value !== null) {
2062
+ byObject.set(value, position);
2063
+ }
2064
+ return position;
2065
+ };
2066
+ const positions = (values) => Array.isArray(values) ? values.map(positionOf) : values;
2067
+ const units = content.units.map((summary) => ({
2068
+ ...summary,
2069
+ transitions: summary.transitions.map((transition) => ({
2070
+ ...transition,
2071
+ conditions: positions(transition.conditions),
2072
+ effects: positions(transition.effects)
2073
+ })),
2074
+ gaps: summary.gaps.map((gap) => ({
2075
+ ...gap,
2076
+ conditions: positions(gap.conditions)
2077
+ }))
2078
+ }));
2079
+ return {
2080
+ file: content.file,
2081
+ intern,
2082
+ units
2083
+ };
2084
+ }
2085
+ function expanded(unit, take) {
2086
+ const values = (positions) => Array.isArray(positions) ? positions.map((at) => take(at)) : positions;
2087
+ const transitions = Array.isArray(unit.transitions) ? unit.transitions : [];
2088
+ const gaps = Array.isArray(unit.gaps) ? unit.gaps : [];
2089
+ return {
2090
+ ...unit,
2091
+ transitions: transitions.map((transition) => ({
2092
+ ...transition,
2093
+ conditions: values(transition.conditions),
2094
+ effects: values(transition.effects)
2095
+ })),
2096
+ gaps: gaps.map((gap) => ({
2097
+ ...gap,
2098
+ conditions: values(gap.conditions)
2099
+ }))
2100
+ };
2101
+ }
2102
+ function takerFor(intern) {
2103
+ const handedOut = /* @__PURE__ */ new Set();
2104
+ return (position) => {
2105
+ const value = intern[position];
2106
+ if (!handedOut.has(position)) {
2107
+ handedOut.add(position);
2108
+ return value;
2109
+ }
2110
+ return copyOfJson(value);
2111
+ };
2112
+ }
2113
+ function copyOfJson(value) {
2114
+ if (Array.isArray(value)) {
2115
+ return value.map(copyOfJson);
2116
+ }
2117
+ if (typeof value === "object" && value !== null) {
2118
+ const copy = {};
2119
+ for (const key of Object.keys(value)) {
2120
+ copy[key] = copyOfJson(value[key]);
2121
+ }
2122
+ return copy;
2123
+ }
2124
+ return value;
2125
+ }
1703
2126
 
1704
2127
  // src/cacheStamps.ts
1705
- import { createHash as createHash2 } from "crypto";
1706
- import fs3 from "fs/promises";
2128
+ import { createHash as createHash4 } from "crypto";
2129
+ import fs5 from "fs/promises";
1707
2130
  var RECENT_CHANGE_MS = 2e3;
1708
2131
  function stampMayMissAWrite(stat, statAt) {
1709
2132
  return Math.max(stat.mtimeMs, stat.ctimeMs) > statAt - RECENT_CHANGE_MS;
@@ -1724,7 +2147,7 @@ async function startRun(files, configPath) {
1724
2147
  }
1725
2148
  async function statAtStart(filePath) {
1726
2149
  try {
1727
- const stat = await fs3.stat(filePath);
2150
+ const stat = await fs5.stat(filePath);
1728
2151
  return {
1729
2152
  stamp: { path: filePath, mtimeMs: stat.mtimeMs, size: stat.size },
1730
2153
  ctimeMs: stat.ctimeMs
@@ -1774,7 +2197,7 @@ async function recordedStamp(start, run, prior) {
1774
2197
  return start.stamp;
1775
2198
  }
1776
2199
  const filePath = start.stamp.path;
1777
- const now = await fs3.stat(filePath).catch(() => null);
2200
+ const now = await fs5.stat(filePath).catch(() => null);
1778
2201
  if (now === null || now.mtimeMs !== start.stamp.mtimeMs || now.size !== start.stamp.size || now.ctimeMs !== start.ctimeMs) {
1779
2202
  return null;
1780
2203
  }
@@ -1791,8 +2214,8 @@ function reusableHash(prior, stamp) {
1791
2214
  }
1792
2215
  async function hashFile(filePath) {
1793
2216
  try {
1794
- const content = await fs3.readFile(filePath);
1795
- return createHash2("sha256").update(content).digest("hex").slice(0, 16);
2217
+ const content = await fs5.readFile(filePath);
2218
+ return createHash4("sha256").update(content).digest("hex").slice(0, 16);
1796
2219
  } catch {
1797
2220
  return null;
1798
2221
  }
@@ -1808,21 +2231,29 @@ function fileStampEquals(a, b) {
1808
2231
  }
1809
2232
 
1810
2233
  // src/ignoredDirectory.ts
1811
- import fs4 from "fs";
1812
- import path5 from "path";
2234
+ import fs6 from "fs";
2235
+ import path7 from "path";
1813
2236
  function makeIgnoredDirectory(dir) {
1814
- fs4.mkdirSync(dir, { recursive: true });
1815
- const ignore = path5.join(dir, ".gitignore");
1816
- if (!fs4.existsSync(ignore)) {
1817
- fs4.writeFileSync(ignore, "*\n");
2237
+ fs6.mkdirSync(dir, { recursive: true });
2238
+ const ignore = path7.join(dir, ".gitignore");
2239
+ if (!fs6.existsSync(ignore)) {
2240
+ fs6.writeFileSync(ignore, "*\n");
1818
2241
  }
1819
2242
  }
1820
2243
 
1821
2244
  // src/cache.ts
1822
- var SCHEMA_VERSION = "9";
2245
+ var SCHEMA_VERSION = "10";
2246
+ var MANIFEST = "manifest.json";
2247
+ var INDEX = "index.json";
1823
2248
  var MAX_ENTRIES = 2;
1824
2249
  var ENTRY_PREFIX = "key-";
1825
2250
  var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
2251
+ function ownersWhereWritten(summaries, walkedFileOf) {
2252
+ return summaries.map((summary) => {
2253
+ const walked = walkedFileOf(summary.location.file);
2254
+ return walked === void 0 ? [] : [walked];
2255
+ });
2256
+ }
1826
2257
  function createCacheLayer(cacheDir) {
1827
2258
  if (cacheDir === null) {
1828
2259
  return {
@@ -1842,7 +2273,7 @@ function createCacheLayer(cacheDir) {
1842
2273
  const lookupWith = async (input, run) => {
1843
2274
  const entryDir = entryDirFor(cacheDir, input);
1844
2275
  const manifest = await manifests.readAtRunStart(
1845
- path6.join(entryDir, "manifest.json")
2276
+ path8.join(entryDir, MANIFEST)
1846
2277
  );
1847
2278
  if (manifest === null) {
1848
2279
  return missDiag(await describeAbsentEntry(cacheDir));
@@ -1857,11 +2288,15 @@ function createCacheLayer(cacheDir) {
1857
2288
  if (!await filesStillMatch(manifest.files, run)) {
1858
2289
  return missDiag("files-changed");
1859
2290
  }
2291
+ const summaries = storedSummaries(entryDir, manifest);
2292
+ if (summaries === null) {
2293
+ return missDiag("no-manifest");
2294
+ }
1860
2295
  await markUsed(entryDir);
1861
2296
  manifests.forget();
1862
2297
  return {
1863
2298
  kind: "hit",
1864
- summaries: manifest.summaries,
2299
+ summaries,
1865
2300
  diagnostic: { kind: "hit" }
1866
2301
  };
1867
2302
  };
@@ -1881,10 +2316,8 @@ function createCacheLayer(cacheDir) {
1881
2316
  },
1882
2317
  async plan(input) {
1883
2318
  const entryDir = entryDirFor(cacheDir, input);
1884
- const manifest = await manifests.readAgain(
1885
- path6.join(entryDir, "manifest.json")
1886
- );
1887
- if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
2319
+ const manifest = await manifests.readAgain(path8.join(entryDir, MANIFEST));
2320
+ if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0) {
1888
2321
  return null;
1889
2322
  }
1890
2323
  const run = await runFor(input);
@@ -1895,14 +2328,17 @@ function createCacheLayer(cacheDir) {
1895
2328
  )) {
1896
2329
  return null;
1897
2330
  }
2331
+ const summaries = storedSummaries(entryDir, manifest);
2332
+ const records = storedRecords(entryDir, manifest);
2333
+ if (summaries === null || records === null) {
2334
+ return null;
2335
+ }
1898
2336
  await markUsed(entryDir);
1899
- return buildPlan(manifest, run);
2337
+ return buildPlan(manifest, records, summaries, run);
1900
2338
  },
1901
2339
  async write(input, summaries, attribution) {
1902
2340
  const entryDir = entryDirFor(cacheDir, input);
1903
- const previous = await manifests.readAgain(
1904
- path6.join(entryDir, "manifest.json")
1905
- );
2341
+ const previous = await manifests.readAgain(path8.join(entryDir, MANIFEST));
1906
2342
  manifests.forget();
1907
2343
  const known = runs.get(input);
1908
2344
  const run = known ?? await runFor(input);
@@ -1918,55 +2354,119 @@ function createCacheLayer(cacheDir) {
1918
2354
  )
1919
2355
  );
1920
2356
  const files = recorded.filter((stamp) => stamp !== null);
2357
+ const encoded = attribution === void 0 ? null : encodeAttribution(
2358
+ attribution,
2359
+ (meta) => writeRecord(entryDir, meta)
2360
+ );
2361
+ makeIgnoredDirectory(cacheDir);
2362
+ const shards = writeShards(entryDir, summaries, encoded);
1921
2363
  const manifest = {
1922
2364
  schemaVersion: SCHEMA_VERSION,
1923
2365
  adapterPacksDigest: input.adapterPacksDigest,
1924
2366
  configStamp,
1925
2367
  files,
1926
- summaries,
1927
- ...attribution === void 0 ? {} : encodeAttribution(attribution)
2368
+ summaryCount: summaries.length,
2369
+ shards,
2370
+ ...encoded === null ? {} : encoded.stored
1928
2371
  };
1929
- makeIgnoredDirectory(cacheDir);
1930
- writeDocumentFile(path6.join(entryDir, "manifest.json"), manifest);
2372
+ writeDocumentFile2(path8.join(entryDir, MANIFEST), manifest);
2373
+ removeStoredOutside(
2374
+ entryDir,
2375
+ /* @__PURE__ */ new Set([...storedIds(manifest), ...storedIds(previous)])
2376
+ );
1931
2377
  await evictOldEntries(cacheDir, entryDir);
1932
2378
  }
1933
2379
  };
1934
2380
  }
1935
- async function buildPlan(manifest, run) {
1936
- const stored = new Map(manifest.files.map((f) => [f.path, f]));
1937
- const current = new Map(run.files.map((f) => [f.stamp.path, f.stamp]));
1938
- const changed = /* @__PURE__ */ new Set();
1939
- const added = /* @__PURE__ */ new Set();
1940
- const removed = /* @__PURE__ */ new Set();
1941
- for (const p of stored.keys()) {
1942
- if (!current.has(p)) {
1943
- removed.add(p);
2381
+ function storedIds(manifest) {
2382
+ return [
2383
+ ...(manifest?.shards ?? []).map((shard) => shard.id),
2384
+ ...(manifest?.rootMeta ?? []).flatMap(
2385
+ (root) => root.record === void 0 ? [] : [root.record]
2386
+ )
2387
+ ];
2388
+ }
2389
+ function writeShards(entryDir, summaries, encoded) {
2390
+ const byFile = /* @__PURE__ */ new Map();
2391
+ summaries.forEach((summary, position) => {
2392
+ const file = summary.location?.file ?? "";
2393
+ const shard = byFile.get(file) ?? {
2394
+ content: { file, units: [] },
2395
+ order: [],
2396
+ owners: []
2397
+ };
2398
+ byFile.set(file, shard);
2399
+ shard.content.units.push(summary);
2400
+ shard.order.push(position);
2401
+ shard.owners.push(encoded?.owners[position] ?? []);
2402
+ });
2403
+ const indexFile = path8.join(entryDir, INDEX);
2404
+ const before = readStoreIndex(indexFile);
2405
+ const known = before === null ? /* @__PURE__ */ new Map() : entriesByShard(before);
2406
+ const sorted = [...byFile].sort(([a], [b]) => a < b ? -1 : 1);
2407
+ const written = sorted.map(([file, { content, order, owners }]) => {
2408
+ const id = writeShard(entryDir, content);
2409
+ return {
2410
+ shard: {
2411
+ file,
2412
+ id,
2413
+ order,
2414
+ ...encoded === null ? {} : { owners }
2415
+ },
2416
+ entries: known.get(id) ?? entriesOf(content.units)
2417
+ };
2418
+ });
2419
+ writeStoreIndex(
2420
+ indexFile,
2421
+ indexOf(
2422
+ written.map(({ shard, entries }) => ({
2423
+ file: shard.file,
2424
+ id: shard.id,
2425
+ entries
2426
+ }))
2427
+ )
2428
+ );
2429
+ return written.map(({ shard }) => shard);
2430
+ }
2431
+ function storedSummaries(entryDir, manifest) {
2432
+ const summaries = new Array(
2433
+ manifest.summaryCount
2434
+ );
2435
+ for (const shard of manifest.shards) {
2436
+ const content = readShard(entryDir, shard.id, shard.file);
2437
+ if (content === null || content.units.length !== shard.order.length) {
2438
+ return null;
1944
2439
  }
2440
+ shard.order.forEach((position, i) => {
2441
+ summaries[position] = content.units[i];
2442
+ });
1945
2443
  }
1946
- const toVerify = [];
1947
- for (const [p, stamp] of current) {
1948
- const before = stored.get(p);
1949
- if (before === void 0) {
1950
- changed.add(p);
1951
- added.add(p);
1952
- } else if (!fileStampEquals(before, stamp)) {
1953
- if (before.contentHash === void 0) {
1954
- changed.add(p);
1955
- } else {
1956
- toVerify.push(p);
1957
- }
1958
- } else if (before.verifyHash === true) {
1959
- toVerify.push(p);
2444
+ for (let position = 0; position < summaries.length; position++) {
2445
+ if (summaries[position] === void 0) {
2446
+ return null;
1960
2447
  }
1961
2448
  }
1962
- await Promise.all(
1963
- toVerify.map(async (p) => {
1964
- const hash = await hashOf(run, p);
1965
- if (hash === null || hash !== stored.get(p)?.contentHash) {
1966
- changed.add(p);
1967
- }
1968
- })
1969
- );
2449
+ return summaries;
2450
+ }
2451
+ function storedRecords(entryDir, manifest) {
2452
+ const records = /* @__PURE__ */ new Map();
2453
+ const roots = manifest.roots ?? [];
2454
+ const readRecord2 = recordReader(entryDir);
2455
+ for (const [i, root] of (manifest.rootMeta ?? []).entries()) {
2456
+ const rootPath = roots[i];
2457
+ if (rootPath === void 0 || root.record === void 0) {
2458
+ continue;
2459
+ }
2460
+ const record = readRecord2(root.record);
2461
+ if (record === null) {
2462
+ return null;
2463
+ }
2464
+ records.set(rootPath, record.meta);
2465
+ }
2466
+ return records;
2467
+ }
2468
+ async function buildPlan(manifest, records, stored, run) {
2469
+ const { changed, added, removed } = await changedSince(manifest, run);
1970
2470
  const roots = /* @__PURE__ */ new Map();
1971
2471
  const rootNames = manifest.roots ?? [];
1972
2472
  const depPaths = manifest.depPaths ?? [];
@@ -1976,23 +2476,24 @@ async function buildPlan(manifest, run) {
1976
2476
  });
1977
2477
  let rootsDeclined = 0;
1978
2478
  rootNames.forEach((rootPath, i) => {
1979
- const meta = manifest.rootMeta?.[i];
1980
- if (meta === void 0) {
2479
+ const record = manifest.rootMeta?.[i];
2480
+ if (record === void 0) {
1981
2481
  return;
1982
2482
  }
1983
- if (!meta.cacheable) {
2483
+ if (!record.cacheable) {
1984
2484
  rootsDeclined += 1;
1985
2485
  }
1986
2486
  roots.set(rootPath, {
1987
2487
  path: rootPath,
1988
- cacheable: meta.cacheable,
1989
- deps: pathsOf(meta.deps),
1990
- claims: meta.claims,
1991
- meta: meta.meta,
1992
- packs: meta.packs,
1993
- ...meta.imports === void 0 ? {} : { imports: pathsOf(meta.imports) }
2488
+ cacheable: record.cacheable,
2489
+ deps: pathsOf(record.deps),
2490
+ claims: record.claims,
2491
+ meta: records.get(rootPath),
2492
+ packs: record.packs,
2493
+ ...record.imports === void 0 ? {} : { imports: pathsOf(record.imports) }
1994
2494
  });
1995
2495
  });
2496
+ const current = new Set(run.files.map((f) => f.stamp.path));
1996
2497
  const unchanged = (filePath, deps) => current.has(filePath) && !changed.has(filePath) && !deps.some((d) => changed.has(d) || removed.has(d));
1997
2498
  const validRoots = /* @__PURE__ */ new Set();
1998
2499
  for (const [rootPath, record] of roots) {
@@ -2000,14 +2501,14 @@ async function buildPlan(manifest, run) {
2000
2501
  validRoots.add(rootPath);
2001
2502
  }
2002
2503
  }
2003
- const units = (manifest.units ?? []).flatMap((stored2) => {
2004
- const file = depPaths[stored2.file];
2504
+ const units = (manifest.units ?? []).flatMap((unit) => {
2505
+ const file = depPaths[unit.file];
2005
2506
  return file === void 0 ? [] : [
2006
2507
  {
2007
- key: stored2.key,
2508
+ key: unit.key,
2008
2509
  file,
2009
- deps: pathsOf(stored2.deps),
2010
- data: stored2.data
2510
+ deps: pathsOf(unit.deps),
2511
+ data: unit.data
2011
2512
  }
2012
2513
  ];
2013
2514
  });
@@ -2025,7 +2526,7 @@ async function buildPlan(manifest, run) {
2025
2526
  }
2026
2527
  }
2027
2528
  }
2028
- const owners = manifest.owners ?? [];
2529
+ const ownerPaths = ownersInRunOrder(manifest);
2029
2530
  return {
2030
2531
  changed,
2031
2532
  removed,
@@ -2037,36 +2538,81 @@ async function buildPlan(manifest, run) {
2037
2538
  reuse(valid) {
2038
2539
  const summaries = [];
2039
2540
  const reusedOwners = [];
2040
- manifest.summaries.forEach((summary, i) => {
2041
- const ownerPaths = (owners[i] ?? []).flatMap((o) => {
2042
- const p = rootNames[o];
2043
- return p === void 0 ? [] : [p];
2044
- });
2045
- if (ownerPaths.some((p) => valid.has(p))) {
2541
+ stored.forEach((summary, i) => {
2542
+ const owners = ownerPaths[i] ?? [];
2543
+ if (owners.some((p) => valid.has(p))) {
2046
2544
  summaries.push(summary);
2047
- reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
2545
+ reusedOwners.push(owners.filter((p) => valid.has(p)));
2048
2546
  }
2049
2547
  });
2050
2548
  return { summaries, owners: reusedOwners };
2051
2549
  },
2052
2550
  allSummaries() {
2053
- return manifest.summaries;
2551
+ return stored;
2054
2552
  },
2055
2553
  attribution() {
2056
2554
  return {
2057
2555
  roots: [...roots.values()],
2058
- owners: owners.map(
2059
- (ownerIds) => ownerIds.flatMap((o) => {
2060
- const p = rootNames[o];
2061
- return p === void 0 ? [] : [p];
2062
- })
2063
- ),
2556
+ owners: ownerPaths,
2064
2557
  units
2065
2558
  };
2066
2559
  }
2067
2560
  };
2068
2561
  }
2069
- function encodeAttribution(attribution) {
2562
+ function ownersInRunOrder(manifest) {
2563
+ const rootNames = manifest.roots ?? [];
2564
+ const owners = Array.from(
2565
+ { length: manifest.summaryCount },
2566
+ () => []
2567
+ );
2568
+ for (const shard of manifest.shards ?? []) {
2569
+ shard.order.forEach((position, i) => {
2570
+ owners[position] = (shard.owners?.[i] ?? []).flatMap((o) => {
2571
+ const p = rootNames[o];
2572
+ return p === void 0 ? [] : [p];
2573
+ });
2574
+ });
2575
+ }
2576
+ return owners;
2577
+ }
2578
+ async function changedSince(manifest, run) {
2579
+ const stored = new Map(manifest.files.map((f) => [f.path, f]));
2580
+ const current = new Map(run.files.map((f) => [f.stamp.path, f.stamp]));
2581
+ const changed = /* @__PURE__ */ new Set();
2582
+ const added = /* @__PURE__ */ new Set();
2583
+ const removed = /* @__PURE__ */ new Set();
2584
+ for (const p of stored.keys()) {
2585
+ if (!current.has(p)) {
2586
+ removed.add(p);
2587
+ }
2588
+ }
2589
+ const toVerify = [];
2590
+ for (const [p, stamp] of current) {
2591
+ const before = stored.get(p);
2592
+ if (before === void 0) {
2593
+ changed.add(p);
2594
+ added.add(p);
2595
+ } else if (!fileStampEquals(before, stamp)) {
2596
+ if (before.contentHash === void 0) {
2597
+ changed.add(p);
2598
+ } else {
2599
+ toVerify.push(p);
2600
+ }
2601
+ } else if (before.verifyHash === true) {
2602
+ toVerify.push(p);
2603
+ }
2604
+ }
2605
+ await Promise.all(
2606
+ toVerify.map(async (p) => {
2607
+ const hash = await hashOf(run, p);
2608
+ if (hash === null || hash !== stored.get(p)?.contentHash) {
2609
+ changed.add(p);
2610
+ }
2611
+ })
2612
+ );
2613
+ return { changed, added, removed };
2614
+ }
2615
+ function encodeAttribution(attribution, storeRecord) {
2070
2616
  const roots = attribution.roots.map((r) => r.path);
2071
2617
  const rootIndex = new Map(roots.map((p, i) => [p, i]));
2072
2618
  const depIndex = /* @__PURE__ */ new Map();
@@ -2082,10 +2628,10 @@ function encodeAttribution(attribution) {
2082
2628
  return id;
2083
2629
  };
2084
2630
  const rootMeta = attribution.roots.map((r) => ({
2631
+ ...r.meta === void 0 ? {} : { record: storeRecord(r.meta) },
2085
2632
  cacheable: r.cacheable,
2086
2633
  deps: r.deps.map(depIdOf),
2087
2634
  claims: r.claims,
2088
- meta: r.meta,
2089
2635
  packs: r.packs,
2090
2636
  ...r.imports === void 0 ? {} : { imports: r.imports.map(depIdOf) }
2091
2637
  }));
@@ -2102,16 +2648,18 @@ function encodeAttribution(attribution) {
2102
2648
  data: unit.data
2103
2649
  }));
2104
2650
  return {
2105
- roots,
2106
- rootMeta,
2107
- depPaths,
2108
- owners,
2109
- ...units === void 0 ? {} : { units }
2651
+ stored: {
2652
+ roots,
2653
+ rootMeta,
2654
+ depPaths,
2655
+ ...units === void 0 ? {} : { units }
2656
+ },
2657
+ owners
2110
2658
  };
2111
2659
  }
2112
2660
  async function describeAbsentEntry(cacheDir) {
2113
2661
  try {
2114
- const entries = await fs5.readdir(cacheDir, { withFileTypes: true });
2662
+ const entries = await fs7.readdir(cacheDir, { withFileTypes: true });
2115
2663
  return entries.some((e) => e.isDirectory() && isEntryDir(e.name)) ? "key-changed" : "no-manifest";
2116
2664
  } catch {
2117
2665
  return "no-manifest";
@@ -2123,8 +2671,8 @@ function entryDirFor(cacheDir, input) {
2123
2671
  input.adapterPacksDigest,
2124
2672
  input.configPath ?? ""
2125
2673
  ].join(" ");
2126
- const name = createHash3("sha256").update(key).digest("hex").slice(0, 16);
2127
- return path6.join(cacheDir, `${ENTRY_PREFIX}${name}`);
2674
+ const name = createHash5("sha256").update(key).digest("hex").slice(0, 16);
2675
+ return path8.join(cacheDir, `${ENTRY_PREFIX}${name}`);
2128
2676
  }
2129
2677
  function isEntryDir(name) {
2130
2678
  return ENTRY_DIR_NAME.test(name);
@@ -2132,24 +2680,24 @@ function isEntryDir(name) {
2132
2680
  async function markUsed(entryDir) {
2133
2681
  const now = /* @__PURE__ */ new Date();
2134
2682
  try {
2135
- await fs5.utimes(entryDir, now, now);
2683
+ await fs7.utimes(entryDir, now, now);
2136
2684
  } catch {
2137
2685
  }
2138
2686
  }
2139
2687
  async function evictOldEntries(cacheDir, keepDir) {
2140
2688
  try {
2141
- await fs5.rm(path6.join(cacheDir, "manifest.json"), { force: true });
2142
- const entries = await fs5.readdir(cacheDir, { withFileTypes: true });
2689
+ await fs7.rm(path8.join(cacheDir, "manifest.json"), { force: true });
2690
+ const entries = await fs7.readdir(cacheDir, { withFileTypes: true });
2143
2691
  const dirs = await Promise.all(
2144
2692
  entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
2145
- const dir = path6.join(cacheDir, e.name);
2146
- const stat = await fs5.stat(dir);
2693
+ const dir = path8.join(cacheDir, e.name);
2694
+ const stat = await fs7.stat(dir);
2147
2695
  return { dir, mtimeMs: stat.mtimeMs };
2148
2696
  })
2149
2697
  );
2150
2698
  const doomed = dirs.filter((d) => d.dir !== keepDir).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(MAX_ENTRIES - 1);
2151
2699
  await Promise.all(
2152
- doomed.map((d) => fs5.rm(d.dir, { recursive: true, force: true }))
2700
+ doomed.map((d) => fs7.rm(d.dir, { recursive: true, force: true }))
2153
2701
  );
2154
2702
  } catch {
2155
2703
  }
@@ -2164,7 +2712,7 @@ var ManifestReads = class {
2164
2712
  last = null;
2165
2713
  async readAtRunStart(manifestPath) {
2166
2714
  const stamp = await manifestStamp(manifestPath);
2167
- const manifest = await readManifest(manifestPath);
2715
+ const manifest = await readManifest2(manifestPath);
2168
2716
  this.last = { path: manifestPath, stamp, manifest };
2169
2717
  return manifest;
2170
2718
  }
@@ -2181,22 +2729,60 @@ var ManifestReads = class {
2181
2729
  };
2182
2730
  async function manifestStamp(manifestPath) {
2183
2731
  try {
2184
- const stat = await fs5.stat(manifestPath);
2732
+ const stat = await fs7.stat(manifestPath);
2185
2733
  return `${stat.ino}:${stat.mtimeMs}:${stat.size}`;
2186
2734
  } catch {
2187
2735
  return null;
2188
2736
  }
2189
2737
  }
2190
- async function readManifest(manifestPath) {
2738
+ async function readManifest2(manifestPath) {
2191
2739
  try {
2192
- return readJsonFile(manifestPath);
2740
+ const manifest = readJsonFile3(manifestPath);
2741
+ return Array.isArray(manifest.shards) ? manifest : null;
2193
2742
  } catch {
2194
2743
  return null;
2195
2744
  }
2196
2745
  }
2197
2746
 
2747
+ // src/cacheLocation.ts
2748
+ import { createHash as createHash6 } from "crypto";
2749
+ import fs8 from "fs";
2750
+ import path9 from "path";
2751
+ var CACHE_DIR_ENV = "SUSS_CACHE_DIR";
2752
+ function chosenCacheRoot() {
2753
+ const chosen = process.env[CACHE_DIR_ENV];
2754
+ if (chosen === void 0 || chosen === "") {
2755
+ return null;
2756
+ }
2757
+ return path9.resolve(chosen);
2758
+ }
2759
+ function realPathOrSelf(dir) {
2760
+ try {
2761
+ return fs8.realpathSync(dir);
2762
+ } catch {
2763
+ return dir;
2764
+ }
2765
+ }
2766
+ function projectKey(projectDir) {
2767
+ return createHash6("sha256").update(realPathOrSelf(path9.resolve(projectDir))).digest("hex").slice(0, 16);
2768
+ }
2769
+ function extractionCacheDirFor(projectDir) {
2770
+ const root = chosenCacheRoot();
2771
+ if (root === null) {
2772
+ return path9.join(projectDir, ".suss", "cache");
2773
+ }
2774
+ return path9.join(root, "cache", projectKey(projectDir));
2775
+ }
2776
+ function liveRecordFor(projectDir) {
2777
+ const root = chosenCacheRoot();
2778
+ if (root === null) {
2779
+ return path9.join(projectDir, ".suss", "live", "server.json");
2780
+ }
2781
+ return path9.join(root, "live", `${projectKey(projectDir)}.json`);
2782
+ }
2783
+
2198
2784
  // src/extractionReport.ts
2199
- import { BOUNDARY_ROLE } from "@suss/behavioral-ir";
2785
+ import { BOUNDARY_ROLE as BOUNDARY_ROLE2 } from "@suss/behavioral-ir";
2200
2786
  var emptyTally = () => ({
2201
2787
  candidateFiles: 0,
2202
2788
  unitsDiscovered: 0,
@@ -2246,7 +2832,7 @@ function summaryCountsByPack(summaries) {
2246
2832
  }
2247
2833
  const entry = counts.get(recognition) ?? emptyCounts();
2248
2834
  entry.bound += 1;
2249
- if (BOUNDARY_ROLE[summary.kind] === "provider") {
2835
+ if (BOUNDARY_ROLE2[summary.kind] === "provider") {
2250
2836
  entry.providers += 1;
2251
2837
  }
2252
2838
  if (summary.transitions.length > 0) {
@@ -2471,6 +3057,17 @@ function undocumentedDeclarations(gradients) {
2471
3057
  detail: gradient.withoutExample.join(", ")
2472
3058
  }));
2473
3059
  }
3060
+ function packConfigCheck(needs) {
3061
+ return {
3062
+ name: "every pack that needs an option was given it",
3063
+ code: "no-config",
3064
+ audience: "run",
3065
+ violations: needs.map((need) => ({
3066
+ label: need.pack,
3067
+ 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>.`
3068
+ }))
3069
+ };
3070
+ }
2474
3071
  function evaluatePackHealth(report) {
2475
3072
  const gradients = packGradients(report);
2476
3073
  return [
@@ -2623,7 +3220,7 @@ function makeTransitionId(functionName, branch) {
2623
3220
  const { terminal } = branch;
2624
3221
  const statusKey = terminal.statusCode === null ? "none" : terminal.statusCode.type === "literal" ? String(terminal.statusCode.value) : `dyn:${terminal.statusCode.sourceText}`;
2625
3222
  const conditionSig = branch.conditions.map((c) => `${c.polarity}:${c.sourceText}`).join(";");
2626
- const conditionHash = createHash4("sha1").update(conditionSig).digest("hex").slice(0, 7);
3223
+ const conditionHash = createHash7("sha1").update(conditionSig).digest("hex").slice(0, 7);
2627
3224
  return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
2628
3225
  }
2629
3226
  function collapseReading(table, reading) {
@@ -3077,6 +3674,7 @@ function paramToInput(param) {
3077
3674
  };
3078
3675
  }
3079
3676
  export {
3677
+ CACHE_DIR_ENV,
3080
3678
  EnclosingNodes,
3081
3679
  IdMap,
3082
3680
  IdSet,
@@ -3094,6 +3692,7 @@ export {
3094
3692
  assembleSummary,
3095
3693
  assessConfidence,
3096
3694
  buildUngatedExtractionReport,
3695
+ chosenCacheRoot,
3097
3696
  commonDirectoryOf,
3098
3697
  composeWrappers,
3099
3698
  computeContentHash,
@@ -3101,6 +3700,7 @@ export {
3101
3700
  configuredCallOption,
3102
3701
  createAdapterStamp,
3103
3702
  createCacheLayer,
3703
+ createCodeHasher,
3104
3704
  createPackTallies,
3105
3705
  createTimer,
3106
3706
  detectGaps,
@@ -3109,6 +3709,7 @@ export {
3109
3709
  enumerateOrDegrade,
3110
3710
  enumerateStructuredPaths,
3111
3711
  evaluatePackHealth,
3712
+ extractionCacheDirFor,
3112
3713
  extractionConfigStamp,
3113
3714
  firstWrittenReading,
3114
3715
  formatPackHealth,
@@ -3116,6 +3717,7 @@ export {
3116
3717
  helperPathsOnVariables,
3117
3718
  httpRouteDiscovery,
3118
3719
  isListedTestFile,
3720
+ liveRecordFor,
3119
3721
  makeIgnoredDirectory,
3120
3722
  makeTransitionId,
3121
3723
  mapReading,
@@ -3125,8 +3727,11 @@ export {
3125
3727
  moduleInitStructure,
3126
3728
  moduleOfFile,
3127
3729
  noopTimer,
3730
+ ownersWhereWritten,
3731
+ packConfigCheck,
3128
3732
  packGradients,
3129
3733
  paramToInput,
3734
+ processCodeHasher,
3130
3735
  projectFileStamp,
3131
3736
  rawConditionToPredicate,
3132
3737
  recordPackFailure,