@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.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  IdSet: () => IdSet,
36
36
  KeptParses: () => KeptParses,
37
37
  MAX_ENTRIES: () => MAX_ENTRIES,
38
+ MAX_HELPER_PATHS: () => MAX_HELPER_PATHS,
38
39
  MAX_PATHS: () => MAX_PATHS,
39
40
  MODULE_SURFACE_RECOGNITION: () => MODULE_SURFACE_RECOGNITION,
40
41
  PathBudgetExceeded: () => PathBudgetExceeded,
@@ -65,12 +66,15 @@ __export(index_exports, {
65
66
  firstWrittenReading: () => firstWrittenReading,
66
67
  formatPackHealth: () => formatPackHealth,
67
68
  guardsHoldOn: () => guardsHoldOn,
69
+ helperPathsOnVariables: () => helperPathsOnVariables,
68
70
  httpRouteDiscovery: () => httpRouteDiscovery,
69
71
  isListedTestFile: () => isListedTestFile,
72
+ makeIgnoredDirectory: () => makeIgnoredDirectory,
70
73
  makeTransitionId: () => makeTransitionId,
71
74
  mapReading: () => mapReading,
72
75
  markReturnsAsExitCode: () => markReturnsAsExitCode,
73
76
  matchesTestFileName: () => matchesTestFileName,
77
+ mergeReadShapes: () => mergeReadShapes,
74
78
  moduleInitStructure: () => moduleInitStructure,
75
79
  moduleOfFile: () => moduleOfFile,
76
80
  noopTimer: () => noopTimer,
@@ -79,6 +83,7 @@ __export(index_exports, {
79
83
  projectFileStamp: () => projectFileStamp,
80
84
  rawConditionToPredicate: () => rawConditionToPredicate,
81
85
  recordPackFailure: () => recordPackFailure,
86
+ redirectDeliveryWhenSet: () => redirectDeliveryWhenSet,
82
87
  registrationHelperDiscovery: () => registrationHelperDiscovery,
83
88
  routeHelperIndex: () => routeHelperIndex,
84
89
  runDigest: () => runDigest,
@@ -87,6 +92,7 @@ __export(index_exports, {
87
92
  settleModules: () => settleModules,
88
93
  sharedGatingConditions: () => sharedGatingConditions,
89
94
  sourceRefsOf: () => sourceRefsOf,
95
+ splitAtHandOffs: () => splitAtHandOffs,
90
96
  stampMayMissAWrite: () => stampMayMissAWrite,
91
97
  stampModuleImports: () => stampModuleImports,
92
98
  stampModules: () => stampModules,
@@ -94,6 +100,7 @@ __export(index_exports, {
94
100
  summaryCountsByPack: () => summaryCountsByPack,
95
101
  tallyUnit: () => tallyUnit,
96
102
  terminalToOutput: () => terminalToOutput,
103
+ throughHelper: () => throughHelper,
97
104
  unreadableReading: () => unreadableReading,
98
105
  unwrapJsonStringify: () => unwrapJsonStringify,
99
106
  valueToReadFurtherFrom: () => valueToReadFurtherFrom,
@@ -103,7 +110,7 @@ __export(index_exports, {
103
110
  });
104
111
  module.exports = __toCommonJS(index_exports);
105
112
  var import_node_crypto4 = require("crypto");
106
- var import_behavioral_ir6 = require("@suss/behavioral-ir");
113
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
107
114
 
108
115
  // src/contractStatusGaps.ts
109
116
  var import_behavioral_ir = require("@suss/behavioral-ir");
@@ -282,29 +289,82 @@ function composeOne(summary, chain, keepGaps) {
282
289
  if (wrappers.length === 0) {
283
290
  return narrowed;
284
291
  }
285
- const responses = respondedInstead(wrappers);
286
- const handled = handledThrows(wrappers);
287
- const transitions = beside(summary, responses, handled);
292
+ const responses = settledUncaught(respondedInstead(wrappers), wrappers);
293
+ const unitOwn = settledUncaught(summary.transitions, wrappers);
294
+ const own = [...responses, ...unitOwn];
295
+ const handled = handledThrows(wrappers, thrownBy(own));
296
+ const transitions = beside(summary, unitOwn, responses, handled);
288
297
  if (!keepGaps) {
289
298
  return transitions === summary.transitions ? narrowed : { ...narrowed, transitions };
290
299
  }
291
300
  const gaps = withContractStatusGaps(
292
301
  summary,
293
- [...responses, ...summary.transitions],
294
- handled
302
+ own,
303
+ everyHandlerOutcome(wrappers)
295
304
  );
296
305
  if (transitions === summary.transitions && gaps === summary.gaps) {
297
306
  return narrowed;
298
307
  }
299
308
  return { ...narrowed, transitions, gaps };
300
309
  }
301
- function beside(summary, responses, handled) {
302
- const throws = summary.transitions.some((t) => t.output.type === "throw");
303
- const onThrow = throws ? handled : [];
304
- if (responses.length === 0 && onThrow.length === 0) {
310
+ function beside(summary, unitOwn, responses, handled) {
311
+ if (responses.length === 0 && handled.length === 0 && unitOwn === summary.transitions) {
305
312
  return summary.transitions;
306
313
  }
307
- return withDistinctIds([...responses, ...summary.transitions, ...onThrow]);
314
+ return withDistinctIds([...responses, ...unitOwn, ...handled]);
315
+ }
316
+ function settledUncaught(transitions, wrappers) {
317
+ const handlers = wrappers.filter(
318
+ (wrapper) => wrapper.reference.onThrow === true
319
+ );
320
+ let changed = false;
321
+ const settled = transitions.flatMap((transition) => {
322
+ const { output } = transition;
323
+ if (output.type !== "throw") {
324
+ return [transition];
325
+ }
326
+ const caught = caughtOnRoute(handlers, output);
327
+ if (caught === "sure") {
328
+ changed = true;
329
+ return [markedCaught(transition)];
330
+ }
331
+ if (output.statusWhenUncaught === void 0) {
332
+ return [transition];
333
+ }
334
+ changed = true;
335
+ const response = {
336
+ ...transition,
337
+ output: {
338
+ type: "response",
339
+ statusCode: { type: "literal", value: output.statusWhenUncaught },
340
+ body: null,
341
+ headers: {}
342
+ }
343
+ };
344
+ return caught === "uncertain" ? [transition, response] : [response];
345
+ });
346
+ return changed ? settled : transitions;
347
+ }
348
+ function markedCaught(transition) {
349
+ return {
350
+ ...transition,
351
+ metadata: (0, import_behavioral_ir2.withWrapperMetadata)(transition.metadata, {
352
+ ...(0, import_behavioral_ir2.readWrapperMetadata)(transition),
353
+ caught: true
354
+ })
355
+ };
356
+ }
357
+ function caughtOnRoute(handlers, thrown) {
358
+ if (handlers.some((handler) => handler.reference.catches === void 0)) {
359
+ return "sure";
360
+ }
361
+ const matches = handlers.map(
362
+ (handler) => catchMatch(handler.reference, thrown)
363
+ );
364
+ if (matches.includes("yes")) {
365
+ return "sure";
366
+ }
367
+ return matches.includes("maybe") ? "uncertain" : "no";
308
368
  }
309
369
  function respondedInstead(wrappers) {
310
370
  return wrappers.flatMap(
@@ -316,11 +376,91 @@ function respondedInstead(wrappers) {
316
376
  )
317
377
  );
318
378
  }
319
- function handledThrows(wrappers) {
379
+ function thrownBy(transitions) {
380
+ return transitions.flatMap(
381
+ (t) => t.output.type === "throw" ? [t.output] : []
382
+ );
383
+ }
384
+ function everyHandlerOutcome(wrappers) {
320
385
  return wrappers.flatMap(
321
386
  (wrapper) => wrapper.reference.onThrow === true ? attribute(wrapper.summary.transitions, wrapper.reference) : []
322
387
  );
323
388
  }
389
+ function handledThrows(wrappers, thrown) {
390
+ const caught = catchesAny(wrappers, thrown);
391
+ return [...oncePerHandler(wrappers, caught)].flatMap(
392
+ ([wrapper, how]) => attribute(
393
+ wrapper.summary.transitions,
394
+ wrapper.reference,
395
+ how === "uncertain"
396
+ )
397
+ );
398
+ }
399
+ function oncePerHandler(wrappers, caught) {
400
+ const byHandler = /* @__PURE__ */ new Map();
401
+ for (const wrapper of wrappers) {
402
+ const how = caught.get(wrapper);
403
+ if (how === void 0) {
404
+ continue;
405
+ }
406
+ const earlier = byHandler.get(wrapper.summary);
407
+ if (earlier === void 0 || earlier[1] === "uncertain" && how === "sure") {
408
+ byHandler.set(wrapper.summary, [wrapper, how]);
409
+ }
410
+ }
411
+ return new Map(byHandler.values());
412
+ }
413
+ function catchesAny(wrappers, thrown) {
414
+ const caught = /* @__PURE__ */ new Map();
415
+ if (thrown.length === 0) {
416
+ return caught;
417
+ }
418
+ const handlers = wrappers.filter(
419
+ (wrapper) => wrapper.reference.onThrow === true
420
+ );
421
+ for (const handler of handlers) {
422
+ if (handler.reference.catches === void 0) {
423
+ caught.set(handler, "sure");
424
+ }
425
+ }
426
+ const listing = handlers.filter(
427
+ (handler) => handler.reference.catches !== void 0
428
+ );
429
+ for (const one of thrown) {
430
+ for (const [handler, how] of catchersOf(listing, one)) {
431
+ if (caught.get(handler) !== "sure") {
432
+ caught.set(handler, how);
433
+ }
434
+ }
435
+ }
436
+ return caught;
437
+ }
438
+ function catchersOf(handlers, thrown) {
439
+ const found = [];
440
+ for (const handler of handlers) {
441
+ const match = catchMatch(handler.reference, thrown);
442
+ if (match === "yes") {
443
+ found.push([handler, found.length === 0 ? "sure" : "uncertain"]);
444
+ return found;
445
+ }
446
+ if (match === "maybe") {
447
+ found.push([handler, "uncertain"]);
448
+ }
449
+ }
450
+ return found;
451
+ }
452
+ function catchMatch(reference, thrown) {
453
+ const ancestors = thrown.exceptionAncestors;
454
+ if (thrown.exceptionType === null || ancestors === void 0) {
455
+ return "maybe";
456
+ }
457
+ const classes = /* @__PURE__ */ new Set([thrown.exceptionType, ...ancestors]);
458
+ if ((reference.catches ?? []).some((name) => classes.has(name))) {
459
+ return "yes";
460
+ }
461
+ const unread = thrown.ancestryIncomplete === true && reference.mayCatchUnreadClasses === true;
462
+ return unread || reference.mayCatchAny === true ? "maybe" : "no";
463
+ }
324
464
  function withContractStatusGaps(summary, composed, handled) {
325
465
  const contract = (0, import_behavioral_ir2.readHttpMetadata)(summary)?.declaredContract;
326
466
  const binding = summary.identity.boundaryBinding;
@@ -351,10 +491,13 @@ function coversUnit(reference, summary) {
351
491
  const binding = summary.identity.boundaryBinding;
352
492
  return binding !== null && (0, import_behavioral_ir2.withinScope)(binding, reference.scope);
353
493
  }
354
- function attribute(transitions, reference) {
494
+ function attribute(transitions, reference, catchUncertain = false) {
355
495
  return transitions.map((transition) => ({
356
496
  ...transition,
357
- metadata: (0, import_behavioral_ir2.withWrapperMetadata)(transition.metadata, { from: reference })
497
+ metadata: (0, import_behavioral_ir2.withWrapperMetadata)(transition.metadata, {
498
+ from: reference,
499
+ ...catchUncertain ? { catchUncertain } : {}
500
+ })
358
501
  }));
359
502
  }
360
503
  function withDistinctIds(transitions) {
@@ -467,6 +610,104 @@ function markExitCodeFrom(summary, marked) {
467
610
  summary.metadata = left;
468
611
  }
469
612
 
613
+ // src/helperHandOff.ts
614
+ var import_behavioral_ir4 = require("@suss/behavioral-ir");
615
+ var MAX_HELPER_PATHS = 16;
616
+ function throughHelper(branch, at, helperBranches) {
617
+ return helperBranches.map((helper) => {
618
+ const conditions = [...branch.conditions, ...helper.conditions];
619
+ const expectedInput = mergeReadShapes(
620
+ branch.expectedInput ?? null,
621
+ helper.expectedInput ?? null
622
+ );
623
+ const reads = expectedInput === null ? {} : { expectedInput };
624
+ if (helper.terminal.kind !== "throw" && helper.terminal.kind !== "exit") {
625
+ return {
626
+ ...branch,
627
+ ...reads,
628
+ conditions,
629
+ isDefault: branch.isDefault && helper.isDefault
630
+ };
631
+ }
632
+ return {
633
+ ...branch,
634
+ ...reads,
635
+ conditions,
636
+ terminal: { ...helper.terminal, location: at },
637
+ location: at,
638
+ isDefault: false
639
+ };
640
+ });
641
+ }
642
+ function mergeReadShapes(a, b) {
643
+ if (a === null || b === null) {
644
+ return a ?? b;
645
+ }
646
+ if (a.type !== "record" || b.type !== "record") {
647
+ return a.type === "record" ? a : b;
648
+ }
649
+ const properties = { ...a.properties };
650
+ for (const [name, shape] of Object.entries(b.properties)) {
651
+ const known = properties[name];
652
+ properties[name] = known === void 0 ? shape : mergeReadShapes(known, shape) ?? shape;
653
+ }
654
+ return { ...a, properties };
655
+ }
656
+ function splitAtHandOffs(branch, sites, from = 0) {
657
+ for (let index = from; index < sites.length; index++) {
658
+ const site = sites[index];
659
+ const reached = site.alwaysRuns === true || runsBefore(site.line, branch.terminal.location.end);
660
+ if (!reached || !guardsHoldOn(site.preconditions, branch.conditions)) {
661
+ continue;
662
+ }
663
+ return throughHelper(branch, site.at, site.helper).flatMap(
664
+ (path7) => path7.terminal === branch.terminal ? splitAtHandOffs(path7, sites, index + 1) : [path7]
665
+ );
666
+ }
667
+ return [branch];
668
+ }
669
+ function helperPathsOnVariables(helperBranches, passed) {
670
+ const renamed = helperBranches.map((branch) => ({
671
+ ...branch,
672
+ conditions: branch.conditions.map((condition) => ({
673
+ ...condition,
674
+ structured: condition.structured === null ? null : onCallerVariables(condition.structured, passed)
675
+ }))
676
+ }));
677
+ const variables = new Set(passed.values());
678
+ const testsWhatItGot = renamed.some(
679
+ (branch) => branch.conditions.some(
680
+ (condition) => condition.structured !== null && (0, import_behavioral_ir4.predicateRefs)(condition.structured).some(
681
+ (ref) => variables.has(rootName(ref) ?? "")
682
+ )
683
+ )
684
+ );
685
+ return testsWhatItGot && renamed.length <= MAX_HELPER_PATHS ? renamed : null;
686
+ }
687
+ function onCallerVariables(predicate, passed) {
688
+ const rename = (ref) => {
689
+ if (ref.type === "derived") {
690
+ return { ...ref, from: rename(ref.from) };
691
+ }
692
+ if (ref.type === "input") {
693
+ const name = passed.get(ref.inputRef);
694
+ return name === void 0 ? ref : { type: "dependency", name, accessChain: ref.path };
695
+ }
696
+ if (ref.type === "dependency") {
697
+ const name = passed.get(ref.name);
698
+ return name === void 0 ? ref : { ...ref, name };
699
+ }
700
+ return ref;
701
+ };
702
+ return (0, import_behavioral_ir4.replacePredicateRefs)(predicate, rename);
703
+ }
704
+ function rootName(ref) {
705
+ if (ref.type === "derived") {
706
+ return rootName(ref.from);
707
+ }
708
+ return ref.type === "dependency" ? ref.name : null;
709
+ }
710
+
470
711
  // src/moduleImports.ts
471
712
  function stampModuleImports(summaries, importsOf) {
472
713
  const byFile = /* @__PURE__ */ new Map();
@@ -720,7 +961,7 @@ var configuredCallOption = import_zod.z.object({
720
961
  }).strict();
721
962
 
722
963
  // src/paths/enumeratePaths.ts
723
- var import_behavioral_ir4 = require("@suss/behavioral-ir");
964
+ var import_behavioral_ir5 = require("@suss/behavioral-ir");
724
965
  var MAX_PATHS = 256;
725
966
  var UnmodeledFlow = class extends Error {
726
967
  };
@@ -764,12 +1005,12 @@ var loopCompletedCond = (headerText, exit) => fixedCond(
764
1005
  exit === "throw" ? "earlyThrow" : "earlyReturn"
765
1006
  );
766
1007
  var catchEntryCond = () => fixedCond(
767
- { sourceText: import_behavioral_ir4.CATCH_ENTRY_TEXT, expression: null },
1008
+ { sourceText: import_behavioral_ir5.CATCH_ENTRY_TEXT, expression: null },
768
1009
  "positive",
769
1010
  "catchBlock"
770
1011
  );
771
- function classify(path6, home, isAncestorOrSelf) {
772
- return path6.map((cond) => {
1012
+ function classify(path7, home, isAncestorOrSelf) {
1013
+ return path7.map((cond) => {
773
1014
  if (cond.branchStmt === null) {
774
1015
  return cond.info;
775
1016
  }
@@ -834,9 +1075,16 @@ function chargeBudget(state) {
834
1075
  );
835
1076
  }
836
1077
  }
837
- function recordTerminal(ctx, terminal, home, path6) {
1078
+ function limitOpenPaths(paths) {
1079
+ if (paths.length > MAX_PATHS) {
1080
+ throw new PathBudgetExceeded(
1081
+ `path budget exceeded, more than ${MAX_PATHS} paths`
1082
+ );
1083
+ }
1084
+ }
1085
+ function recordTerminal(ctx, terminal, home, path7) {
838
1086
  chargeBudget(ctx.state);
839
- const info = classify(path6, home, ctx.isAncestorOrSelf);
1087
+ const info = classify(path7, home, ctx.isAncestorOrSelf);
840
1088
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
841
1089
  existing.push(info);
842
1090
  ctx.state.byTerminal.set(terminal, existing);
@@ -847,15 +1095,15 @@ function containsTerminal(ctx, stmt) {
847
1095
  }
848
1096
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
849
1097
  }
850
- function stepIf(stmt, path6, ctx) {
1098
+ function stepIf(stmt, path7, ctx) {
851
1099
  const thenExit = exitKindOfList(stmt.thenBody);
852
1100
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
853
1101
  const thenPath = [
854
- ...path6,
1102
+ ...path7,
855
1103
  branchCond(stmt, stmt.condition, "positive", elseExit)
856
1104
  ];
857
1105
  const elsePath = [
858
- ...path6,
1106
+ ...path7,
859
1107
  branchCond(
860
1108
  stmt,
861
1109
  stmt.condition,
@@ -872,7 +1120,7 @@ function stepIf(stmt, path6, ctx) {
872
1120
  }
873
1121
  return out;
874
1122
  }
875
- function stepSwitch(stmt, path6, ctx) {
1123
+ function stepSwitch(stmt, path7, ctx) {
876
1124
  const out = [];
877
1125
  const negations = [];
878
1126
  let defaultRanBody = false;
@@ -882,7 +1130,7 @@ function stepSwitch(stmt, path6, ctx) {
882
1130
  if (hasStrayBreak(group.body)) {
883
1131
  throw new UnmodeledFlow("non-trailing break in switch clause");
884
1132
  }
885
- const groupPath = group.condition === null ? [...path6, ...negations] : [...path6, fixedCond(group.condition, "positive", "explicit")];
1133
+ const groupPath = group.condition === null ? [...path7, ...negations] : [...path7, fixedCond(group.condition, "positive", "explicit")];
886
1134
  const conts = enumerate(ctx, group.body, groupPath);
887
1135
  if (conts.length > 0) {
888
1136
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -899,24 +1147,24 @@ function stepSwitch(stmt, path6, ctx) {
899
1147
  }
900
1148
  }
901
1149
  if (!defaultRanBody) {
902
- out.push([...path6, ...negations]);
1150
+ out.push([...path7, ...negations]);
903
1151
  }
904
1152
  return out;
905
1153
  }
906
- function stepLoop(stmt, path6, ctx) {
1154
+ function stepLoop(stmt, path7, ctx) {
907
1155
  const bodyExit = exitKindOfList(stmt.body);
908
1156
  if (containsTerminal(ctx, stmt)) {
909
1157
  enumerate(ctx, stmt.body, [
910
- ...path6,
1158
+ ...path7,
911
1159
  loopIterationCond(stmt.condition.sourceText)
912
1160
  ]);
913
1161
  }
914
1162
  if (bodyExit !== null) {
915
- return [[...path6, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
1163
+ return [[...path7, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
916
1164
  }
917
- return [path6];
1165
+ return [path7];
918
1166
  }
919
- function stepTry(stmt, path6, ctx) {
1167
+ function stepTry(stmt, path7, ctx) {
920
1168
  if (stmt.finallyBody !== null) {
921
1169
  const finallyHasTerminal = stmt.finallyBody.some(
922
1170
  (s) => containsTerminal(ctx, s)
@@ -926,22 +1174,22 @@ function stepTry(stmt, path6, ctx) {
926
1174
  }
927
1175
  }
928
1176
  const out = [];
929
- out.push(...enumerate(ctx, stmt.tryBody, path6));
1177
+ out.push(...enumerate(ctx, stmt.tryBody, path7));
930
1178
  if (stmt.catchBody !== null) {
931
- const catchPath = [...path6, catchEntryCond()];
1179
+ const catchPath = [...path7, catchEntryCond()];
932
1180
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
933
1181
  }
934
1182
  return out;
935
1183
  }
936
- function stepExit(stmt, path6, ctx) {
1184
+ function stepExit(stmt, path7, ctx) {
937
1185
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
938
1186
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
939
- collector.push(path6);
1187
+ collector.push(path7);
940
1188
  }
941
1189
  return [];
942
1190
  }
943
- function stepOpaque(_stmt, path6, _ctx) {
944
- return [path6];
1191
+ function stepOpaque(_stmt, path7, _ctx) {
1192
+ return [path7];
945
1193
  }
946
1194
  var stepHandlers = {
947
1195
  if: stepIf,
@@ -951,22 +1199,23 @@ var stepHandlers = {
951
1199
  exit: stepExit,
952
1200
  opaque: stepOpaque
953
1201
  };
954
- function stepCallback(ctx, body, path6) {
1202
+ function stepCallback(ctx, body, path7) {
955
1203
  const exits = [];
956
1204
  ctx.state.callbackExits.push(exits);
957
1205
  let fellThrough;
958
1206
  try {
959
- fellThrough = enumerate(ctx, body, path6);
1207
+ fellThrough = enumerate(ctx, body, path7);
960
1208
  } finally {
961
1209
  ctx.state.callbackExits.pop();
962
1210
  }
963
1211
  const out = [...fellThrough, ...exits];
964
- return out.length === 0 ? [path6] : out;
1212
+ return out.length === 0 ? [path7] : out;
965
1213
  }
966
- function stepStatement(ctx, stmt, path6) {
967
- let paths = [path6];
1214
+ function stepStatement(ctx, stmt, path7) {
1215
+ let paths = [path7];
968
1216
  for (const body of stmt.callbacks ?? []) {
969
1217
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
1218
+ limitOpenPaths(paths);
970
1219
  }
971
1220
  const ownTerminals = ctx.terminalsByStmt.get(stmt) ?? [];
972
1221
  for (const each of withRejoined(paths)) {
@@ -983,7 +1232,7 @@ function stepStatement(ctx, stmt, path6) {
983
1232
  )
984
1233
  );
985
1234
  }
986
- function soleDisagreement(left, right) {
1235
+ function soleDisagreement(left, right, keepsArms) {
987
1236
  if (left.length !== right.length) {
988
1237
  return null;
989
1238
  }
@@ -1002,9 +1251,10 @@ function soleDisagreement(left, right) {
1002
1251
  }
1003
1252
  found = at;
1004
1253
  }
1005
- return found;
1254
+ const test = found === null ? null : left[found]?.info.expression ?? null;
1255
+ return test !== null && keepsArms(test) ? null : found;
1006
1256
  }
1007
- function mergeRejoined(paths) {
1257
+ function mergeRejoined(paths, keepsArms = KEEPS_NO_ARMS) {
1008
1258
  let current = paths;
1009
1259
  for (let round = 0; round < current.length; round++) {
1010
1260
  const out = [];
@@ -1019,7 +1269,11 @@ function mergeRejoined(paths) {
1019
1269
  if (taken.has(j)) {
1020
1270
  continue;
1021
1271
  }
1022
- const at = soleDisagreement(combined, current[j]);
1272
+ const at = soleDisagreement(
1273
+ combined,
1274
+ current[j],
1275
+ keepsArms
1276
+ );
1023
1277
  if (at === null) {
1024
1278
  continue;
1025
1279
  }
@@ -1045,21 +1299,18 @@ function enumerate(ctx, stmts, prefix) {
1045
1299
  let frontiers = [[...prefix]];
1046
1300
  for (const stmt of stmts) {
1047
1301
  const nextFrontiers = [];
1048
- for (const path6 of mergeRejoined(frontiers)) {
1049
- nextFrontiers.push(...stepStatement(ctx, stmt, path6));
1302
+ for (const path7 of mergeRejoined(frontiers, ctx.keepsArms)) {
1303
+ nextFrontiers.push(...stepStatement(ctx, stmt, path7));
1050
1304
  }
1051
1305
  frontiers = nextFrontiers;
1052
- if (frontiers.length > MAX_PATHS) {
1053
- throw new PathBudgetExceeded(
1054
- `path budget exceeded, more than ${MAX_PATHS} paths`
1055
- );
1056
- }
1306
+ limitOpenPaths(frontiers);
1057
1307
  if (frontiers.length === 0) {
1058
1308
  break;
1059
1309
  }
1060
1310
  }
1061
1311
  return frontiers;
1062
1312
  }
1313
+ var KEEPS_NO_ARMS = () => false;
1063
1314
  function buildParentMap(statements) {
1064
1315
  const parentOf = /* @__PURE__ */ new Map();
1065
1316
  const visit = (stmt, parent) => {
@@ -1093,13 +1344,14 @@ function enumerateStructuredPaths(input) {
1093
1344
  };
1094
1345
  const ctx = {
1095
1346
  terminalsByStmt: input.terminalsByStmt,
1347
+ keepsArms: input.keepsArms ?? KEEPS_NO_ARMS,
1096
1348
  state,
1097
1349
  isAncestorOrSelf
1098
1350
  };
1099
1351
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
1100
- for (const path6 of continuations) {
1352
+ for (const path7 of continuations) {
1101
1353
  chargeBudget(state);
1102
- state.fallthrough.push(classify(path6, null, isAncestorOrSelf));
1354
+ state.fallthrough.push(classify(path7, null, isAncestorOrSelf));
1103
1355
  }
1104
1356
  return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
1105
1357
  }
@@ -1142,8 +1394,8 @@ function sharedGatingConditions(paths, readPredicate) {
1142
1394
  return [];
1143
1395
  }
1144
1396
  const shared = new Set(first.map(keyOf2));
1145
- for (const path6 of rest) {
1146
- const here = new Set(path6.map(keyOf2));
1397
+ for (const path7 of rest) {
1398
+ const here = new Set(path7.map(keyOf2));
1147
1399
  for (const value of shared) {
1148
1400
  if (!here.has(value)) {
1149
1401
  shared.delete(value);
@@ -1251,15 +1503,12 @@ var EnclosingNodes = class {
1251
1503
 
1252
1504
  // src/paths/nodeWalk.ts
1253
1505
  var SKIP_CHILDREN = /* @__PURE__ */ Symbol("skipChildren");
1254
- function walkDescendants(root, carried, visitor) {
1255
- for (const child of root.namedChildren) {
1256
- if (child === null) {
1257
- continue;
1258
- }
1506
+ function walkDescendants(root, carried, childrenOf2, visitor) {
1507
+ for (const child of childrenOf2(root)) {
1259
1508
  visitor.at(child, carried);
1260
1509
  const inner = visitor.into(child, carried);
1261
1510
  if (inner !== SKIP_CHILDREN) {
1262
- walkDescendants(child, inner, visitor);
1511
+ walkDescendants(child, inner, childrenOf2, visitor);
1263
1512
  }
1264
1513
  }
1265
1514
  }
@@ -1338,6 +1587,11 @@ function valueToReadFurtherFrom(reading) {
1338
1587
  return reading.kind === "written" ? reading.value : null;
1339
1588
  }
1340
1589
 
1590
+ // src/redirectOption.ts
1591
+ function redirectDeliveryWhenSet(option, value) {
1592
+ return option.handsBack.includes(value) ? "response" : "followed";
1593
+ }
1594
+
1341
1595
  // src/testFiles.ts
1342
1596
  function isListedTestFile(file, listed) {
1343
1597
  if (listed === void 0) {
@@ -1506,7 +1760,8 @@ function extractionConfigStamp(config) {
1506
1760
  ["workspaceRoot", config.workspaceRoot],
1507
1761
  ["projectRoot", config.projectRoot],
1508
1762
  ["importRoots", config.importRoots?.join(import_node_path4.default.delimiter)],
1509
- ["modules", modulesStamp(config.modules)]
1763
+ ["modules", modulesStamp(config.modules)],
1764
+ ["walkedDirectory", config.walkedDirectory]
1510
1765
  ];
1511
1766
  return parts.flatMap(
1512
1767
  ([name, value]) => value === void 0 ? [] : [`${name}=${value}`]
@@ -1534,7 +1789,8 @@ function analysisBundles() {
1534
1789
  // src/cache.ts
1535
1790
  var import_node_crypto3 = require("crypto");
1536
1791
  var import_promises2 = __toESM(require("fs/promises"), 1);
1537
- var import_node_path5 = __toESM(require("path"), 1);
1792
+ var import_node_path6 = __toESM(require("path"), 1);
1793
+ var import_files = require("@suss/behavioral-ir/files");
1538
1794
 
1539
1795
  // src/cacheStamps.ts
1540
1796
  var import_node_crypto2 = require("crypto");
@@ -1642,6 +1898,17 @@ function fileStampEquals(a, b) {
1642
1898
  return a.path === b.path && a.mtimeMs === b.mtimeMs && a.size === b.size;
1643
1899
  }
1644
1900
 
1901
+ // src/ignoredDirectory.ts
1902
+ var import_node_fs3 = __toESM(require("fs"), 1);
1903
+ var import_node_path5 = __toESM(require("path"), 1);
1904
+ function makeIgnoredDirectory(dir) {
1905
+ import_node_fs3.default.mkdirSync(dir, { recursive: true });
1906
+ const ignore = import_node_path5.default.join(dir, ".gitignore");
1907
+ if (!import_node_fs3.default.existsSync(ignore)) {
1908
+ import_node_fs3.default.writeFileSync(ignore, "*\n");
1909
+ }
1910
+ }
1911
+
1645
1912
  // src/cache.ts
1646
1913
  var SCHEMA_VERSION = "9";
1647
1914
  var MAX_ENTRIES = 2;
@@ -1666,7 +1933,7 @@ function createCacheLayer(cacheDir) {
1666
1933
  const lookupWith = async (input, run) => {
1667
1934
  const entryDir = entryDirFor(cacheDir, input);
1668
1935
  const manifest = await manifests.readAtRunStart(
1669
- import_node_path5.default.join(entryDir, "manifest.json")
1936
+ import_node_path6.default.join(entryDir, "manifest.json")
1670
1937
  );
1671
1938
  if (manifest === null) {
1672
1939
  return missDiag(await describeAbsentEntry(cacheDir));
@@ -1706,7 +1973,7 @@ function createCacheLayer(cacheDir) {
1706
1973
  async plan(input) {
1707
1974
  const entryDir = entryDirFor(cacheDir, input);
1708
1975
  const manifest = await manifests.readAgain(
1709
- import_node_path5.default.join(entryDir, "manifest.json")
1976
+ import_node_path6.default.join(entryDir, "manifest.json")
1710
1977
  );
1711
1978
  if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
1712
1979
  return null;
@@ -1725,7 +1992,7 @@ function createCacheLayer(cacheDir) {
1725
1992
  async write(input, summaries, attribution) {
1726
1993
  const entryDir = entryDirFor(cacheDir, input);
1727
1994
  const previous = await manifests.readAgain(
1728
- import_node_path5.default.join(entryDir, "manifest.json")
1995
+ import_node_path6.default.join(entryDir, "manifest.json")
1729
1996
  );
1730
1997
  manifests.forget();
1731
1998
  const known = runs.get(input);
@@ -1750,11 +2017,8 @@ function createCacheLayer(cacheDir) {
1750
2017
  summaries,
1751
2018
  ...attribution === void 0 ? {} : encodeAttribution(attribution)
1752
2019
  };
1753
- await import_promises2.default.mkdir(entryDir, { recursive: true });
1754
- await import_promises2.default.writeFile(
1755
- import_node_path5.default.join(entryDir, "manifest.json"),
1756
- JSON.stringify(manifest)
1757
- );
2020
+ makeIgnoredDirectory(cacheDir);
2021
+ (0, import_files.writeDocumentFile)(import_node_path6.default.join(entryDir, "manifest.json"), manifest);
1758
2022
  await evictOldEntries(cacheDir, entryDir);
1759
2023
  }
1760
2024
  };
@@ -1951,7 +2215,7 @@ function entryDirFor(cacheDir, input) {
1951
2215
  input.configPath ?? ""
1952
2216
  ].join(" ");
1953
2217
  const name = (0, import_node_crypto3.createHash)("sha256").update(key).digest("hex").slice(0, 16);
1954
- return import_node_path5.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
2218
+ return import_node_path6.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
1955
2219
  }
1956
2220
  function isEntryDir(name) {
1957
2221
  return ENTRY_DIR_NAME.test(name);
@@ -1965,11 +2229,11 @@ async function markUsed(entryDir) {
1965
2229
  }
1966
2230
  async function evictOldEntries(cacheDir, keepDir) {
1967
2231
  try {
1968
- await import_promises2.default.rm(import_node_path5.default.join(cacheDir, "manifest.json"), { force: true });
2232
+ await import_promises2.default.rm(import_node_path6.default.join(cacheDir, "manifest.json"), { force: true });
1969
2233
  const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
1970
2234
  const dirs = await Promise.all(
1971
2235
  entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
1972
- const dir = import_node_path5.default.join(cacheDir, e.name);
2236
+ const dir = import_node_path6.default.join(cacheDir, e.name);
1973
2237
  const stat = await import_promises2.default.stat(dir);
1974
2238
  return { dir, mtimeMs: stat.mtimeMs };
1975
2239
  })
@@ -2016,15 +2280,14 @@ async function manifestStamp(manifestPath) {
2016
2280
  }
2017
2281
  async function readManifest(manifestPath) {
2018
2282
  try {
2019
- const raw = await import_promises2.default.readFile(manifestPath, "utf-8");
2020
- return JSON.parse(raw);
2283
+ return (0, import_files.readJsonFile)(manifestPath);
2021
2284
  } catch {
2022
2285
  return null;
2023
2286
  }
2024
2287
  }
2025
2288
 
2026
2289
  // src/extractionReport.ts
2027
- var import_behavioral_ir5 = require("@suss/behavioral-ir");
2290
+ var import_behavioral_ir6 = require("@suss/behavioral-ir");
2028
2291
  var emptyTally = () => ({
2029
2292
  candidateFiles: 0,
2030
2293
  unitsDiscovered: 0,
@@ -2074,7 +2337,7 @@ function summaryCountsByPack(summaries) {
2074
2337
  }
2075
2338
  const entry = counts.get(recognition) ?? emptyCounts();
2076
2339
  entry.bound += 1;
2077
- if (import_behavioral_ir5.BOUNDARY_ROLE[summary.kind] === "provider") {
2340
+ if (import_behavioral_ir6.BOUNDARY_ROLE[summary.kind] === "provider") {
2078
2341
  entry.providers += 1;
2079
2342
  }
2080
2343
  if (summary.transitions.length > 0) {
@@ -2510,6 +2773,7 @@ function unreadSentences(raw) {
2510
2773
  ...raw.branches.flatMap(readingsOfBranch)
2511
2774
  ];
2512
2775
  return [
2776
+ ...raw.readFailure !== void 0 ? [raw.readFailure] : [],
2513
2777
  ...raw.unreadBinding !== void 0 ? [raw.unreadBinding] : [],
2514
2778
  ...handedOver.map(unreadReasonOf).filter((reason) => reason !== null)
2515
2779
  ];
@@ -2519,17 +2783,17 @@ function effectsOfBranch(branch) {
2519
2783
  ...branch.effects.map(effectToIR),
2520
2784
  ...branch.extraEffects ?? []
2521
2785
  ];
2522
- return (0, import_behavioral_ir6.foldRepeatedEffects)(effects.map(withNormalizedCallee));
2786
+ return (0, import_behavioral_ir7.foldRepeatedEffects)(effects.map(withNormalizedCallee));
2523
2787
  }
2524
2788
  function provenanceOf(raw, effects) {
2525
2789
  if (raw.length === 0) {
2526
2790
  return [];
2527
2791
  }
2528
- const positionOf = new Map(effects.map((one, at) => [(0, import_behavioral_ir6.effectKey)(one), at]));
2792
+ const positionOf = new Map(effects.map((one, at) => [(0, import_behavioral_ir7.effectKey)(one), at]));
2529
2793
  const bySlot = /* @__PURE__ */ new Map();
2530
2794
  const said = /* @__PURE__ */ new Map();
2531
2795
  for (const one of raw) {
2532
- const effect = positionOf.get((0, import_behavioral_ir6.effectKey)(withNormalizedCallee(one.effect)));
2796
+ const effect = positionOf.get((0, import_behavioral_ir7.effectKey)(withNormalizedCallee(one.effect)));
2533
2797
  if (effect === void 0) {
2534
2798
  continue;
2535
2799
  }
@@ -2561,7 +2825,7 @@ function withNormalizedCallee(effect) {
2561
2825
  if (effect.callee === void 0) {
2562
2826
  return effect;
2563
2827
  }
2564
- return { ...effect, callee: (0, import_behavioral_ir6.normalizeCalleeText)(effect.callee) };
2828
+ return { ...effect, callee: (0, import_behavioral_ir7.normalizeCalleeText)(effect.callee) };
2565
2829
  }
2566
2830
  function assembleSummary(raw, options = DEFAULT_OPTIONS) {
2567
2831
  const transitions = raw.branches.map((rawBranch) => {
@@ -2626,28 +2890,28 @@ function buildMetadata(raw) {
2626
2890
  let metadata = {};
2627
2891
  const http = buildHttpMetadataValue(raw);
2628
2892
  if (http !== null) {
2629
- metadata = (0, import_behavioral_ir6.withHttpMetadata)(metadata, http);
2893
+ metadata = (0, import_behavioral_ir7.withHttpMetadata)(metadata, http);
2630
2894
  }
2631
2895
  const graphql = buildGraphqlMetadataValue(raw);
2632
2896
  if (graphql !== null) {
2633
- metadata = (0, import_behavioral_ir6.withGraphqlMetadata)(metadata, graphql);
2897
+ metadata = (0, import_behavioral_ir7.withGraphqlMetadata)(metadata, graphql);
2634
2898
  }
2635
2899
  if (raw.sourceDocumentLabel !== void 0) {
2636
- metadata = (0, import_behavioral_ir6.withSourceDocumentMetadata)(metadata, {
2900
+ metadata = (0, import_behavioral_ir7.withSourceDocumentMetadata)(metadata, {
2637
2901
  label: raw.sourceDocumentLabel
2638
2902
  });
2639
2903
  }
2640
2904
  if (raw.mount !== void 0) {
2641
- metadata = (0, import_behavioral_ir6.withMountMetadata)(metadata, raw.mount);
2905
+ metadata = (0, import_behavioral_ir7.withMountMetadata)(metadata, raw.mount);
2642
2906
  }
2643
2907
  if (raw.wrappers !== void 0 && raw.wrappers.length > 0) {
2644
- metadata = (0, import_behavioral_ir6.withWrapperMetadata)(metadata, { applied: raw.wrappers });
2908
+ metadata = (0, import_behavioral_ir7.withWrapperMetadata)(metadata, { applied: raw.wrappers });
2645
2909
  }
2646
2910
  if (raw.requestSpelling !== void 0) {
2647
- metadata = (0, import_behavioral_ir6.withRequestSpellingMetadata)(metadata, raw.requestSpelling);
2911
+ metadata = (0, import_behavioral_ir7.withRequestSpellingMetadata)(metadata, raw.requestSpelling);
2648
2912
  }
2649
2913
  if (raw.test !== void 0) {
2650
- metadata = (0, import_behavioral_ir6.withTestMetadata)(metadata, raw.test);
2914
+ metadata = (0, import_behavioral_ir7.withTestMetadata)(metadata, raw.test);
2651
2915
  }
2652
2916
  return Object.keys(metadata).length > 0 ? metadata : null;
2653
2917
  }
@@ -2672,6 +2936,12 @@ function buildHttpMetadataValue(raw) {
2672
2936
  if (raw.failureDelivery !== void 0) {
2673
2937
  http.failureDelivery = raw.failureDelivery;
2674
2938
  }
2939
+ if (raw.redirectDelivery !== void 0) {
2940
+ http.redirectDelivery = raw.redirectDelivery;
2941
+ }
2942
+ if (raw.declaredStatuses !== void 0 && raw.declaredStatuses.length > 0) {
2943
+ http.declaredStatuses = raw.declaredStatuses;
2944
+ }
2675
2945
  return Object.keys(http).length > 0 ? http : null;
2676
2946
  }
2677
2947
  function buildGraphqlMetadataValue(raw) {
@@ -2700,7 +2970,7 @@ function answersWithHttpResponses(raw) {
2700
2970
  if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
2701
2971
  return true;
2702
2972
  }
2703
- return (0, import_behavioral_ir6.exchangesHttpResponses)(raw.boundaryBinding);
2973
+ return (0, import_behavioral_ir7.exchangesHttpResponses)(raw.boundaryBinding);
2704
2974
  }
2705
2975
  function detectGaps(raw, transitions, options) {
2706
2976
  if (options.gapHandling === "silent") {
@@ -2739,13 +3009,16 @@ function detectGaps(raw, transitions, options) {
2739
3009
  return gaps;
2740
3010
  }
2741
3011
  function bodyWentUnread(raw) {
3012
+ if (raw.readFailure !== void 0) {
3013
+ return true;
3014
+ }
2742
3015
  if (raw.bodyContent === "absent" || raw.bodyContent === "elsewhere") {
2743
3016
  return true;
2744
3017
  }
2745
3018
  return raw.bodyContent === "statements" && raw.branches.length === 0;
2746
3019
  }
2747
3020
  function describeUnreadBody(raw) {
2748
- if (!bodyWentUnread(raw)) {
3021
+ if (!bodyWentUnread(raw) || raw.readFailure !== void 0) {
2749
3022
  return null;
2750
3023
  }
2751
3024
  if (raw.bodyContent === "absent") {
@@ -2817,10 +3090,16 @@ var terminalConverters = {
2817
3090
  headers: {}
2818
3091
  };
2819
3092
  }
3093
+ const ancestry = t.exceptionAncestry;
2820
3094
  return {
2821
3095
  type: "throw",
2822
3096
  exceptionType: t.exceptionType,
2823
- message: t.message
3097
+ message: t.message,
3098
+ ...ancestry === void 0 ? {} : {
3099
+ exceptionAncestors: ancestry.ancestors,
3100
+ ...ancestry.incomplete ? { ancestryIncomplete: true } : {}
3101
+ },
3102
+ ...t.statusWhenUncaught === void 0 ? {} : { statusWhenUncaught: t.statusWhenUncaught }
2824
3103
  };
2825
3104
  },
2826
3105
  render: (t) => ({
@@ -2895,6 +3174,7 @@ function paramToInput(param) {
2895
3174
  IdSet,
2896
3175
  KeptParses,
2897
3176
  MAX_ENTRIES,
3177
+ MAX_HELPER_PATHS,
2898
3178
  MAX_PATHS,
2899
3179
  MODULE_SURFACE_RECOGNITION,
2900
3180
  PathBudgetExceeded,
@@ -2925,12 +3205,15 @@ function paramToInput(param) {
2925
3205
  firstWrittenReading,
2926
3206
  formatPackHealth,
2927
3207
  guardsHoldOn,
3208
+ helperPathsOnVariables,
2928
3209
  httpRouteDiscovery,
2929
3210
  isListedTestFile,
3211
+ makeIgnoredDirectory,
2930
3212
  makeTransitionId,
2931
3213
  mapReading,
2932
3214
  markReturnsAsExitCode,
2933
3215
  matchesTestFileName,
3216
+ mergeReadShapes,
2934
3217
  moduleInitStructure,
2935
3218
  moduleOfFile,
2936
3219
  noopTimer,
@@ -2939,6 +3222,7 @@ function paramToInput(param) {
2939
3222
  projectFileStamp,
2940
3223
  rawConditionToPredicate,
2941
3224
  recordPackFailure,
3225
+ redirectDeliveryWhenSet,
2942
3226
  registrationHelperDiscovery,
2943
3227
  routeHelperIndex,
2944
3228
  runDigest,
@@ -2947,6 +3231,7 @@ function paramToInput(param) {
2947
3231
  settleModules,
2948
3232
  sharedGatingConditions,
2949
3233
  sourceRefsOf,
3234
+ splitAtHandOffs,
2950
3235
  stampMayMissAWrite,
2951
3236
  stampModuleImports,
2952
3237
  stampModules,
@@ -2954,6 +3239,7 @@ function paramToInput(param) {
2954
3239
  summaryCountsByPack,
2955
3240
  tallyUnit,
2956
3241
  terminalToOutput,
3242
+ throughHelper,
2957
3243
  unreadableReading,
2958
3244
  unwrapJsonStringify,
2959
3245
  valueToReadFurtherFrom,