@suss/extractor 0.34.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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,6 +520,104 @@ 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
+ (path7) => path7.terminal === branch.terminal ? splitAtHandOffs(path7, sites, index + 1) : [path7]
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
622
  function stampModuleImports(summaries, importsOf) {
389
623
  const byFile = /* @__PURE__ */ new Map();
@@ -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(path7, home, isAncestorOrSelf) {
923
+ return path7.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, path7) {
755
996
  chargeBudget(ctx.state);
756
- const info = classify(path6, home, ctx.isAncestorOrSelf);
997
+ const info = classify(path7, 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, path7, 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
+ ...path7,
772
1013
  branchCond(stmt, stmt.condition, "positive", elseExit)
773
1014
  ];
774
1015
  const elsePath = [
775
- ...path6,
1016
+ ...path7,
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, path7, 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 ? [...path7, ...negations] : [...path7, 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([...path7, ...negations]);
820
1061
  }
821
1062
  return out;
822
1063
  }
823
- function stepLoop(stmt, path6, ctx) {
1064
+ function stepLoop(stmt, path7, ctx) {
824
1065
  const bodyExit = exitKindOfList(stmt.body);
825
1066
  if (containsTerminal(ctx, stmt)) {
826
1067
  enumerate(ctx, stmt.body, [
827
- ...path6,
1068
+ ...path7,
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 [[...path7, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
833
1074
  }
834
- return [path6];
1075
+ return [path7];
835
1076
  }
836
- function stepTry(stmt, path6, ctx) {
1077
+ function stepTry(stmt, path7, 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, path7));
847
1088
  if (stmt.catchBody !== null) {
848
- const catchPath = [...path6, catchEntryCond()];
1089
+ const catchPath = [...path7, 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, path7, 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(path7);
857
1098
  }
858
1099
  return [];
859
1100
  }
860
- function stepOpaque(_stmt, path6, _ctx) {
861
- return [path6];
1101
+ function stepOpaque(_stmt, path7, _ctx) {
1102
+ return [path7];
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, path7) {
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, path7);
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 ? [path7] : out;
882
1123
  }
883
- function stepStatement(ctx, stmt, path6) {
884
- let paths = [path6];
1124
+ function stepStatement(ctx, stmt, path7) {
1125
+ let paths = [path7];
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 path7 of mergeRejoined(frontiers, ctx.keepsArms)) {
1213
+ nextFrontiers.push(...stepStatement(ctx, stmt, path7));
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 path7 of continuations) {
1018
1263
  chargeBudget(state);
1019
- state.fallthrough.push(classify(path6, null, isAncestorOrSelf));
1264
+ state.fallthrough.push(classify(path7, 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 path7 of rest) {
1308
+ const here = new Set(path7.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) {
@@ -1422,7 +1669,8 @@ function extractionConfigStamp(config) {
1422
1669
  ["workspaceRoot", config.workspaceRoot],
1423
1670
  ["projectRoot", config.projectRoot],
1424
1671
  ["importRoots", config.importRoots?.join(path4.delimiter)],
1425
- ["modules", modulesStamp(config.modules)]
1672
+ ["modules", modulesStamp(config.modules)],
1673
+ ["walkedDirectory", config.walkedDirectory]
1426
1674
  ];
1427
1675
  return parts.flatMap(
1428
1676
  ([name, value]) => value === void 0 ? [] : [`${name}=${value}`]
@@ -1449,8 +1697,9 @@ function analysisBundles() {
1449
1697
 
1450
1698
  // src/cache.ts
1451
1699
  import { createHash as createHash3 } from "crypto";
1452
- import fs4 from "fs/promises";
1453
- import path5 from "path";
1700
+ import fs5 from "fs/promises";
1701
+ import path6 from "path";
1702
+ import { readJsonFile, writeDocumentFile } from "@suss/behavioral-ir/files";
1454
1703
 
1455
1704
  // src/cacheStamps.ts
1456
1705
  import { createHash as createHash2 } from "crypto";
@@ -1558,6 +1807,17 @@ function fileStampEquals(a, b) {
1558
1807
  return a.path === b.path && a.mtimeMs === b.mtimeMs && a.size === b.size;
1559
1808
  }
1560
1809
 
1810
+ // src/ignoredDirectory.ts
1811
+ import fs4 from "fs";
1812
+ import path5 from "path";
1813
+ function makeIgnoredDirectory(dir) {
1814
+ fs4.mkdirSync(dir, { recursive: true });
1815
+ const ignore = path5.join(dir, ".gitignore");
1816
+ if (!fs4.existsSync(ignore)) {
1817
+ fs4.writeFileSync(ignore, "*\n");
1818
+ }
1819
+ }
1820
+
1561
1821
  // src/cache.ts
1562
1822
  var SCHEMA_VERSION = "9";
1563
1823
  var MAX_ENTRIES = 2;
@@ -1582,7 +1842,7 @@ function createCacheLayer(cacheDir) {
1582
1842
  const lookupWith = async (input, run) => {
1583
1843
  const entryDir = entryDirFor(cacheDir, input);
1584
1844
  const manifest = await manifests.readAtRunStart(
1585
- path5.join(entryDir, "manifest.json")
1845
+ path6.join(entryDir, "manifest.json")
1586
1846
  );
1587
1847
  if (manifest === null) {
1588
1848
  return missDiag(await describeAbsentEntry(cacheDir));
@@ -1622,7 +1882,7 @@ function createCacheLayer(cacheDir) {
1622
1882
  async plan(input) {
1623
1883
  const entryDir = entryDirFor(cacheDir, input);
1624
1884
  const manifest = await manifests.readAgain(
1625
- path5.join(entryDir, "manifest.json")
1885
+ path6.join(entryDir, "manifest.json")
1626
1886
  );
1627
1887
  if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
1628
1888
  return null;
@@ -1641,7 +1901,7 @@ function createCacheLayer(cacheDir) {
1641
1901
  async write(input, summaries, attribution) {
1642
1902
  const entryDir = entryDirFor(cacheDir, input);
1643
1903
  const previous = await manifests.readAgain(
1644
- path5.join(entryDir, "manifest.json")
1904
+ path6.join(entryDir, "manifest.json")
1645
1905
  );
1646
1906
  manifests.forget();
1647
1907
  const known = runs.get(input);
@@ -1666,11 +1926,8 @@ function createCacheLayer(cacheDir) {
1666
1926
  summaries,
1667
1927
  ...attribution === void 0 ? {} : encodeAttribution(attribution)
1668
1928
  };
1669
- await fs4.mkdir(entryDir, { recursive: true });
1670
- await fs4.writeFile(
1671
- path5.join(entryDir, "manifest.json"),
1672
- JSON.stringify(manifest)
1673
- );
1929
+ makeIgnoredDirectory(cacheDir);
1930
+ writeDocumentFile(path6.join(entryDir, "manifest.json"), manifest);
1674
1931
  await evictOldEntries(cacheDir, entryDir);
1675
1932
  }
1676
1933
  };
@@ -1854,7 +2111,7 @@ function encodeAttribution(attribution) {
1854
2111
  }
1855
2112
  async function describeAbsentEntry(cacheDir) {
1856
2113
  try {
1857
- const entries = await fs4.readdir(cacheDir, { withFileTypes: true });
2114
+ const entries = await fs5.readdir(cacheDir, { withFileTypes: true });
1858
2115
  return entries.some((e) => e.isDirectory() && isEntryDir(e.name)) ? "key-changed" : "no-manifest";
1859
2116
  } catch {
1860
2117
  return "no-manifest";
@@ -1867,7 +2124,7 @@ function entryDirFor(cacheDir, input) {
1867
2124
  input.configPath ?? ""
1868
2125
  ].join(" ");
1869
2126
  const name = createHash3("sha256").update(key).digest("hex").slice(0, 16);
1870
- return path5.join(cacheDir, `${ENTRY_PREFIX}${name}`);
2127
+ return path6.join(cacheDir, `${ENTRY_PREFIX}${name}`);
1871
2128
  }
1872
2129
  function isEntryDir(name) {
1873
2130
  return ENTRY_DIR_NAME.test(name);
@@ -1875,24 +2132,24 @@ function isEntryDir(name) {
1875
2132
  async function markUsed(entryDir) {
1876
2133
  const now = /* @__PURE__ */ new Date();
1877
2134
  try {
1878
- await fs4.utimes(entryDir, now, now);
2135
+ await fs5.utimes(entryDir, now, now);
1879
2136
  } catch {
1880
2137
  }
1881
2138
  }
1882
2139
  async function evictOldEntries(cacheDir, keepDir) {
1883
2140
  try {
1884
- await fs4.rm(path5.join(cacheDir, "manifest.json"), { force: true });
1885
- const entries = await fs4.readdir(cacheDir, { withFileTypes: true });
2141
+ await fs5.rm(path6.join(cacheDir, "manifest.json"), { force: true });
2142
+ const entries = await fs5.readdir(cacheDir, { withFileTypes: true });
1886
2143
  const dirs = await Promise.all(
1887
2144
  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);
2145
+ const dir = path6.join(cacheDir, e.name);
2146
+ const stat = await fs5.stat(dir);
1890
2147
  return { dir, mtimeMs: stat.mtimeMs };
1891
2148
  })
1892
2149
  );
1893
2150
  const doomed = dirs.filter((d) => d.dir !== keepDir).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(MAX_ENTRIES - 1);
1894
2151
  await Promise.all(
1895
- doomed.map((d) => fs4.rm(d.dir, { recursive: true, force: true }))
2152
+ doomed.map((d) => fs5.rm(d.dir, { recursive: true, force: true }))
1896
2153
  );
1897
2154
  } catch {
1898
2155
  }
@@ -1924,7 +2181,7 @@ var ManifestReads = class {
1924
2181
  };
1925
2182
  async function manifestStamp(manifestPath) {
1926
2183
  try {
1927
- const stat = await fs4.stat(manifestPath);
2184
+ const stat = await fs5.stat(manifestPath);
1928
2185
  return `${stat.ino}:${stat.mtimeMs}:${stat.size}`;
1929
2186
  } catch {
1930
2187
  return null;
@@ -1932,8 +2189,7 @@ async function manifestStamp(manifestPath) {
1932
2189
  }
1933
2190
  async function readManifest(manifestPath) {
1934
2191
  try {
1935
- const raw = await fs4.readFile(manifestPath, "utf-8");
1936
- return JSON.parse(raw);
2192
+ return readJsonFile(manifestPath);
1937
2193
  } catch {
1938
2194
  return null;
1939
2195
  }
@@ -2426,6 +2682,7 @@ function unreadSentences(raw) {
2426
2682
  ...raw.branches.flatMap(readingsOfBranch)
2427
2683
  ];
2428
2684
  return [
2685
+ ...raw.readFailure !== void 0 ? [raw.readFailure] : [],
2429
2686
  ...raw.unreadBinding !== void 0 ? [raw.unreadBinding] : [],
2430
2687
  ...handedOver.map(unreadReasonOf).filter((reason) => reason !== null)
2431
2688
  ];
@@ -2588,6 +2845,12 @@ function buildHttpMetadataValue(raw) {
2588
2845
  if (raw.failureDelivery !== void 0) {
2589
2846
  http.failureDelivery = raw.failureDelivery;
2590
2847
  }
2848
+ if (raw.redirectDelivery !== void 0) {
2849
+ http.redirectDelivery = raw.redirectDelivery;
2850
+ }
2851
+ if (raw.declaredStatuses !== void 0 && raw.declaredStatuses.length > 0) {
2852
+ http.declaredStatuses = raw.declaredStatuses;
2853
+ }
2591
2854
  return Object.keys(http).length > 0 ? http : null;
2592
2855
  }
2593
2856
  function buildGraphqlMetadataValue(raw) {
@@ -2655,13 +2918,16 @@ function detectGaps(raw, transitions, options) {
2655
2918
  return gaps;
2656
2919
  }
2657
2920
  function bodyWentUnread(raw) {
2921
+ if (raw.readFailure !== void 0) {
2922
+ return true;
2923
+ }
2658
2924
  if (raw.bodyContent === "absent" || raw.bodyContent === "elsewhere") {
2659
2925
  return true;
2660
2926
  }
2661
2927
  return raw.bodyContent === "statements" && raw.branches.length === 0;
2662
2928
  }
2663
2929
  function describeUnreadBody(raw) {
2664
- if (!bodyWentUnread(raw)) {
2930
+ if (!bodyWentUnread(raw) || raw.readFailure !== void 0) {
2665
2931
  return null;
2666
2932
  }
2667
2933
  if (raw.bodyContent === "absent") {
@@ -2733,10 +2999,16 @@ var terminalConverters = {
2733
2999
  headers: {}
2734
3000
  };
2735
3001
  }
3002
+ const ancestry = t.exceptionAncestry;
2736
3003
  return {
2737
3004
  type: "throw",
2738
3005
  exceptionType: t.exceptionType,
2739
- message: t.message
3006
+ message: t.message,
3007
+ ...ancestry === void 0 ? {} : {
3008
+ exceptionAncestors: ancestry.ancestors,
3009
+ ...ancestry.incomplete ? { ancestryIncomplete: true } : {}
3010
+ },
3011
+ ...t.statusWhenUncaught === void 0 ? {} : { statusWhenUncaught: t.statusWhenUncaught }
2740
3012
  };
2741
3013
  },
2742
3014
  render: (t) => ({
@@ -2810,6 +3082,7 @@ export {
2810
3082
  IdSet,
2811
3083
  KeptParses,
2812
3084
  MAX_ENTRIES,
3085
+ MAX_HELPER_PATHS,
2813
3086
  MAX_PATHS,
2814
3087
  MODULE_SURFACE_RECOGNITION,
2815
3088
  PathBudgetExceeded,
@@ -2840,12 +3113,15 @@ export {
2840
3113
  firstWrittenReading,
2841
3114
  formatPackHealth,
2842
3115
  guardsHoldOn,
3116
+ helperPathsOnVariables,
2843
3117
  httpRouteDiscovery,
2844
3118
  isListedTestFile,
3119
+ makeIgnoredDirectory,
2845
3120
  makeTransitionId,
2846
3121
  mapReading,
2847
3122
  markReturnsAsExitCode,
2848
3123
  matchesTestFileName,
3124
+ mergeReadShapes,
2849
3125
  moduleInitStructure,
2850
3126
  moduleOfFile,
2851
3127
  noopTimer,
@@ -2854,6 +3130,7 @@ export {
2854
3130
  projectFileStamp,
2855
3131
  rawConditionToPredicate,
2856
3132
  recordPackFailure,
3133
+ redirectDeliveryWhenSet,
2857
3134
  registrationHelperDiscovery,
2858
3135
  routeHelperIndex,
2859
3136
  runDigest,
@@ -2862,6 +3139,7 @@ export {
2862
3139
  settleModules,
2863
3140
  sharedGatingConditions,
2864
3141
  sourceRefsOf,
3142
+ splitAtHandOffs,
2865
3143
  stampMayMissAWrite,
2866
3144
  stampModuleImports,
2867
3145
  stampModules,
@@ -2869,6 +3147,7 @@ export {
2869
3147
  summaryCountsByPack,
2870
3148
  tallyUnit,
2871
3149
  terminalToOutput,
3150
+ throughHelper,
2872
3151
  unreadableReading,
2873
3152
  unwrapJsonStringify,
2874
3153
  valueToReadFurtherFrom,