@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.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,
@@ -199,29 +199,82 @@ function composeOne(summary, chain, keepGaps) {
199
199
  if (wrappers.length === 0) {
200
200
  return narrowed;
201
201
  }
202
- const responses = respondedInstead(wrappers);
203
- const handled = handledThrows(wrappers);
204
- const transitions = beside(summary, responses, handled);
202
+ const responses = settledUncaught(respondedInstead(wrappers), wrappers);
203
+ const unitOwn = settledUncaught(summary.transitions, wrappers);
204
+ const own = [...responses, ...unitOwn];
205
+ const handled = handledThrows(wrappers, thrownBy(own));
206
+ const transitions = beside(summary, unitOwn, responses, handled);
205
207
  if (!keepGaps) {
206
208
  return transitions === summary.transitions ? narrowed : { ...narrowed, transitions };
207
209
  }
208
210
  const gaps = withContractStatusGaps(
209
211
  summary,
210
- [...responses, ...summary.transitions],
211
- handled
212
+ own,
213
+ everyHandlerOutcome(wrappers)
212
214
  );
213
215
  if (transitions === summary.transitions && gaps === summary.gaps) {
214
216
  return narrowed;
215
217
  }
216
218
  return { ...narrowed, transitions, gaps };
217
219
  }
218
- function beside(summary, responses, handled) {
219
- const throws = summary.transitions.some((t) => t.output.type === "throw");
220
- const onThrow = throws ? handled : [];
221
- if (responses.length === 0 && onThrow.length === 0) {
220
+ function beside(summary, unitOwn, responses, handled) {
221
+ if (responses.length === 0 && handled.length === 0 && unitOwn === summary.transitions) {
222
222
  return summary.transitions;
223
223
  }
224
- return withDistinctIds([...responses, ...summary.transitions, ...onThrow]);
224
+ return withDistinctIds([...responses, ...unitOwn, ...handled]);
225
+ }
226
+ function settledUncaught(transitions, wrappers) {
227
+ const handlers = wrappers.filter(
228
+ (wrapper) => wrapper.reference.onThrow === true
229
+ );
230
+ let changed = false;
231
+ const settled = transitions.flatMap((transition) => {
232
+ const { output } = transition;
233
+ if (output.type !== "throw") {
234
+ return [transition];
235
+ }
236
+ const caught = caughtOnRoute(handlers, output);
237
+ if (caught === "sure") {
238
+ changed = true;
239
+ return [markedCaught(transition)];
240
+ }
241
+ if (output.statusWhenUncaught === void 0) {
242
+ return [transition];
243
+ }
244
+ changed = true;
245
+ const response = {
246
+ ...transition,
247
+ output: {
248
+ type: "response",
249
+ statusCode: { type: "literal", value: output.statusWhenUncaught },
250
+ body: null,
251
+ headers: {}
252
+ }
253
+ };
254
+ return caught === "uncertain" ? [transition, response] : [response];
255
+ });
256
+ return changed ? settled : transitions;
257
+ }
258
+ function markedCaught(transition) {
259
+ return {
260
+ ...transition,
261
+ metadata: withWrapperMetadata(transition.metadata, {
262
+ ...readWrapperMetadata2(transition),
263
+ caught: true
264
+ })
265
+ };
266
+ }
267
+ function caughtOnRoute(handlers, thrown) {
268
+ if (handlers.some((handler) => handler.reference.catches === void 0)) {
269
+ return "sure";
270
+ }
271
+ const matches = handlers.map(
272
+ (handler) => catchMatch(handler.reference, thrown)
273
+ );
274
+ if (matches.includes("yes")) {
275
+ return "sure";
276
+ }
277
+ return matches.includes("maybe") ? "uncertain" : "no";
225
278
  }
226
279
  function respondedInstead(wrappers) {
227
280
  return wrappers.flatMap(
@@ -233,11 +286,91 @@ function respondedInstead(wrappers) {
233
286
  )
234
287
  );
235
288
  }
236
- function handledThrows(wrappers) {
289
+ function thrownBy(transitions) {
290
+ return transitions.flatMap(
291
+ (t) => t.output.type === "throw" ? [t.output] : []
292
+ );
293
+ }
294
+ function everyHandlerOutcome(wrappers) {
237
295
  return wrappers.flatMap(
238
296
  (wrapper) => wrapper.reference.onThrow === true ? attribute(wrapper.summary.transitions, wrapper.reference) : []
239
297
  );
240
298
  }
299
+ function handledThrows(wrappers, thrown) {
300
+ const caught = catchesAny(wrappers, thrown);
301
+ return [...oncePerHandler(wrappers, caught)].flatMap(
302
+ ([wrapper, how]) => attribute(
303
+ wrapper.summary.transitions,
304
+ wrapper.reference,
305
+ how === "uncertain"
306
+ )
307
+ );
308
+ }
309
+ function oncePerHandler(wrappers, caught) {
310
+ const byHandler = /* @__PURE__ */ new Map();
311
+ for (const wrapper of wrappers) {
312
+ const how = caught.get(wrapper);
313
+ if (how === void 0) {
314
+ continue;
315
+ }
316
+ const earlier = byHandler.get(wrapper.summary);
317
+ if (earlier === void 0 || earlier[1] === "uncertain" && how === "sure") {
318
+ byHandler.set(wrapper.summary, [wrapper, how]);
319
+ }
320
+ }
321
+ return new Map(byHandler.values());
322
+ }
323
+ function catchesAny(wrappers, thrown) {
324
+ const caught = /* @__PURE__ */ new Map();
325
+ if (thrown.length === 0) {
326
+ return caught;
327
+ }
328
+ const handlers = wrappers.filter(
329
+ (wrapper) => wrapper.reference.onThrow === true
330
+ );
331
+ for (const handler of handlers) {
332
+ if (handler.reference.catches === void 0) {
333
+ caught.set(handler, "sure");
334
+ }
335
+ }
336
+ const listing = handlers.filter(
337
+ (handler) => handler.reference.catches !== void 0
338
+ );
339
+ for (const one of thrown) {
340
+ for (const [handler, how] of catchersOf(listing, one)) {
341
+ if (caught.get(handler) !== "sure") {
342
+ caught.set(handler, how);
343
+ }
344
+ }
345
+ }
346
+ return caught;
347
+ }
348
+ function catchersOf(handlers, thrown) {
349
+ const found = [];
350
+ for (const handler of handlers) {
351
+ const match = catchMatch(handler.reference, thrown);
352
+ if (match === "yes") {
353
+ found.push([handler, found.length === 0 ? "sure" : "uncertain"]);
354
+ return found;
355
+ }
356
+ if (match === "maybe") {
357
+ found.push([handler, "uncertain"]);
358
+ }
359
+ }
360
+ return found;
361
+ }
362
+ function catchMatch(reference, thrown) {
363
+ const ancestors = thrown.exceptionAncestors;
364
+ if (thrown.exceptionType === null || ancestors === void 0) {
365
+ return "maybe";
366
+ }
367
+ const classes = /* @__PURE__ */ new Set([thrown.exceptionType, ...ancestors]);
368
+ if ((reference.catches ?? []).some((name) => classes.has(name))) {
369
+ return "yes";
370
+ }
371
+ const unread = thrown.ancestryIncomplete === true && reference.mayCatchUnreadClasses === true;
372
+ return unread || reference.mayCatchAny === true ? "maybe" : "no";
373
+ }
241
374
  function withContractStatusGaps(summary, composed, handled) {
242
375
  const contract = readHttpMetadata(summary)?.declaredContract;
243
376
  const binding = summary.identity.boundaryBinding;
@@ -268,10 +401,13 @@ function coversUnit(reference, summary) {
268
401
  const binding = summary.identity.boundaryBinding;
269
402
  return binding !== null && withinScope(binding, reference.scope);
270
403
  }
271
- function attribute(transitions, reference) {
404
+ function attribute(transitions, reference, catchUncertain = false) {
272
405
  return transitions.map((transition) => ({
273
406
  ...transition,
274
- metadata: withWrapperMetadata(transition.metadata, { from: reference })
407
+ metadata: withWrapperMetadata(transition.metadata, {
408
+ from: reference,
409
+ ...catchUncertain ? { catchUncertain } : {}
410
+ })
275
411
  }));
276
412
  }
277
413
  function withDistinctIds(transitions) {
@@ -384,13 +520,111 @@ function markExitCodeFrom(summary, marked) {
384
520
  summary.metadata = left;
385
521
  }
386
522
 
523
+ // src/helperHandOff.ts
524
+ import { predicateRefs, replacePredicateRefs } from "@suss/behavioral-ir";
525
+ var MAX_HELPER_PATHS = 16;
526
+ function throughHelper(branch, at, helperBranches) {
527
+ return helperBranches.map((helper) => {
528
+ const conditions = [...branch.conditions, ...helper.conditions];
529
+ const expectedInput = mergeReadShapes(
530
+ branch.expectedInput ?? null,
531
+ helper.expectedInput ?? null
532
+ );
533
+ const reads = expectedInput === null ? {} : { expectedInput };
534
+ if (helper.terminal.kind !== "throw" && helper.terminal.kind !== "exit") {
535
+ return {
536
+ ...branch,
537
+ ...reads,
538
+ conditions,
539
+ isDefault: branch.isDefault && helper.isDefault
540
+ };
541
+ }
542
+ return {
543
+ ...branch,
544
+ ...reads,
545
+ conditions,
546
+ terminal: { ...helper.terminal, location: at },
547
+ location: at,
548
+ isDefault: false
549
+ };
550
+ });
551
+ }
552
+ function mergeReadShapes(a, b) {
553
+ if (a === null || b === null) {
554
+ return a ?? b;
555
+ }
556
+ if (a.type !== "record" || b.type !== "record") {
557
+ return a.type === "record" ? a : b;
558
+ }
559
+ const properties = { ...a.properties };
560
+ for (const [name, shape] of Object.entries(b.properties)) {
561
+ const known = properties[name];
562
+ properties[name] = known === void 0 ? shape : mergeReadShapes(known, shape) ?? shape;
563
+ }
564
+ return { ...a, properties };
565
+ }
566
+ function splitAtHandOffs(branch, sites, from = 0) {
567
+ for (let index = from; index < sites.length; index++) {
568
+ const site = sites[index];
569
+ const reached = site.alwaysRuns === true || runsBefore(site.line, branch.terminal.location.end);
570
+ if (!reached || !guardsHoldOn(site.preconditions, branch.conditions)) {
571
+ continue;
572
+ }
573
+ return throughHelper(branch, site.at, site.helper).flatMap(
574
+ (path10) => path10.terminal === branch.terminal ? splitAtHandOffs(path10, sites, index + 1) : [path10]
575
+ );
576
+ }
577
+ return [branch];
578
+ }
579
+ function helperPathsOnVariables(helperBranches, passed) {
580
+ const renamed = helperBranches.map((branch) => ({
581
+ ...branch,
582
+ conditions: branch.conditions.map((condition) => ({
583
+ ...condition,
584
+ structured: condition.structured === null ? null : onCallerVariables(condition.structured, passed)
585
+ }))
586
+ }));
587
+ const variables = new Set(passed.values());
588
+ const testsWhatItGot = renamed.some(
589
+ (branch) => branch.conditions.some(
590
+ (condition) => condition.structured !== null && predicateRefs(condition.structured).some(
591
+ (ref) => variables.has(rootName(ref) ?? "")
592
+ )
593
+ )
594
+ );
595
+ return testsWhatItGot && renamed.length <= MAX_HELPER_PATHS ? renamed : null;
596
+ }
597
+ function onCallerVariables(predicate, passed) {
598
+ const rename = (ref) => {
599
+ if (ref.type === "derived") {
600
+ return { ...ref, from: rename(ref.from) };
601
+ }
602
+ if (ref.type === "input") {
603
+ const name = passed.get(ref.inputRef);
604
+ return name === void 0 ? ref : { type: "dependency", name, accessChain: ref.path };
605
+ }
606
+ if (ref.type === "dependency") {
607
+ const name = passed.get(ref.name);
608
+ return name === void 0 ? ref : { ...ref, name };
609
+ }
610
+ return ref;
611
+ };
612
+ return replacePredicateRefs(predicate, rename);
613
+ }
614
+ function rootName(ref) {
615
+ if (ref.type === "derived") {
616
+ return rootName(ref.from);
617
+ }
618
+ return ref.type === "dependency" ? ref.name : null;
619
+ }
620
+
387
621
  // src/moduleImports.ts
388
- function stampModuleImports(summaries, importsOf) {
622
+ function stampModuleImports(summaries, importsOf2) {
389
623
  const byFile = /* @__PURE__ */ new Map();
390
624
  for (const summary of summaries) {
391
625
  const file = summary.location.file;
392
626
  if (!byFile.has(file)) {
393
- const found = importsOf(file);
627
+ const found = importsOf2(file);
394
628
  byFile.set(
395
629
  file,
396
630
  found === void 0 ? void 0 : [...new Set(found)].sort()
@@ -685,8 +919,8 @@ var catchEntryCond = () => fixedCond(
685
919
  "positive",
686
920
  "catchBlock"
687
921
  );
688
- function classify(path6, home, isAncestorOrSelf) {
689
- return path6.map((cond) => {
922
+ function classify(path10, home, isAncestorOrSelf) {
923
+ return path10.map((cond) => {
690
924
  if (cond.branchStmt === null) {
691
925
  return cond.info;
692
926
  }
@@ -751,9 +985,16 @@ function chargeBudget(state) {
751
985
  );
752
986
  }
753
987
  }
754
- function recordTerminal(ctx, terminal, home, path6) {
988
+ function limitOpenPaths(paths) {
989
+ if (paths.length > MAX_PATHS) {
990
+ throw new PathBudgetExceeded(
991
+ `path budget exceeded, more than ${MAX_PATHS} paths`
992
+ );
993
+ }
994
+ }
995
+ function recordTerminal(ctx, terminal, home, path10) {
755
996
  chargeBudget(ctx.state);
756
- const info = classify(path6, home, ctx.isAncestorOrSelf);
997
+ const info = classify(path10, home, ctx.isAncestorOrSelf);
757
998
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
758
999
  existing.push(info);
759
1000
  ctx.state.byTerminal.set(terminal, existing);
@@ -764,15 +1005,15 @@ function containsTerminal(ctx, stmt) {
764
1005
  }
765
1006
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
766
1007
  }
767
- function stepIf(stmt, path6, ctx) {
1008
+ function stepIf(stmt, path10, ctx) {
768
1009
  const thenExit = exitKindOfList(stmt.thenBody);
769
1010
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
770
1011
  const thenPath = [
771
- ...path6,
1012
+ ...path10,
772
1013
  branchCond(stmt, stmt.condition, "positive", elseExit)
773
1014
  ];
774
1015
  const elsePath = [
775
- ...path6,
1016
+ ...path10,
776
1017
  branchCond(
777
1018
  stmt,
778
1019
  stmt.condition,
@@ -789,7 +1030,7 @@ function stepIf(stmt, path6, ctx) {
789
1030
  }
790
1031
  return out;
791
1032
  }
792
- function stepSwitch(stmt, path6, ctx) {
1033
+ function stepSwitch(stmt, path10, ctx) {
793
1034
  const out = [];
794
1035
  const negations = [];
795
1036
  let defaultRanBody = false;
@@ -799,7 +1040,7 @@ function stepSwitch(stmt, path6, ctx) {
799
1040
  if (hasStrayBreak(group.body)) {
800
1041
  throw new UnmodeledFlow("non-trailing break in switch clause");
801
1042
  }
802
- const groupPath = group.condition === null ? [...path6, ...negations] : [...path6, fixedCond(group.condition, "positive", "explicit")];
1043
+ const groupPath = group.condition === null ? [...path10, ...negations] : [...path10, fixedCond(group.condition, "positive", "explicit")];
803
1044
  const conts = enumerate(ctx, group.body, groupPath);
804
1045
  if (conts.length > 0) {
805
1046
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -816,24 +1057,24 @@ function stepSwitch(stmt, path6, ctx) {
816
1057
  }
817
1058
  }
818
1059
  if (!defaultRanBody) {
819
- out.push([...path6, ...negations]);
1060
+ out.push([...path10, ...negations]);
820
1061
  }
821
1062
  return out;
822
1063
  }
823
- function stepLoop(stmt, path6, ctx) {
1064
+ function stepLoop(stmt, path10, ctx) {
824
1065
  const bodyExit = exitKindOfList(stmt.body);
825
1066
  if (containsTerminal(ctx, stmt)) {
826
1067
  enumerate(ctx, stmt.body, [
827
- ...path6,
1068
+ ...path10,
828
1069
  loopIterationCond(stmt.condition.sourceText)
829
1070
  ]);
830
1071
  }
831
1072
  if (bodyExit !== null) {
832
- return [[...path6, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1073
+ return [[...path10, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
833
1074
  }
834
- return [path6];
1075
+ return [path10];
835
1076
  }
836
- function stepTry(stmt, path6, ctx) {
1077
+ function stepTry(stmt, path10, ctx) {
837
1078
  if (stmt.finallyBody !== null) {
838
1079
  const finallyHasTerminal = stmt.finallyBody.some(
839
1080
  (s) => containsTerminal(ctx, s)
@@ -843,22 +1084,22 @@ function stepTry(stmt, path6, ctx) {
843
1084
  }
844
1085
  }
845
1086
  const out = [];
846
- out.push(...enumerate(ctx, stmt.tryBody, path6));
1087
+ out.push(...enumerate(ctx, stmt.tryBody, path10));
847
1088
  if (stmt.catchBody !== null) {
848
- const catchPath = [...path6, catchEntryCond()];
1089
+ const catchPath = [...path10, catchEntryCond()];
849
1090
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
850
1091
  }
851
1092
  return out;
852
1093
  }
853
- function stepExit(stmt, path6, ctx) {
1094
+ function stepExit(stmt, path10, ctx) {
854
1095
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
855
1096
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
856
- collector.push(path6);
1097
+ collector.push(path10);
857
1098
  }
858
1099
  return [];
859
1100
  }
860
- function stepOpaque(_stmt, path6, _ctx) {
861
- return [path6];
1101
+ function stepOpaque(_stmt, path10, _ctx) {
1102
+ return [path10];
862
1103
  }
863
1104
  var stepHandlers = {
864
1105
  if: stepIf,
@@ -868,22 +1109,23 @@ var stepHandlers = {
868
1109
  exit: stepExit,
869
1110
  opaque: stepOpaque
870
1111
  };
871
- function stepCallback(ctx, body, path6) {
1112
+ function stepCallback(ctx, body, path10) {
872
1113
  const exits = [];
873
1114
  ctx.state.callbackExits.push(exits);
874
1115
  let fellThrough;
875
1116
  try {
876
- fellThrough = enumerate(ctx, body, path6);
1117
+ fellThrough = enumerate(ctx, body, path10);
877
1118
  } finally {
878
1119
  ctx.state.callbackExits.pop();
879
1120
  }
880
1121
  const out = [...fellThrough, ...exits];
881
- return out.length === 0 ? [path6] : out;
1122
+ return out.length === 0 ? [path10] : out;
882
1123
  }
883
- function stepStatement(ctx, stmt, path6) {
884
- let paths = [path6];
1124
+ function stepStatement(ctx, stmt, path10) {
1125
+ let paths = [path10];
885
1126
  for (const body of stmt.callbacks ?? []) {
886
1127
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
1128
+ limitOpenPaths(paths);
887
1129
  }
888
1130
  const ownTerminals = ctx.terminalsByStmt.get(stmt) ?? [];
889
1131
  for (const each of withRejoined(paths)) {
@@ -900,7 +1142,7 @@ function stepStatement(ctx, stmt, path6) {
900
1142
  )
901
1143
  );
902
1144
  }
903
- function soleDisagreement(left, right) {
1145
+ function soleDisagreement(left, right, keepsArms) {
904
1146
  if (left.length !== right.length) {
905
1147
  return null;
906
1148
  }
@@ -919,9 +1161,10 @@ function soleDisagreement(left, right) {
919
1161
  }
920
1162
  found = at;
921
1163
  }
922
- return found;
1164
+ const test = found === null ? null : left[found]?.info.expression ?? null;
1165
+ return test !== null && keepsArms(test) ? null : found;
923
1166
  }
924
- function mergeRejoined(paths) {
1167
+ function mergeRejoined(paths, keepsArms = KEEPS_NO_ARMS) {
925
1168
  let current = paths;
926
1169
  for (let round = 0; round < current.length; round++) {
927
1170
  const out = [];
@@ -936,7 +1179,11 @@ function mergeRejoined(paths) {
936
1179
  if (taken.has(j)) {
937
1180
  continue;
938
1181
  }
939
- const at = soleDisagreement(combined, current[j]);
1182
+ const at = soleDisagreement(
1183
+ combined,
1184
+ current[j],
1185
+ keepsArms
1186
+ );
940
1187
  if (at === null) {
941
1188
  continue;
942
1189
  }
@@ -962,21 +1209,18 @@ function enumerate(ctx, stmts, prefix) {
962
1209
  let frontiers = [[...prefix]];
963
1210
  for (const stmt of stmts) {
964
1211
  const nextFrontiers = [];
965
- for (const path6 of mergeRejoined(frontiers)) {
966
- nextFrontiers.push(...stepStatement(ctx, stmt, path6));
1212
+ for (const path10 of mergeRejoined(frontiers, ctx.keepsArms)) {
1213
+ nextFrontiers.push(...stepStatement(ctx, stmt, path10));
967
1214
  }
968
1215
  frontiers = nextFrontiers;
969
- if (frontiers.length > MAX_PATHS) {
970
- throw new PathBudgetExceeded(
971
- `path budget exceeded, more than ${MAX_PATHS} paths`
972
- );
973
- }
1216
+ limitOpenPaths(frontiers);
974
1217
  if (frontiers.length === 0) {
975
1218
  break;
976
1219
  }
977
1220
  }
978
1221
  return frontiers;
979
1222
  }
1223
+ var KEEPS_NO_ARMS = () => false;
980
1224
  function buildParentMap(statements) {
981
1225
  const parentOf = /* @__PURE__ */ new Map();
982
1226
  const visit = (stmt, parent) => {
@@ -1010,13 +1254,14 @@ function enumerateStructuredPaths(input) {
1010
1254
  };
1011
1255
  const ctx = {
1012
1256
  terminalsByStmt: input.terminalsByStmt,
1257
+ keepsArms: input.keepsArms ?? KEEPS_NO_ARMS,
1013
1258
  state,
1014
1259
  isAncestorOrSelf
1015
1260
  };
1016
1261
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
1017
- for (const path6 of continuations) {
1262
+ for (const path10 of continuations) {
1018
1263
  chargeBudget(state);
1019
- state.fallthrough.push(classify(path6, null, isAncestorOrSelf));
1264
+ state.fallthrough.push(classify(path10, null, isAncestorOrSelf));
1020
1265
  }
1021
1266
  return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
1022
1267
  }
@@ -1059,8 +1304,8 @@ function sharedGatingConditions(paths, readPredicate) {
1059
1304
  return [];
1060
1305
  }
1061
1306
  const shared = new Set(first.map(keyOf2));
1062
- for (const path6 of rest) {
1063
- const here = new Set(path6.map(keyOf2));
1307
+ for (const path10 of rest) {
1308
+ const here = new Set(path10.map(keyOf2));
1064
1309
  for (const value of shared) {
1065
1310
  if (!here.has(value)) {
1066
1311
  shared.delete(value);
@@ -1168,15 +1413,12 @@ var EnclosingNodes = class {
1168
1413
 
1169
1414
  // src/paths/nodeWalk.ts
1170
1415
  var SKIP_CHILDREN = /* @__PURE__ */ Symbol("skipChildren");
1171
- function walkDescendants(root, carried, visitor) {
1172
- for (const child of root.namedChildren) {
1173
- if (child === null) {
1174
- continue;
1175
- }
1416
+ function walkDescendants(root, carried, childrenOf2, visitor) {
1417
+ for (const child of childrenOf2(root)) {
1176
1418
  visitor.at(child, carried);
1177
1419
  const inner = visitor.into(child, carried);
1178
1420
  if (inner !== SKIP_CHILDREN) {
1179
- walkDescendants(child, inner, visitor);
1421
+ walkDescendants(child, inner, childrenOf2, visitor);
1180
1422
  }
1181
1423
  }
1182
1424
  }
@@ -1255,6 +1497,11 @@ function valueToReadFurtherFrom(reading) {
1255
1497
  return reading.kind === "written" ? reading.value : null;
1256
1498
  }
1257
1499
 
1500
+ // src/redirectOption.ts
1501
+ function redirectDeliveryWhenSet(option, value) {
1502
+ return option.handsBack.includes(value) ? "response" : "followed";
1503
+ }
1504
+
1258
1505
  // src/testFiles.ts
1259
1506
  function isListedTestFile(file, listed) {
1260
1507
  if (listed === void 0) {
@@ -1324,17 +1571,179 @@ function quoted(text, key) {
1324
1571
  }
1325
1572
 
1326
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
1327
1580
  import { createHash } from "crypto";
1328
1581
  import fs2 from "fs";
1329
1582
  import { createRequire } from "module";
1330
1583
  import path4 from "path";
1331
- import { fileURLToPath } from "url";
1332
- var ANALYSIS_PACKAGES = [
1333
- "@suss/extractor",
1334
- "@suss/resolution",
1335
- "@suss/datalog",
1336
- "@suss/behavioral-ir"
1337
- ];
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
1338
1747
  var SOURCE_STAMP = { kind: "source" };
1339
1748
  function createAdapterStamp(config) {
1340
1749
  let cachedCodeStamp = null;
@@ -1369,19 +1778,19 @@ function createAdapterStamp(config) {
1369
1778
  function readAdapterCodeStamp(moduleUrl) {
1370
1779
  try {
1371
1780
  const selfPath = fileURLToPath(moduleUrl);
1372
- const hash = computeDistHashFrom(path4.dirname(selfPath));
1781
+ const hash = computeDistHashFrom(path5.dirname(selfPath), processCodeHasher);
1373
1782
  return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
1374
1783
  } catch {
1375
1784
  return SOURCE_STAMP;
1376
1785
  }
1377
1786
  }
1378
- function computeDistHashFrom(dir) {
1379
- 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")];
1380
1789
  for (const candidate of candidates) {
1381
- if (!fs2.existsSync(candidate)) {
1790
+ if (!fs3.existsSync(candidate)) {
1382
1791
  continue;
1383
1792
  }
1384
- return computeContentHash([candidate, ...analysisBundles()]);
1793
+ return hasher.codeHash(candidate);
1385
1794
  }
1386
1795
  return "";
1387
1796
  }
@@ -1389,10 +1798,10 @@ function computeContentHash(paths) {
1389
1798
  if (paths.length === 0) {
1390
1799
  return "";
1391
1800
  }
1392
- const hash = createHash("sha256");
1801
+ const hash = createHash2("sha256");
1393
1802
  for (const file of paths) {
1394
1803
  try {
1395
- hash.update(fs2.readFileSync(file));
1804
+ hash.update(fs3.readFileSync(file));
1396
1805
  } catch {
1397
1806
  return "";
1398
1807
  }
@@ -1403,12 +1812,12 @@ function projectFileStamp(paths) {
1403
1812
  if (paths.length === 0) {
1404
1813
  return "none";
1405
1814
  }
1406
- const hash = createHash("sha256");
1815
+ const hash = createHash2("sha256");
1407
1816
  for (const file of [...paths].sort()) {
1408
1817
  hash.update(file);
1409
1818
  hash.update("\0");
1410
1819
  try {
1411
- hash.update(fs2.readFileSync(file));
1820
+ hash.update(fs3.readFileSync(file));
1412
1821
  } catch {
1413
1822
  hash.update("absent");
1414
1823
  }
@@ -1421,8 +1830,9 @@ function extractionConfigStamp(config) {
1421
1830
  ["includeReachable", optionalText(config.includeReachable)],
1422
1831
  ["workspaceRoot", config.workspaceRoot],
1423
1832
  ["projectRoot", config.projectRoot],
1424
- ["importRoots", config.importRoots?.join(path4.delimiter)],
1425
- ["modules", modulesStamp(config.modules)]
1833
+ ["importRoots", config.importRoots?.join(path5.delimiter)],
1834
+ ["modules", modulesStamp(config.modules)],
1835
+ ["walkedDirectory", config.walkedDirectory]
1426
1836
  ];
1427
1837
  return parts.flatMap(
1428
1838
  ([name, value]) => value === void 0 ? [] : [`${name}=${value}`]
@@ -1435,26 +1845,288 @@ function runDigest(packsDigest, packs, files) {
1435
1845
  const inputs = packs.flatMap((pack) => pack.discoveryInputs?.(files) ?? []);
1436
1846
  return `${packsDigest}|reads:${projectFileStamp(inputs)}`;
1437
1847
  }
1438
- function analysisBundles() {
1439
- const require2 = createRequire(import.meta.url);
1440
- const found = [];
1441
- for (const name of ANALYSIS_PACKAGES) {
1442
- try {
1443
- found.push(require2.resolve(name));
1444
- } 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]);
1445
1929
  }
1446
1930
  }
1447
- 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
+ }
1448
1955
  }
1449
1956
 
1450
- // src/cache.ts
1957
+ // src/cacheShards.ts
1451
1958
  import { createHash as createHash3 } from "crypto";
1452
- import fs4 from "fs/promises";
1453
- import path5 from "path";
1959
+ import fs4 from "fs";
1960
+ import path6 from "path";
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
+ }
1454
2126
 
1455
2127
  // src/cacheStamps.ts
1456
- import { createHash as createHash2 } from "crypto";
1457
- import fs3 from "fs/promises";
2128
+ import { createHash as createHash4 } from "crypto";
2129
+ import fs5 from "fs/promises";
1458
2130
  var RECENT_CHANGE_MS = 2e3;
1459
2131
  function stampMayMissAWrite(stat, statAt) {
1460
2132
  return Math.max(stat.mtimeMs, stat.ctimeMs) > statAt - RECENT_CHANGE_MS;
@@ -1475,7 +2147,7 @@ async function startRun(files, configPath) {
1475
2147
  }
1476
2148
  async function statAtStart(filePath) {
1477
2149
  try {
1478
- const stat = await fs3.stat(filePath);
2150
+ const stat = await fs5.stat(filePath);
1479
2151
  return {
1480
2152
  stamp: { path: filePath, mtimeMs: stat.mtimeMs, size: stat.size },
1481
2153
  ctimeMs: stat.ctimeMs
@@ -1525,7 +2197,7 @@ async function recordedStamp(start, run, prior) {
1525
2197
  return start.stamp;
1526
2198
  }
1527
2199
  const filePath = start.stamp.path;
1528
- const now = await fs3.stat(filePath).catch(() => null);
2200
+ const now = await fs5.stat(filePath).catch(() => null);
1529
2201
  if (now === null || now.mtimeMs !== start.stamp.mtimeMs || now.size !== start.stamp.size || now.ctimeMs !== start.ctimeMs) {
1530
2202
  return null;
1531
2203
  }
@@ -1542,8 +2214,8 @@ function reusableHash(prior, stamp) {
1542
2214
  }
1543
2215
  async function hashFile(filePath) {
1544
2216
  try {
1545
- const content = await fs3.readFile(filePath);
1546
- 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);
1547
2219
  } catch {
1548
2220
  return null;
1549
2221
  }
@@ -1558,11 +2230,30 @@ function fileStampEquals(a, b) {
1558
2230
  return a.path === b.path && a.mtimeMs === b.mtimeMs && a.size === b.size;
1559
2231
  }
1560
2232
 
2233
+ // src/ignoredDirectory.ts
2234
+ import fs6 from "fs";
2235
+ import path7 from "path";
2236
+ function makeIgnoredDirectory(dir) {
2237
+ fs6.mkdirSync(dir, { recursive: true });
2238
+ const ignore = path7.join(dir, ".gitignore");
2239
+ if (!fs6.existsSync(ignore)) {
2240
+ fs6.writeFileSync(ignore, "*\n");
2241
+ }
2242
+ }
2243
+
1561
2244
  // src/cache.ts
1562
- var SCHEMA_VERSION = "9";
2245
+ var SCHEMA_VERSION = "10";
2246
+ var MANIFEST = "manifest.json";
2247
+ var INDEX = "index.json";
1563
2248
  var MAX_ENTRIES = 2;
1564
2249
  var ENTRY_PREFIX = "key-";
1565
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
+ }
1566
2257
  function createCacheLayer(cacheDir) {
1567
2258
  if (cacheDir === null) {
1568
2259
  return {
@@ -1582,7 +2273,7 @@ function createCacheLayer(cacheDir) {
1582
2273
  const lookupWith = async (input, run) => {
1583
2274
  const entryDir = entryDirFor(cacheDir, input);
1584
2275
  const manifest = await manifests.readAtRunStart(
1585
- path5.join(entryDir, "manifest.json")
2276
+ path8.join(entryDir, MANIFEST)
1586
2277
  );
1587
2278
  if (manifest === null) {
1588
2279
  return missDiag(await describeAbsentEntry(cacheDir));
@@ -1597,11 +2288,15 @@ function createCacheLayer(cacheDir) {
1597
2288
  if (!await filesStillMatch(manifest.files, run)) {
1598
2289
  return missDiag("files-changed");
1599
2290
  }
2291
+ const summaries = storedSummaries(entryDir, manifest);
2292
+ if (summaries === null) {
2293
+ return missDiag("no-manifest");
2294
+ }
1600
2295
  await markUsed(entryDir);
1601
2296
  manifests.forget();
1602
2297
  return {
1603
2298
  kind: "hit",
1604
- summaries: manifest.summaries,
2299
+ summaries,
1605
2300
  diagnostic: { kind: "hit" }
1606
2301
  };
1607
2302
  };
@@ -1621,10 +2316,8 @@ function createCacheLayer(cacheDir) {
1621
2316
  },
1622
2317
  async plan(input) {
1623
2318
  const entryDir = entryDirFor(cacheDir, input);
1624
- const manifest = await manifests.readAgain(
1625
- path5.join(entryDir, "manifest.json")
1626
- );
1627
- 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) {
1628
2321
  return null;
1629
2322
  }
1630
2323
  const run = await runFor(input);
@@ -1635,14 +2328,17 @@ function createCacheLayer(cacheDir) {
1635
2328
  )) {
1636
2329
  return null;
1637
2330
  }
2331
+ const summaries = storedSummaries(entryDir, manifest);
2332
+ const records = storedRecords(entryDir, manifest);
2333
+ if (summaries === null || records === null) {
2334
+ return null;
2335
+ }
1638
2336
  await markUsed(entryDir);
1639
- return buildPlan(manifest, run);
2337
+ return buildPlan(manifest, records, summaries, run);
1640
2338
  },
1641
2339
  async write(input, summaries, attribution) {
1642
2340
  const entryDir = entryDirFor(cacheDir, input);
1643
- const previous = await manifests.readAgain(
1644
- path5.join(entryDir, "manifest.json")
1645
- );
2341
+ const previous = await manifests.readAgain(path8.join(entryDir, MANIFEST));
1646
2342
  manifests.forget();
1647
2343
  const known = runs.get(input);
1648
2344
  const run = known ?? await runFor(input);
@@ -1658,58 +2354,119 @@ function createCacheLayer(cacheDir) {
1658
2354
  )
1659
2355
  );
1660
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);
1661
2363
  const manifest = {
1662
2364
  schemaVersion: SCHEMA_VERSION,
1663
2365
  adapterPacksDigest: input.adapterPacksDigest,
1664
2366
  configStamp,
1665
2367
  files,
1666
- summaries,
1667
- ...attribution === void 0 ? {} : encodeAttribution(attribution)
2368
+ summaryCount: summaries.length,
2369
+ shards,
2370
+ ...encoded === null ? {} : encoded.stored
1668
2371
  };
1669
- await fs4.mkdir(entryDir, { recursive: true });
1670
- await fs4.writeFile(
1671
- path5.join(entryDir, "manifest.json"),
1672
- JSON.stringify(manifest)
2372
+ writeDocumentFile2(path8.join(entryDir, MANIFEST), manifest);
2373
+ removeStoredOutside(
2374
+ entryDir,
2375
+ /* @__PURE__ */ new Set([...storedIds(manifest), ...storedIds(previous)])
1673
2376
  );
1674
2377
  await evictOldEntries(cacheDir, entryDir);
1675
2378
  }
1676
2379
  };
1677
2380
  }
1678
- async function buildPlan(manifest, run) {
1679
- const stored = new Map(manifest.files.map((f) => [f.path, f]));
1680
- const current = new Map(run.files.map((f) => [f.stamp.path, f.stamp]));
1681
- const changed = /* @__PURE__ */ new Set();
1682
- const added = /* @__PURE__ */ new Set();
1683
- const removed = /* @__PURE__ */ new Set();
1684
- for (const p of stored.keys()) {
1685
- if (!current.has(p)) {
1686
- 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;
1687
2439
  }
2440
+ shard.order.forEach((position, i) => {
2441
+ summaries[position] = content.units[i];
2442
+ });
1688
2443
  }
1689
- const toVerify = [];
1690
- for (const [p, stamp] of current) {
1691
- const before = stored.get(p);
1692
- if (before === void 0) {
1693
- changed.add(p);
1694
- added.add(p);
1695
- } else if (!fileStampEquals(before, stamp)) {
1696
- if (before.contentHash === void 0) {
1697
- changed.add(p);
1698
- } else {
1699
- toVerify.push(p);
1700
- }
1701
- } else if (before.verifyHash === true) {
1702
- toVerify.push(p);
2444
+ for (let position = 0; position < summaries.length; position++) {
2445
+ if (summaries[position] === void 0) {
2446
+ return null;
1703
2447
  }
1704
2448
  }
1705
- await Promise.all(
1706
- toVerify.map(async (p) => {
1707
- const hash = await hashOf(run, p);
1708
- if (hash === null || hash !== stored.get(p)?.contentHash) {
1709
- changed.add(p);
1710
- }
1711
- })
1712
- );
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);
1713
2470
  const roots = /* @__PURE__ */ new Map();
1714
2471
  const rootNames = manifest.roots ?? [];
1715
2472
  const depPaths = manifest.depPaths ?? [];
@@ -1719,23 +2476,24 @@ async function buildPlan(manifest, run) {
1719
2476
  });
1720
2477
  let rootsDeclined = 0;
1721
2478
  rootNames.forEach((rootPath, i) => {
1722
- const meta = manifest.rootMeta?.[i];
1723
- if (meta === void 0) {
2479
+ const record = manifest.rootMeta?.[i];
2480
+ if (record === void 0) {
1724
2481
  return;
1725
2482
  }
1726
- if (!meta.cacheable) {
2483
+ if (!record.cacheable) {
1727
2484
  rootsDeclined += 1;
1728
2485
  }
1729
2486
  roots.set(rootPath, {
1730
2487
  path: rootPath,
1731
- cacheable: meta.cacheable,
1732
- deps: pathsOf(meta.deps),
1733
- claims: meta.claims,
1734
- meta: meta.meta,
1735
- packs: meta.packs,
1736
- ...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) }
1737
2494
  });
1738
2495
  });
2496
+ const current = new Set(run.files.map((f) => f.stamp.path));
1739
2497
  const unchanged = (filePath, deps) => current.has(filePath) && !changed.has(filePath) && !deps.some((d) => changed.has(d) || removed.has(d));
1740
2498
  const validRoots = /* @__PURE__ */ new Set();
1741
2499
  for (const [rootPath, record] of roots) {
@@ -1743,14 +2501,14 @@ async function buildPlan(manifest, run) {
1743
2501
  validRoots.add(rootPath);
1744
2502
  }
1745
2503
  }
1746
- const units = (manifest.units ?? []).flatMap((stored2) => {
1747
- const file = depPaths[stored2.file];
2504
+ const units = (manifest.units ?? []).flatMap((unit) => {
2505
+ const file = depPaths[unit.file];
1748
2506
  return file === void 0 ? [] : [
1749
2507
  {
1750
- key: stored2.key,
2508
+ key: unit.key,
1751
2509
  file,
1752
- deps: pathsOf(stored2.deps),
1753
- data: stored2.data
2510
+ deps: pathsOf(unit.deps),
2511
+ data: unit.data
1754
2512
  }
1755
2513
  ];
1756
2514
  });
@@ -1768,7 +2526,7 @@ async function buildPlan(manifest, run) {
1768
2526
  }
1769
2527
  }
1770
2528
  }
1771
- const owners = manifest.owners ?? [];
2529
+ const ownerPaths = ownersInRunOrder(manifest);
1772
2530
  return {
1773
2531
  changed,
1774
2532
  removed,
@@ -1780,36 +2538,81 @@ async function buildPlan(manifest, run) {
1780
2538
  reuse(valid) {
1781
2539
  const summaries = [];
1782
2540
  const reusedOwners = [];
1783
- manifest.summaries.forEach((summary, i) => {
1784
- const ownerPaths = (owners[i] ?? []).flatMap((o) => {
1785
- const p = rootNames[o];
1786
- return p === void 0 ? [] : [p];
1787
- });
1788
- 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))) {
1789
2544
  summaries.push(summary);
1790
- reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
2545
+ reusedOwners.push(owners.filter((p) => valid.has(p)));
1791
2546
  }
1792
2547
  });
1793
2548
  return { summaries, owners: reusedOwners };
1794
2549
  },
1795
2550
  allSummaries() {
1796
- return manifest.summaries;
2551
+ return stored;
1797
2552
  },
1798
2553
  attribution() {
1799
2554
  return {
1800
2555
  roots: [...roots.values()],
1801
- owners: owners.map(
1802
- (ownerIds) => ownerIds.flatMap((o) => {
1803
- const p = rootNames[o];
1804
- return p === void 0 ? [] : [p];
1805
- })
1806
- ),
2556
+ owners: ownerPaths,
1807
2557
  units
1808
2558
  };
1809
2559
  }
1810
2560
  };
1811
2561
  }
1812
- 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) {
1813
2616
  const roots = attribution.roots.map((r) => r.path);
1814
2617
  const rootIndex = new Map(roots.map((p, i) => [p, i]));
1815
2618
  const depIndex = /* @__PURE__ */ new Map();
@@ -1825,10 +2628,10 @@ function encodeAttribution(attribution) {
1825
2628
  return id;
1826
2629
  };
1827
2630
  const rootMeta = attribution.roots.map((r) => ({
2631
+ ...r.meta === void 0 ? {} : { record: storeRecord(r.meta) },
1828
2632
  cacheable: r.cacheable,
1829
2633
  deps: r.deps.map(depIdOf),
1830
2634
  claims: r.claims,
1831
- meta: r.meta,
1832
2635
  packs: r.packs,
1833
2636
  ...r.imports === void 0 ? {} : { imports: r.imports.map(depIdOf) }
1834
2637
  }));
@@ -1845,16 +2648,18 @@ function encodeAttribution(attribution) {
1845
2648
  data: unit.data
1846
2649
  }));
1847
2650
  return {
1848
- roots,
1849
- rootMeta,
1850
- depPaths,
1851
- owners,
1852
- ...units === void 0 ? {} : { units }
2651
+ stored: {
2652
+ roots,
2653
+ rootMeta,
2654
+ depPaths,
2655
+ ...units === void 0 ? {} : { units }
2656
+ },
2657
+ owners
1853
2658
  };
1854
2659
  }
1855
2660
  async function describeAbsentEntry(cacheDir) {
1856
2661
  try {
1857
- const entries = await fs4.readdir(cacheDir, { withFileTypes: true });
2662
+ const entries = await fs7.readdir(cacheDir, { withFileTypes: true });
1858
2663
  return entries.some((e) => e.isDirectory() && isEntryDir(e.name)) ? "key-changed" : "no-manifest";
1859
2664
  } catch {
1860
2665
  return "no-manifest";
@@ -1866,8 +2671,8 @@ function entryDirFor(cacheDir, input) {
1866
2671
  input.adapterPacksDigest,
1867
2672
  input.configPath ?? ""
1868
2673
  ].join(" ");
1869
- const name = createHash3("sha256").update(key).digest("hex").slice(0, 16);
1870
- return path5.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}`);
1871
2676
  }
1872
2677
  function isEntryDir(name) {
1873
2678
  return ENTRY_DIR_NAME.test(name);
@@ -1875,24 +2680,24 @@ function isEntryDir(name) {
1875
2680
  async function markUsed(entryDir) {
1876
2681
  const now = /* @__PURE__ */ new Date();
1877
2682
  try {
1878
- await fs4.utimes(entryDir, now, now);
2683
+ await fs7.utimes(entryDir, now, now);
1879
2684
  } catch {
1880
2685
  }
1881
2686
  }
1882
2687
  async function evictOldEntries(cacheDir, keepDir) {
1883
2688
  try {
1884
- await fs4.rm(path5.join(cacheDir, "manifest.json"), { force: true });
1885
- const entries = await fs4.readdir(cacheDir, { withFileTypes: true });
2689
+ await fs7.rm(path8.join(cacheDir, "manifest.json"), { force: true });
2690
+ const entries = await fs7.readdir(cacheDir, { withFileTypes: true });
1886
2691
  const dirs = await Promise.all(
1887
2692
  entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
1888
- const dir = path5.join(cacheDir, e.name);
1889
- const stat = await fs4.stat(dir);
2693
+ const dir = path8.join(cacheDir, e.name);
2694
+ const stat = await fs7.stat(dir);
1890
2695
  return { dir, mtimeMs: stat.mtimeMs };
1891
2696
  })
1892
2697
  );
1893
2698
  const doomed = dirs.filter((d) => d.dir !== keepDir).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(MAX_ENTRIES - 1);
1894
2699
  await Promise.all(
1895
- doomed.map((d) => fs4.rm(d.dir, { recursive: true, force: true }))
2700
+ doomed.map((d) => fs7.rm(d.dir, { recursive: true, force: true }))
1896
2701
  );
1897
2702
  } catch {
1898
2703
  }
@@ -1907,7 +2712,7 @@ var ManifestReads = class {
1907
2712
  last = null;
1908
2713
  async readAtRunStart(manifestPath) {
1909
2714
  const stamp = await manifestStamp(manifestPath);
1910
- const manifest = await readManifest(manifestPath);
2715
+ const manifest = await readManifest2(manifestPath);
1911
2716
  this.last = { path: manifestPath, stamp, manifest };
1912
2717
  return manifest;
1913
2718
  }
@@ -1924,23 +2729,60 @@ var ManifestReads = class {
1924
2729
  };
1925
2730
  async function manifestStamp(manifestPath) {
1926
2731
  try {
1927
- const stat = await fs4.stat(manifestPath);
2732
+ const stat = await fs7.stat(manifestPath);
1928
2733
  return `${stat.ino}:${stat.mtimeMs}:${stat.size}`;
1929
2734
  } catch {
1930
2735
  return null;
1931
2736
  }
1932
2737
  }
1933
- async function readManifest(manifestPath) {
2738
+ async function readManifest2(manifestPath) {
1934
2739
  try {
1935
- const raw = await fs4.readFile(manifestPath, "utf-8");
1936
- return JSON.parse(raw);
2740
+ const manifest = readJsonFile3(manifestPath);
2741
+ return Array.isArray(manifest.shards) ? manifest : null;
1937
2742
  } catch {
1938
2743
  return null;
1939
2744
  }
1940
2745
  }
1941
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
+
1942
2784
  // src/extractionReport.ts
1943
- import { BOUNDARY_ROLE } from "@suss/behavioral-ir";
2785
+ import { BOUNDARY_ROLE as BOUNDARY_ROLE2 } from "@suss/behavioral-ir";
1944
2786
  var emptyTally = () => ({
1945
2787
  candidateFiles: 0,
1946
2788
  unitsDiscovered: 0,
@@ -1990,7 +2832,7 @@ function summaryCountsByPack(summaries) {
1990
2832
  }
1991
2833
  const entry = counts.get(recognition) ?? emptyCounts();
1992
2834
  entry.bound += 1;
1993
- if (BOUNDARY_ROLE[summary.kind] === "provider") {
2835
+ if (BOUNDARY_ROLE2[summary.kind] === "provider") {
1994
2836
  entry.providers += 1;
1995
2837
  }
1996
2838
  if (summary.transitions.length > 0) {
@@ -2215,6 +3057,17 @@ function undocumentedDeclarations(gradients) {
2215
3057
  detail: gradient.withoutExample.join(", ")
2216
3058
  }));
2217
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
+ }
2218
3071
  function evaluatePackHealth(report) {
2219
3072
  const gradients = packGradients(report);
2220
3073
  return [
@@ -2367,7 +3220,7 @@ function makeTransitionId(functionName, branch) {
2367
3220
  const { terminal } = branch;
2368
3221
  const statusKey = terminal.statusCode === null ? "none" : terminal.statusCode.type === "literal" ? String(terminal.statusCode.value) : `dyn:${terminal.statusCode.sourceText}`;
2369
3222
  const conditionSig = branch.conditions.map((c) => `${c.polarity}:${c.sourceText}`).join(";");
2370
- const conditionHash = createHash4("sha1").update(conditionSig).digest("hex").slice(0, 7);
3223
+ const conditionHash = createHash7("sha1").update(conditionSig).digest("hex").slice(0, 7);
2371
3224
  return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
2372
3225
  }
2373
3226
  function collapseReading(table, reading) {
@@ -2426,6 +3279,7 @@ function unreadSentences(raw) {
2426
3279
  ...raw.branches.flatMap(readingsOfBranch)
2427
3280
  ];
2428
3281
  return [
3282
+ ...raw.readFailure !== void 0 ? [raw.readFailure] : [],
2429
3283
  ...raw.unreadBinding !== void 0 ? [raw.unreadBinding] : [],
2430
3284
  ...handedOver.map(unreadReasonOf).filter((reason) => reason !== null)
2431
3285
  ];
@@ -2588,6 +3442,12 @@ function buildHttpMetadataValue(raw) {
2588
3442
  if (raw.failureDelivery !== void 0) {
2589
3443
  http.failureDelivery = raw.failureDelivery;
2590
3444
  }
3445
+ if (raw.redirectDelivery !== void 0) {
3446
+ http.redirectDelivery = raw.redirectDelivery;
3447
+ }
3448
+ if (raw.declaredStatuses !== void 0 && raw.declaredStatuses.length > 0) {
3449
+ http.declaredStatuses = raw.declaredStatuses;
3450
+ }
2591
3451
  return Object.keys(http).length > 0 ? http : null;
2592
3452
  }
2593
3453
  function buildGraphqlMetadataValue(raw) {
@@ -2655,13 +3515,16 @@ function detectGaps(raw, transitions, options) {
2655
3515
  return gaps;
2656
3516
  }
2657
3517
  function bodyWentUnread(raw) {
3518
+ if (raw.readFailure !== void 0) {
3519
+ return true;
3520
+ }
2658
3521
  if (raw.bodyContent === "absent" || raw.bodyContent === "elsewhere") {
2659
3522
  return true;
2660
3523
  }
2661
3524
  return raw.bodyContent === "statements" && raw.branches.length === 0;
2662
3525
  }
2663
3526
  function describeUnreadBody(raw) {
2664
- if (!bodyWentUnread(raw)) {
3527
+ if (!bodyWentUnread(raw) || raw.readFailure !== void 0) {
2665
3528
  return null;
2666
3529
  }
2667
3530
  if (raw.bodyContent === "absent") {
@@ -2733,10 +3596,16 @@ var terminalConverters = {
2733
3596
  headers: {}
2734
3597
  };
2735
3598
  }
3599
+ const ancestry = t.exceptionAncestry;
2736
3600
  return {
2737
3601
  type: "throw",
2738
3602
  exceptionType: t.exceptionType,
2739
- message: t.message
3603
+ message: t.message,
3604
+ ...ancestry === void 0 ? {} : {
3605
+ exceptionAncestors: ancestry.ancestors,
3606
+ ...ancestry.incomplete ? { ancestryIncomplete: true } : {}
3607
+ },
3608
+ ...t.statusWhenUncaught === void 0 ? {} : { statusWhenUncaught: t.statusWhenUncaught }
2740
3609
  };
2741
3610
  },
2742
3611
  render: (t) => ({
@@ -2805,11 +3674,13 @@ function paramToInput(param) {
2805
3674
  };
2806
3675
  }
2807
3676
  export {
3677
+ CACHE_DIR_ENV,
2808
3678
  EnclosingNodes,
2809
3679
  IdMap,
2810
3680
  IdSet,
2811
3681
  KeptParses,
2812
3682
  MAX_ENTRIES,
3683
+ MAX_HELPER_PATHS,
2813
3684
  MAX_PATHS,
2814
3685
  MODULE_SURFACE_RECOGNITION,
2815
3686
  PathBudgetExceeded,
@@ -2821,6 +3692,7 @@ export {
2821
3692
  assembleSummary,
2822
3693
  assessConfidence,
2823
3694
  buildUngatedExtractionReport,
3695
+ chosenCacheRoot,
2824
3696
  commonDirectoryOf,
2825
3697
  composeWrappers,
2826
3698
  computeContentHash,
@@ -2828,6 +3700,7 @@ export {
2828
3700
  configuredCallOption,
2829
3701
  createAdapterStamp,
2830
3702
  createCacheLayer,
3703
+ createCodeHasher,
2831
3704
  createPackTallies,
2832
3705
  createTimer,
2833
3706
  detectGaps,
@@ -2836,24 +3709,33 @@ export {
2836
3709
  enumerateOrDegrade,
2837
3710
  enumerateStructuredPaths,
2838
3711
  evaluatePackHealth,
3712
+ extractionCacheDirFor,
2839
3713
  extractionConfigStamp,
2840
3714
  firstWrittenReading,
2841
3715
  formatPackHealth,
2842
3716
  guardsHoldOn,
3717
+ helperPathsOnVariables,
2843
3718
  httpRouteDiscovery,
2844
3719
  isListedTestFile,
3720
+ liveRecordFor,
3721
+ makeIgnoredDirectory,
2845
3722
  makeTransitionId,
2846
3723
  mapReading,
2847
3724
  markReturnsAsExitCode,
2848
3725
  matchesTestFileName,
3726
+ mergeReadShapes,
2849
3727
  moduleInitStructure,
2850
3728
  moduleOfFile,
2851
3729
  noopTimer,
3730
+ ownersWhereWritten,
3731
+ packConfigCheck,
2852
3732
  packGradients,
2853
3733
  paramToInput,
3734
+ processCodeHasher,
2854
3735
  projectFileStamp,
2855
3736
  rawConditionToPredicate,
2856
3737
  recordPackFailure,
3738
+ redirectDeliveryWhenSet,
2857
3739
  registrationHelperDiscovery,
2858
3740
  routeHelperIndex,
2859
3741
  runDigest,
@@ -2862,6 +3744,7 @@ export {
2862
3744
  settleModules,
2863
3745
  sharedGatingConditions,
2864
3746
  sourceRefsOf,
3747
+ splitAtHandOffs,
2865
3748
  stampMayMissAWrite,
2866
3749
  stampModuleImports,
2867
3750
  stampModules,
@@ -2869,6 +3752,7 @@ export {
2869
3752
  summaryCountsByPack,
2870
3753
  tallyUnit,
2871
3754
  terminalToOutput,
3755
+ throughHelper,
2872
3756
  unreadableReading,
2873
3757
  unwrapJsonStringify,
2874
3758
  valueToReadFurtherFrom,