@suss/extractor 0.25.0 → 0.26.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
@@ -43,6 +43,7 @@ __export(index_exports, {
43
43
  assembleSummary: () => assembleSummary,
44
44
  assessConfidence: () => assessConfidence,
45
45
  buildUngatedExtractionReport: () => buildUngatedExtractionReport,
46
+ commonDirectoryOf: () => commonDirectoryOf,
46
47
  composeWrappers: () => composeWrappers,
47
48
  computeContentHash: () => computeContentHash,
48
49
  computeDistHashFrom: () => computeDistHashFrom,
@@ -295,8 +296,8 @@ var catchEntryCond = () => fixedCond(
295
296
  "positive",
296
297
  "catchBlock"
297
298
  );
298
- function classify(path3, home, isAncestorOrSelf) {
299
- return path3.map((cond) => {
299
+ function classify(path4, home, isAncestorOrSelf) {
300
+ return path4.map((cond) => {
300
301
  if (cond.branchStmt === null) {
301
302
  return cond.info;
302
303
  }
@@ -361,9 +362,9 @@ function chargeBudget(state) {
361
362
  );
362
363
  }
363
364
  }
364
- function recordTerminal(ctx, terminal, home, path3) {
365
+ function recordTerminal(ctx, terminal, home, path4) {
365
366
  chargeBudget(ctx.state);
366
- const info = classify(path3, home, ctx.isAncestorOrSelf);
367
+ const info = classify(path4, home, ctx.isAncestorOrSelf);
367
368
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
368
369
  existing.push(info);
369
370
  ctx.state.byTerminal.set(terminal, existing);
@@ -374,15 +375,15 @@ function containsTerminal(ctx, stmt) {
374
375
  }
375
376
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
376
377
  }
377
- function stepIf(stmt, path3, ctx) {
378
+ function stepIf(stmt, path4, ctx) {
378
379
  const thenExit = exitKindOfList(stmt.thenBody);
379
380
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
380
381
  const thenPath = [
381
- ...path3,
382
+ ...path4,
382
383
  branchCond(stmt, stmt.condition, "positive", elseExit)
383
384
  ];
384
385
  const elsePath = [
385
- ...path3,
386
+ ...path4,
386
387
  branchCond(
387
388
  stmt,
388
389
  stmt.condition,
@@ -399,7 +400,7 @@ function stepIf(stmt, path3, ctx) {
399
400
  }
400
401
  return out;
401
402
  }
402
- function stepSwitch(stmt, path3, ctx) {
403
+ function stepSwitch(stmt, path4, ctx) {
403
404
  const out = [];
404
405
  const negations = [];
405
406
  let defaultRanBody = false;
@@ -409,7 +410,7 @@ function stepSwitch(stmt, path3, ctx) {
409
410
  if (hasStrayBreak(group.body)) {
410
411
  throw new UnmodeledFlow("non-trailing break in switch clause");
411
412
  }
412
- const groupPath = group.condition === null ? [...path3, ...negations] : [...path3, fixedCond(group.condition, "positive", "explicit")];
413
+ const groupPath = group.condition === null ? [...path4, ...negations] : [...path4, fixedCond(group.condition, "positive", "explicit")];
413
414
  const conts = enumerate(ctx, group.body, groupPath);
414
415
  if (conts.length > 0) {
415
416
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -426,24 +427,24 @@ function stepSwitch(stmt, path3, ctx) {
426
427
  }
427
428
  }
428
429
  if (!defaultRanBody) {
429
- out.push([...path3, ...negations]);
430
+ out.push([...path4, ...negations]);
430
431
  }
431
432
  return out;
432
433
  }
433
- function stepLoop(stmt, path3, ctx) {
434
+ function stepLoop(stmt, path4, ctx) {
434
435
  const bodyExit = exitKindOfList(stmt.body);
435
436
  if (containsTerminal(ctx, stmt)) {
436
437
  enumerate(ctx, stmt.body, [
437
- ...path3,
438
+ ...path4,
438
439
  loopIterationCond(stmt.condition.sourceText)
439
440
  ]);
440
441
  }
441
442
  if (bodyExit !== null) {
442
- return [[...path3, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
443
+ return [[...path4, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
443
444
  }
444
- return [path3];
445
+ return [path4];
445
446
  }
446
- function stepTry(stmt, path3, ctx) {
447
+ function stepTry(stmt, path4, ctx) {
447
448
  if (stmt.finallyBody !== null) {
448
449
  const finallyHasTerminal = stmt.finallyBody.some(
449
450
  (s) => containsTerminal(ctx, s)
@@ -453,22 +454,22 @@ function stepTry(stmt, path3, ctx) {
453
454
  }
454
455
  }
455
456
  const out = [];
456
- out.push(...enumerate(ctx, stmt.tryBody, path3));
457
+ out.push(...enumerate(ctx, stmt.tryBody, path4));
457
458
  if (stmt.catchBody !== null) {
458
- const catchPath = [...path3, catchEntryCond()];
459
+ const catchPath = [...path4, catchEntryCond()];
459
460
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
460
461
  }
461
462
  return out;
462
463
  }
463
- function stepExit(stmt, path3, ctx) {
464
+ function stepExit(stmt, path4, ctx) {
464
465
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
465
466
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
466
- collector.push(path3);
467
+ collector.push(path4);
467
468
  }
468
469
  return [];
469
470
  }
470
- function stepOpaque(_stmt, path3, _ctx) {
471
- return [path3];
471
+ function stepOpaque(_stmt, path4, _ctx) {
472
+ return [path4];
472
473
  }
473
474
  var stepHandlers = {
474
475
  if: stepIf,
@@ -478,20 +479,20 @@ var stepHandlers = {
478
479
  exit: stepExit,
479
480
  opaque: stepOpaque
480
481
  };
481
- function stepCallback(ctx, body, path3) {
482
+ function stepCallback(ctx, body, path4) {
482
483
  const exits = [];
483
484
  ctx.state.callbackExits.push(exits);
484
485
  let fellThrough;
485
486
  try {
486
- fellThrough = enumerate(ctx, body, path3);
487
+ fellThrough = enumerate(ctx, body, path4);
487
488
  } finally {
488
489
  ctx.state.callbackExits.pop();
489
490
  }
490
491
  const out = [...fellThrough, ...exits];
491
- return out.length === 0 ? [path3] : out;
492
+ return out.length === 0 ? [path4] : out;
492
493
  }
493
- function stepStatement(ctx, stmt, path3) {
494
- let paths = [path3];
494
+ function stepStatement(ctx, stmt, path4) {
495
+ let paths = [path4];
495
496
  for (const body of stmt.callbacks ?? []) {
496
497
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
497
498
  }
@@ -572,8 +573,8 @@ function enumerate(ctx, stmts, prefix) {
572
573
  let frontiers = [[...prefix]];
573
574
  for (const stmt of stmts) {
574
575
  const nextFrontiers = [];
575
- for (const path3 of mergeRejoined(frontiers)) {
576
- nextFrontiers.push(...stepStatement(ctx, stmt, path3));
576
+ for (const path4 of mergeRejoined(frontiers)) {
577
+ nextFrontiers.push(...stepStatement(ctx, stmt, path4));
577
578
  }
578
579
  frontiers = nextFrontiers;
579
580
  if (frontiers.length > MAX_PATHS) {
@@ -624,9 +625,9 @@ function enumerateStructuredPaths(input) {
624
625
  isAncestorOrSelf
625
626
  };
626
627
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
627
- for (const path3 of continuations) {
628
+ for (const path4 of continuations) {
628
629
  chargeBudget(state);
629
- state.fallthrough.push(classify(path3, null, isAncestorOrSelf));
630
+ state.fallthrough.push(classify(path4, null, isAncestorOrSelf));
630
631
  }
631
632
  return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
632
633
  }
@@ -1114,8 +1115,8 @@ function sharedGatingConditions(paths, readPredicate) {
1114
1115
  return [];
1115
1116
  }
1116
1117
  const shared = new Set(first.map(keyOf2));
1117
- for (const path3 of rest) {
1118
- const here = new Set(path3.map(keyOf2));
1118
+ for (const path4 of rest) {
1119
+ const here = new Set(path4.map(keyOf2));
1119
1120
  for (const value of shared) {
1120
1121
  if (!here.has(value)) {
1121
1122
  shared.delete(value);
@@ -1191,6 +1192,27 @@ function walkDescendants(root, carried, visitor) {
1191
1192
  }
1192
1193
  }
1193
1194
 
1195
+ // src/projectRoot.ts
1196
+ var import_node_path = __toESM(require("path"), 1);
1197
+ function commonDirectoryOf(files) {
1198
+ const absolute = files.filter((f) => import_node_path.default.isAbsolute(f));
1199
+ const first = absolute[0];
1200
+ if (first === void 0) {
1201
+ return void 0;
1202
+ }
1203
+ let shared = import_node_path.default.dirname(first).split(import_node_path.default.sep);
1204
+ for (const file of absolute.slice(1)) {
1205
+ const parts = import_node_path.default.dirname(file).split(import_node_path.default.sep);
1206
+ let i = 0;
1207
+ while (i < shared.length && i < parts.length && shared[i] === parts[i]) {
1208
+ i += 1;
1209
+ }
1210
+ shared = shared.slice(0, i);
1211
+ }
1212
+ const joined = shared.join(import_node_path.default.sep);
1213
+ return joined.length > 1 ? joined : void 0;
1214
+ }
1215
+
1194
1216
  // src/reading.ts
1195
1217
  function writtenReading(value, range) {
1196
1218
  return { kind: "written", value, range };
@@ -1248,7 +1270,7 @@ function valueToReadFurtherFrom(reading) {
1248
1270
  var import_node_crypto = require("crypto");
1249
1271
  var import_node_fs = __toESM(require("fs"), 1);
1250
1272
  var import_node_module = require("module");
1251
- var import_node_path = __toESM(require("path"), 1);
1273
+ var import_node_path2 = __toESM(require("path"), 1);
1252
1274
  var import_node_url = require("url");
1253
1275
  var import_meta = {};
1254
1276
  var ANALYSIS_PACKAGES = [
@@ -1291,14 +1313,14 @@ function createAdapterStamp(config) {
1291
1313
  function readAdapterCodeStamp(moduleUrl) {
1292
1314
  try {
1293
1315
  const selfPath = (0, import_node_url.fileURLToPath)(moduleUrl);
1294
- const hash = computeDistHashFrom(import_node_path.default.dirname(selfPath));
1316
+ const hash = computeDistHashFrom(import_node_path2.default.dirname(selfPath));
1295
1317
  return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
1296
1318
  } catch {
1297
1319
  return SOURCE_STAMP;
1298
1320
  }
1299
1321
  }
1300
1322
  function computeDistHashFrom(dir) {
1301
- const candidates = [import_node_path.default.join(dir, "index.js"), import_node_path.default.join(dir, "index.cjs")];
1323
+ const candidates = [import_node_path2.default.join(dir, "index.js"), import_node_path2.default.join(dir, "index.cjs")];
1302
1324
  for (const candidate of candidates) {
1303
1325
  if (!import_node_fs.default.existsSync(candidate)) {
1304
1326
  continue;
@@ -1356,7 +1378,7 @@ function analysisBundles() {
1356
1378
  // src/cache.ts
1357
1379
  var import_node_crypto2 = require("crypto");
1358
1380
  var import_promises = __toESM(require("fs/promises"), 1);
1359
- var import_node_path2 = __toESM(require("path"), 1);
1381
+ var import_node_path3 = __toESM(require("path"), 1);
1360
1382
  var SCHEMA_VERSION = "7";
1361
1383
  var MAX_ENTRIES = 2;
1362
1384
  var ENTRY_PREFIX = "key-";
@@ -1382,7 +1404,7 @@ function createCacheLayer(cacheDir) {
1382
1404
  async lookup(input) {
1383
1405
  const entryDir = entryDirFor(cacheDir, input);
1384
1406
  const manifest = await readManifest(
1385
- import_node_path2.default.join(entryDir, "manifest.json")
1407
+ import_node_path3.default.join(entryDir, "manifest.json")
1386
1408
  );
1387
1409
  if (manifest === null) {
1388
1410
  return missDiag(await describeAbsentEntry(cacheDir));
@@ -1405,7 +1427,7 @@ function createCacheLayer(cacheDir) {
1405
1427
  async plan(input) {
1406
1428
  const entryDir = entryDirFor(cacheDir, input);
1407
1429
  const manifest = await readManifest(
1408
- import_node_path2.default.join(entryDir, "manifest.json")
1430
+ import_node_path3.default.join(entryDir, "manifest.json")
1409
1431
  );
1410
1432
  if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
1411
1433
  return null;
@@ -1420,7 +1442,7 @@ function createCacheLayer(cacheDir) {
1420
1442
  async write(input, summaries, attribution) {
1421
1443
  const entryDir = entryDirFor(cacheDir, input);
1422
1444
  const previous = await readManifest(
1423
- import_node_path2.default.join(entryDir, "manifest.json")
1445
+ import_node_path3.default.join(entryDir, "manifest.json")
1424
1446
  );
1425
1447
  const configStamp = await stampConfigFile(input.configPath);
1426
1448
  const files = await hashStamps(await resolveFileStamps(input), previous);
@@ -1434,7 +1456,7 @@ function createCacheLayer(cacheDir) {
1434
1456
  };
1435
1457
  await import_promises.default.mkdir(entryDir, { recursive: true });
1436
1458
  await import_promises.default.writeFile(
1437
- import_node_path2.default.join(entryDir, "manifest.json"),
1459
+ import_node_path3.default.join(entryDir, "manifest.json"),
1438
1460
  JSON.stringify(manifest)
1439
1461
  );
1440
1462
  await evictOldEntries(cacheDir, entryDir);
@@ -1613,7 +1635,7 @@ function entryDirFor(cacheDir, input) {
1613
1635
  input.configPath ?? ""
1614
1636
  ].join(" ");
1615
1637
  const name = (0, import_node_crypto2.createHash)("sha256").update(key).digest("hex").slice(0, 16);
1616
- return import_node_path2.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
1638
+ return import_node_path3.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
1617
1639
  }
1618
1640
  function isEntryDir(name) {
1619
1641
  return ENTRY_DIR_NAME.test(name);
@@ -1627,11 +1649,11 @@ async function markUsed(entryDir) {
1627
1649
  }
1628
1650
  async function evictOldEntries(cacheDir, keepDir) {
1629
1651
  try {
1630
- await import_promises.default.rm(import_node_path2.default.join(cacheDir, "manifest.json"), { force: true });
1652
+ await import_promises.default.rm(import_node_path3.default.join(cacheDir, "manifest.json"), { force: true });
1631
1653
  const entries = await import_promises.default.readdir(cacheDir, { withFileTypes: true });
1632
1654
  const dirs = await Promise.all(
1633
1655
  entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
1634
- const dir = import_node_path2.default.join(cacheDir, e.name);
1656
+ const dir = import_node_path3.default.join(cacheDir, e.name);
1635
1657
  const stat = await import_promises.default.stat(dir);
1636
1658
  return { dir, mtimeMs: stat.mtimeMs };
1637
1659
  })
@@ -2458,6 +2480,7 @@ function paramToInput(param) {
2458
2480
  assembleSummary,
2459
2481
  assessConfidence,
2460
2482
  buildUngatedExtractionReport,
2483
+ commonDirectoryOf,
2461
2484
  composeWrappers,
2462
2485
  computeContentHash,
2463
2486
  computeDistHashFrom,
package/dist/index.d.cts CHANGED
@@ -2272,6 +2272,16 @@ interface NodeVisitor<T, C> {
2272
2272
  */
2273
2273
  declare function walkDescendants<T extends WalkableNode<T>, C>(root: T, carried: C, visitor: NodeVisitor<T, C>): void;
2274
2274
 
2275
+ /**
2276
+ * The project root when nothing declares one.
2277
+ *
2278
+ * Resolution from anywhere inside a tree finds the same `node_modules`,
2279
+ * and a recursive reader pointed at the directory every file shares
2280
+ * reaches all of them, so the deepest common directory serves both.
2281
+ */
2282
+ /** The deepest directory containing every file, or undefined when they share no absolute root. */
2283
+ declare function commonDirectoryOf(files: ReadonlyArray<string>): string | undefined;
2284
+
2275
2285
  /**
2276
2286
  * The small surface every language adapter implements, whatever that
2277
2287
  * language's own tooling looks like underneath.
@@ -3434,4 +3444,4 @@ declare function rawConditionToPredicate(c: RawCondition): Predicate;
3434
3444
  declare function effectToIR(effect: RawEffect): Effect;
3435
3445
  declare function paramToInput(param: RawParameter): Input;
3436
3446
 
3437
- export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, type WrapperMethodRegistration, type WrapperOptionRegistration, type WrapperRegistration, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
3447
+ export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, type WrapperMethodRegistration, type WrapperOptionRegistration, type WrapperRegistration, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, commonDirectoryOf, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
package/dist/index.d.ts CHANGED
@@ -2272,6 +2272,16 @@ interface NodeVisitor<T, C> {
2272
2272
  */
2273
2273
  declare function walkDescendants<T extends WalkableNode<T>, C>(root: T, carried: C, visitor: NodeVisitor<T, C>): void;
2274
2274
 
2275
+ /**
2276
+ * The project root when nothing declares one.
2277
+ *
2278
+ * Resolution from anywhere inside a tree finds the same `node_modules`,
2279
+ * and a recursive reader pointed at the directory every file shares
2280
+ * reaches all of them, so the deepest common directory serves both.
2281
+ */
2282
+ /** The deepest directory containing every file, or undefined when they share no absolute root. */
2283
+ declare function commonDirectoryOf(files: ReadonlyArray<string>): string | undefined;
2284
+
2275
2285
  /**
2276
2286
  * The small surface every language adapter implements, whatever that
2277
2287
  * language's own tooling looks like underneath.
@@ -3434,4 +3444,4 @@ declare function rawConditionToPredicate(c: RawCondition): Predicate;
3434
3444
  declare function effectToIR(effect: RawEffect): Effect;
3435
3445
  declare function paramToInput(param: RawParameter): Input;
3436
3446
 
3437
- export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, type WrapperMethodRegistration, type WrapperOptionRegistration, type WrapperRegistration, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
3447
+ export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, type WrapperMethodRegistration, type WrapperOptionRegistration, type WrapperRegistration, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, commonDirectoryOf, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
package/dist/index.js CHANGED
@@ -218,8 +218,8 @@ var catchEntryCond = () => fixedCond(
218
218
  "positive",
219
219
  "catchBlock"
220
220
  );
221
- function classify(path3, home, isAncestorOrSelf) {
222
- return path3.map((cond) => {
221
+ function classify(path4, home, isAncestorOrSelf) {
222
+ return path4.map((cond) => {
223
223
  if (cond.branchStmt === null) {
224
224
  return cond.info;
225
225
  }
@@ -284,9 +284,9 @@ function chargeBudget(state) {
284
284
  );
285
285
  }
286
286
  }
287
- function recordTerminal(ctx, terminal, home, path3) {
287
+ function recordTerminal(ctx, terminal, home, path4) {
288
288
  chargeBudget(ctx.state);
289
- const info = classify(path3, home, ctx.isAncestorOrSelf);
289
+ const info = classify(path4, home, ctx.isAncestorOrSelf);
290
290
  const existing = ctx.state.byTerminal.get(terminal) ?? [];
291
291
  existing.push(info);
292
292
  ctx.state.byTerminal.set(terminal, existing);
@@ -297,15 +297,15 @@ function containsTerminal(ctx, stmt) {
297
297
  }
298
298
  return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
299
299
  }
300
- function stepIf(stmt, path3, ctx) {
300
+ function stepIf(stmt, path4, ctx) {
301
301
  const thenExit = exitKindOfList(stmt.thenBody);
302
302
  const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
303
303
  const thenPath = [
304
- ...path3,
304
+ ...path4,
305
305
  branchCond(stmt, stmt.condition, "positive", elseExit)
306
306
  ];
307
307
  const elsePath = [
308
- ...path3,
308
+ ...path4,
309
309
  branchCond(
310
310
  stmt,
311
311
  stmt.condition,
@@ -322,7 +322,7 @@ function stepIf(stmt, path3, ctx) {
322
322
  }
323
323
  return out;
324
324
  }
325
- function stepSwitch(stmt, path3, ctx) {
325
+ function stepSwitch(stmt, path4, ctx) {
326
326
  const out = [];
327
327
  const negations = [];
328
328
  let defaultRanBody = false;
@@ -332,7 +332,7 @@ function stepSwitch(stmt, path3, ctx) {
332
332
  if (hasStrayBreak(group.body)) {
333
333
  throw new UnmodeledFlow("non-trailing break in switch clause");
334
334
  }
335
- const groupPath = group.condition === null ? [...path3, ...negations] : [...path3, fixedCond(group.condition, "positive", "explicit")];
335
+ const groupPath = group.condition === null ? [...path4, ...negations] : [...path4, fixedCond(group.condition, "positive", "explicit")];
336
336
  const conts = enumerate(ctx, group.body, groupPath);
337
337
  if (conts.length > 0) {
338
338
  if (!group.hasTrailingBreak && !fallthroughSafe) {
@@ -349,24 +349,24 @@ function stepSwitch(stmt, path3, ctx) {
349
349
  }
350
350
  }
351
351
  if (!defaultRanBody) {
352
- out.push([...path3, ...negations]);
352
+ out.push([...path4, ...negations]);
353
353
  }
354
354
  return out;
355
355
  }
356
- function stepLoop(stmt, path3, ctx) {
356
+ function stepLoop(stmt, path4, ctx) {
357
357
  const bodyExit = exitKindOfList(stmt.body);
358
358
  if (containsTerminal(ctx, stmt)) {
359
359
  enumerate(ctx, stmt.body, [
360
- ...path3,
360
+ ...path4,
361
361
  loopIterationCond(stmt.condition.sourceText)
362
362
  ]);
363
363
  }
364
364
  if (bodyExit !== null) {
365
- return [[...path3, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
365
+ return [[...path4, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
366
366
  }
367
- return [path3];
367
+ return [path4];
368
368
  }
369
- function stepTry(stmt, path3, ctx) {
369
+ function stepTry(stmt, path4, ctx) {
370
370
  if (stmt.finallyBody !== null) {
371
371
  const finallyHasTerminal = stmt.finallyBody.some(
372
372
  (s) => containsTerminal(ctx, s)
@@ -376,22 +376,22 @@ function stepTry(stmt, path3, ctx) {
376
376
  }
377
377
  }
378
378
  const out = [];
379
- out.push(...enumerate(ctx, stmt.tryBody, path3));
379
+ out.push(...enumerate(ctx, stmt.tryBody, path4));
380
380
  if (stmt.catchBody !== null) {
381
- const catchPath = [...path3, catchEntryCond()];
381
+ const catchPath = [...path4, catchEntryCond()];
382
382
  out.push(...enumerate(ctx, stmt.catchBody, catchPath));
383
383
  }
384
384
  return out;
385
385
  }
386
- function stepExit(stmt, path3, ctx) {
386
+ function stepExit(stmt, path4, ctx) {
387
387
  const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
388
388
  if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
389
- collector.push(path3);
389
+ collector.push(path4);
390
390
  }
391
391
  return [];
392
392
  }
393
- function stepOpaque(_stmt, path3, _ctx) {
394
- return [path3];
393
+ function stepOpaque(_stmt, path4, _ctx) {
394
+ return [path4];
395
395
  }
396
396
  var stepHandlers = {
397
397
  if: stepIf,
@@ -401,20 +401,20 @@ var stepHandlers = {
401
401
  exit: stepExit,
402
402
  opaque: stepOpaque
403
403
  };
404
- function stepCallback(ctx, body, path3) {
404
+ function stepCallback(ctx, body, path4) {
405
405
  const exits = [];
406
406
  ctx.state.callbackExits.push(exits);
407
407
  let fellThrough;
408
408
  try {
409
- fellThrough = enumerate(ctx, body, path3);
409
+ fellThrough = enumerate(ctx, body, path4);
410
410
  } finally {
411
411
  ctx.state.callbackExits.pop();
412
412
  }
413
413
  const out = [...fellThrough, ...exits];
414
- return out.length === 0 ? [path3] : out;
414
+ return out.length === 0 ? [path4] : out;
415
415
  }
416
- function stepStatement(ctx, stmt, path3) {
417
- let paths = [path3];
416
+ function stepStatement(ctx, stmt, path4) {
417
+ let paths = [path4];
418
418
  for (const body of stmt.callbacks ?? []) {
419
419
  paths = paths.flatMap((each) => stepCallback(ctx, body, each));
420
420
  }
@@ -495,8 +495,8 @@ function enumerate(ctx, stmts, prefix) {
495
495
  let frontiers = [[...prefix]];
496
496
  for (const stmt of stmts) {
497
497
  const nextFrontiers = [];
498
- for (const path3 of mergeRejoined(frontiers)) {
499
- nextFrontiers.push(...stepStatement(ctx, stmt, path3));
498
+ for (const path4 of mergeRejoined(frontiers)) {
499
+ nextFrontiers.push(...stepStatement(ctx, stmt, path4));
500
500
  }
501
501
  frontiers = nextFrontiers;
502
502
  if (frontiers.length > MAX_PATHS) {
@@ -547,9 +547,9 @@ function enumerateStructuredPaths(input) {
547
547
  isAncestorOrSelf
548
548
  };
549
549
  const continuations = withRejoined(enumerate(ctx, input.statements, []));
550
- for (const path3 of continuations) {
550
+ for (const path4 of continuations) {
551
551
  chargeBudget(state);
552
- state.fallthrough.push(classify(path3, null, isAncestorOrSelf));
552
+ state.fallthrough.push(classify(path4, null, isAncestorOrSelf));
553
553
  }
554
554
  return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
555
555
  }
@@ -1037,8 +1037,8 @@ function sharedGatingConditions(paths, readPredicate) {
1037
1037
  return [];
1038
1038
  }
1039
1039
  const shared = new Set(first.map(keyOf2));
1040
- for (const path3 of rest) {
1041
- const here = new Set(path3.map(keyOf2));
1040
+ for (const path4 of rest) {
1041
+ const here = new Set(path4.map(keyOf2));
1042
1042
  for (const value of shared) {
1043
1043
  if (!here.has(value)) {
1044
1044
  shared.delete(value);
@@ -1114,6 +1114,27 @@ function walkDescendants(root, carried, visitor) {
1114
1114
  }
1115
1115
  }
1116
1116
 
1117
+ // src/projectRoot.ts
1118
+ import path from "path";
1119
+ function commonDirectoryOf(files) {
1120
+ const absolute = files.filter((f) => path.isAbsolute(f));
1121
+ const first = absolute[0];
1122
+ if (first === void 0) {
1123
+ return void 0;
1124
+ }
1125
+ let shared = path.dirname(first).split(path.sep);
1126
+ for (const file of absolute.slice(1)) {
1127
+ const parts = path.dirname(file).split(path.sep);
1128
+ let i = 0;
1129
+ while (i < shared.length && i < parts.length && shared[i] === parts[i]) {
1130
+ i += 1;
1131
+ }
1132
+ shared = shared.slice(0, i);
1133
+ }
1134
+ const joined = shared.join(path.sep);
1135
+ return joined.length > 1 ? joined : void 0;
1136
+ }
1137
+
1117
1138
  // src/reading.ts
1118
1139
  function writtenReading(value, range) {
1119
1140
  return { kind: "written", value, range };
@@ -1171,7 +1192,7 @@ function valueToReadFurtherFrom(reading) {
1171
1192
  import { createHash } from "crypto";
1172
1193
  import fs from "fs";
1173
1194
  import { createRequire } from "module";
1174
- import path from "path";
1195
+ import path2 from "path";
1175
1196
  import { fileURLToPath } from "url";
1176
1197
  var ANALYSIS_PACKAGES = [
1177
1198
  "@suss/extractor",
@@ -1213,14 +1234,14 @@ function createAdapterStamp(config) {
1213
1234
  function readAdapterCodeStamp(moduleUrl) {
1214
1235
  try {
1215
1236
  const selfPath = fileURLToPath(moduleUrl);
1216
- const hash = computeDistHashFrom(path.dirname(selfPath));
1237
+ const hash = computeDistHashFrom(path2.dirname(selfPath));
1217
1238
  return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
1218
1239
  } catch {
1219
1240
  return SOURCE_STAMP;
1220
1241
  }
1221
1242
  }
1222
1243
  function computeDistHashFrom(dir) {
1223
- const candidates = [path.join(dir, "index.js"), path.join(dir, "index.cjs")];
1244
+ const candidates = [path2.join(dir, "index.js"), path2.join(dir, "index.cjs")];
1224
1245
  for (const candidate of candidates) {
1225
1246
  if (!fs.existsSync(candidate)) {
1226
1247
  continue;
@@ -1278,7 +1299,7 @@ function analysisBundles() {
1278
1299
  // src/cache.ts
1279
1300
  import { createHash as createHash2 } from "crypto";
1280
1301
  import fs2 from "fs/promises";
1281
- import path2 from "path";
1302
+ import path3 from "path";
1282
1303
  var SCHEMA_VERSION = "7";
1283
1304
  var MAX_ENTRIES = 2;
1284
1305
  var ENTRY_PREFIX = "key-";
@@ -1304,7 +1325,7 @@ function createCacheLayer(cacheDir) {
1304
1325
  async lookup(input) {
1305
1326
  const entryDir = entryDirFor(cacheDir, input);
1306
1327
  const manifest = await readManifest(
1307
- path2.join(entryDir, "manifest.json")
1328
+ path3.join(entryDir, "manifest.json")
1308
1329
  );
1309
1330
  if (manifest === null) {
1310
1331
  return missDiag(await describeAbsentEntry(cacheDir));
@@ -1327,7 +1348,7 @@ function createCacheLayer(cacheDir) {
1327
1348
  async plan(input) {
1328
1349
  const entryDir = entryDirFor(cacheDir, input);
1329
1350
  const manifest = await readManifest(
1330
- path2.join(entryDir, "manifest.json")
1351
+ path3.join(entryDir, "manifest.json")
1331
1352
  );
1332
1353
  if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
1333
1354
  return null;
@@ -1342,7 +1363,7 @@ function createCacheLayer(cacheDir) {
1342
1363
  async write(input, summaries, attribution) {
1343
1364
  const entryDir = entryDirFor(cacheDir, input);
1344
1365
  const previous = await readManifest(
1345
- path2.join(entryDir, "manifest.json")
1366
+ path3.join(entryDir, "manifest.json")
1346
1367
  );
1347
1368
  const configStamp = await stampConfigFile(input.configPath);
1348
1369
  const files = await hashStamps(await resolveFileStamps(input), previous);
@@ -1356,7 +1377,7 @@ function createCacheLayer(cacheDir) {
1356
1377
  };
1357
1378
  await fs2.mkdir(entryDir, { recursive: true });
1358
1379
  await fs2.writeFile(
1359
- path2.join(entryDir, "manifest.json"),
1380
+ path3.join(entryDir, "manifest.json"),
1360
1381
  JSON.stringify(manifest)
1361
1382
  );
1362
1383
  await evictOldEntries(cacheDir, entryDir);
@@ -1535,7 +1556,7 @@ function entryDirFor(cacheDir, input) {
1535
1556
  input.configPath ?? ""
1536
1557
  ].join(" ");
1537
1558
  const name = createHash2("sha256").update(key).digest("hex").slice(0, 16);
1538
- return path2.join(cacheDir, `${ENTRY_PREFIX}${name}`);
1559
+ return path3.join(cacheDir, `${ENTRY_PREFIX}${name}`);
1539
1560
  }
1540
1561
  function isEntryDir(name) {
1541
1562
  return ENTRY_DIR_NAME.test(name);
@@ -1549,11 +1570,11 @@ async function markUsed(entryDir) {
1549
1570
  }
1550
1571
  async function evictOldEntries(cacheDir, keepDir) {
1551
1572
  try {
1552
- await fs2.rm(path2.join(cacheDir, "manifest.json"), { force: true });
1573
+ await fs2.rm(path3.join(cacheDir, "manifest.json"), { force: true });
1553
1574
  const entries = await fs2.readdir(cacheDir, { withFileTypes: true });
1554
1575
  const dirs = await Promise.all(
1555
1576
  entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
1556
- const dir = path2.join(cacheDir, e.name);
1577
+ const dir = path3.join(cacheDir, e.name);
1557
1578
  const stat = await fs2.stat(dir);
1558
1579
  return { dir, mtimeMs: stat.mtimeMs };
1559
1580
  })
@@ -2379,6 +2400,7 @@ export {
2379
2400
  assembleSummary,
2380
2401
  assessConfidence,
2381
2402
  buildUngatedExtractionReport,
2403
+ commonDirectoryOf,
2382
2404
  composeWrappers,
2383
2405
  computeContentHash,
2384
2406
  computeDistHashFrom,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/extractor",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "type": "module",
5
5
  "description": "The suss extraction pipeline, which turns what a language adapter read into the summaries the checker pairs across a boundary.",
6
6
  "keywords": [
@@ -46,7 +46,7 @@
46
46
  "typecheck": "tsc --noEmit"
47
47
  },
48
48
  "dependencies": {
49
- "@suss/behavioral-ir": "0.25.0",
49
+ "@suss/behavioral-ir": "0.26.0",
50
50
  "zod": "^4.0.0"
51
51
  },
52
52
  "devDependencies": {