@suss/extractor 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -22,6 +32,7 @@ var index_exports = {};
22
32
  __export(index_exports, {
23
33
  IdMap: () => IdMap,
24
34
  IdSet: () => IdSet,
35
+ MAX_ENTRIES: () => MAX_ENTRIES,
25
36
  MAX_PATHS: () => MAX_PATHS,
26
37
  PathBudgetExceeded: () => PathBudgetExceeded,
27
38
  SKIP_CHILDREN: () => SKIP_CHILDREN,
@@ -31,25 +42,44 @@ __export(index_exports, {
31
42
  andThenReading: () => andThenReading,
32
43
  assembleSummary: () => assembleSummary,
33
44
  assessConfidence: () => assessConfidence,
45
+ buildUngatedExtractionReport: () => buildUngatedExtractionReport,
34
46
  composeWrappers: () => composeWrappers,
47
+ computeContentHash: () => computeContentHash,
48
+ computeDistHashFrom: () => computeDistHashFrom,
35
49
  configuredCallOption: () => configuredCallOption,
50
+ createAdapterStamp: () => createAdapterStamp,
51
+ createCacheLayer: () => createCacheLayer,
52
+ createPackTallies: () => createPackTallies,
53
+ createTimer: () => createTimer,
36
54
  detectGaps: () => detectGaps,
37
55
  effectToIR: () => effectToIR,
56
+ emptyTally: () => emptyTally,
38
57
  enumerateOrDegrade: () => enumerateOrDegrade,
39
58
  enumerateStructuredPaths: () => enumerateStructuredPaths,
59
+ evaluatePackHealth: () => evaluatePackHealth,
40
60
  firstWrittenReading: () => firstWrittenReading,
61
+ formatPackHealth: () => formatPackHealth,
41
62
  guardsHoldOn: () => guardsHoldOn,
42
63
  httpRouteDiscovery: () => httpRouteDiscovery,
43
64
  makeTransitionId: () => makeTransitionId,
44
65
  mapReading: () => mapReading,
66
+ moduleInitStructure: () => moduleInitStructure,
67
+ noopTimer: () => noopTimer,
68
+ packGradients: () => packGradients,
45
69
  paramToInput: () => paramToInput,
70
+ projectFileStamp: () => projectFileStamp,
46
71
  rawConditionToPredicate: () => rawConditionToPredicate,
72
+ recordPackFailure: () => recordPackFailure,
47
73
  registrationHelperDiscovery: () => registrationHelperDiscovery,
48
74
  routeHelperIndex: () => routeHelperIndex,
75
+ runDigest: () => runDigest,
49
76
  runsBefore: () => runsBefore,
50
77
  scopeOption: () => scopeOption,
51
78
  sharedGatingConditions: () => sharedGatingConditions,
79
+ stampModuleImports: () => stampModuleImports,
52
80
  storageSystemOption: () => storageSystemOption,
81
+ summaryCountsByPack: () => summaryCountsByPack,
82
+ tallyUnit: () => tallyUnit,
53
83
  terminalToOutput: () => terminalToOutput,
54
84
  unreadableReading: () => unreadableReading,
55
85
  unwrapJsonStringify: () => unwrapJsonStringify,
@@ -59,8 +89,8 @@ __export(index_exports, {
59
89
  writtenReading: () => writtenReading
60
90
  });
61
91
  module.exports = __toCommonJS(index_exports);
62
- var import_node_crypto = require("crypto");
63
- var import_behavioral_ir3 = require("@suss/behavioral-ir");
92
+ var import_node_crypto3 = require("crypto");
93
+ var import_behavioral_ir4 = require("@suss/behavioral-ir");
64
94
 
65
95
  // src/inputReads.ts
66
96
  function inputReadsOf(args) {
@@ -204,8 +234,8 @@ var catchEntryCond = () => fixedCond(
204
234
  "positive",
205
235
  "catchBlock"
206
236
  );
207
- function classify(path, home, isAncestorOrSelf) {
208
- return path.map((cond) => {
237
+ function classify(path3, home, isAncestorOrSelf) {
238
+ return path3.map((cond) => {
209
239
  if (cond.branchStmt === null) {
210
240
  return cond.info;
211
241
  }
@@ -270,9 +300,9 @@ function chargeBudget(state) {
270
300
  );
271
301
  }
272
302
  }
273
- function recordTerminal(ctx, terminal, home, path) {
303
+ function recordTerminal(ctx, terminal, home, path3) {
274
304
  chargeBudget(ctx.state);
275
- const info = classify(path, home, ctx.isAncestorOrSelf);
305
+ const info = classify(path3, home, ctx.isAncestorOrSelf);
276
306
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
277
307
  existing.push(info);
278
308
  ctx.state.byTerminal.set(terminal, existing);
@@ -283,15 +313,15 @@ function containsTerminal(ctx, stmt) {
283
313
  }
284
314
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
285
315
  }
286
- function stepIf(stmt, path, ctx) {
316
+ function stepIf(stmt, path3, ctx) {
287
317
  const thenExit = exitKindOfList(stmt.thenBody);
288
318
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
289
319
  const thenPath = [
290
- ...path,
320
+ ...path3,
291
321
  branchCond(stmt, stmt.condition, "positive", elseExit)
292
322
  ];
293
323
  const elsePath = [
294
- ...path,
324
+ ...path3,
295
325
  branchCond(
296
326
  stmt,
297
327
  stmt.condition,
@@ -308,7 +338,7 @@ function stepIf(stmt, path, ctx) {
308
338
  }
309
339
  return out;
310
340
  }
311
- function stepSwitch(stmt, path, ctx) {
341
+ function stepSwitch(stmt, path3, ctx) {
312
342
  const out = [];
313
343
  const negations = [];
314
344
  let defaultRanBody = false;
@@ -318,7 +348,7 @@ function stepSwitch(stmt, path, ctx) {
318
348
  if (hasStrayBreak(group.body)) {
319
349
  throw new UnmodeledFlow("non-trailing break in switch clause");
320
350
  }
321
- const groupPath = group.condition === null ? [...path, ...negations] : [...path, fixedCond(group.condition, "positive", "explicit")];
351
+ const groupPath = group.condition === null ? [...path3, ...negations] : [...path3, fixedCond(group.condition, "positive", "explicit")];
322
352
  const conts = enumerate(ctx, group.body, groupPath);
323
353
  if (conts.length > 0) {
324
354
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -335,24 +365,24 @@ function stepSwitch(stmt, path, ctx) {
335
365
  }
336
366
  }
337
367
  if (!defaultRanBody) {
338
- out.push([...path, ...negations]);
368
+ out.push([...path3, ...negations]);
339
369
  }
340
370
  return out;
341
371
  }
342
- function stepLoop(stmt, path, ctx) {
372
+ function stepLoop(stmt, path3, ctx) {
343
373
  const bodyExit = exitKindOfList(stmt.body);
344
374
  if (containsTerminal(ctx, stmt)) {
345
375
  enumerate(ctx, stmt.body, [
346
- ...path,
376
+ ...path3,
347
377
  loopIterationCond(stmt.condition.sourceText)
348
378
  ]);
349
379
  }
350
380
  if (bodyExit !== null) {
351
- return [[...path, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
381
+ return [[...path3, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
352
382
  }
353
- return [path];
383
+ return [path3];
354
384
  }
355
- function stepTry(stmt, path, ctx) {
385
+ function stepTry(stmt, path3, ctx) {
356
386
  if (stmt.finallyBody !== null) {
357
387
  const finallyHasTerminal = stmt.finallyBody.some(
358
388
  (s) => containsTerminal(ctx, s)
@@ -362,22 +392,22 @@ function stepTry(stmt, path, ctx) {
362
392
  }
363
393
  }
364
394
  const out = [];
365
- out.push(...enumerate(ctx, stmt.tryBody, path));
395
+ out.push(...enumerate(ctx, stmt.tryBody, path3));
366
396
  if (stmt.catchBody !== null) {
367
- const catchPath = [...path, catchEntryCond()];
397
+ const catchPath = [...path3, catchEntryCond()];
368
398
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
369
399
  }
370
400
  return out;
371
401
  }
372
- function stepExit(stmt, path, ctx) {
402
+ function stepExit(stmt, path3, ctx) {
373
403
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
374
404
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
375
- collector.push(path);
405
+ collector.push(path3);
376
406
  }
377
407
  return [];
378
408
  }
379
- function stepOpaque(_stmt, path, _ctx) {
380
- return [path];
409
+ function stepOpaque(_stmt, path3, _ctx) {
410
+ return [path3];
381
411
  }
382
412
  var stepHandlers = {
383
413
  if: stepIf,
@@ -387,20 +417,20 @@ var stepHandlers = {
387
417
  exit: stepExit,
388
418
  opaque: stepOpaque
389
419
  };
390
- function stepCallback(ctx, body, path) {
420
+ function stepCallback(ctx, body, path3) {
391
421
  const exits = [];
392
422
  ctx.state.callbackExits.push(exits);
393
423
  let fellThrough;
394
424
  try {
395
- fellThrough = enumerate(ctx, body, path);
425
+ fellThrough = enumerate(ctx, body, path3);
396
426
  } finally {
397
427
  ctx.state.callbackExits.pop();
398
428
  }
399
429
  const out = [...fellThrough, ...exits];
400
- return out.length === 0 ? [path] : out;
430
+ return out.length === 0 ? [path3] : out;
401
431
  }
402
- function stepStatement(ctx, stmt, path) {
403
- let paths = [path];
432
+ function stepStatement(ctx, stmt, path3) {
433
+ let paths = [path3];
404
434
  for (const body of stmt.callbacks ?? []) {
405
435
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
406
436
  }
@@ -481,8 +511,8 @@ function enumerate(ctx, stmts, prefix) {
481
511
  let frontiers = [[...prefix]];
482
512
  for (const stmt of stmts) {
483
513
  const nextFrontiers = [];
484
- for (const path of mergeRejoined(frontiers)) {
485
- nextFrontiers.push(...stepStatement(ctx, stmt, path));
514
+ for (const path3 of mergeRejoined(frontiers)) {
515
+ nextFrontiers.push(...stepStatement(ctx, stmt, path3));
486
516
  }
487
517
  frontiers = nextFrontiers;
488
518
  if (frontiers.length > MAX_PATHS) {
@@ -533,9 +563,9 @@ function enumerateStructuredPaths(input) {
533
563
  isAncestorOrSelf
534
564
  };
535
565
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
536
- for (const path of continuations) {
566
+ for (const path3 of continuations) {
537
567
  chargeBudget(state);
538
- state.fallthrough.push(classify(path, null, isAncestorOrSelf));
568
+ state.fallthrough.push(classify(path3, null, isAncestorOrSelf));
539
569
  }
540
570
  return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
541
571
  }
@@ -730,6 +760,70 @@ function runsBefore(effectLine, terminalEndLine) {
730
760
  return effectLine <= terminalEndLine;
731
761
  }
732
762
 
763
+ // src/moduleImports.ts
764
+ function stampModuleImports(summaries, importsOf) {
765
+ const byFile = /* @__PURE__ */ new Map();
766
+ for (const summary of summaries) {
767
+ const file = summary.location.file;
768
+ if (!byFile.has(file)) {
769
+ const found = importsOf(file);
770
+ byFile.set(
771
+ file,
772
+ found === void 0 ? void 0 : [...new Set(found)].sort()
773
+ );
774
+ }
775
+ const imports = byFile.get(file);
776
+ if (imports === void 0) {
777
+ continue;
778
+ }
779
+ summary.metadata = {
780
+ ...summary.metadata ?? {},
781
+ moduleImports: imports
782
+ };
783
+ }
784
+ }
785
+
786
+ // src/moduleInit.ts
787
+ function moduleInitStructure(options) {
788
+ const { name, file, range, effects } = options;
789
+ return {
790
+ identity: {
791
+ name,
792
+ kind: "module-init",
793
+ file,
794
+ range,
795
+ exportName: null,
796
+ exportPath: null
797
+ },
798
+ boundaryBinding: null,
799
+ parameters: [],
800
+ branches: [
801
+ {
802
+ conditions: [],
803
+ terminal: {
804
+ kind: "void",
805
+ statusCode: null,
806
+ body: null,
807
+ exceptionType: null,
808
+ message: null,
809
+ component: null,
810
+ renderTree: null,
811
+ delegateTarget: null,
812
+ emitEvent: null,
813
+ location: range
814
+ },
815
+ effects: [],
816
+ extraEffects: effects,
817
+ location: range,
818
+ isDefault: true
819
+ }
820
+ ],
821
+ unmatchedReturns: 0,
822
+ dependencyCalls: [],
823
+ declaredContract: null
824
+ };
825
+ }
826
+
733
827
  // src/packHelpers.ts
734
828
  function httpRouteDiscovery(opts) {
735
829
  const kind = opts.kind ?? "handler";
@@ -927,8 +1021,8 @@ function sharedGatingConditions(paths, readPredicate) {
927
1021
  return [];
928
1022
  }
929
1023
  const shared = new Set(first.map(keyOf2));
930
- for (const path of rest) {
931
- const here = new Set(path.map(keyOf2));
1024
+ for (const path3 of rest) {
1025
+ const here = new Set(path3.map(keyOf2));
932
1026
  for (const value of shared) {
933
1027
  if (!here.has(value)) {
934
1028
  shared.delete(value);
@@ -1057,6 +1151,841 @@ function valueToReadFurtherFrom(reading) {
1057
1151
  return reading.kind === "written" ? reading.value : null;
1058
1152
  }
1059
1153
 
1154
+ // src/adapterStamp.ts
1155
+ var import_node_crypto = require("crypto");
1156
+ var import_node_fs = __toESM(require("fs"), 1);
1157
+ var import_node_module = require("module");
1158
+ var import_node_path = __toESM(require("path"), 1);
1159
+ var import_node_url = require("url");
1160
+ var import_meta = {};
1161
+ var ANALYSIS_PACKAGES = [
1162
+ "@suss/extractor",
1163
+ "@suss/resolution",
1164
+ "@suss/datalog",
1165
+ "@suss/behavioral-ir"
1166
+ ];
1167
+ var SOURCE_STAMP = { kind: "source" };
1168
+ function createAdapterStamp(config) {
1169
+ let cachedCodeStamp = null;
1170
+ let saidWhyNoCache = false;
1171
+ function codeStamp() {
1172
+ if (cachedCodeStamp !== null) {
1173
+ return cachedCodeStamp;
1174
+ }
1175
+ cachedCodeStamp = readAdapterCodeStamp(config.moduleUrl);
1176
+ return cachedCodeStamp;
1177
+ }
1178
+ function packsDigest(packVersions) {
1179
+ const sortedPacks = [...packVersions].map((p) => `${p.name}@${p.version ?? "unset"}`).sort();
1180
+ const stamp = codeStamp();
1181
+ const adapterStamp = stamp.kind === "bundle" ? `adapter@${config.version}+${stamp.hash}` : `adapter@${config.version}+source`;
1182
+ return `${adapterStamp}|${sortedPacks.join(",")}`;
1183
+ }
1184
+ function declineWhenRunFromSource(cacheDir) {
1185
+ if (cacheDir === null || codeStamp().kind === "bundle") {
1186
+ return cacheDir;
1187
+ }
1188
+ if (!saidWhyNoCache) {
1189
+ saidWhyNoCache = true;
1190
+ process.stderr.write(
1191
+ "[suss] extraction cache off: this process loaded the adapter from source, where nothing can tell one build of it from another. Run the built adapter to cache.\n"
1192
+ );
1193
+ }
1194
+ return null;
1195
+ }
1196
+ return { codeStamp, packsDigest, declineWhenRunFromSource };
1197
+ }
1198
+ function readAdapterCodeStamp(moduleUrl) {
1199
+ try {
1200
+ const selfPath = (0, import_node_url.fileURLToPath)(moduleUrl);
1201
+ const hash = computeDistHashFrom(import_node_path.default.dirname(selfPath));
1202
+ return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
1203
+ } catch {
1204
+ return SOURCE_STAMP;
1205
+ }
1206
+ }
1207
+ function computeDistHashFrom(dir) {
1208
+ const candidates = [import_node_path.default.join(dir, "index.js"), import_node_path.default.join(dir, "index.cjs")];
1209
+ for (const candidate of candidates) {
1210
+ if (!import_node_fs.default.existsSync(candidate)) {
1211
+ continue;
1212
+ }
1213
+ return computeContentHash([candidate, ...analysisBundles()]);
1214
+ }
1215
+ return "";
1216
+ }
1217
+ function computeContentHash(paths) {
1218
+ if (paths.length === 0) {
1219
+ return "";
1220
+ }
1221
+ const hash = (0, import_node_crypto.createHash)("sha256");
1222
+ for (const file of paths) {
1223
+ try {
1224
+ hash.update(import_node_fs.default.readFileSync(file));
1225
+ } catch {
1226
+ return "";
1227
+ }
1228
+ }
1229
+ return hash.digest("hex").slice(0, 16);
1230
+ }
1231
+ function projectFileStamp(paths) {
1232
+ if (paths.length === 0) {
1233
+ return "none";
1234
+ }
1235
+ const hash = (0, import_node_crypto.createHash)("sha256");
1236
+ for (const file of [...paths].sort()) {
1237
+ hash.update(file);
1238
+ hash.update("\0");
1239
+ try {
1240
+ hash.update(import_node_fs.default.readFileSync(file));
1241
+ } catch {
1242
+ hash.update("absent");
1243
+ }
1244
+ }
1245
+ return hash.digest("hex").slice(0, 16);
1246
+ }
1247
+ function runDigest(packsDigest, packs, files) {
1248
+ const inputs = packs.flatMap((pack) => pack.discoveryInputs?.(files) ?? []);
1249
+ return `${packsDigest}|reads:${projectFileStamp(inputs)}`;
1250
+ }
1251
+ function analysisBundles() {
1252
+ const require2 = (0, import_node_module.createRequire)(import_meta.url);
1253
+ const found = [];
1254
+ for (const name of ANALYSIS_PACKAGES) {
1255
+ try {
1256
+ found.push(require2.resolve(name));
1257
+ } catch {
1258
+ }
1259
+ }
1260
+ return found;
1261
+ }
1262
+
1263
+ // src/cache.ts
1264
+ var import_node_crypto2 = require("crypto");
1265
+ var import_promises = __toESM(require("fs/promises"), 1);
1266
+ var import_node_path2 = __toESM(require("path"), 1);
1267
+ var SCHEMA_VERSION = "7";
1268
+ var MAX_ENTRIES = 2;
1269
+ var ENTRY_PREFIX = "key-";
1270
+ var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
1271
+ function createCacheLayer(cacheDir) {
1272
+ if (cacheDir === null) {
1273
+ return {
1274
+ tryHit: async () => null,
1275
+ lookup: async () => ({
1276
+ kind: "miss",
1277
+ diagnostic: { kind: "miss", missReason: "no-manifest" }
1278
+ }),
1279
+ plan: async () => null,
1280
+ write: async () => {
1281
+ }
1282
+ };
1283
+ }
1284
+ return {
1285
+ async tryHit(input) {
1286
+ const result = await this.lookup(input);
1287
+ return result.kind === "hit" ? result.summaries : null;
1288
+ },
1289
+ async lookup(input) {
1290
+ const entryDir = entryDirFor(cacheDir, input);
1291
+ const manifest = await readManifest(
1292
+ import_node_path2.default.join(entryDir, "manifest.json")
1293
+ );
1294
+ if (manifest === null) {
1295
+ return missDiag(await describeAbsentEntry(cacheDir));
1296
+ }
1297
+ const currentConfigStamp = await stampConfigFile(input.configPath);
1298
+ if (!fileStampEquals(manifest.configStamp, currentConfigStamp)) {
1299
+ return missDiag("config-changed");
1300
+ }
1301
+ const currentFiles = await resolveFileStamps(input);
1302
+ if (!fileStampsEqual(manifest.files, currentFiles)) {
1303
+ return missDiag("files-changed");
1304
+ }
1305
+ await markUsed(entryDir);
1306
+ return {
1307
+ kind: "hit",
1308
+ summaries: manifest.summaries,
1309
+ diagnostic: { kind: "hit" }
1310
+ };
1311
+ },
1312
+ async plan(input) {
1313
+ const entryDir = entryDirFor(cacheDir, input);
1314
+ const manifest = await readManifest(
1315
+ import_node_path2.default.join(entryDir, "manifest.json")
1316
+ );
1317
+ if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
1318
+ return null;
1319
+ }
1320
+ const currentConfigStamp = await stampConfigFile(input.configPath);
1321
+ if (!fileStampEquals(manifest.configStamp, currentConfigStamp)) {
1322
+ return null;
1323
+ }
1324
+ await markUsed(entryDir);
1325
+ return buildPlan(manifest, await resolveFileStamps(input));
1326
+ },
1327
+ async write(input, summaries, attribution) {
1328
+ const entryDir = entryDirFor(cacheDir, input);
1329
+ const previous = await readManifest(
1330
+ import_node_path2.default.join(entryDir, "manifest.json")
1331
+ );
1332
+ const configStamp = await stampConfigFile(input.configPath);
1333
+ const files = await hashStamps(await resolveFileStamps(input), previous);
1334
+ const manifest = {
1335
+ schemaVersion: SCHEMA_VERSION,
1336
+ adapterPacksDigest: input.adapterPacksDigest,
1337
+ configStamp,
1338
+ files,
1339
+ summaries,
1340
+ ...attribution === void 0 ? {} : encodeAttribution(attribution)
1341
+ };
1342
+ await import_promises.default.mkdir(entryDir, { recursive: true });
1343
+ await import_promises.default.writeFile(
1344
+ import_node_path2.default.join(entryDir, "manifest.json"),
1345
+ JSON.stringify(manifest)
1346
+ );
1347
+ await evictOldEntries(cacheDir, entryDir);
1348
+ }
1349
+ };
1350
+ }
1351
+ async function buildPlan(manifest, currentStamps) {
1352
+ const stored = new Map(manifest.files.map((f) => [f.path, f]));
1353
+ const current = new Map(currentStamps.map((f) => [f.path, f]));
1354
+ const changed = /* @__PURE__ */ new Set();
1355
+ const removed = /* @__PURE__ */ new Set();
1356
+ for (const p of stored.keys()) {
1357
+ if (!current.has(p)) {
1358
+ removed.add(p);
1359
+ }
1360
+ }
1361
+ const toVerify = [];
1362
+ for (const [p, stamp] of current) {
1363
+ const before = stored.get(p);
1364
+ if (before === void 0) {
1365
+ changed.add(p);
1366
+ } else if (!fileStampEquals(before, stamp)) {
1367
+ if (before.contentHash === void 0) {
1368
+ changed.add(p);
1369
+ } else {
1370
+ toVerify.push(p);
1371
+ }
1372
+ }
1373
+ }
1374
+ await Promise.all(
1375
+ toVerify.map(async (p) => {
1376
+ const hash = await hashFile(p);
1377
+ if (hash === null || hash !== stored.get(p)?.contentHash) {
1378
+ changed.add(p);
1379
+ }
1380
+ })
1381
+ );
1382
+ const roots = /* @__PURE__ */ new Map();
1383
+ const rootNames = manifest.roots ?? [];
1384
+ const depPaths = manifest.depPaths ?? [];
1385
+ let rootsDeclined = 0;
1386
+ rootNames.forEach((rootPath, i) => {
1387
+ const meta = manifest.rootMeta?.[i];
1388
+ if (meta === void 0) {
1389
+ return;
1390
+ }
1391
+ if (!meta.cacheable) {
1392
+ rootsDeclined += 1;
1393
+ }
1394
+ roots.set(rootPath, {
1395
+ path: rootPath,
1396
+ cacheable: meta.cacheable,
1397
+ deps: meta.deps.flatMap((d) => {
1398
+ const p = depPaths[d];
1399
+ return p === void 0 ? [] : [p];
1400
+ }),
1401
+ claims: meta.claims,
1402
+ meta: meta.meta,
1403
+ packs: meta.packs
1404
+ });
1405
+ });
1406
+ const validRoots = /* @__PURE__ */ new Set();
1407
+ for (const [rootPath, record] of roots) {
1408
+ if (!record.cacheable || !current.has(rootPath)) {
1409
+ continue;
1410
+ }
1411
+ if (changed.has(rootPath)) {
1412
+ continue;
1413
+ }
1414
+ const depMoved = record.deps.some((d) => changed.has(d) || removed.has(d));
1415
+ if (!depMoved) {
1416
+ validRoots.add(rootPath);
1417
+ }
1418
+ }
1419
+ const owners = manifest.owners ?? [];
1420
+ return {
1421
+ changed,
1422
+ removed,
1423
+ roots,
1424
+ validRoots,
1425
+ rootsDeclined,
1426
+ reuse(valid) {
1427
+ const summaries = [];
1428
+ const reusedOwners = [];
1429
+ manifest.summaries.forEach((summary, i) => {
1430
+ const ownerPaths = (owners[i] ?? []).flatMap((o) => {
1431
+ const p = rootNames[o];
1432
+ return p === void 0 ? [] : [p];
1433
+ });
1434
+ if (ownerPaths.some((p) => valid.has(p))) {
1435
+ summaries.push(summary);
1436
+ reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
1437
+ }
1438
+ });
1439
+ return { summaries, owners: reusedOwners };
1440
+ },
1441
+ allSummaries() {
1442
+ return manifest.summaries;
1443
+ },
1444
+ attribution() {
1445
+ return {
1446
+ roots: [...roots.values()],
1447
+ owners: owners.map(
1448
+ (ownerIds) => ownerIds.flatMap((o) => {
1449
+ const p = rootNames[o];
1450
+ return p === void 0 ? [] : [p];
1451
+ })
1452
+ )
1453
+ };
1454
+ }
1455
+ };
1456
+ }
1457
+ function encodeAttribution(attribution) {
1458
+ const roots = attribution.roots.map((r) => r.path);
1459
+ const rootIndex = new Map(roots.map((p, i) => [p, i]));
1460
+ const depIndex = /* @__PURE__ */ new Map();
1461
+ const depPaths = [];
1462
+ const depIdOf = (p) => {
1463
+ const existing = depIndex.get(p);
1464
+ if (existing !== void 0) {
1465
+ return existing;
1466
+ }
1467
+ const id = depPaths.length;
1468
+ depPaths.push(p);
1469
+ depIndex.set(p, id);
1470
+ return id;
1471
+ };
1472
+ const rootMeta = attribution.roots.map((r) => ({
1473
+ cacheable: r.cacheable,
1474
+ deps: r.deps.map(depIdOf),
1475
+ claims: r.claims,
1476
+ meta: r.meta,
1477
+ packs: r.packs
1478
+ }));
1479
+ const owners = attribution.owners.map(
1480
+ (ownerPaths) => ownerPaths.flatMap((p) => {
1481
+ const i = rootIndex.get(p);
1482
+ return i === void 0 ? [] : [i];
1483
+ })
1484
+ );
1485
+ return { roots, rootMeta, depPaths, owners };
1486
+ }
1487
+ async function hashStamps(stamps, previous) {
1488
+ const before = new Map((previous?.files ?? []).map((f) => [f.path, f]));
1489
+ return Promise.all(
1490
+ stamps.map(async (stamp) => {
1491
+ const prior = before.get(stamp.path);
1492
+ if (prior !== void 0 && prior.contentHash !== void 0 && fileStampEquals(prior, stamp)) {
1493
+ return { ...stamp, contentHash: prior.contentHash };
1494
+ }
1495
+ const contentHash = await hashFile(stamp.path);
1496
+ return contentHash === null ? stamp : { ...stamp, contentHash };
1497
+ })
1498
+ );
1499
+ }
1500
+ async function hashFile(filePath) {
1501
+ try {
1502
+ const content = await import_promises.default.readFile(filePath);
1503
+ return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
1504
+ } catch {
1505
+ return null;
1506
+ }
1507
+ }
1508
+ async function describeAbsentEntry(cacheDir) {
1509
+ try {
1510
+ const entries = await import_promises.default.readdir(cacheDir, { withFileTypes: true });
1511
+ return entries.some((e) => e.isDirectory() && isEntryDir(e.name)) ? "key-changed" : "no-manifest";
1512
+ } catch {
1513
+ return "no-manifest";
1514
+ }
1515
+ }
1516
+ function entryDirFor(cacheDir, input) {
1517
+ const key = [
1518
+ SCHEMA_VERSION,
1519
+ input.adapterPacksDigest,
1520
+ input.configPath ?? ""
1521
+ ].join(" ");
1522
+ const name = (0, import_node_crypto2.createHash)("sha256").update(key).digest("hex").slice(0, 16);
1523
+ return import_node_path2.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
1524
+ }
1525
+ function isEntryDir(name) {
1526
+ return ENTRY_DIR_NAME.test(name);
1527
+ }
1528
+ async function markUsed(entryDir) {
1529
+ const now = /* @__PURE__ */ new Date();
1530
+ try {
1531
+ await import_promises.default.utimes(entryDir, now, now);
1532
+ } catch {
1533
+ }
1534
+ }
1535
+ async function evictOldEntries(cacheDir, keepDir) {
1536
+ try {
1537
+ await import_promises.default.rm(import_node_path2.default.join(cacheDir, "manifest.json"), { force: true });
1538
+ const entries = await import_promises.default.readdir(cacheDir, { withFileTypes: true });
1539
+ const dirs = await Promise.all(
1540
+ entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
1541
+ const dir = import_node_path2.default.join(cacheDir, e.name);
1542
+ const stat = await import_promises.default.stat(dir);
1543
+ return { dir, mtimeMs: stat.mtimeMs };
1544
+ })
1545
+ );
1546
+ const doomed = dirs.filter((d) => d.dir !== keepDir).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(MAX_ENTRIES - 1);
1547
+ await Promise.all(
1548
+ doomed.map((d) => import_promises.default.rm(d.dir, { recursive: true, force: true }))
1549
+ );
1550
+ } catch {
1551
+ }
1552
+ }
1553
+ function missDiag(reason) {
1554
+ return {
1555
+ kind: "miss",
1556
+ diagnostic: { kind: "miss", missReason: reason }
1557
+ };
1558
+ }
1559
+ async function readManifest(manifestPath) {
1560
+ try {
1561
+ const raw = await import_promises.default.readFile(manifestPath, "utf-8");
1562
+ return JSON.parse(raw);
1563
+ } catch {
1564
+ return null;
1565
+ }
1566
+ }
1567
+ async function stampConfigFile(configPath) {
1568
+ if (configPath === void 0) {
1569
+ return null;
1570
+ }
1571
+ try {
1572
+ const stat = await import_promises.default.stat(configPath);
1573
+ return { path: configPath, mtimeMs: stat.mtimeMs, size: stat.size };
1574
+ } catch {
1575
+ return null;
1576
+ }
1577
+ }
1578
+ async function resolveFileStamps(input) {
1579
+ const stamped = await Promise.all(
1580
+ input.files.map(async (p) => {
1581
+ try {
1582
+ const stat = await import_promises.default.stat(p);
1583
+ return { path: p, mtimeMs: stat.mtimeMs, size: stat.size };
1584
+ } catch {
1585
+ return { path: p, mtimeMs: -1, size: -1 };
1586
+ }
1587
+ })
1588
+ );
1589
+ stamped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1590
+ return stamped;
1591
+ }
1592
+ function fileStampsEqual(a, b) {
1593
+ return a.length === b.length && a.every((s, i) => fileStampEquals(s, b[i]));
1594
+ }
1595
+ function fileStampEquals(a, b) {
1596
+ if (a === null && b === null) {
1597
+ return true;
1598
+ }
1599
+ if (a === null || b === null) {
1600
+ return false;
1601
+ }
1602
+ return a.path === b.path && a.mtimeMs === b.mtimeMs && a.size === b.size;
1603
+ }
1604
+
1605
+ // src/extractionReport.ts
1606
+ var import_behavioral_ir3 = require("@suss/behavioral-ir");
1607
+ var emptyTally = () => ({
1608
+ candidateFiles: 0,
1609
+ unitsDiscovered: 0,
1610
+ unitsInGatedFiles: 0,
1611
+ effectsRecognized: 0,
1612
+ unitsClaimed: 0,
1613
+ selfCollisions: 0,
1614
+ summariesProduced: 0,
1615
+ failures: [],
1616
+ helpersMatched: /* @__PURE__ */ new Set()
1617
+ });
1618
+ function recordPackFailure(tally, failure) {
1619
+ const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
1620
+ tally?.failures.push({
1621
+ hook: failure.hook,
1622
+ file: failure.file,
1623
+ message
1624
+ });
1625
+ return `[suss] pack "${failure.pack}" threw from ${failure.hook} while reading ${failure.file}: ${message}
1626
+ `;
1627
+ }
1628
+ function createPackTallies(packs) {
1629
+ const tallies = /* @__PURE__ */ new Map();
1630
+ for (const pack of packs) {
1631
+ tallies.set(pack.name, emptyTally());
1632
+ }
1633
+ return tallies;
1634
+ }
1635
+ function tallyUnit(tallies, recognition) {
1636
+ const tally = recognition === void 0 ? void 0 : tallies.get(recognition);
1637
+ if (tally === void 0) {
1638
+ return;
1639
+ }
1640
+ tally.unitsDiscovered += 1;
1641
+ tally.summariesProduced += 1;
1642
+ }
1643
+ var emptyCounts = () => ({
1644
+ bound: 0,
1645
+ providers: 0,
1646
+ withBehavior: 0
1647
+ });
1648
+ function summaryCountsByPack(summaries) {
1649
+ const counts = /* @__PURE__ */ new Map();
1650
+ for (const summary of summaries) {
1651
+ const recognition = summary.identity.boundaryBinding?.recognition;
1652
+ if (recognition === void 0 || recognition === null) {
1653
+ continue;
1654
+ }
1655
+ const entry = counts.get(recognition) ?? emptyCounts();
1656
+ entry.bound += 1;
1657
+ if (import_behavioral_ir3.BOUNDARY_ROLE[summary.kind] === "provider") {
1658
+ entry.providers += 1;
1659
+ }
1660
+ if (summary.transitions.length > 0) {
1661
+ entry.withBehavior += 1;
1662
+ }
1663
+ counts.set(recognition, entry);
1664
+ }
1665
+ return counts;
1666
+ }
1667
+ function buildUngatedExtractionReport(args) {
1668
+ const bySummary = summaryCountsByPack(args.summaries);
1669
+ const packFunnels = args.packs.map((pack) => {
1670
+ const tally = args.tallies.get(pack.name) ?? emptyTally();
1671
+ const counted = bySummary.get(pack.name) ?? emptyCounts();
1672
+ return {
1673
+ pack: pack.name,
1674
+ version: pack.version,
1675
+ discovers: pack.discovers,
1676
+ recognizes: false,
1677
+ gates: [],
1678
+ unresolvedGates: [],
1679
+ candidateFiles: args.filesWalked,
1680
+ unitsDiscovered: tally.unitsDiscovered,
1681
+ unitsInGatedFiles: 0,
1682
+ effectsRecognized: 0,
1683
+ unitsClaimed: tally.unitsDiscovered,
1684
+ selfCollisions: 0,
1685
+ summariesProduced: tally.summariesProduced,
1686
+ failures: tally.failures,
1687
+ helpersUnmatched: [],
1688
+ summariesBound: counted.bound,
1689
+ providerSummaries: counted.providers,
1690
+ summariesWithBehavior: counted.withBehavior,
1691
+ declarations: null
1692
+ };
1693
+ });
1694
+ return {
1695
+ filesInProject: null,
1696
+ filesWalked: args.filesWalked,
1697
+ packs: packFunnels,
1698
+ summaries: args.summaries.length,
1699
+ filesWithUnreadableExports: [],
1700
+ reassignedNamesUnstated: 0,
1701
+ emptyStage: args.summaries.length === 0 ? firstEmptyUngatedStage(packFunnels, args.filesWalked) : null
1702
+ };
1703
+ }
1704
+ function firstEmptyUngatedStage(packs, filesWalked) {
1705
+ if (filesWalked === 0) {
1706
+ return "candidateFiles";
1707
+ }
1708
+ if (packs.every((p) => p.unitsDiscovered === 0)) {
1709
+ return "discovery";
1710
+ }
1711
+ return "assembly";
1712
+ }
1713
+
1714
+ // src/packHealth.ts
1715
+ function stagesOf(funnel) {
1716
+ const stages = [];
1717
+ const gateSaysSomething = funnel.gates.length > 0 && funnel.unresolvedGates.length === 0;
1718
+ if (funnel.recognizes && !funnel.discovers && gateSaysSomething) {
1719
+ stages.push({
1720
+ from: { count: funnel.unitsInGatedFiles, name: "unit bodies" },
1721
+ to: { count: funnel.effectsRecognized, name: "effects" }
1722
+ });
1723
+ }
1724
+ if (funnel.discovers && gateSaysSomething) {
1725
+ stages.push({
1726
+ from: { count: funnel.candidateFiles, name: "source files" },
1727
+ to: { count: funnel.unitsDiscovered, name: "units" }
1728
+ });
1729
+ }
1730
+ stages.push(
1731
+ {
1732
+ from: { count: funnel.unitsClaimed, name: "units" },
1733
+ to: { count: funnel.summariesBound, name: "summaries" }
1734
+ },
1735
+ {
1736
+ from: { count: funnel.summariesBound, name: "summaries" },
1737
+ to: { count: funnel.summariesWithBehavior, name: "transitions" }
1738
+ }
1739
+ );
1740
+ return stages;
1741
+ }
1742
+ function funnelDrops(packs) {
1743
+ const violations = [];
1744
+ for (const funnel of packs) {
1745
+ for (const stage of stagesOf(funnel)) {
1746
+ if (stage.from.count === 0 || stage.to.count > 0) {
1747
+ continue;
1748
+ }
1749
+ violations.push({
1750
+ label: funnel.pack,
1751
+ detail: `${stage.from.count} ${stage.from.name} -> 0 ${stage.to.name}`
1752
+ });
1753
+ }
1754
+ }
1755
+ return violations;
1756
+ }
1757
+ function recognizersWithNoUnits(packs) {
1758
+ return packs.filter(
1759
+ (funnel) => funnel.recognizes && !funnel.discovers && funnel.gates.length > 0 && funnel.unresolvedGates.length === 0 && funnel.candidateFiles > 0 && funnel.unitsInGatedFiles === 0 && // The closure walks a recognizer-only pack's gated exports as
1760
+ // roots, so effects recognized there mean the run worked and
1761
+ // the missing framework pack costs attribution, not existence.
1762
+ funnel.effectsRecognized === 0
1763
+ ).map((funnel) => ({
1764
+ label: funnel.pack,
1765
+ detail: `${funnel.candidateFiles} gated files, and no pack in this run discovered a unit in them. ${funnel.pack} reads calls inside units another pack finds, so add the pack that finds this project's handlers (-f express, -f fastify, ...), or run suss init to work out which.`
1766
+ }));
1767
+ }
1768
+ function helpersThatMatchedNothing(packs) {
1769
+ return packs.flatMap(
1770
+ (funnel) => funnel.helpersUnmatched.map((helper) => ({
1771
+ label: funnel.pack,
1772
+ detail: `${helper} matched no call in this run, so whatever it registers is missing. suss read that helper out of the project and then failed to match a call to it, which is a bug in suss rather than in your code.`
1773
+ }))
1774
+ );
1775
+ }
1776
+ function unversionedPacks(packs) {
1777
+ return packs.filter((funnel) => funnel.version === null).map((funnel) => ({
1778
+ label: funnel.pack,
1779
+ detail: "no version declared"
1780
+ }));
1781
+ }
1782
+ function selfCollisions(packs) {
1783
+ return packs.filter((funnel) => funnel.selfCollisions > 0).map((funnel) => ({
1784
+ label: funnel.pack,
1785
+ detail: `${funnel.selfCollisions} ${funnel.selfCollisions === 1 ? "unit" : "units"} matched twice, second dropped`
1786
+ }));
1787
+ }
1788
+ function threwWhileReading(packs) {
1789
+ return packs.filter((funnel) => funnel.failures.length > 0).map((funnel) => {
1790
+ const first = funnel.failures[0];
1791
+ const rest = funnel.failures.length - 1;
1792
+ const alsoIn = rest > 0 ? ` (+${rest} more)` : "";
1793
+ return {
1794
+ label: funnel.pack,
1795
+ detail: `${first.hook} on ${first.file}${alsoIn}: ${first.message}`
1796
+ };
1797
+ });
1798
+ }
1799
+ function packGradients(report) {
1800
+ const gradients = [];
1801
+ for (const funnel of report.packs) {
1802
+ if (funnel.declarations === null) {
1803
+ continue;
1804
+ }
1805
+ gradients.push(gradientOf(funnel.pack, funnel.declarations.declarations));
1806
+ }
1807
+ return gradients;
1808
+ }
1809
+ function gradientOf(pack, declarations) {
1810
+ const gradient = {
1811
+ pack,
1812
+ dataLinks: 0,
1813
+ functionLinks: [],
1814
+ astLinks: [],
1815
+ withoutExample: []
1816
+ };
1817
+ for (const declaration of declarations) {
1818
+ gradient.dataLinks += declaration.dataLinks;
1819
+ for (const question of declaration.functionLinks) {
1820
+ gradient.functionLinks.push(`${declaration.name}.${question}`);
1821
+ }
1822
+ for (const question of declaration.astLinks) {
1823
+ gradient.astLinks.push(`${declaration.name}.${question}`);
1824
+ }
1825
+ if (declaration.example === null) {
1826
+ gradient.withoutExample.push(declaration.name);
1827
+ }
1828
+ }
1829
+ return gradient;
1830
+ }
1831
+ function opaqueLinks(gradients) {
1832
+ return gradients.filter((gradient) => gradient.functionLinks.length > 0).map((gradient) => ({
1833
+ label: gradient.pack,
1834
+ detail: `${gradient.functionLinks.join(", ")} (${gradient.dataLinks} data)`
1835
+ }));
1836
+ }
1837
+ function reachesTheSyntaxTree(gradients) {
1838
+ return gradients.filter((gradient) => gradient.astLinks.length > 0).map((gradient) => ({
1839
+ label: gradient.pack,
1840
+ detail: gradient.astLinks.join(", ")
1841
+ }));
1842
+ }
1843
+ function undocumentedDeclarations(gradients) {
1844
+ return gradients.filter((gradient) => gradient.withoutExample.length > 0).map((gradient) => ({
1845
+ label: gradient.pack,
1846
+ detail: gradient.withoutExample.join(", ")
1847
+ }));
1848
+ }
1849
+ function evaluatePackHealth(report) {
1850
+ const gradients = packGradients(report);
1851
+ return [
1852
+ {
1853
+ name: "no pack throws while it reads",
1854
+ code: "threw",
1855
+ audience: "run",
1856
+ violations: threwWhileReading(report.packs)
1857
+ },
1858
+ {
1859
+ name: "no pack finds something and records nothing",
1860
+ code: "no-output",
1861
+ audience: "run",
1862
+ violations: funnelDrops(report.packs)
1863
+ },
1864
+ {
1865
+ name: "no pack collides with itself",
1866
+ code: "double-match",
1867
+ audience: "run",
1868
+ violations: selfCollisions(report.packs)
1869
+ },
1870
+ {
1871
+ name: "every recognizer had units to look inside",
1872
+ code: "no-units",
1873
+ audience: "run",
1874
+ violations: recognizersWithNoUnits(report.packs)
1875
+ },
1876
+ {
1877
+ name: "every registration helper the run read matched a call",
1878
+ code: "no-helper",
1879
+ audience: "run",
1880
+ violations: helpersThatMatchedNothing(report.packs)
1881
+ },
1882
+ {
1883
+ name: "every pack declares a version",
1884
+ code: "no-version",
1885
+ audience: "pack",
1886
+ violations: unversionedPacks(report.packs)
1887
+ },
1888
+ {
1889
+ name: "every link a declared pack states is data",
1890
+ code: "fn-link",
1891
+ audience: "pack",
1892
+ violations: opaqueLinks(gradients)
1893
+ },
1894
+ {
1895
+ name: "no declared pack reads the syntax tree",
1896
+ code: "ast-link",
1897
+ audience: "pack",
1898
+ violations: reachesTheSyntaxTree(gradients)
1899
+ },
1900
+ {
1901
+ name: "every declaration states an example",
1902
+ code: "no-example",
1903
+ audience: "pack",
1904
+ violations: undocumentedDeclarations(gradients)
1905
+ }
1906
+ ];
1907
+ }
1908
+ function formatPackHealth(checks, audiences) {
1909
+ const fired = checks.filter(
1910
+ (check) => check.violations.length > 0 && audiences.includes(check.audience)
1911
+ );
1912
+ if (fired.length === 0) {
1913
+ return "";
1914
+ }
1915
+ const rows = fired.flatMap(
1916
+ (check) => check.violations.map((violation) => ({
1917
+ code: check.code,
1918
+ pack: violation.label,
1919
+ detail: violation.detail
1920
+ }))
1921
+ );
1922
+ const codeWidth = Math.max(...rows.map((row) => row.code.length));
1923
+ const packWidth = Math.max(...rows.map((row) => row.pack.length));
1924
+ const lines = ["", `Pack health (${rows.length}):`];
1925
+ for (const row of rows) {
1926
+ lines.push(
1927
+ ` ${row.code.padEnd(codeWidth)} ${row.pack.padEnd(packWidth)} ${row.detail}`
1928
+ );
1929
+ }
1930
+ return `${lines.join("\n")}
1931
+ `;
1932
+ }
1933
+
1934
+ // src/timing.ts
1935
+ function createTimer() {
1936
+ const phases = /* @__PURE__ */ new Map();
1937
+ const start = performance.now();
1938
+ function record(label, deltaMs) {
1939
+ const existing = phases.get(label);
1940
+ if (existing === void 0) {
1941
+ phases.set(label, { durationMs: deltaMs, calls: 1 });
1942
+ } else {
1943
+ existing.durationMs += deltaMs;
1944
+ existing.calls += 1;
1945
+ }
1946
+ }
1947
+ return {
1948
+ time(label, fn) {
1949
+ const t0 = performance.now();
1950
+ try {
1951
+ return fn();
1952
+ } finally {
1953
+ record(label, performance.now() - t0);
1954
+ }
1955
+ },
1956
+ async timeAsync(label, fn) {
1957
+ const t0 = performance.now();
1958
+ try {
1959
+ return await fn();
1960
+ } finally {
1961
+ record(label, performance.now() - t0);
1962
+ }
1963
+ },
1964
+ report() {
1965
+ const totalMs = performance.now() - start;
1966
+ const ordered = [...phases.entries()].map(([label, s]) => ({
1967
+ label,
1968
+ durationMs: s.durationMs,
1969
+ calls: s.calls
1970
+ })).sort((a, b) => b.durationMs - a.durationMs);
1971
+ return { totalMs, phases: ordered };
1972
+ }
1973
+ };
1974
+ }
1975
+ function noopTimer() {
1976
+ return {
1977
+ time(_label, fn) {
1978
+ return fn();
1979
+ },
1980
+ async timeAsync(_label, fn) {
1981
+ return fn();
1982
+ },
1983
+ report() {
1984
+ return { totalMs: 0, phases: [] };
1985
+ }
1986
+ };
1987
+ }
1988
+
1060
1989
  // src/index.ts
1061
1990
  function valuesOfOutput(output) {
1062
1991
  if (output.type === "response" && output.statusCode !== null) {
@@ -1069,7 +1998,7 @@ function makeTransitionId(functionName, branch) {
1069
1998
  const { terminal } = branch;
1070
1999
  const statusKey = terminal.statusCode === null ? "none" : terminal.statusCode.type === "literal" ? String(terminal.statusCode.value) : `dyn:${terminal.statusCode.sourceText}`;
1071
2000
  const conditionSig = branch.conditions.map((c) => `${c.polarity}:${c.sourceText}`).join(";");
1072
- const conditionHash = (0, import_node_crypto.createHash)("sha1").update(conditionSig).digest("hex").slice(0, 7);
2001
+ const conditionHash = (0, import_node_crypto3.createHash)("sha1").update(conditionSig).digest("hex").slice(0, 7);
1073
2002
  return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
1074
2003
  }
1075
2004
  function collapseReading(table, reading) {
@@ -1193,22 +2122,22 @@ function buildMetadata(raw) {
1193
2122
  let metadata = {};
1194
2123
  const http = buildHttpMetadataValue(raw);
1195
2124
  if (http !== null) {
1196
- metadata = (0, import_behavioral_ir3.withHttpMetadata)(metadata, http);
2125
+ metadata = (0, import_behavioral_ir4.withHttpMetadata)(metadata, http);
1197
2126
  }
1198
2127
  const graphql = buildGraphqlMetadataValue(raw);
1199
2128
  if (graphql !== null) {
1200
- metadata = (0, import_behavioral_ir3.withGraphqlMetadata)(metadata, graphql);
2129
+ metadata = (0, import_behavioral_ir4.withGraphqlMetadata)(metadata, graphql);
1201
2130
  }
1202
2131
  if (raw.sourceDocumentLabel !== void 0) {
1203
- metadata = (0, import_behavioral_ir3.withSourceDocumentMetadata)(metadata, {
2132
+ metadata = (0, import_behavioral_ir4.withSourceDocumentMetadata)(metadata, {
1204
2133
  label: raw.sourceDocumentLabel
1205
2134
  });
1206
2135
  }
1207
2136
  if (raw.mount !== void 0) {
1208
- metadata = (0, import_behavioral_ir3.withMountMetadata)(metadata, raw.mount);
2137
+ metadata = (0, import_behavioral_ir4.withMountMetadata)(metadata, raw.mount);
1209
2138
  }
1210
2139
  if (raw.wrappers !== void 0 && raw.wrappers.length > 0) {
1211
- metadata = (0, import_behavioral_ir3.withWrapperMetadata)(metadata, { applied: raw.wrappers });
2140
+ metadata = (0, import_behavioral_ir4.withWrapperMetadata)(metadata, { applied: raw.wrappers });
1212
2141
  }
1213
2142
  return Object.keys(metadata).length > 0 ? metadata : null;
1214
2143
  }
@@ -1258,7 +2187,7 @@ function answersWithHttpResponses(raw) {
1258
2187
  if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
1259
2188
  return true;
1260
2189
  }
1261
- return (0, import_behavioral_ir3.exchangesHttpResponses)(raw.boundaryBinding);
2190
+ return (0, import_behavioral_ir4.exchangesHttpResponses)(raw.boundaryBinding);
1262
2191
  }
1263
2192
  function detectGaps(raw, transitions, options) {
1264
2193
  if (options.gapHandling === "silent") {
@@ -1473,6 +2402,7 @@ function paramToInput(param) {
1473
2402
  0 && (module.exports = {
1474
2403
  IdMap,
1475
2404
  IdSet,
2405
+ MAX_ENTRIES,
1476
2406
  MAX_PATHS,
1477
2407
  PathBudgetExceeded,
1478
2408
  SKIP_CHILDREN,
@@ -1482,25 +2412,44 @@ function paramToInput(param) {
1482
2412
  andThenReading,
1483
2413
  assembleSummary,
1484
2414
  assessConfidence,
2415
+ buildUngatedExtractionReport,
1485
2416
  composeWrappers,
2417
+ computeContentHash,
2418
+ computeDistHashFrom,
1486
2419
  configuredCallOption,
2420
+ createAdapterStamp,
2421
+ createCacheLayer,
2422
+ createPackTallies,
2423
+ createTimer,
1487
2424
  detectGaps,
1488
2425
  effectToIR,
2426
+ emptyTally,
1489
2427
  enumerateOrDegrade,
1490
2428
  enumerateStructuredPaths,
2429
+ evaluatePackHealth,
1491
2430
  firstWrittenReading,
2431
+ formatPackHealth,
1492
2432
  guardsHoldOn,
1493
2433
  httpRouteDiscovery,
1494
2434
  makeTransitionId,
1495
2435
  mapReading,
2436
+ moduleInitStructure,
2437
+ noopTimer,
2438
+ packGradients,
1496
2439
  paramToInput,
2440
+ projectFileStamp,
1497
2441
  rawConditionToPredicate,
2442
+ recordPackFailure,
1498
2443
  registrationHelperDiscovery,
1499
2444
  routeHelperIndex,
2445
+ runDigest,
1500
2446
  runsBefore,
1501
2447
  scopeOption,
1502
2448
  sharedGatingConditions,
2449
+ stampModuleImports,
1503
2450
  storageSystemOption,
2451
+ summaryCountsByPack,
2452
+ tallyUnit,
1504
2453
  terminalToOutput,
1505
2454
  unreadableReading,
1506
2455
  unwrapJsonStringify,