@suss/extractor 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,11 +30,13 @@ 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,
36
37
  KeptParses: () => KeptParses,
37
38
  MAX_ENTRIES: () => MAX_ENTRIES,
39
+ MAX_HELPER_PATHS: () => MAX_HELPER_PATHS,
38
40
  MAX_PATHS: () => MAX_PATHS,
39
41
  MODULE_SURFACE_RECOGNITION: () => MODULE_SURFACE_RECOGNITION,
40
42
  PathBudgetExceeded: () => PathBudgetExceeded,
@@ -46,6 +48,7 @@ __export(index_exports, {
46
48
  assembleSummary: () => assembleSummary,
47
49
  assessConfidence: () => assessConfidence,
48
50
  buildUngatedExtractionReport: () => buildUngatedExtractionReport,
51
+ chosenCacheRoot: () => chosenCacheRoot,
49
52
  commonDirectoryOf: () => commonDirectoryOf,
50
53
  composeWrappers: () => composeWrappers,
51
54
  computeContentHash: () => computeContentHash,
@@ -53,6 +56,7 @@ __export(index_exports, {
53
56
  configuredCallOption: () => configuredCallOption,
54
57
  createAdapterStamp: () => createAdapterStamp,
55
58
  createCacheLayer: () => createCacheLayer,
59
+ createCodeHasher: () => createCodeHasher,
56
60
  createPackTallies: () => createPackTallies,
57
61
  createTimer: () => createTimer,
58
62
  detectGaps: () => detectGaps,
@@ -61,24 +65,33 @@ __export(index_exports, {
61
65
  enumerateOrDegrade: () => enumerateOrDegrade,
62
66
  enumerateStructuredPaths: () => enumerateStructuredPaths,
63
67
  evaluatePackHealth: () => evaluatePackHealth,
68
+ extractionCacheDirFor: () => extractionCacheDirFor,
64
69
  extractionConfigStamp: () => extractionConfigStamp,
65
70
  firstWrittenReading: () => firstWrittenReading,
66
71
  formatPackHealth: () => formatPackHealth,
67
72
  guardsHoldOn: () => guardsHoldOn,
73
+ helperPathsOnVariables: () => helperPathsOnVariables,
68
74
  httpRouteDiscovery: () => httpRouteDiscovery,
69
75
  isListedTestFile: () => isListedTestFile,
76
+ liveRecordFor: () => liveRecordFor,
77
+ makeIgnoredDirectory: () => makeIgnoredDirectory,
70
78
  makeTransitionId: () => makeTransitionId,
71
79
  mapReading: () => mapReading,
72
80
  markReturnsAsExitCode: () => markReturnsAsExitCode,
73
81
  matchesTestFileName: () => matchesTestFileName,
82
+ mergeReadShapes: () => mergeReadShapes,
74
83
  moduleInitStructure: () => moduleInitStructure,
75
84
  moduleOfFile: () => moduleOfFile,
76
85
  noopTimer: () => noopTimer,
86
+ ownersWhereWritten: () => ownersWhereWritten,
87
+ packConfigCheck: () => packConfigCheck,
77
88
  packGradients: () => packGradients,
78
89
  paramToInput: () => paramToInput,
90
+ processCodeHasher: () => processCodeHasher,
79
91
  projectFileStamp: () => projectFileStamp,
80
92
  rawConditionToPredicate: () => rawConditionToPredicate,
81
93
  recordPackFailure: () => recordPackFailure,
94
+ redirectDeliveryWhenSet: () => redirectDeliveryWhenSet,
82
95
  registrationHelperDiscovery: () => registrationHelperDiscovery,
83
96
  routeHelperIndex: () => routeHelperIndex,
84
97
  runDigest: () => runDigest,
@@ -87,6 +100,7 @@ __export(index_exports, {
87
100
  settleModules: () => settleModules,
88
101
  sharedGatingConditions: () => sharedGatingConditions,
89
102
  sourceRefsOf: () => sourceRefsOf,
103
+ splitAtHandOffs: () => splitAtHandOffs,
90
104
  stampMayMissAWrite: () => stampMayMissAWrite,
91
105
  stampModuleImports: () => stampModuleImports,
92
106
  stampModules: () => stampModules,
@@ -94,6 +108,7 @@ __export(index_exports, {
94
108
  summaryCountsByPack: () => summaryCountsByPack,
95
109
  tallyUnit: () => tallyUnit,
96
110
  terminalToOutput: () => terminalToOutput,
111
+ throughHelper: () => throughHelper,
97
112
  unreadableReading: () => unreadableReading,
98
113
  unwrapJsonStringify: () => unwrapJsonStringify,
99
114
  valueToReadFurtherFrom: () => valueToReadFurtherFrom,
@@ -102,8 +117,8 @@ __export(index_exports, {
102
117
  writtenReading: () => writtenReading
103
118
  });
104
119
  module.exports = __toCommonJS(index_exports);
105
- var import_node_crypto4 = require("crypto");
106
- var import_behavioral_ir6 = require("@suss/behavioral-ir");
120
+ var import_node_crypto7 = require("crypto");
121
+ var import_behavioral_ir8 = require("@suss/behavioral-ir");
107
122
 
108
123
  // src/contractStatusGaps.ts
109
124
  var import_behavioral_ir = require("@suss/behavioral-ir");
@@ -282,29 +297,82 @@ function composeOne(summary, chain, keepGaps) {
282
297
  if (wrappers.length === 0) {
283
298
  return narrowed;
284
299
  }
285
- const responses = respondedInstead(wrappers);
286
- const handled = handledThrows(wrappers);
287
- const transitions = beside(summary, responses, handled);
300
+ const responses = settledUncaught(respondedInstead(wrappers), wrappers);
301
+ const unitOwn = settledUncaught(summary.transitions, wrappers);
302
+ const own = [...responses, ...unitOwn];
303
+ const handled = handledThrows(wrappers, thrownBy(own));
304
+ const transitions = beside(summary, unitOwn, responses, handled);
288
305
  if (!keepGaps) {
289
306
  return transitions === summary.transitions ? narrowed : { ...narrowed, transitions };
290
307
  }
291
308
  const gaps = withContractStatusGaps(
292
309
  summary,
293
- [...responses, ...summary.transitions],
294
- handled
310
+ own,
311
+ everyHandlerOutcome(wrappers)
295
312
  );
296
313
  if (transitions === summary.transitions && gaps === summary.gaps) {
297
314
  return narrowed;
298
315
  }
299
316
  return { ...narrowed, transitions, gaps };
300
317
  }
301
- function beside(summary, responses, handled) {
302
- const throws = summary.transitions.some((t) => t.output.type === "throw");
303
- const onThrow = throws ? handled : [];
304
- if (responses.length === 0 && onThrow.length === 0) {
318
+ function beside(summary, unitOwn, responses, handled) {
319
+ if (responses.length === 0 && handled.length === 0 && unitOwn === summary.transitions) {
305
320
  return summary.transitions;
306
321
  }
307
- return withDistinctIds([...responses, ...summary.transitions, ...onThrow]);
322
+ return withDistinctIds([...responses, ...unitOwn, ...handled]);
323
+ }
324
+ function settledUncaught(transitions, wrappers) {
325
+ const handlers = wrappers.filter(
326
+ (wrapper) => wrapper.reference.onThrow === true
327
+ );
328
+ let changed = false;
329
+ const settled = transitions.flatMap((transition) => {
330
+ const { output } = transition;
331
+ if (output.type !== "throw") {
332
+ return [transition];
333
+ }
334
+ const caught = caughtOnRoute(handlers, output);
335
+ if (caught === "sure") {
336
+ changed = true;
337
+ return [markedCaught(transition)];
338
+ }
339
+ if (output.statusWhenUncaught === void 0) {
340
+ return [transition];
341
+ }
342
+ changed = true;
343
+ const response = {
344
+ ...transition,
345
+ output: {
346
+ type: "response",
347
+ statusCode: { type: "literal", value: output.statusWhenUncaught },
348
+ body: null,
349
+ headers: {}
350
+ }
351
+ };
352
+ return caught === "uncertain" ? [transition, response] : [response];
353
+ });
354
+ return changed ? settled : transitions;
355
+ }
356
+ function markedCaught(transition) {
357
+ return {
358
+ ...transition,
359
+ metadata: (0, import_behavioral_ir2.withWrapperMetadata)(transition.metadata, {
360
+ ...(0, import_behavioral_ir2.readWrapperMetadata)(transition),
361
+ caught: true
362
+ })
363
+ };
364
+ }
365
+ function caughtOnRoute(handlers, thrown) {
366
+ if (handlers.some((handler) => handler.reference.catches === void 0)) {
367
+ return "sure";
368
+ }
369
+ const matches = handlers.map(
370
+ (handler) => catchMatch(handler.reference, thrown)
371
+ );
372
+ if (matches.includes("yes")) {
373
+ return "sure";
374
+ }
375
+ return matches.includes("maybe") ? "uncertain" : "no";
308
376
  }
309
377
  function respondedInstead(wrappers) {
310
378
  return wrappers.flatMap(
@@ -316,11 +384,91 @@ function respondedInstead(wrappers) {
316
384
  )
317
385
  );
318
386
  }
319
- function handledThrows(wrappers) {
387
+ function thrownBy(transitions) {
388
+ return transitions.flatMap(
389
+ (t) => t.output.type === "throw" ? [t.output] : []
390
+ );
391
+ }
392
+ function everyHandlerOutcome(wrappers) {
320
393
  return wrappers.flatMap(
321
394
  (wrapper) => wrapper.reference.onThrow === true ? attribute(wrapper.summary.transitions, wrapper.reference) : []
322
395
  );
323
396
  }
397
+ function handledThrows(wrappers, thrown) {
398
+ const caught = catchesAny(wrappers, thrown);
399
+ return [...oncePerHandler(wrappers, caught)].flatMap(
400
+ ([wrapper, how]) => attribute(
401
+ wrapper.summary.transitions,
402
+ wrapper.reference,
403
+ how === "uncertain"
404
+ )
405
+ );
406
+ }
407
+ function oncePerHandler(wrappers, caught) {
408
+ const byHandler = /* @__PURE__ */ new Map();
409
+ for (const wrapper of wrappers) {
410
+ const how = caught.get(wrapper);
411
+ if (how === void 0) {
412
+ continue;
413
+ }
414
+ const earlier = byHandler.get(wrapper.summary);
415
+ if (earlier === void 0 || earlier[1] === "uncertain" && how === "sure") {
416
+ byHandler.set(wrapper.summary, [wrapper, how]);
417
+ }
418
+ }
419
+ return new Map(byHandler.values());
420
+ }
421
+ function catchesAny(wrappers, thrown) {
422
+ const caught = /* @__PURE__ */ new Map();
423
+ if (thrown.length === 0) {
424
+ return caught;
425
+ }
426
+ const handlers = wrappers.filter(
427
+ (wrapper) => wrapper.reference.onThrow === true
428
+ );
429
+ for (const handler of handlers) {
430
+ if (handler.reference.catches === void 0) {
431
+ caught.set(handler, "sure");
432
+ }
433
+ }
434
+ const listing = handlers.filter(
435
+ (handler) => handler.reference.catches !== void 0
436
+ );
437
+ for (const one of thrown) {
438
+ for (const [handler, how] of catchersOf(listing, one)) {
439
+ if (caught.get(handler) !== "sure") {
440
+ caught.set(handler, how);
441
+ }
442
+ }
443
+ }
444
+ return caught;
445
+ }
446
+ function catchersOf(handlers, thrown) {
447
+ const found = [];
448
+ for (const handler of handlers) {
449
+ const match = catchMatch(handler.reference, thrown);
450
+ if (match === "yes") {
451
+ found.push([handler, found.length === 0 ? "sure" : "uncertain"]);
452
+ return found;
453
+ }
454
+ if (match === "maybe") {
455
+ found.push([handler, "uncertain"]);
456
+ }
457
+ }
458
+ return found;
459
+ }
460
+ function catchMatch(reference, thrown) {
461
+ const ancestors = thrown.exceptionAncestors;
462
+ if (thrown.exceptionType === null || ancestors === void 0) {
463
+ return "maybe";
464
+ }
465
+ const classes = /* @__PURE__ */ new Set([thrown.exceptionType, ...ancestors]);
466
+ if ((reference.catches ?? []).some((name) => classes.has(name))) {
467
+ return "yes";
468
+ }
469
+ const unread = thrown.ancestryIncomplete === true && reference.mayCatchUnreadClasses === true;
470
+ return unread || reference.mayCatchAny === true ? "maybe" : "no";
471
+ }
324
472
  function withContractStatusGaps(summary, composed, handled) {
325
473
  const contract = (0, import_behavioral_ir2.readHttpMetadata)(summary)?.declaredContract;
326
474
  const binding = summary.identity.boundaryBinding;
@@ -351,10 +499,13 @@ function coversUnit(reference, summary) {
351
499
  const binding = summary.identity.boundaryBinding;
352
500
  return binding !== null && (0, import_behavioral_ir2.withinScope)(binding, reference.scope);
353
501
  }
354
- function attribute(transitions, reference) {
502
+ function attribute(transitions, reference, catchUncertain = false) {
355
503
  return transitions.map((transition) => ({
356
504
  ...transition,
357
- metadata: (0, import_behavioral_ir2.withWrapperMetadata)(transition.metadata, { from: reference })
505
+ metadata: (0, import_behavioral_ir2.withWrapperMetadata)(transition.metadata, {
506
+ from: reference,
507
+ ...catchUncertain ? { catchUncertain } : {}
508
+ })
358
509
  }));
359
510
  }
360
511
  function withDistinctIds(transitions) {
@@ -467,13 +618,111 @@ function markExitCodeFrom(summary, marked) {
467
618
  summary.metadata = left;
468
619
  }
469
620
 
621
+ // src/helperHandOff.ts
622
+ var import_behavioral_ir4 = require("@suss/behavioral-ir");
623
+ var MAX_HELPER_PATHS = 16;
624
+ function throughHelper(branch, at, helperBranches) {
625
+ return helperBranches.map((helper) => {
626
+ const conditions = [...branch.conditions, ...helper.conditions];
627
+ const expectedInput = mergeReadShapes(
628
+ branch.expectedInput ?? null,
629
+ helper.expectedInput ?? null
630
+ );
631
+ const reads = expectedInput === null ? {} : { expectedInput };
632
+ if (helper.terminal.kind !== "throw" && helper.terminal.kind !== "exit") {
633
+ return {
634
+ ...branch,
635
+ ...reads,
636
+ conditions,
637
+ isDefault: branch.isDefault && helper.isDefault
638
+ };
639
+ }
640
+ return {
641
+ ...branch,
642
+ ...reads,
643
+ conditions,
644
+ terminal: { ...helper.terminal, location: at },
645
+ location: at,
646
+ isDefault: false
647
+ };
648
+ });
649
+ }
650
+ function mergeReadShapes(a, b) {
651
+ if (a === null || b === null) {
652
+ return a ?? b;
653
+ }
654
+ if (a.type !== "record" || b.type !== "record") {
655
+ return a.type === "record" ? a : b;
656
+ }
657
+ const properties = { ...a.properties };
658
+ for (const [name, shape] of Object.entries(b.properties)) {
659
+ const known = properties[name];
660
+ properties[name] = known === void 0 ? shape : mergeReadShapes(known, shape) ?? shape;
661
+ }
662
+ return { ...a, properties };
663
+ }
664
+ function splitAtHandOffs(branch, sites, from = 0) {
665
+ for (let index = from; index < sites.length; index++) {
666
+ const site = sites[index];
667
+ const reached = site.alwaysRuns === true || runsBefore(site.line, branch.terminal.location.end);
668
+ if (!reached || !guardsHoldOn(site.preconditions, branch.conditions)) {
669
+ continue;
670
+ }
671
+ return throughHelper(branch, site.at, site.helper).flatMap(
672
+ (path10) => path10.terminal === branch.terminal ? splitAtHandOffs(path10, sites, index + 1) : [path10]
673
+ );
674
+ }
675
+ return [branch];
676
+ }
677
+ function helperPathsOnVariables(helperBranches, passed) {
678
+ const renamed = helperBranches.map((branch) => ({
679
+ ...branch,
680
+ conditions: branch.conditions.map((condition) => ({
681
+ ...condition,
682
+ structured: condition.structured === null ? null : onCallerVariables(condition.structured, passed)
683
+ }))
684
+ }));
685
+ const variables = new Set(passed.values());
686
+ const testsWhatItGot = renamed.some(
687
+ (branch) => branch.conditions.some(
688
+ (condition) => condition.structured !== null && (0, import_behavioral_ir4.predicateRefs)(condition.structured).some(
689
+ (ref) => variables.has(rootName(ref) ?? "")
690
+ )
691
+ )
692
+ );
693
+ return testsWhatItGot && renamed.length <= MAX_HELPER_PATHS ? renamed : null;
694
+ }
695
+ function onCallerVariables(predicate, passed) {
696
+ const rename = (ref) => {
697
+ if (ref.type === "derived") {
698
+ return { ...ref, from: rename(ref.from) };
699
+ }
700
+ if (ref.type === "input") {
701
+ const name = passed.get(ref.inputRef);
702
+ return name === void 0 ? ref : { type: "dependency", name, accessChain: ref.path };
703
+ }
704
+ if (ref.type === "dependency") {
705
+ const name = passed.get(ref.name);
706
+ return name === void 0 ? ref : { ...ref, name };
707
+ }
708
+ return ref;
709
+ };
710
+ return (0, import_behavioral_ir4.replacePredicateRefs)(predicate, rename);
711
+ }
712
+ function rootName(ref) {
713
+ if (ref.type === "derived") {
714
+ return rootName(ref.from);
715
+ }
716
+ return ref.type === "dependency" ? ref.name : null;
717
+ }
718
+
470
719
  // src/moduleImports.ts
471
- function stampModuleImports(summaries, importsOf) {
720
+ function stampModuleImports(summaries, importsOf2) {
472
721
  const byFile = /* @__PURE__ */ new Map();
473
722
  for (const summary of summaries) {
474
723
  const file = summary.location.file;
475
724
  if (!byFile.has(file)) {
476
- const found = importsOf(file);
725
+ const found = importsOf2(file);
477
726
  byFile.set(
478
727
  file,
479
728
  found === void 0 ? void 0 : [...new Set(found)].sort()
@@ -720,7 +969,7 @@ var configuredCallOption = import_zod.z.object({
720
969
  }).strict();
721
970
 
722
971
  // src/paths/enumeratePaths.ts
723
- var import_behavioral_ir4 = require("@suss/behavioral-ir");
972
+ var import_behavioral_ir5 = require("@suss/behavioral-ir");
724
973
  var MAX_PATHS = 256;
725
974
  var UnmodeledFlow = class extends Error {
726
975
  };
@@ -764,12 +1013,12 @@ var loopCompletedCond = (headerText, exit) => fixedCond(
764
1013
  exit === "throw" ? "earlyThrow" : "earlyReturn"
765
1014
  );
766
1015
  var catchEntryCond = () => fixedCond(
767
- { sourceText: import_behavioral_ir4.CATCH_ENTRY_TEXT, expression: null },
1016
+ { sourceText: import_behavioral_ir5.CATCH_ENTRY_TEXT, expression: null },
768
1017
  "positive",
769
1018
  "catchBlock"
770
1019
  );
771
- function classify(path6, home, isAncestorOrSelf) {
772
- return path6.map((cond) => {
1020
+ function classify(path10, home, isAncestorOrSelf) {
1021
+ return path10.map((cond) => {
773
1022
  if (cond.branchStmt === null) {
774
1023
  return cond.info;
775
1024
  }
@@ -834,9 +1083,16 @@ function chargeBudget(state) {
834
1083
  );
835
1084
  }
836
1085
  }
837
- function recordTerminal(ctx, terminal, home, path6) {
1086
+ function limitOpenPaths(paths) {
1087
+ if (paths.length > MAX_PATHS) {
1088
+ throw new PathBudgetExceeded(
1089
+ `path budget exceeded, more than ${MAX_PATHS} paths`
1090
+ );
1091
+ }
1092
+ }
1093
+ function recordTerminal(ctx, terminal, home, path10) {
838
1094
  chargeBudget(ctx.state);
839
- const info = classify(path6, home, ctx.isAncestorOrSelf);
1095
+ const info = classify(path10, home, ctx.isAncestorOrSelf);
840
1096
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
841
1097
  existing.push(info);
842
1098
  ctx.state.byTerminal.set(terminal, existing);
@@ -847,15 +1103,15 @@ function containsTerminal(ctx, stmt) {
847
1103
  }
848
1104
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
849
1105
  }
850
- function stepIf(stmt, path6, ctx) {
1106
+ function stepIf(stmt, path10, ctx) {
851
1107
  const thenExit = exitKindOfList(stmt.thenBody);
852
1108
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
853
1109
  const thenPath = [
854
- ...path6,
1110
+ ...path10,
855
1111
  branchCond(stmt, stmt.condition, "positive", elseExit)
856
1112
  ];
857
1113
  const elsePath = [
858
- ...path6,
1114
+ ...path10,
859
1115
  branchCond(
860
1116
  stmt,
861
1117
  stmt.condition,
@@ -872,7 +1128,7 @@ function stepIf(stmt, path6, ctx) {
872
1128
  }
873
1129
  return out;
874
1130
  }
875
- function stepSwitch(stmt, path6, ctx) {
1131
+ function stepSwitch(stmt, path10, ctx) {
876
1132
  const out = [];
877
1133
  const negations = [];
878
1134
  let defaultRanBody = false;
@@ -882,7 +1138,7 @@ function stepSwitch(stmt, path6, ctx) {
882
1138
  if (hasStrayBreak(group.body)) {
883
1139
  throw new UnmodeledFlow("non-trailing break in switch clause");
884
1140
  }
885
- const groupPath = group.condition === null ? [...path6, ...negations] : [...path6, fixedCond(group.condition, "positive", "explicit")];
1141
+ const groupPath = group.condition === null ? [...path10, ...negations] : [...path10, fixedCond(group.condition, "positive", "explicit")];
886
1142
  const conts = enumerate(ctx, group.body, groupPath);
887
1143
  if (conts.length > 0) {
888
1144
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -899,24 +1155,24 @@ function stepSwitch(stmt, path6, ctx) {
899
1155
  }
900
1156
  }
901
1157
  if (!defaultRanBody) {
902
- out.push([...path6, ...negations]);
1158
+ out.push([...path10, ...negations]);
903
1159
  }
904
1160
  return out;
905
1161
  }
906
- function stepLoop(stmt, path6, ctx) {
1162
+ function stepLoop(stmt, path10, ctx) {
907
1163
  const bodyExit = exitKindOfList(stmt.body);
908
1164
  if (containsTerminal(ctx, stmt)) {
909
1165
  enumerate(ctx, stmt.body, [
910
- ...path6,
1166
+ ...path10,
911
1167
  loopIterationCond(stmt.condition.sourceText)
912
1168
  ]);
913
1169
  }
914
1170
  if (bodyExit !== null) {
915
- return [[...path6, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1171
+ return [[...path10, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
916
1172
  }
917
- return [path6];
1173
+ return [path10];
918
1174
  }
919
- function stepTry(stmt, path6, ctx) {
1175
+ function stepTry(stmt, path10, ctx) {
920
1176
  if (stmt.finallyBody !== null) {
921
1177
  const finallyHasTerminal = stmt.finallyBody.some(
922
1178
  (s) => containsTerminal(ctx, s)
@@ -926,22 +1182,22 @@ function stepTry(stmt, path6, ctx) {
926
1182
  }
927
1183
  }
928
1184
  const out = [];
929
- out.push(...enumerate(ctx, stmt.tryBody, path6));
1185
+ out.push(...enumerate(ctx, stmt.tryBody, path10));
930
1186
  if (stmt.catchBody !== null) {
931
- const catchPath = [...path6, catchEntryCond()];
1187
+ const catchPath = [...path10, catchEntryCond()];
932
1188
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
933
1189
  }
934
1190
  return out;
935
1191
  }
936
- function stepExit(stmt, path6, ctx) {
1192
+ function stepExit(stmt, path10, ctx) {
937
1193
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
938
1194
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
939
- collector.push(path6);
1195
+ collector.push(path10);
940
1196
  }
941
1197
  return [];
942
1198
  }
943
- function stepOpaque(_stmt, path6, _ctx) {
944
- return [path6];
1199
+ function stepOpaque(_stmt, path10, _ctx) {
1200
+ return [path10];
945
1201
  }
946
1202
  var stepHandlers = {
947
1203
  if: stepIf,
@@ -951,22 +1207,23 @@ var stepHandlers = {
951
1207
  exit: stepExit,
952
1208
  opaque: stepOpaque
953
1209
  };
954
- function stepCallback(ctx, body, path6) {
1210
+ function stepCallback(ctx, body, path10) {
955
1211
  const exits = [];
956
1212
  ctx.state.callbackExits.push(exits);
957
1213
  let fellThrough;
958
1214
  try {
959
- fellThrough = enumerate(ctx, body, path6);
1215
+ fellThrough = enumerate(ctx, body, path10);
960
1216
  } finally {
961
1217
  ctx.state.callbackExits.pop();
962
1218
  }
963
1219
  const out = [...fellThrough, ...exits];
964
- return out.length === 0 ? [path6] : out;
1220
+ return out.length === 0 ? [path10] : out;
965
1221
  }
966
- function stepStatement(ctx, stmt, path6) {
967
- let paths = [path6];
1222
+ function stepStatement(ctx, stmt, path10) {
1223
+ let paths = [path10];
968
1224
  for (const body of stmt.callbacks ?? []) {
969
1225
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
1226
+ limitOpenPaths(paths);
970
1227
  }
971
1228
  const ownTerminals = ctx.terminalsByStmt.get(stmt) ?? [];
972
1229
  for (const each of withRejoined(paths)) {
@@ -983,7 +1240,7 @@ function stepStatement(ctx, stmt, path6) {
983
1240
  )
984
1241
  );
985
1242
  }
986
- function soleDisagreement(left, right) {
1243
+ function soleDisagreement(left, right, keepsArms) {
987
1244
  if (left.length !== right.length) {
988
1245
  return null;
989
1246
  }
@@ -1002,9 +1259,10 @@ function soleDisagreement(left, right) {
1002
1259
  }
1003
1260
  found = at;
1004
1261
  }
1005
- return found;
1262
+ const test = found === null ? null : left[found]?.info.expression ?? null;
1263
+ return test !== null && keepsArms(test) ? null : found;
1006
1264
  }
1007
- function mergeRejoined(paths) {
1265
+ function mergeRejoined(paths, keepsArms = KEEPS_NO_ARMS) {
1008
1266
  let current = paths;
1009
1267
  for (let round = 0; round < current.length; round++) {
1010
1268
  const out = [];
@@ -1019,7 +1277,11 @@ function mergeRejoined(paths) {
1019
1277
  if (taken.has(j)) {
1020
1278
  continue;
1021
1279
  }
1022
- const at = soleDisagreement(combined, current[j]);
1280
+ const at = soleDisagreement(
1281
+ combined,
1282
+ current[j],
1283
+ keepsArms
1284
+ );
1023
1285
  if (at === null) {
1024
1286
  continue;
1025
1287
  }
@@ -1045,21 +1307,18 @@ function enumerate(ctx, stmts, prefix) {
1045
1307
  let frontiers = [[...prefix]];
1046
1308
  for (const stmt of stmts) {
1047
1309
  const nextFrontiers = [];
1048
- for (const path6 of mergeRejoined(frontiers)) {
1049
- nextFrontiers.push(...stepStatement(ctx, stmt, path6));
1310
+ for (const path10 of mergeRejoined(frontiers, ctx.keepsArms)) {
1311
+ nextFrontiers.push(...stepStatement(ctx, stmt, path10));
1050
1312
  }
1051
1313
  frontiers = nextFrontiers;
1052
- if (frontiers.length > MAX_PATHS) {
1053
- throw new PathBudgetExceeded(
1054
- `path budget exceeded, more than ${MAX_PATHS} paths`
1055
- );
1056
- }
1314
+ limitOpenPaths(frontiers);
1057
1315
  if (frontiers.length === 0) {
1058
1316
  break;
1059
1317
  }
1060
1318
  }
1061
1319
  return frontiers;
1062
1320
  }
1321
+ var KEEPS_NO_ARMS = () => false;
1063
1322
  function buildParentMap(statements) {
1064
1323
  const parentOf = /* @__PURE__ */ new Map();
1065
1324
  const visit = (stmt, parent) => {
@@ -1093,13 +1352,14 @@ function enumerateStructuredPaths(input) {
1093
1352
  };
1094
1353
  const ctx = {
1095
1354
  terminalsByStmt: input.terminalsByStmt,
1355
+ keepsArms: input.keepsArms ?? KEEPS_NO_ARMS,
1096
1356
  state,
1097
1357
  isAncestorOrSelf
1098
1358
  };
1099
1359
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
1100
- for (const path6 of continuations) {
1360
+ for (const path10 of continuations) {
1101
1361
  chargeBudget(state);
1102
- state.fallthrough.push(classify(path6, null, isAncestorOrSelf));
1362
+ state.fallthrough.push(classify(path10, null, isAncestorOrSelf));
1103
1363
  }
1104
1364
  return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
1105
1365
  }
@@ -1142,8 +1402,8 @@ function sharedGatingConditions(paths, readPredicate) {
1142
1402
  return [];
1143
1403
  }
1144
1404
  const shared = new Set(first.map(keyOf2));
1145
- for (const path6 of rest) {
1146
- const here = new Set(path6.map(keyOf2));
1405
+ for (const path10 of rest) {
1406
+ const here = new Set(path10.map(keyOf2));
1147
1407
  for (const value of shared) {
1148
1408
  if (!here.has(value)) {
1149
1409
  shared.delete(value);
@@ -1251,15 +1511,12 @@ var EnclosingNodes = class {
1251
1511
 
1252
1512
  // src/paths/nodeWalk.ts
1253
1513
  var SKIP_CHILDREN = /* @__PURE__ */ Symbol("skipChildren");
1254
- function walkDescendants(root, carried, visitor) {
1255
- for (const child of root.namedChildren) {
1256
- if (child === null) {
1257
- continue;
1258
- }
1514
+ function walkDescendants(root, carried, childrenOf2, visitor) {
1515
+ for (const child of childrenOf2(root)) {
1259
1516
  visitor.at(child, carried);
1260
1517
  const inner = visitor.into(child, carried);
1261
1518
  if (inner !== SKIP_CHILDREN) {
1262
- walkDescendants(child, inner, visitor);
1519
+ walkDescendants(child, inner, childrenOf2, visitor);
1263
1520
  }
1264
1521
  }
1265
1522
  }
@@ -1338,6 +1595,11 @@ function valueToReadFurtherFrom(reading) {
1338
1595
  return reading.kind === "written" ? reading.value : null;
1339
1596
  }
1340
1597
 
1598
+ // src/redirectOption.ts
1599
+ function redirectDeliveryWhenSet(option, value) {
1600
+ return option.handsBack.includes(value) ? "response" : "followed";
1601
+ }
1602
+
1341
1603
  // src/testFiles.ts
1342
1604
  function isListedTestFile(file, listed) {
1343
1605
  if (listed === void 0) {
@@ -1407,18 +1669,179 @@ function quoted(text, key) {
1407
1669
  }
1408
1670
 
1409
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
1410
1678
  var import_node_crypto = require("crypto");
1411
1679
  var import_node_fs2 = __toESM(require("fs"), 1);
1412
1680
  var import_node_module = require("module");
1413
1681
  var import_node_path4 = __toESM(require("path"), 1);
1414
- var import_node_url = require("url");
1415
- var import_meta = {};
1416
- var ANALYSIS_PACKAGES = [
1417
- "@suss/extractor",
1418
- "@suss/resolution",
1419
- "@suss/datalog",
1420
- "@suss/behavioral-ir"
1421
- ];
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
1422
1845
  var SOURCE_STAMP = { kind: "source" };
1423
1846
  function createAdapterStamp(config) {
1424
1847
  let cachedCodeStamp = null;
@@ -1453,19 +1876,19 @@ function createAdapterStamp(config) {
1453
1876
  function readAdapterCodeStamp(moduleUrl) {
1454
1877
  try {
1455
1878
  const selfPath = (0, import_node_url.fileURLToPath)(moduleUrl);
1456
- const hash = computeDistHashFrom(import_node_path4.default.dirname(selfPath));
1879
+ const hash = computeDistHashFrom(import_node_path5.default.dirname(selfPath), processCodeHasher);
1457
1880
  return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
1458
1881
  } catch {
1459
1882
  return SOURCE_STAMP;
1460
1883
  }
1461
1884
  }
1462
- function computeDistHashFrom(dir) {
1463
- 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")];
1464
1887
  for (const candidate of candidates) {
1465
- if (!import_node_fs2.default.existsSync(candidate)) {
1888
+ if (!import_node_fs3.default.existsSync(candidate)) {
1466
1889
  continue;
1467
1890
  }
1468
- return computeContentHash([candidate, ...analysisBundles()]);
1891
+ return hasher.codeHash(candidate);
1469
1892
  }
1470
1893
  return "";
1471
1894
  }
@@ -1473,10 +1896,10 @@ function computeContentHash(paths) {
1473
1896
  if (paths.length === 0) {
1474
1897
  return "";
1475
1898
  }
1476
- const hash = (0, import_node_crypto.createHash)("sha256");
1899
+ const hash = (0, import_node_crypto2.createHash)("sha256");
1477
1900
  for (const file of paths) {
1478
1901
  try {
1479
- hash.update(import_node_fs2.default.readFileSync(file));
1902
+ hash.update(import_node_fs3.default.readFileSync(file));
1480
1903
  } catch {
1481
1904
  return "";
1482
1905
  }
@@ -1487,12 +1910,12 @@ function projectFileStamp(paths) {
1487
1910
  if (paths.length === 0) {
1488
1911
  return "none";
1489
1912
  }
1490
- const hash = (0, import_node_crypto.createHash)("sha256");
1913
+ const hash = (0, import_node_crypto2.createHash)("sha256");
1491
1914
  for (const file of [...paths].sort()) {
1492
1915
  hash.update(file);
1493
1916
  hash.update("\0");
1494
1917
  try {
1495
- hash.update(import_node_fs2.default.readFileSync(file));
1918
+ hash.update(import_node_fs3.default.readFileSync(file));
1496
1919
  } catch {
1497
1920
  hash.update("absent");
1498
1921
  }
@@ -1505,8 +1928,9 @@ function extractionConfigStamp(config) {
1505
1928
  ["includeReachable", optionalText(config.includeReachable)],
1506
1929
  ["workspaceRoot", config.workspaceRoot],
1507
1930
  ["projectRoot", config.projectRoot],
1508
- ["importRoots", config.importRoots?.join(import_node_path4.default.delimiter)],
1509
- ["modules", modulesStamp(config.modules)]
1931
+ ["importRoots", config.importRoots?.join(import_node_path5.default.delimiter)],
1932
+ ["modules", modulesStamp(config.modules)],
1933
+ ["walkedDirectory", config.walkedDirectory]
1510
1934
  ];
1511
1935
  return parts.flatMap(
1512
1936
  ([name, value]) => value === void 0 ? [] : [`${name}=${value}`]
@@ -1519,25 +1943,283 @@ function runDigest(packsDigest, packs, files) {
1519
1943
  const inputs = packs.flatMap((pack) => pack.discoveryInputs?.(files) ?? []);
1520
1944
  return `${packsDigest}|reads:${projectFileStamp(inputs)}`;
1521
1945
  }
1522
- function analysisBundles() {
1523
- const require2 = (0, import_node_module.createRequire)(import_meta.url);
1524
- const found = [];
1525
- for (const name of ANALYSIS_PACKAGES) {
1526
- try {
1527
- found.push(require2.resolve(name));
1528
- } 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]);
1529
2027
  }
1530
2028
  }
1531
- 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
+ }
1532
2053
  }
1533
2054
 
1534
- // src/cache.ts
2055
+ // src/cacheShards.ts
1535
2056
  var import_node_crypto3 = require("crypto");
1536
- var import_promises2 = __toESM(require("fs/promises"), 1);
1537
- var import_node_path5 = __toESM(require("path"), 1);
2057
+ var import_node_fs4 = __toESM(require("fs"), 1);
2058
+ var import_node_path6 = __toESM(require("path"), 1);
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
+ }
1538
2220
 
1539
2221
  // src/cacheStamps.ts
1540
- var import_node_crypto2 = require("crypto");
2222
+ var import_node_crypto4 = require("crypto");
1541
2223
  var import_promises = __toESM(require("fs/promises"), 1);
1542
2224
  var RECENT_CHANGE_MS = 2e3;
1543
2225
  function stampMayMissAWrite(stat, statAt) {
@@ -1627,7 +2309,7 @@ function reusableHash(prior, stamp) {
1627
2309
  async function hashFile(filePath) {
1628
2310
  try {
1629
2311
  const content = await import_promises.default.readFile(filePath);
1630
- 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);
1631
2313
  } catch {
1632
2314
  return null;
1633
2315
  }
@@ -1642,11 +2324,30 @@ function fileStampEquals(a, b) {
1642
2324
  return a.path === b.path && a.mtimeMs === b.mtimeMs && a.size === b.size;
1643
2325
  }
1644
2326
 
2327
+ // src/ignoredDirectory.ts
2328
+ var import_node_fs5 = __toESM(require("fs"), 1);
2329
+ var import_node_path7 = __toESM(require("path"), 1);
2330
+ function makeIgnoredDirectory(dir) {
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");
2335
+ }
2336
+ }
2337
+
1645
2338
  // src/cache.ts
1646
- var SCHEMA_VERSION = "9";
2339
+ var SCHEMA_VERSION = "10";
2340
+ var MANIFEST = "manifest.json";
2341
+ var INDEX = "index.json";
1647
2342
  var MAX_ENTRIES = 2;
1648
2343
  var ENTRY_PREFIX = "key-";
1649
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
+ }
1650
2351
  function createCacheLayer(cacheDir) {
1651
2352
  if (cacheDir === null) {
1652
2353
  return {
@@ -1666,7 +2367,7 @@ function createCacheLayer(cacheDir) {
1666
2367
  const lookupWith = async (input, run) => {
1667
2368
  const entryDir = entryDirFor(cacheDir, input);
1668
2369
  const manifest = await manifests.readAtRunStart(
1669
- import_node_path5.default.join(entryDir, "manifest.json")
2370
+ import_node_path8.default.join(entryDir, MANIFEST)
1670
2371
  );
1671
2372
  if (manifest === null) {
1672
2373
  return missDiag(await describeAbsentEntry(cacheDir));
@@ -1681,11 +2382,15 @@ function createCacheLayer(cacheDir) {
1681
2382
  if (!await filesStillMatch(manifest.files, run)) {
1682
2383
  return missDiag("files-changed");
1683
2384
  }
2385
+ const summaries = storedSummaries(entryDir, manifest);
2386
+ if (summaries === null) {
2387
+ return missDiag("no-manifest");
2388
+ }
1684
2389
  await markUsed(entryDir);
1685
2390
  manifests.forget();
1686
2391
  return {
1687
2392
  kind: "hit",
1688
- summaries: manifest.summaries,
2393
+ summaries,
1689
2394
  diagnostic: { kind: "hit" }
1690
2395
  };
1691
2396
  };
@@ -1705,10 +2410,8 @@ function createCacheLayer(cacheDir) {
1705
2410
  },
1706
2411
  async plan(input) {
1707
2412
  const entryDir = entryDirFor(cacheDir, input);
1708
- const manifest = await manifests.readAgain(
1709
- import_node_path5.default.join(entryDir, "manifest.json")
1710
- );
1711
- 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) {
1712
2415
  return null;
1713
2416
  }
1714
2417
  const run = await runFor(input);
@@ -1719,14 +2422,17 @@ function createCacheLayer(cacheDir) {
1719
2422
  )) {
1720
2423
  return null;
1721
2424
  }
2425
+ const summaries = storedSummaries(entryDir, manifest);
2426
+ const records = storedRecords(entryDir, manifest);
2427
+ if (summaries === null || records === null) {
2428
+ return null;
2429
+ }
1722
2430
  await markUsed(entryDir);
1723
- return buildPlan(manifest, run);
2431
+ return buildPlan(manifest, records, summaries, run);
1724
2432
  },
1725
2433
  async write(input, summaries, attribution) {
1726
2434
  const entryDir = entryDirFor(cacheDir, input);
1727
- const previous = await manifests.readAgain(
1728
- import_node_path5.default.join(entryDir, "manifest.json")
1729
- );
2435
+ const previous = await manifests.readAgain(import_node_path8.default.join(entryDir, MANIFEST));
1730
2436
  manifests.forget();
1731
2437
  const known = runs.get(input);
1732
2438
  const run = known ?? await runFor(input);
@@ -1742,58 +2448,119 @@ function createCacheLayer(cacheDir) {
1742
2448
  )
1743
2449
  );
1744
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);
1745
2457
  const manifest = {
1746
2458
  schemaVersion: SCHEMA_VERSION,
1747
2459
  adapterPacksDigest: input.adapterPacksDigest,
1748
2460
  configStamp,
1749
2461
  files,
1750
- summaries,
1751
- ...attribution === void 0 ? {} : encodeAttribution(attribution)
2462
+ summaryCount: summaries.length,
2463
+ shards,
2464
+ ...encoded === null ? {} : encoded.stored
1752
2465
  };
1753
- await import_promises2.default.mkdir(entryDir, { recursive: true });
1754
- await import_promises2.default.writeFile(
1755
- import_node_path5.default.join(entryDir, "manifest.json"),
1756
- JSON.stringify(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)])
1757
2470
  );
1758
2471
  await evictOldEntries(cacheDir, entryDir);
1759
2472
  }
1760
2473
  };
1761
2474
  }
1762
- async function buildPlan(manifest, run) {
1763
- const stored = new Map(manifest.files.map((f) => [f.path, f]));
1764
- const current = new Map(run.files.map((f) => [f.stamp.path, f.stamp]));
1765
- const changed = /* @__PURE__ */ new Set();
1766
- const added = /* @__PURE__ */ new Set();
1767
- const removed = /* @__PURE__ */ new Set();
1768
- for (const p of stored.keys()) {
1769
- if (!current.has(p)) {
1770
- 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;
1771
2533
  }
2534
+ shard.order.forEach((position, i) => {
2535
+ summaries[position] = content.units[i];
2536
+ });
1772
2537
  }
1773
- const toVerify = [];
1774
- for (const [p, stamp] of current) {
1775
- const before = stored.get(p);
1776
- if (before === void 0) {
1777
- changed.add(p);
1778
- added.add(p);
1779
- } else if (!fileStampEquals(before, stamp)) {
1780
- if (before.contentHash === void 0) {
1781
- changed.add(p);
1782
- } else {
1783
- toVerify.push(p);
1784
- }
1785
- } else if (before.verifyHash === true) {
1786
- toVerify.push(p);
2538
+ for (let position = 0; position < summaries.length; position++) {
2539
+ if (summaries[position] === void 0) {
2540
+ return null;
1787
2541
  }
1788
2542
  }
1789
- await Promise.all(
1790
- toVerify.map(async (p) => {
1791
- const hash = await hashOf(run, p);
1792
- if (hash === null || hash !== stored.get(p)?.contentHash) {
1793
- changed.add(p);
1794
- }
1795
- })
1796
- );
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);
1797
2564
  const roots = /* @__PURE__ */ new Map();
1798
2565
  const rootNames = manifest.roots ?? [];
1799
2566
  const depPaths = manifest.depPaths ?? [];
@@ -1803,23 +2570,24 @@ async function buildPlan(manifest, run) {
1803
2570
  });
1804
2571
  let rootsDeclined = 0;
1805
2572
  rootNames.forEach((rootPath, i) => {
1806
- const meta = manifest.rootMeta?.[i];
1807
- if (meta === void 0) {
2573
+ const record = manifest.rootMeta?.[i];
2574
+ if (record === void 0) {
1808
2575
  return;
1809
2576
  }
1810
- if (!meta.cacheable) {
2577
+ if (!record.cacheable) {
1811
2578
  rootsDeclined += 1;
1812
2579
  }
1813
2580
  roots.set(rootPath, {
1814
2581
  path: rootPath,
1815
- cacheable: meta.cacheable,
1816
- deps: pathsOf(meta.deps),
1817
- claims: meta.claims,
1818
- meta: meta.meta,
1819
- packs: meta.packs,
1820
- ...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) }
1821
2588
  });
1822
2589
  });
2590
+ const current = new Set(run.files.map((f) => f.stamp.path));
1823
2591
  const unchanged = (filePath, deps) => current.has(filePath) && !changed.has(filePath) && !deps.some((d) => changed.has(d) || removed.has(d));
1824
2592
  const validRoots = /* @__PURE__ */ new Set();
1825
2593
  for (const [rootPath, record] of roots) {
@@ -1827,14 +2595,14 @@ async function buildPlan(manifest, run) {
1827
2595
  validRoots.add(rootPath);
1828
2596
  }
1829
2597
  }
1830
- const units = (manifest.units ?? []).flatMap((stored2) => {
1831
- const file = depPaths[stored2.file];
2598
+ const units = (manifest.units ?? []).flatMap((unit) => {
2599
+ const file = depPaths[unit.file];
1832
2600
  return file === void 0 ? [] : [
1833
2601
  {
1834
- key: stored2.key,
2602
+ key: unit.key,
1835
2603
  file,
1836
- deps: pathsOf(stored2.deps),
1837
- data: stored2.data
2604
+ deps: pathsOf(unit.deps),
2605
+ data: unit.data
1838
2606
  }
1839
2607
  ];
1840
2608
  });
@@ -1852,7 +2620,7 @@ async function buildPlan(manifest, run) {
1852
2620
  }
1853
2621
  }
1854
2622
  }
1855
- const owners = manifest.owners ?? [];
2623
+ const ownerPaths = ownersInRunOrder(manifest);
1856
2624
  return {
1857
2625
  changed,
1858
2626
  removed,
@@ -1864,36 +2632,81 @@ async function buildPlan(manifest, run) {
1864
2632
  reuse(valid) {
1865
2633
  const summaries = [];
1866
2634
  const reusedOwners = [];
1867
- manifest.summaries.forEach((summary, i) => {
1868
- const ownerPaths = (owners[i] ?? []).flatMap((o) => {
1869
- const p = rootNames[o];
1870
- return p === void 0 ? [] : [p];
1871
- });
1872
- 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))) {
1873
2638
  summaries.push(summary);
1874
- reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
2639
+ reusedOwners.push(owners.filter((p) => valid.has(p)));
1875
2640
  }
1876
2641
  });
1877
2642
  return { summaries, owners: reusedOwners };
1878
2643
  },
1879
2644
  allSummaries() {
1880
- return manifest.summaries;
2645
+ return stored;
1881
2646
  },
1882
2647
  attribution() {
1883
2648
  return {
1884
2649
  roots: [...roots.values()],
1885
- owners: owners.map(
1886
- (ownerIds) => ownerIds.flatMap((o) => {
1887
- const p = rootNames[o];
1888
- return p === void 0 ? [] : [p];
1889
- })
1890
- ),
2650
+ owners: ownerPaths,
1891
2651
  units
1892
2652
  };
1893
2653
  }
1894
2654
  };
1895
2655
  }
1896
- 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) {
1897
2710
  const roots = attribution.roots.map((r) => r.path);
1898
2711
  const rootIndex = new Map(roots.map((p, i) => [p, i]));
1899
2712
  const depIndex = /* @__PURE__ */ new Map();
@@ -1909,10 +2722,10 @@ function encodeAttribution(attribution) {
1909
2722
  return id;
1910
2723
  };
1911
2724
  const rootMeta = attribution.roots.map((r) => ({
2725
+ ...r.meta === void 0 ? {} : { record: storeRecord(r.meta) },
1912
2726
  cacheable: r.cacheable,
1913
2727
  deps: r.deps.map(depIdOf),
1914
2728
  claims: r.claims,
1915
- meta: r.meta,
1916
2729
  packs: r.packs,
1917
2730
  ...r.imports === void 0 ? {} : { imports: r.imports.map(depIdOf) }
1918
2731
  }));
@@ -1929,11 +2742,13 @@ function encodeAttribution(attribution) {
1929
2742
  data: unit.data
1930
2743
  }));
1931
2744
  return {
1932
- roots,
1933
- rootMeta,
1934
- depPaths,
1935
- owners,
1936
- ...units === void 0 ? {} : { units }
2745
+ stored: {
2746
+ roots,
2747
+ rootMeta,
2748
+ depPaths,
2749
+ ...units === void 0 ? {} : { units }
2750
+ },
2751
+ owners
1937
2752
  };
1938
2753
  }
1939
2754
  async function describeAbsentEntry(cacheDir) {
@@ -1950,8 +2765,8 @@ function entryDirFor(cacheDir, input) {
1950
2765
  input.adapterPacksDigest,
1951
2766
  input.configPath ?? ""
1952
2767
  ].join(" ");
1953
- const name = (0, import_node_crypto3.createHash)("sha256").update(key).digest("hex").slice(0, 16);
1954
- return import_node_path5.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}`);
1955
2770
  }
1956
2771
  function isEntryDir(name) {
1957
2772
  return ENTRY_DIR_NAME.test(name);
@@ -1965,11 +2780,11 @@ async function markUsed(entryDir) {
1965
2780
  }
1966
2781
  async function evictOldEntries(cacheDir, keepDir) {
1967
2782
  try {
1968
- await import_promises2.default.rm(import_node_path5.default.join(cacheDir, "manifest.json"), { force: true });
2783
+ await import_promises2.default.rm(import_node_path8.default.join(cacheDir, "manifest.json"), { force: true });
1969
2784
  const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
1970
2785
  const dirs = await Promise.all(
1971
2786
  entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
1972
- const dir = import_node_path5.default.join(cacheDir, e.name);
2787
+ const dir = import_node_path8.default.join(cacheDir, e.name);
1973
2788
  const stat = await import_promises2.default.stat(dir);
1974
2789
  return { dir, mtimeMs: stat.mtimeMs };
1975
2790
  })
@@ -1991,7 +2806,7 @@ var ManifestReads = class {
1991
2806
  last = null;
1992
2807
  async readAtRunStart(manifestPath) {
1993
2808
  const stamp = await manifestStamp(manifestPath);
1994
- const manifest = await readManifest(manifestPath);
2809
+ const manifest = await readManifest2(manifestPath);
1995
2810
  this.last = { path: manifestPath, stamp, manifest };
1996
2811
  return manifest;
1997
2812
  }
@@ -2014,17 +2829,54 @@ async function manifestStamp(manifestPath) {
2014
2829
  return null;
2015
2830
  }
2016
2831
  }
2017
- async function readManifest(manifestPath) {
2832
+ async function readManifest2(manifestPath) {
2018
2833
  try {
2019
- const raw = await import_promises2.default.readFile(manifestPath, "utf-8");
2020
- return JSON.parse(raw);
2834
+ const manifest = (0, import_files3.readJsonFile)(manifestPath);
2835
+ return Array.isArray(manifest.shards) ? manifest : null;
2021
2836
  } catch {
2022
2837
  return null;
2023
2838
  }
2024
2839
  }
2025
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
+
2026
2878
  // src/extractionReport.ts
2027
- var import_behavioral_ir5 = require("@suss/behavioral-ir");
2879
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
2028
2880
  var emptyTally = () => ({
2029
2881
  candidateFiles: 0,
2030
2882
  unitsDiscovered: 0,
@@ -2074,7 +2926,7 @@ function summaryCountsByPack(summaries) {
2074
2926
  }
2075
2927
  const entry = counts.get(recognition) ?? emptyCounts();
2076
2928
  entry.bound += 1;
2077
- if (import_behavioral_ir5.BOUNDARY_ROLE[summary.kind] === "provider") {
2929
+ if (import_behavioral_ir7.BOUNDARY_ROLE[summary.kind] === "provider") {
2078
2930
  entry.providers += 1;
2079
2931
  }
2080
2932
  if (summary.transitions.length > 0) {
@@ -2299,6 +3151,17 @@ function undocumentedDeclarations(gradients) {
2299
3151
  detail: gradient.withoutExample.join(", ")
2300
3152
  }));
2301
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
+ }
2302
3165
  function evaluatePackHealth(report) {
2303
3166
  const gradients = packGradients(report);
2304
3167
  return [
@@ -2451,7 +3314,7 @@ function makeTransitionId(functionName, branch) {
2451
3314
  const { terminal } = branch;
2452
3315
  const statusKey = terminal.statusCode === null ? "none" : terminal.statusCode.type === "literal" ? String(terminal.statusCode.value) : `dyn:${terminal.statusCode.sourceText}`;
2453
3316
  const conditionSig = branch.conditions.map((c) => `${c.polarity}:${c.sourceText}`).join(";");
2454
- 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);
2455
3318
  return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
2456
3319
  }
2457
3320
  function collapseReading(table, reading) {
@@ -2510,6 +3373,7 @@ function unreadSentences(raw) {
2510
3373
  ...raw.branches.flatMap(readingsOfBranch)
2511
3374
  ];
2512
3375
  return [
3376
+ ...raw.readFailure !== void 0 ? [raw.readFailure] : [],
2513
3377
  ...raw.unreadBinding !== void 0 ? [raw.unreadBinding] : [],
2514
3378
  ...handedOver.map(unreadReasonOf).filter((reason) => reason !== null)
2515
3379
  ];
@@ -2519,17 +3383,17 @@ function effectsOfBranch(branch) {
2519
3383
  ...branch.effects.map(effectToIR),
2520
3384
  ...branch.extraEffects ?? []
2521
3385
  ];
2522
- return (0, import_behavioral_ir6.foldRepeatedEffects)(effects.map(withNormalizedCallee));
3386
+ return (0, import_behavioral_ir8.foldRepeatedEffects)(effects.map(withNormalizedCallee));
2523
3387
  }
2524
3388
  function provenanceOf(raw, effects) {
2525
3389
  if (raw.length === 0) {
2526
3390
  return [];
2527
3391
  }
2528
- const positionOf = new Map(effects.map((one, at) => [(0, import_behavioral_ir6.effectKey)(one), at]));
3392
+ const positionOf = new Map(effects.map((one, at) => [(0, import_behavioral_ir8.effectKey)(one), at]));
2529
3393
  const bySlot = /* @__PURE__ */ new Map();
2530
3394
  const said = /* @__PURE__ */ new Map();
2531
3395
  for (const one of raw) {
2532
- const effect = positionOf.get((0, import_behavioral_ir6.effectKey)(withNormalizedCallee(one.effect)));
3396
+ const effect = positionOf.get((0, import_behavioral_ir8.effectKey)(withNormalizedCallee(one.effect)));
2533
3397
  if (effect === void 0) {
2534
3398
  continue;
2535
3399
  }
@@ -2561,7 +3425,7 @@ function withNormalizedCallee(effect) {
2561
3425
  if (effect.callee === void 0) {
2562
3426
  return effect;
2563
3427
  }
2564
- return { ...effect, callee: (0, import_behavioral_ir6.normalizeCalleeText)(effect.callee) };
3428
+ return { ...effect, callee: (0, import_behavioral_ir8.normalizeCalleeText)(effect.callee) };
2565
3429
  }
2566
3430
  function assembleSummary(raw, options = DEFAULT_OPTIONS) {
2567
3431
  const transitions = raw.branches.map((rawBranch) => {
@@ -2626,28 +3490,28 @@ function buildMetadata(raw) {
2626
3490
  let metadata = {};
2627
3491
  const http = buildHttpMetadataValue(raw);
2628
3492
  if (http !== null) {
2629
- metadata = (0, import_behavioral_ir6.withHttpMetadata)(metadata, http);
3493
+ metadata = (0, import_behavioral_ir8.withHttpMetadata)(metadata, http);
2630
3494
  }
2631
3495
  const graphql = buildGraphqlMetadataValue(raw);
2632
3496
  if (graphql !== null) {
2633
- metadata = (0, import_behavioral_ir6.withGraphqlMetadata)(metadata, graphql);
3497
+ metadata = (0, import_behavioral_ir8.withGraphqlMetadata)(metadata, graphql);
2634
3498
  }
2635
3499
  if (raw.sourceDocumentLabel !== void 0) {
2636
- metadata = (0, import_behavioral_ir6.withSourceDocumentMetadata)(metadata, {
3500
+ metadata = (0, import_behavioral_ir8.withSourceDocumentMetadata)(metadata, {
2637
3501
  label: raw.sourceDocumentLabel
2638
3502
  });
2639
3503
  }
2640
3504
  if (raw.mount !== void 0) {
2641
- metadata = (0, import_behavioral_ir6.withMountMetadata)(metadata, raw.mount);
3505
+ metadata = (0, import_behavioral_ir8.withMountMetadata)(metadata, raw.mount);
2642
3506
  }
2643
3507
  if (raw.wrappers !== void 0 && raw.wrappers.length > 0) {
2644
- metadata = (0, import_behavioral_ir6.withWrapperMetadata)(metadata, { applied: raw.wrappers });
3508
+ metadata = (0, import_behavioral_ir8.withWrapperMetadata)(metadata, { applied: raw.wrappers });
2645
3509
  }
2646
3510
  if (raw.requestSpelling !== void 0) {
2647
- metadata = (0, import_behavioral_ir6.withRequestSpellingMetadata)(metadata, raw.requestSpelling);
3511
+ metadata = (0, import_behavioral_ir8.withRequestSpellingMetadata)(metadata, raw.requestSpelling);
2648
3512
  }
2649
3513
  if (raw.test !== void 0) {
2650
- metadata = (0, import_behavioral_ir6.withTestMetadata)(metadata, raw.test);
3514
+ metadata = (0, import_behavioral_ir8.withTestMetadata)(metadata, raw.test);
2651
3515
  }
2652
3516
  return Object.keys(metadata).length > 0 ? metadata : null;
2653
3517
  }
@@ -2672,6 +3536,12 @@ function buildHttpMetadataValue(raw) {
2672
3536
  if (raw.failureDelivery !== void 0) {
2673
3537
  http.failureDelivery = raw.failureDelivery;
2674
3538
  }
3539
+ if (raw.redirectDelivery !== void 0) {
3540
+ http.redirectDelivery = raw.redirectDelivery;
3541
+ }
3542
+ if (raw.declaredStatuses !== void 0 && raw.declaredStatuses.length > 0) {
3543
+ http.declaredStatuses = raw.declaredStatuses;
3544
+ }
2675
3545
  return Object.keys(http).length > 0 ? http : null;
2676
3546
  }
2677
3547
  function buildGraphqlMetadataValue(raw) {
@@ -2700,7 +3570,7 @@ function answersWithHttpResponses(raw) {
2700
3570
  if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
2701
3571
  return true;
2702
3572
  }
2703
- return (0, import_behavioral_ir6.exchangesHttpResponses)(raw.boundaryBinding);
3573
+ return (0, import_behavioral_ir8.exchangesHttpResponses)(raw.boundaryBinding);
2704
3574
  }
2705
3575
  function detectGaps(raw, transitions, options) {
2706
3576
  if (options.gapHandling === "silent") {
@@ -2739,13 +3609,16 @@ function detectGaps(raw, transitions, options) {
2739
3609
  return gaps;
2740
3610
  }
2741
3611
  function bodyWentUnread(raw) {
3612
+ if (raw.readFailure !== void 0) {
3613
+ return true;
3614
+ }
2742
3615
  if (raw.bodyContent === "absent" || raw.bodyContent === "elsewhere") {
2743
3616
  return true;
2744
3617
  }
2745
3618
  return raw.bodyContent === "statements" && raw.branches.length === 0;
2746
3619
  }
2747
3620
  function describeUnreadBody(raw) {
2748
- if (!bodyWentUnread(raw)) {
3621
+ if (!bodyWentUnread(raw) || raw.readFailure !== void 0) {
2749
3622
  return null;
2750
3623
  }
2751
3624
  if (raw.bodyContent === "absent") {
@@ -2817,10 +3690,16 @@ var terminalConverters = {
2817
3690
  headers: {}
2818
3691
  };
2819
3692
  }
3693
+ const ancestry = t.exceptionAncestry;
2820
3694
  return {
2821
3695
  type: "throw",
2822
3696
  exceptionType: t.exceptionType,
2823
- message: t.message
3697
+ message: t.message,
3698
+ ...ancestry === void 0 ? {} : {
3699
+ exceptionAncestors: ancestry.ancestors,
3700
+ ...ancestry.incomplete ? { ancestryIncomplete: true } : {}
3701
+ },
3702
+ ...t.statusWhenUncaught === void 0 ? {} : { statusWhenUncaught: t.statusWhenUncaught }
2824
3703
  };
2825
3704
  },
2826
3705
  render: (t) => ({
@@ -2890,11 +3769,13 @@ function paramToInput(param) {
2890
3769
  }
2891
3770
  // Annotate the CommonJS export names for ESM import in node:
2892
3771
  0 && (module.exports = {
3772
+ CACHE_DIR_ENV,
2893
3773
  EnclosingNodes,
2894
3774
  IdMap,
2895
3775
  IdSet,
2896
3776
  KeptParses,
2897
3777
  MAX_ENTRIES,
3778
+ MAX_HELPER_PATHS,
2898
3779
  MAX_PATHS,
2899
3780
  MODULE_SURFACE_RECOGNITION,
2900
3781
  PathBudgetExceeded,
@@ -2906,6 +3787,7 @@ function paramToInput(param) {
2906
3787
  assembleSummary,
2907
3788
  assessConfidence,
2908
3789
  buildUngatedExtractionReport,
3790
+ chosenCacheRoot,
2909
3791
  commonDirectoryOf,
2910
3792
  composeWrappers,
2911
3793
  computeContentHash,
@@ -2913,6 +3795,7 @@ function paramToInput(param) {
2913
3795
  configuredCallOption,
2914
3796
  createAdapterStamp,
2915
3797
  createCacheLayer,
3798
+ createCodeHasher,
2916
3799
  createPackTallies,
2917
3800
  createTimer,
2918
3801
  detectGaps,
@@ -2921,24 +3804,33 @@ function paramToInput(param) {
2921
3804
  enumerateOrDegrade,
2922
3805
  enumerateStructuredPaths,
2923
3806
  evaluatePackHealth,
3807
+ extractionCacheDirFor,
2924
3808
  extractionConfigStamp,
2925
3809
  firstWrittenReading,
2926
3810
  formatPackHealth,
2927
3811
  guardsHoldOn,
3812
+ helperPathsOnVariables,
2928
3813
  httpRouteDiscovery,
2929
3814
  isListedTestFile,
3815
+ liveRecordFor,
3816
+ makeIgnoredDirectory,
2930
3817
  makeTransitionId,
2931
3818
  mapReading,
2932
3819
  markReturnsAsExitCode,
2933
3820
  matchesTestFileName,
3821
+ mergeReadShapes,
2934
3822
  moduleInitStructure,
2935
3823
  moduleOfFile,
2936
3824
  noopTimer,
3825
+ ownersWhereWritten,
3826
+ packConfigCheck,
2937
3827
  packGradients,
2938
3828
  paramToInput,
3829
+ processCodeHasher,
2939
3830
  projectFileStamp,
2940
3831
  rawConditionToPredicate,
2941
3832
  recordPackFailure,
3833
+ redirectDeliveryWhenSet,
2942
3834
  registrationHelperDiscovery,
2943
3835
  routeHelperIndex,
2944
3836
  runDigest,
@@ -2947,6 +3839,7 @@ function paramToInput(param) {
2947
3839
  settleModules,
2948
3840
  sharedGatingConditions,
2949
3841
  sourceRefsOf,
3842
+ splitAtHandOffs,
2950
3843
  stampMayMissAWrite,
2951
3844
  stampModuleImports,
2952
3845
  stampModules,
@@ -2954,6 +3847,7 @@ function paramToInput(param) {
2954
3847
  summaryCountsByPack,
2955
3848
  tallyUnit,
2956
3849
  terminalToOutput,
3850
+ throughHelper,
2957
3851
  unreadableReading,
2958
3852
  unwrapJsonStringify,
2959
3853
  valueToReadFurtherFrom,