@suss/adapter-typescript 0.9.0 → 0.11.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
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ADAPTER_VERSION: () => ADAPTER_VERSION,
34
34
  ResolutionStore: () => ResolutionStore,
35
+ WhySession: () => WhySession,
35
36
  collectAncestorBranches: () => collectAncestorBranches,
36
37
  collectClientFieldAccesses: () => collectClientFieldAccesses,
37
38
  computeAdapterPacksDigest: () => computeAdapterPacksDigest,
@@ -57,7 +58,8 @@ __export(index_exports, {
57
58
  readName: () => readName,
58
59
  resolveAliasedSymbol: () => resolveAliasedSymbol,
59
60
  resolveSubject: () => resolveSubject,
60
- rootIdentifier: () => rootIdentifier
61
+ rootIdentifier: () => rootIdentifier2,
62
+ workspaceRootFor: () => workspaceRootFor
61
63
  });
62
64
  module.exports = __toCommonJS(index_exports);
63
65
 
@@ -1076,24 +1078,64 @@ function collectFallthroughCaseLabels(caseClause) {
1076
1078
  }
1077
1079
 
1078
1080
  // src/paths/pathConditions.ts
1079
- var import_ts_morph8 = require("ts-morph");
1081
+ var import_ts_morph9 = require("ts-morph");
1080
1082
  var import_extractor2 = require("@suss/extractor");
1081
1083
 
1082
- // src/paths/lowering.ts
1084
+ // src/walk/descent.ts
1083
1085
  var import_ts_morph7 = require("ts-morph");
1086
+ var NO_BARRIERS = /* @__PURE__ */ new Set();
1087
+ function isDescentStop(node, func, barriers = NO_BARRIERS) {
1088
+ if (node === func) {
1089
+ return false;
1090
+ }
1091
+ if (import_ts_morph7.Node.isFunctionDeclaration(node) || import_ts_morph7.Node.isMethodDeclaration(node)) {
1092
+ return true;
1093
+ }
1094
+ if (import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isFunctionExpression(node)) {
1095
+ return barriers.has(node);
1096
+ }
1097
+ return false;
1098
+ }
1099
+ function isInlineCallback(node, func, barriers = NO_BARRIERS) {
1100
+ if (node === func) {
1101
+ return false;
1102
+ }
1103
+ if (!(import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isFunctionExpression(node))) {
1104
+ return false;
1105
+ }
1106
+ return !barriers.has(node);
1107
+ }
1108
+ function isModuleScopeStop(node) {
1109
+ return import_ts_morph7.Node.isFunctionDeclaration(node) || import_ts_morph7.Node.isMethodDeclaration(node) || import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isFunctionExpression(node) || import_ts_morph7.Node.isClassDeclaration(node) || import_ts_morph7.Node.isClassExpression(node);
1110
+ }
1111
+ function crossesNestedFunctionScope(node, func) {
1112
+ let current2 = node;
1113
+ while (current2 !== void 0 && current2 !== func) {
1114
+ if (startsItsOwnScope(current2)) {
1115
+ return true;
1116
+ }
1117
+ current2 = current2.getParent();
1118
+ }
1119
+ return false;
1120
+ }
1121
+ function startsItsOwnScope(node) {
1122
+ return import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isFunctionExpression(node) || import_ts_morph7.Node.isFunctionDeclaration(node) || import_ts_morph7.Node.isMethodDeclaration(node) || import_ts_morph7.Node.isGetAccessorDeclaration(node) || import_ts_morph7.Node.isSetAccessorDeclaration(node) || import_ts_morph7.Node.isConstructorDeclaration(node);
1123
+ }
1124
+
1125
+ // src/paths/lowering.ts
1126
+ var import_ts_morph8 = require("ts-morph");
1084
1127
  var import_extractor = require("@suss/extractor");
1085
- var isFunctionBoundary = (node) => import_ts_morph7.Node.isFunctionDeclaration(node) || import_ts_morph7.Node.isFunctionExpression(node) || import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isMethodDeclaration(node);
1086
1128
  function exitKindOf(stmt) {
1087
1129
  let sawReturn = false;
1088
1130
  let sawThrow = false;
1089
1131
  const visit = (node) => {
1090
- if (node !== stmt && isFunctionBoundary(node)) {
1132
+ if (node !== stmt && startsItsOwnScope(node)) {
1091
1133
  return;
1092
1134
  }
1093
- if (import_ts_morph7.Node.isReturnStatement(node)) {
1135
+ if (import_ts_morph8.Node.isReturnStatement(node)) {
1094
1136
  sawReturn = true;
1095
1137
  }
1096
- if (import_ts_morph7.Node.isThrowStatement(node)) {
1138
+ if (import_ts_morph8.Node.isThrowStatement(node)) {
1097
1139
  sawThrow = true;
1098
1140
  }
1099
1141
  for (const child of node.getChildren()) {
@@ -1110,14 +1152,14 @@ var statementsOf = (stmt) => {
1110
1152
  if (stmt === void 0) {
1111
1153
  return [];
1112
1154
  }
1113
- if (import_ts_morph7.Node.isBlock(stmt)) {
1155
+ if (import_ts_morph8.Node.isBlock(stmt)) {
1114
1156
  return stmt.getStatements();
1115
1157
  }
1116
1158
  return [stmt];
1117
1159
  };
1118
- var isLoop = (stmt) => import_ts_morph7.Node.isForOfStatement(stmt) || import_ts_morph7.Node.isForInStatement(stmt) || import_ts_morph7.Node.isForStatement(stmt) || import_ts_morph7.Node.isWhileStatement(stmt) || import_ts_morph7.Node.isDoStatement(stmt);
1160
+ var isLoop = (stmt) => import_ts_morph8.Node.isForOfStatement(stmt) || import_ts_morph8.Node.isForInStatement(stmt) || import_ts_morph8.Node.isForStatement(stmt) || import_ts_morph8.Node.isWhileStatement(stmt) || import_ts_morph8.Node.isDoStatement(stmt);
1119
1161
  function getLoopBody(stmt) {
1120
- if (import_ts_morph7.Node.isForOfStatement(stmt) || import_ts_morph7.Node.isForInStatement(stmt) || import_ts_morph7.Node.isForStatement(stmt) || import_ts_morph7.Node.isWhileStatement(stmt) || import_ts_morph7.Node.isDoStatement(stmt)) {
1162
+ if (import_ts_morph8.Node.isForOfStatement(stmt) || import_ts_morph8.Node.isForInStatement(stmt) || import_ts_morph8.Node.isForStatement(stmt) || import_ts_morph8.Node.isWhileStatement(stmt) || import_ts_morph8.Node.isDoStatement(stmt)) {
1121
1163
  return stmt.getStatement();
1122
1164
  }
1123
1165
  return void 0;
@@ -1132,37 +1174,97 @@ var conditionOf = (expr) => ({
1132
1174
  sourceText: expr.getText(),
1133
1175
  expression: expr
1134
1176
  });
1135
- function lowerStatement(stmt, rawToStructured) {
1136
- const structured = buildStructured(stmt, rawToStructured);
1137
- rawToStructured.set(stmt, structured);
1177
+ function lowerStatement(stmt, ctx) {
1178
+ const structured = {
1179
+ ...buildStructured(stmt, ctx),
1180
+ callbacks: lowerCallbacksIn(ownExpressionsOf(stmt), ctx)
1181
+ };
1182
+ ctx.rawToStructured.set(stmt, structured);
1138
1183
  return structured;
1139
1184
  }
1140
- function lowerList(stmts, rawToStructured) {
1141
- return spliceBlocks(stmts).map((s) => lowerStatement(s, rawToStructured));
1185
+ function lowerList(stmts, ctx) {
1186
+ return spliceBlocks(stmts).map((s) => lowerStatement(s, ctx));
1187
+ }
1188
+ function ownExpressionsOf(stmt) {
1189
+ if (import_ts_morph8.Node.isIfStatement(stmt) || import_ts_morph8.Node.isSwitchStatement(stmt)) {
1190
+ return [stmt.getExpression()];
1191
+ }
1192
+ if (import_ts_morph8.Node.isWhileStatement(stmt) || import_ts_morph8.Node.isDoStatement(stmt)) {
1193
+ return [stmt.getExpression()];
1194
+ }
1195
+ if (import_ts_morph8.Node.isForOfStatement(stmt) || import_ts_morph8.Node.isForInStatement(stmt)) {
1196
+ return [stmt.getInitializer(), stmt.getExpression()];
1197
+ }
1198
+ if (import_ts_morph8.Node.isForStatement(stmt)) {
1199
+ return [
1200
+ stmt.getInitializer(),
1201
+ stmt.getCondition(),
1202
+ stmt.getIncrementor()
1203
+ ].filter((n) => n !== void 0);
1204
+ }
1205
+ if (import_ts_morph8.Node.isTryStatement(stmt)) {
1206
+ return [];
1207
+ }
1208
+ if (import_ts_morph8.Node.isReturnStatement(stmt) || import_ts_morph8.Node.isThrowStatement(stmt)) {
1209
+ const value = stmt.getExpression();
1210
+ return value === void 0 ? [] : [value];
1211
+ }
1212
+ if (import_ts_morph8.Node.isBreakStatement(stmt) || import_ts_morph8.Node.isContinueStatement(stmt)) {
1213
+ return [];
1214
+ }
1215
+ return [stmt];
1216
+ }
1217
+ function lowerCallbacksIn(parts, ctx) {
1218
+ const bodies = [];
1219
+ const visit = (node, root) => {
1220
+ if (node !== root && isDescentStop(node, ctx.func, ctx.barriers)) {
1221
+ return;
1222
+ }
1223
+ if (node !== root && (import_ts_morph8.Node.isArrowFunction(node) || import_ts_morph8.Node.isFunctionExpression(node)) && isInlineCallback(node, ctx.func, ctx.barriers)) {
1224
+ const body = node.getBody();
1225
+ if (body !== void 0 && import_ts_morph8.Node.isBlock(body)) {
1226
+ bodies.push(lowerList(body.getStatements(), ctx));
1227
+ return;
1228
+ }
1229
+ } else if (node !== root && startsItsOwnScope(node)) {
1230
+ return;
1231
+ }
1232
+ for (const child of node.getChildren()) {
1233
+ visit(child, root);
1234
+ }
1235
+ };
1236
+ for (const part of parts) {
1237
+ visit(part, part);
1238
+ }
1239
+ return bodies;
1240
+ }
1241
+ function lowerExpressionBodyCallbacks(expression, func, barriers = NO_BARRIERS) {
1242
+ return lowerCallbacksIn([expression], {
1243
+ func,
1244
+ barriers,
1245
+ rawToStructured: /* @__PURE__ */ new Map()
1246
+ });
1142
1247
  }
1143
1248
  function spliceBlocks(stmts) {
1144
1249
  return stmts.flatMap(
1145
- (s) => import_ts_morph7.Node.isBlock(s) ? spliceBlocks(s.getStatements()) : [s]
1250
+ (s) => import_ts_morph8.Node.isBlock(s) ? spliceBlocks(s.getStatements()) : [s]
1146
1251
  );
1147
1252
  }
1148
- function buildStructured(stmt, rawToStructured) {
1149
- if (import_ts_morph7.Node.isIfStatement(stmt)) {
1253
+ function buildStructured(stmt, ctx) {
1254
+ if (import_ts_morph8.Node.isIfStatement(stmt)) {
1150
1255
  const elseBranch = stmt.getElseStatement();
1151
1256
  return {
1152
1257
  kind: "if",
1153
1258
  condition: conditionOf(stmt.getExpression()),
1154
- thenBody: lowerList(
1155
- statementsOf(stmt.getThenStatement()),
1156
- rawToStructured
1157
- ),
1158
- elseBody: elseBranch === void 0 ? null : lowerList(statementsOf(elseBranch), rawToStructured),
1259
+ thenBody: lowerList(statementsOf(stmt.getThenStatement()), ctx),
1260
+ elseBody: elseBranch === void 0 ? null : lowerList(statementsOf(elseBranch), ctx),
1159
1261
  exitKind: exitKindOf(stmt)
1160
1262
  };
1161
1263
  }
1162
- if (import_ts_morph7.Node.isSwitchStatement(stmt)) {
1264
+ if (import_ts_morph8.Node.isSwitchStatement(stmt)) {
1163
1265
  return {
1164
1266
  kind: "switch",
1165
- groups: lowerSwitchGroups(stmt, rawToStructured),
1267
+ groups: lowerSwitchGroups(stmt, ctx),
1166
1268
  exitKind: exitKindOf(stmt)
1167
1269
  };
1168
1270
  }
@@ -1170,36 +1272,36 @@ function buildStructured(stmt, rawToStructured) {
1170
1272
  return {
1171
1273
  kind: "loop",
1172
1274
  condition: { sourceText: loopHeaderText(stmt), expression: null },
1173
- body: lowerList(statementsOf(getLoopBody(stmt)), rawToStructured),
1275
+ body: lowerList(statementsOf(getLoopBody(stmt)), ctx),
1174
1276
  exitKind: exitKindOf(stmt)
1175
1277
  };
1176
1278
  }
1177
- if (import_ts_morph7.Node.isTryStatement(stmt)) {
1279
+ if (import_ts_morph8.Node.isTryStatement(stmt)) {
1178
1280
  const catchClause = stmt.getCatchClause();
1179
1281
  const finallyBlock = stmt.getFinallyBlock();
1180
1282
  return {
1181
1283
  kind: "try",
1182
- tryBody: lowerList(stmt.getTryBlock().getStatements(), rawToStructured),
1183
- catchBody: catchClause === void 0 ? null : lowerList(catchClause.getBlock().getStatements(), rawToStructured),
1184
- finallyBody: finallyBlock === void 0 ? null : lowerList(finallyBlock.getStatements(), rawToStructured),
1284
+ tryBody: lowerList(stmt.getTryBlock().getStatements(), ctx),
1285
+ catchBody: catchClause === void 0 ? null : lowerList(catchClause.getBlock().getStatements(), ctx),
1286
+ finallyBody: finallyBlock === void 0 ? null : lowerList(finallyBlock.getStatements(), ctx),
1185
1287
  exitKind: exitKindOf(stmt)
1186
1288
  };
1187
1289
  }
1188
- if (import_ts_morph7.Node.isReturnStatement(stmt)) {
1290
+ if (import_ts_morph8.Node.isReturnStatement(stmt)) {
1189
1291
  return { kind: "exit", exit: "return", exitKind: exitKindOf(stmt) };
1190
1292
  }
1191
- if (import_ts_morph7.Node.isThrowStatement(stmt)) {
1293
+ if (import_ts_morph8.Node.isThrowStatement(stmt)) {
1192
1294
  return { kind: "exit", exit: "throw", exitKind: exitKindOf(stmt) };
1193
1295
  }
1194
- if (import_ts_morph7.Node.isBreakStatement(stmt)) {
1296
+ if (import_ts_morph8.Node.isBreakStatement(stmt)) {
1195
1297
  return { kind: "exit", exit: "break", exitKind: exitKindOf(stmt) };
1196
1298
  }
1197
- if (import_ts_morph7.Node.isContinueStatement(stmt)) {
1299
+ if (import_ts_morph8.Node.isContinueStatement(stmt)) {
1198
1300
  return { kind: "exit", exit: "continue", exitKind: exitKindOf(stmt) };
1199
1301
  }
1200
1302
  return { kind: "opaque", exitKind: exitKindOf(stmt) };
1201
1303
  }
1202
- function lowerSwitchGroups(stmt, rawToStructured) {
1304
+ function lowerSwitchGroups(stmt, ctx) {
1203
1305
  const switchText = stmt.getExpression().getText();
1204
1306
  const groups = [];
1205
1307
  let pendingLabels = [];
@@ -1208,7 +1310,7 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1208
1310
  if (sawDefault) {
1209
1311
  throw new import_extractor.UnmodeledFlow("default clause is not last");
1210
1312
  }
1211
- if (import_ts_morph7.Node.isDefaultClause(clause)) {
1313
+ if (import_ts_morph8.Node.isDefaultClause(clause)) {
1212
1314
  sawDefault = true;
1213
1315
  const stmts2 = clause.getStatements();
1214
1316
  if (stmts2.length === 0) {
@@ -1216,11 +1318,11 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1216
1318
  }
1217
1319
  groups.push({
1218
1320
  condition: null,
1219
- ...lowerGroupBody(stmts2, rawToStructured)
1321
+ ...lowerGroupBody(stmts2, ctx)
1220
1322
  });
1221
1323
  continue;
1222
1324
  }
1223
- if (!import_ts_morph7.Node.isCaseClause(clause)) {
1325
+ if (!import_ts_morph8.Node.isCaseClause(clause)) {
1224
1326
  continue;
1225
1327
  }
1226
1328
  pendingLabels.push(clause.getExpression().getText());
@@ -1233,7 +1335,7 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1233
1335
  const sourceText = labels.map((l) => `${switchText} === ${l}`).join(" || ");
1234
1336
  groups.push({
1235
1337
  condition: { sourceText, expression: null },
1236
- ...lowerGroupBody(stmts, rawToStructured)
1338
+ ...lowerGroupBody(stmts, ctx)
1237
1339
  });
1238
1340
  }
1239
1341
  return groups;
@@ -1241,41 +1343,42 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1241
1343
  function validateClauseBreaks(stmts) {
1242
1344
  const last = stmts[stmts.length - 1];
1243
1345
  for (const stmt of stmts) {
1244
- if (stmt === last && import_ts_morph7.Node.isBreakStatement(stmt)) {
1346
+ if (stmt === last && import_ts_morph8.Node.isBreakStatement(stmt)) {
1245
1347
  continue;
1246
1348
  }
1247
- if (import_ts_morph7.Node.isSwitchStatement(stmt) || isLoop(stmt)) {
1349
+ if (import_ts_morph8.Node.isSwitchStatement(stmt) || isLoop(stmt)) {
1248
1350
  continue;
1249
1351
  }
1250
1352
  stmt.forEachDescendant((node, traversal) => {
1251
- if (isFunctionBoundary(node) || import_ts_morph7.Node.isSwitchStatement(node) || isLoop(node)) {
1353
+ if (startsItsOwnScope(node) || import_ts_morph8.Node.isSwitchStatement(node) || isLoop(node)) {
1252
1354
  traversal.skip();
1253
1355
  return;
1254
1356
  }
1255
- if (import_ts_morph7.Node.isBreakStatement(node)) {
1357
+ if (import_ts_morph8.Node.isBreakStatement(node)) {
1256
1358
  throw new import_extractor.UnmodeledFlow("non-trailing break in switch clause");
1257
1359
  }
1258
1360
  });
1259
- if (import_ts_morph7.Node.isBreakStatement(stmt)) {
1361
+ if (import_ts_morph8.Node.isBreakStatement(stmt)) {
1260
1362
  throw new import_extractor.UnmodeledFlow("non-trailing break in switch clause");
1261
1363
  }
1262
1364
  }
1263
1365
  }
1264
- function lowerGroupBody(rawStmts, rawToStructured) {
1366
+ function lowerGroupBody(rawStmts, ctx) {
1265
1367
  const stmts = spliceBlocks(rawStmts);
1266
1368
  validateClauseBreaks(stmts);
1267
1369
  const last = stmts[stmts.length - 1];
1268
- const hasTrailingBreak = last !== void 0 && import_ts_morph7.Node.isBreakStatement(last);
1370
+ const hasTrailingBreak = last !== void 0 && import_ts_morph8.Node.isBreakStatement(last);
1269
1371
  const kept = hasTrailingBreak ? stmts.slice(0, -1) : stmts;
1270
- const body = lowerList(kept, rawToStructured);
1372
+ const body = lowerList(kept, ctx);
1271
1373
  if (hasTrailingBreak && last !== void 0) {
1272
- lowerStatement(last, rawToStructured);
1374
+ lowerStatement(last, ctx);
1273
1375
  }
1274
1376
  return { hasTrailingBreak, body };
1275
1377
  }
1276
- function lowerFunctionBody(body, terminalNodes) {
1378
+ function lowerFunctionBody(body, terminalNodes, func = body, barriers = NO_BARRIERS) {
1277
1379
  const rawToStructured = /* @__PURE__ */ new Map();
1278
- const statements = lowerList(body.getStatements(), rawToStructured);
1380
+ const ctx = { func, barriers, rawToStructured };
1381
+ const statements = lowerList(body.getStatements(), ctx);
1279
1382
  const terminalsByStmt = /* @__PURE__ */ new Map();
1280
1383
  const terminalHomeRaw = /* @__PURE__ */ new Map();
1281
1384
  for (const terminal of terminalNodes) {
@@ -1297,7 +1400,7 @@ function lowerFunctionBody(body, terminalNodes) {
1297
1400
  function enclosingLoweredStatement(terminal, body, rawToStructured) {
1298
1401
  let current2 = terminal;
1299
1402
  while (current2 !== void 0 && current2 !== body) {
1300
- if (import_ts_morph7.Node.isStatement(current2) && rawToStructured.has(current2)) {
1403
+ if (import_ts_morph8.Node.isStatement(current2) && rawToStructured.has(current2)) {
1301
1404
  return current2;
1302
1405
  }
1303
1406
  current2 = current2.getParent();
@@ -1306,39 +1409,42 @@ function enclosingLoweredStatement(terminal, body, rawToStructured) {
1306
1409
  }
1307
1410
 
1308
1411
  // src/paths/pathConditions.ts
1309
- function containsUnmodeledFlow(body) {
1412
+ function containsUnmodeledFlow(body, func, barriers) {
1310
1413
  let found = false;
1311
1414
  body.forEachDescendant((node, traversal) => {
1312
- if (isFunctionBoundary(node)) {
1415
+ if (isDescentStop(node, func, barriers)) {
1313
1416
  traversal.skip();
1314
1417
  return;
1315
1418
  }
1316
- if (import_ts_morph8.Node.isLabeledStatement(node)) {
1419
+ if (import_ts_morph9.Node.isLabeledStatement(node)) {
1317
1420
  found = true;
1318
1421
  traversal.stop();
1319
1422
  }
1320
1423
  });
1321
1424
  return found;
1322
1425
  }
1323
- function computePathConditions(func, terminalNodes) {
1426
+ function loweredExpressionBody(body, terminalNodes, func, barriers) {
1427
+ const statement = {
1428
+ kind: "opaque",
1429
+ exitKind: null,
1430
+ callbacks: lowerExpressionBodyCallbacks(body, func, barriers)
1431
+ };
1432
+ return {
1433
+ statements: [statement],
1434
+ terminalsByStmt: /* @__PURE__ */ new Map([[statement, terminalNodes]]),
1435
+ terminalHomeRaw: /* @__PURE__ */ new Map()
1436
+ };
1437
+ }
1438
+ function computePathConditions(func, terminalNodes, barriers = NO_BARRIERS) {
1324
1439
  const body = func.getBody();
1325
1440
  if (body === void 0) {
1326
1441
  return { byTerminal: /* @__PURE__ */ new Map(), fallthrough: [] };
1327
1442
  }
1328
- if (!import_ts_morph8.Node.isBlock(body)) {
1329
- const byTerminal = /* @__PURE__ */ new Map();
1330
- for (const terminal of terminalNodes) {
1331
- byTerminal.set(terminal, [
1332
- collectAncestorConditionInfosBelow(terminal, func)
1333
- ]);
1334
- }
1335
- return { byTerminal, fallthrough: [] };
1336
- }
1337
- if (containsUnmodeledFlow(body)) {
1443
+ if (containsUnmodeledFlow(body, func, barriers)) {
1338
1444
  return degradedResult(func, terminalNodes, "labeled statement");
1339
1445
  }
1340
1446
  try {
1341
- const lowered = lowerFunctionBody(body, terminalNodes);
1447
+ const lowered = import_ts_morph9.Node.isBlock(body) ? lowerFunctionBody(body, terminalNodes, func, barriers) : loweredExpressionBody(body, terminalNodes, func, barriers);
1342
1448
  const result = (0, import_extractor2.enumerateStructuredPaths)({
1343
1449
  statements: lowered.statements,
1344
1450
  terminalsByStmt: lowered.terminalsByStmt
@@ -1405,12 +1511,12 @@ function lineAt(node, pos) {
1405
1511
  }
1406
1512
 
1407
1513
  // src/moduleExports.ts
1408
- var import_ts_morph10 = require("ts-morph");
1514
+ var import_ts_morph11 = require("ts-morph");
1409
1515
 
1410
1516
  // src/bootstrap/lazyProjectInit.ts
1411
1517
  var import_promises = __toESM(require("fs/promises"), 1);
1412
1518
  var import_node_path = __toESM(require("path"), 1);
1413
- var import_ts_morph9 = require("ts-morph");
1519
+ var import_ts_morph10 = require("ts-morph");
1414
1520
 
1415
1521
  // src/facts/moduleGraph.ts
1416
1522
  var import_datalog = require("@suss/datalog");
@@ -1538,7 +1644,7 @@ var ModuleGraph = class {
1538
1644
  return created;
1539
1645
  }
1540
1646
  assert(relation, tuple) {
1541
- if (this.db.add(relation, tuple)) {
1647
+ if (this.db.add(relation, tuple) === "added") {
1542
1648
  this.stale = true;
1543
1649
  }
1544
1650
  }
@@ -1755,8 +1861,8 @@ function loadImportGraphsDepthFirstFromPaths(project, rootPaths) {
1755
1861
  stack.pop();
1756
1862
  loadOrder.push(top.path);
1757
1863
  let deepest = 0;
1758
- for (const path11 of top.deps) {
1759
- deepest = Math.max(deepest, chainDepth.get(path11) ?? 0);
1864
+ for (const path12 of top.deps) {
1865
+ deepest = Math.max(deepest, chainDepth.get(path12) ?? 0);
1760
1866
  }
1761
1867
  chainDepth.set(top.path, deepest + 1);
1762
1868
  }
@@ -1828,10 +1934,10 @@ function readSpecifierPaths(project, filePath, options, host) {
1828
1934
  if (text === null) {
1829
1935
  return [];
1830
1936
  }
1831
- const scanned = import_ts_morph9.ts.preProcessFile(text, true, true);
1937
+ const scanned = import_ts_morph10.ts.preProcessFile(text, true, true);
1832
1938
  const paths = [];
1833
1939
  for (const imported of scanned.importedFiles) {
1834
- const resolved = import_ts_morph9.ts.resolveModuleName(
1940
+ const resolved = import_ts_morph10.ts.resolveModuleName(
1835
1941
  imported.fileName,
1836
1942
  filePath,
1837
1943
  options,
@@ -1855,13 +1961,13 @@ function readFileText(project, filePath) {
1855
1961
  }
1856
1962
  }
1857
1963
  function parseTsconfig(tsConfigFilePath) {
1858
- const configFile = import_ts_morph9.ts.readConfigFile(tsConfigFilePath, import_ts_morph9.ts.sys.readFile);
1964
+ const configFile = import_ts_morph10.ts.readConfigFile(tsConfigFilePath, import_ts_morph10.ts.sys.readFile);
1859
1965
  if (configFile.error !== void 0) {
1860
1966
  return { fileNames: [], options: {} };
1861
1967
  }
1862
- const parsed = import_ts_morph9.ts.parseJsonConfigFileContent(
1968
+ const parsed = import_ts_morph10.ts.parseJsonConfigFileContent(
1863
1969
  configFile.config,
1864
- import_ts_morph9.ts.sys,
1970
+ import_ts_morph10.ts.sys,
1865
1971
  import_node_path.default.dirname(tsConfigFilePath),
1866
1972
  /*existingOptions*/
1867
1973
  void 0,
@@ -1921,17 +2027,17 @@ function addImportersOfMatches(fileImports, matched, options) {
1921
2027
  }
1922
2028
  function internalSpecifierResolver(files, options) {
1923
2029
  const fileSet = new Set(files);
1924
- const cache = import_ts_morph9.ts.createModuleResolutionCache(
1925
- import_ts_morph9.ts.sys.getCurrentDirectory(),
2030
+ const cache = import_ts_morph10.ts.createModuleResolutionCache(
2031
+ import_ts_morph10.ts.sys.getCurrentDirectory(),
1926
2032
  (name) => name,
1927
2033
  options
1928
2034
  );
1929
2035
  return (fromFile, spec) => {
1930
- const resolved = import_ts_morph9.ts.resolveModuleName(
2036
+ const resolved = import_ts_morph10.ts.resolveModuleName(
1931
2037
  spec,
1932
2038
  fromFile,
1933
2039
  options,
1934
- import_ts_morph9.ts.sys,
2040
+ import_ts_morph10.ts.sys,
1935
2041
  cache
1936
2042
  ).resolvedModule?.resolvedFileName;
1937
2043
  if (resolved === void 0) {
@@ -1940,7 +2046,7 @@ function internalSpecifierResolver(files, options) {
1940
2046
  if (fileSet.has(resolved)) {
1941
2047
  return resolved;
1942
2048
  }
1943
- const real = import_ts_morph9.ts.sys.realpath?.(resolved) ?? resolved;
2049
+ const real = import_ts_morph10.ts.sys.realpath?.(resolved) ?? resolved;
1944
2050
  return fileSet.has(real) ? real : null;
1945
2051
  };
1946
2052
  }
@@ -1966,7 +2072,7 @@ async function readImportsConcurrently(paths) {
1966
2072
  const p = paths[i];
1967
2073
  try {
1968
2074
  const text = await import_promises.default.readFile(p, "utf-8");
1969
- const pre = import_ts_morph9.ts.preProcessFile(text, true, false);
2075
+ const pre = import_ts_morph10.ts.preProcessFile(text, true, false);
1970
2076
  results[i] = {
1971
2077
  path: p,
1972
2078
  importedModules: pre.importedFiles.map((f) => f.fileName)
@@ -1984,6 +2090,30 @@ async function readImportsConcurrently(paths) {
1984
2090
  return results;
1985
2091
  }
1986
2092
 
2093
+ // src/depTracking.ts
2094
+ function createDependencySink() {
2095
+ return { files: /* @__PURE__ */ new Set(), mountPrefixes: /* @__PURE__ */ new Map(), claims: [] };
2096
+ }
2097
+ var activeSink = null;
2098
+ function withDependencySink(sink, fn) {
2099
+ const previous = activeSink;
2100
+ activeSink = sink;
2101
+ try {
2102
+ return fn();
2103
+ } finally {
2104
+ activeSink = previous;
2105
+ }
2106
+ }
2107
+ function recordFileDependency(filePath) {
2108
+ activeSink?.files.add(filePath);
2109
+ }
2110
+ function recordMountPrefix(childId, prefix) {
2111
+ activeSink?.mountPrefixes.set(childId, prefix);
2112
+ }
2113
+ function recordUnitClaim(key, pack) {
2114
+ activeSink?.claims.push({ key, pack });
2115
+ }
2116
+
1987
2117
  // src/perFileCache.ts
1988
2118
  function createPerFileCache() {
1989
2119
  const byParse = /* @__PURE__ */ new WeakMap();
@@ -1999,6 +2129,7 @@ function createPerFileCache() {
1999
2129
 
2000
2130
  // src/moduleExports.ts
2001
2131
  function exportedDeclarationsOf(sourceFile) {
2132
+ recordFileDependency(sourceFile.getFilePath());
2002
2133
  const cached = exportsByFile.get(sourceFile);
2003
2134
  if (cached !== void 0) {
2004
2135
  return cached;
@@ -2013,7 +2144,14 @@ function resolveAliasedSymbol(symbol) {
2013
2144
  warmReExportChains(declaration.getSourceFile(), "every import");
2014
2145
  }
2015
2146
  try {
2016
- return symbol.getAliasedSymbol();
2147
+ const aliased = symbol.getAliasedSymbol();
2148
+ for (const declaration of aliased?.getDeclarations() ?? []) {
2149
+ const file = declaration.getSourceFile();
2150
+ if (!file.isInNodeModules()) {
2151
+ recordFileDependency(file.getFilePath());
2152
+ }
2153
+ }
2154
+ return aliased;
2017
2155
  } catch (error) {
2018
2156
  if (!(error instanceof RangeError)) {
2019
2157
  throw error;
@@ -2069,11 +2207,11 @@ function walkAndWarm(root, reach) {
2069
2207
  if (warmedFiles.get(target) !== void 0) {
2070
2208
  continue;
2071
2209
  }
2072
- const path11 = target.getFilePath();
2073
- if (visited.has(path11)) {
2210
+ const path12 = target.getFilePath();
2211
+ if (visited.has(path12)) {
2074
2212
  continue;
2075
2213
  }
2076
- visited.add(path11);
2214
+ visited.add(path12);
2077
2215
  stack.push(frameOf(target, reach));
2078
2216
  continue;
2079
2217
  }
@@ -2090,7 +2228,7 @@ function sitsOnWarmChains(file) {
2090
2228
  return false;
2091
2229
  }
2092
2230
  const imported = importedFilePathsOf(project, file.getFilePath());
2093
- return imported.length > 0 && imported.every((path11) => warmed.has(path11));
2231
+ return imported.length > 0 && imported.every((path12) => warmed.has(path12));
2094
2232
  }
2095
2233
  function markWarmed(file) {
2096
2234
  warmedFiles.set(file, true);
@@ -2128,7 +2266,7 @@ function reExportShapeOf(file, reach) {
2128
2266
  for (const assignment of file.getExportAssignments()) {
2129
2267
  exportNodes.push(assignment);
2130
2268
  const expression = assignment.getExpression();
2131
- if (import_ts_morph10.Node.isIdentifier(expression)) {
2269
+ if (import_ts_morph11.Node.isIdentifier(expression)) {
2132
2270
  localNames.add(expression.getText());
2133
2271
  }
2134
2272
  }
@@ -2204,38 +2342,6 @@ function reportUnreadableExports(file) {
2204
2342
  );
2205
2343
  }
2206
2344
 
2207
- // src/walk/descent.ts
2208
- var import_ts_morph11 = require("ts-morph");
2209
- var NO_BARRIERS = /* @__PURE__ */ new Set();
2210
- function isDescentStop(node, func, barriers = NO_BARRIERS) {
2211
- if (node === func) {
2212
- return false;
2213
- }
2214
- if (import_ts_morph11.Node.isFunctionDeclaration(node) || import_ts_morph11.Node.isMethodDeclaration(node)) {
2215
- return true;
2216
- }
2217
- if (import_ts_morph11.Node.isArrowFunction(node) || import_ts_morph11.Node.isFunctionExpression(node)) {
2218
- return barriers.has(node);
2219
- }
2220
- return false;
2221
- }
2222
- function isModuleScopeStop(node) {
2223
- return import_ts_morph11.Node.isFunctionDeclaration(node) || import_ts_morph11.Node.isMethodDeclaration(node) || import_ts_morph11.Node.isArrowFunction(node) || import_ts_morph11.Node.isFunctionExpression(node) || import_ts_morph11.Node.isClassDeclaration(node) || import_ts_morph11.Node.isClassExpression(node);
2224
- }
2225
- function crossesNestedFunctionScope(node, func) {
2226
- let current2 = node;
2227
- while (current2 !== void 0 && current2 !== func) {
2228
- if (startsItsOwnScope(current2)) {
2229
- return true;
2230
- }
2231
- current2 = current2.getParent();
2232
- }
2233
- return false;
2234
- }
2235
- function startsItsOwnScope(node) {
2236
- return import_ts_morph11.Node.isArrowFunction(node) || import_ts_morph11.Node.isFunctionExpression(node) || import_ts_morph11.Node.isFunctionDeclaration(node) || import_ts_morph11.Node.isMethodDeclaration(node) || import_ts_morph11.Node.isGetAccessorDeclaration(node) || import_ts_morph11.Node.isSetAccessorDeclaration(node) || import_ts_morph11.Node.isConstructorDeclaration(node);
2237
- }
2238
-
2239
2345
  // src/resolve/invocationEffects.ts
2240
2346
  function isImportedFrom(identifier, expectedModule) {
2241
2347
  if (!import_ts_morph12.Node.isIdentifier(identifier)) {
@@ -2255,14 +2361,38 @@ function isImportedFrom(identifier, expectedModule) {
2255
2361
  (decl) => decl.getSourceFile().getFilePath().includes(`/node_modules/${expectedModule}/`)
2256
2362
  );
2257
2363
  }
2258
- function methodDeclaredIn(callee, expectedModule) {
2364
+ function methodDeclaredIn(callee, expectedModule, resolve2) {
2259
2365
  if (!import_ts_morph12.Node.isPropertyAccessExpression(callee)) {
2260
2366
  return false;
2261
2367
  }
2262
2368
  const declarations = callee.getNameNode().getSymbol()?.getDeclarations();
2263
- return declarations !== void 0 && declarations.some(
2369
+ if (declarations !== void 0 && declarations.some(
2264
2370
  (declaration) => declaration.getSourceFile().getFilePath().includes(`/node_modules/${expectedModule}/`)
2265
- );
2371
+ )) {
2372
+ return true;
2373
+ }
2374
+ if (resolve2 === void 0) {
2375
+ return false;
2376
+ }
2377
+ const written = resolve2(callee.getExpression());
2378
+ const made = maker(written);
2379
+ return made !== null && isImportedFrom(rootIdentifier(made), expectedModule);
2380
+ }
2381
+ function maker(written) {
2382
+ if (written === null) {
2383
+ return null;
2384
+ }
2385
+ if (import_ts_morph12.Node.isNewExpression(written) || import_ts_morph12.Node.isCallExpression(written)) {
2386
+ return written.getExpression();
2387
+ }
2388
+ return null;
2389
+ }
2390
+ function rootIdentifier(expression) {
2391
+ let root = expression;
2392
+ while (import_ts_morph12.Node.isPropertyAccessExpression(root)) {
2393
+ root = root.getExpression();
2394
+ }
2395
+ return root;
2266
2396
  }
2267
2397
  function importSpecifierMatches(decl, expectedModule) {
2268
2398
  if (import_ts_morph12.Node.isImportSpecifier(decl)) {
@@ -2342,6 +2472,26 @@ function extractInvocationEffects(func, barriers = NO_BARRIERS) {
2342
2472
  }
2343
2473
  return;
2344
2474
  }
2475
+ if (import_ts_morph12.Node.isReturnStatement(node)) {
2476
+ const returned = node.getExpression();
2477
+ const { call, async } = returned === void 0 ? { call: null, async: false } : unwrapCall(returned);
2478
+ if (call !== null) {
2479
+ const preconditions = collectPreconditions(node, func);
2480
+ results.push({
2481
+ effect: {
2482
+ type: "invocation",
2483
+ callee: call.getExpression().getText(),
2484
+ args: extractArgs(call),
2485
+ async,
2486
+ ...preconditions.length > 0 ? { preconditions } : {}
2487
+ },
2488
+ line: startLineOf(node),
2489
+ neverTerminal: false,
2490
+ node: call
2491
+ });
2492
+ }
2493
+ return;
2494
+ }
2345
2495
  if (import_ts_morph12.Node.isCallExpression(node)) {
2346
2496
  const parent = node.getParent();
2347
2497
  if (parent === void 0) {
@@ -4940,7 +5090,8 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4940
5090
  ];
4941
5091
  const { byTerminal } = computePathConditions(
4942
5092
  func,
4943
- terminals.map(({ node }) => node)
5093
+ terminals.map(({ node }) => node),
5094
+ barriers
4944
5095
  );
4945
5096
  const wantsFallthrough = terminalPatterns.some(
4946
5097
  (p) => p.match.type === "functionFallthrough"
@@ -4952,7 +5103,11 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4952
5103
  if (!hasDefaultTerminal) {
4953
5104
  const synthetic = makeFallthroughTerminal(func);
4954
5105
  terminals.push(synthetic);
4955
- const pathResult = computePathConditions(func, [synthetic.node]);
5106
+ const pathResult = computePathConditions(
5107
+ func,
5108
+ [synthetic.node],
5109
+ barriers
5110
+ );
4956
5111
  byTerminal.set(
4957
5112
  synthetic.node,
4958
5113
  pathResult.fallthrough.length > 0 ? pathResult.fallthrough : [[]]
@@ -4998,7 +5153,10 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4998
5153
  const terminalLines = new Set(
4999
5154
  distinctBranches.map((b) => b.terminal.location.start)
5000
5155
  );
5001
- defaultBranch.effects = invocations.filter((i) => i.neverTerminal || !terminalLines.has(i.line)).map((i) => i.effect);
5156
+ const terminalNodes = new Set(terminals.map(({ node }) => node));
5157
+ defaultBranch.effects = invocations.filter(
5158
+ (i) => i.node !== void 0 ? !terminalNodes.has(i.node) : i.neverTerminal || !terminalLines.has(i.line)
5159
+ ).map((i) => i.effect);
5002
5160
  }
5003
5161
  }
5004
5162
  if (recognized.length > 0) {
@@ -5043,9 +5201,9 @@ function createSourceFileLookup(project) {
5043
5201
  if (sf === null) {
5044
5202
  return null;
5045
5203
  }
5046
- const path11 = sf.getFilePath();
5047
- const index = functionsByFile.get(path11) ?? indexFunctions(sf);
5048
- functionsByFile.set(path11, index);
5204
+ const path12 = sf.getFilePath();
5205
+ const index = functionsByFile.get(path12) ?? indexFunctions(sf);
5206
+ functionsByFile.set(path12, index);
5049
5207
  return index.get(rangeKey(location.range)) ?? null;
5050
5208
  }
5051
5209
  };
@@ -5083,7 +5241,7 @@ function indexFunctions(sf) {
5083
5241
  var import_node_crypto2 = require("crypto");
5084
5242
  var import_promises2 = __toESM(require("fs/promises"), 1);
5085
5243
  var import_node_path2 = __toESM(require("path"), 1);
5086
- var SCHEMA_VERSION = "5";
5244
+ var SCHEMA_VERSION = "6";
5087
5245
  var MAX_ENTRIES = 2;
5088
5246
  var ENTRY_PREFIX = "key-";
5089
5247
  var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
@@ -5095,6 +5253,7 @@ function createCacheLayer(cacheDir) {
5095
5253
  kind: "miss",
5096
5254
  diagnostic: { kind: "miss", missReason: "no-manifest" }
5097
5255
  }),
5256
+ plan: async () => null,
5098
5257
  write: async () => {
5099
5258
  }
5100
5259
  };
@@ -5125,17 +5284,32 @@ function createCacheLayer(cacheDir) {
5125
5284
  diagnostic: { kind: "hit" }
5126
5285
  };
5127
5286
  },
5128
- async write(input, summaries) {
5287
+ async plan(input) {
5288
+ const entryDir = entryDirFor(cacheDir, input);
5289
+ const manifest = await readManifest(import_node_path2.default.join(entryDir, "manifest.json"));
5290
+ if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
5291
+ return null;
5292
+ }
5293
+ const currentTsconfigStamp = await stampTsconfig(input.tsconfigPath);
5294
+ if (!fileStampEquals(manifest.tsconfigStamp, currentTsconfigStamp)) {
5295
+ return null;
5296
+ }
5297
+ await markUsed(entryDir);
5298
+ return buildPlan(manifest, await resolveFileStamps(input));
5299
+ },
5300
+ async write(input, summaries, attribution) {
5301
+ const entryDir = entryDirFor(cacheDir, input);
5302
+ const previous = await readManifest(import_node_path2.default.join(entryDir, "manifest.json"));
5129
5303
  const tsconfigStamp = await stampTsconfig(input.tsconfigPath);
5130
- const files = await resolveFileStamps(input);
5304
+ const files = await hashStamps(await resolveFileStamps(input), previous);
5131
5305
  const manifest = {
5132
5306
  schemaVersion: SCHEMA_VERSION,
5133
5307
  adapterPacksDigest: input.adapterPacksDigest,
5134
5308
  tsconfigStamp,
5135
5309
  files,
5136
- summaries
5310
+ summaries,
5311
+ ...attribution === void 0 ? {} : encodeAttribution(attribution)
5137
5312
  };
5138
- const entryDir = entryDirFor(cacheDir, input);
5139
5313
  await import_promises2.default.mkdir(entryDir, { recursive: true });
5140
5314
  await import_promises2.default.writeFile(
5141
5315
  import_node_path2.default.join(entryDir, "manifest.json"),
@@ -5145,6 +5319,163 @@ function createCacheLayer(cacheDir) {
5145
5319
  }
5146
5320
  };
5147
5321
  }
5322
+ async function buildPlan(manifest, currentStamps) {
5323
+ const stored = new Map(manifest.files.map((f) => [f.path, f]));
5324
+ const current2 = new Map(currentStamps.map((f) => [f.path, f]));
5325
+ const changed = /* @__PURE__ */ new Set();
5326
+ const removed = /* @__PURE__ */ new Set();
5327
+ for (const p of stored.keys()) {
5328
+ if (!current2.has(p)) {
5329
+ removed.add(p);
5330
+ }
5331
+ }
5332
+ const toVerify = [];
5333
+ for (const [p, stamp] of current2) {
5334
+ const before = stored.get(p);
5335
+ if (before === void 0) {
5336
+ changed.add(p);
5337
+ } else if (!fileStampEquals(before, stamp)) {
5338
+ if (before.contentHash === void 0) {
5339
+ changed.add(p);
5340
+ } else {
5341
+ toVerify.push(p);
5342
+ }
5343
+ }
5344
+ }
5345
+ await Promise.all(
5346
+ toVerify.map(async (p) => {
5347
+ const hash = await hashFile(p);
5348
+ if (hash === null || hash !== stored.get(p)?.contentHash) {
5349
+ changed.add(p);
5350
+ }
5351
+ })
5352
+ );
5353
+ const roots = /* @__PURE__ */ new Map();
5354
+ const rootNames = manifest.roots ?? [];
5355
+ const depPaths = manifest.depPaths ?? [];
5356
+ let rootsDeclined = 0;
5357
+ rootNames.forEach((rootPath, i) => {
5358
+ const meta = manifest.rootMeta?.[i];
5359
+ if (meta === void 0) {
5360
+ return;
5361
+ }
5362
+ if (!meta.cacheable) {
5363
+ rootsDeclined += 1;
5364
+ }
5365
+ roots.set(rootPath, {
5366
+ path: rootPath,
5367
+ cacheable: meta.cacheable,
5368
+ deps: meta.deps.flatMap((d) => {
5369
+ const p = depPaths[d];
5370
+ return p === void 0 ? [] : [p];
5371
+ }),
5372
+ claims: meta.claims,
5373
+ mountPrefixes: meta.mountPrefixes,
5374
+ packs: meta.packs
5375
+ });
5376
+ });
5377
+ const validRoots = /* @__PURE__ */ new Set();
5378
+ for (const [rootPath, record] of roots) {
5379
+ if (!record.cacheable || !current2.has(rootPath)) {
5380
+ continue;
5381
+ }
5382
+ if (changed.has(rootPath)) {
5383
+ continue;
5384
+ }
5385
+ const depMoved = record.deps.some((d) => changed.has(d) || removed.has(d));
5386
+ if (!depMoved) {
5387
+ validRoots.add(rootPath);
5388
+ }
5389
+ }
5390
+ const owners = manifest.owners ?? [];
5391
+ return {
5392
+ changed,
5393
+ removed,
5394
+ roots,
5395
+ validRoots,
5396
+ rootsDeclined,
5397
+ reuse(valid) {
5398
+ const summaries = [];
5399
+ const reusedOwners = [];
5400
+ manifest.summaries.forEach((summary, i) => {
5401
+ const ownerPaths = (owners[i] ?? []).flatMap((o) => {
5402
+ const p = rootNames[o];
5403
+ return p === void 0 ? [] : [p];
5404
+ });
5405
+ if (ownerPaths.some((p) => valid.has(p))) {
5406
+ summaries.push(summary);
5407
+ reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
5408
+ }
5409
+ });
5410
+ return { summaries, owners: reusedOwners };
5411
+ },
5412
+ allSummaries() {
5413
+ return manifest.summaries;
5414
+ },
5415
+ attribution() {
5416
+ return {
5417
+ roots: [...roots.values()],
5418
+ owners: owners.map(
5419
+ (ownerIds) => ownerIds.flatMap((o) => {
5420
+ const p = rootNames[o];
5421
+ return p === void 0 ? [] : [p];
5422
+ })
5423
+ )
5424
+ };
5425
+ }
5426
+ };
5427
+ }
5428
+ function encodeAttribution(attribution) {
5429
+ const roots = attribution.roots.map((r) => r.path);
5430
+ const rootIndex = new Map(roots.map((p, i) => [p, i]));
5431
+ const depIndex = /* @__PURE__ */ new Map();
5432
+ const depPaths = [];
5433
+ const depIdOf = (p) => {
5434
+ const existing = depIndex.get(p);
5435
+ if (existing !== void 0) {
5436
+ return existing;
5437
+ }
5438
+ const id = depPaths.length;
5439
+ depPaths.push(p);
5440
+ depIndex.set(p, id);
5441
+ return id;
5442
+ };
5443
+ const rootMeta = attribution.roots.map((r) => ({
5444
+ cacheable: r.cacheable,
5445
+ deps: r.deps.map(depIdOf),
5446
+ claims: r.claims,
5447
+ mountPrefixes: r.mountPrefixes,
5448
+ packs: r.packs
5449
+ }));
5450
+ const owners = attribution.owners.map(
5451
+ (ownerPaths) => ownerPaths.flatMap((p) => {
5452
+ const i = rootIndex.get(p);
5453
+ return i === void 0 ? [] : [i];
5454
+ })
5455
+ );
5456
+ return { roots, rootMeta, depPaths, owners };
5457
+ }
5458
+ async function hashStamps(stamps, previous) {
5459
+ const before = new Map((previous?.files ?? []).map((f) => [f.path, f]));
5460
+ return Promise.all(
5461
+ stamps.map(async (stamp) => {
5462
+ const prior = before.get(stamp.path);
5463
+ if (prior !== void 0 && prior.contentHash !== void 0 && fileStampEquals(prior, stamp)) {
5464
+ return { ...stamp, contentHash: prior.contentHash };
5465
+ }
5466
+ const contentHash = await hashFile(stamp.path);
5467
+ return contentHash === null ? stamp : { ...stamp, contentHash };
5468
+ })
5469
+ );
5470
+ }
5471
+ async function hashFile(filePath) {
5472
+ try {
5473
+ const content = await import_promises2.default.readFile(filePath);
5474
+ return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
5475
+ } catch {
5476
+ return null;
5477
+ }
5478
+ }
5148
5479
  async function describeAbsentEntry(cacheDir) {
5149
5480
  try {
5150
5481
  const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
@@ -5158,7 +5489,7 @@ function entryDirFor(cacheDir, input) {
5158
5489
  SCHEMA_VERSION,
5159
5490
  input.adapterPacksDigest,
5160
5491
  input.tsconfigPath ?? ""
5161
- ].join("\0");
5492
+ ].join(" ");
5162
5493
  const name = (0, import_node_crypto2.createHash)("sha256").update(key).digest("hex").slice(0, 16);
5163
5494
  return import_node_path2.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
5164
5495
  }
@@ -5399,7 +5730,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
5399
5730
  }
5400
5731
  const responses = [];
5401
5732
  let method;
5402
- let path11;
5733
+ let path12;
5403
5734
  for (const prop of endpointNode.getProperties()) {
5404
5735
  if (!import_ts_morph25.Node.isPropertyAssignment(prop)) {
5405
5736
  continue;
@@ -5414,7 +5745,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
5414
5745
  if (propName === pattern.pathProperty) {
5415
5746
  const val = prop.getInitializer();
5416
5747
  if (val !== void 0 && import_ts_morph25.Node.isStringLiteral(val)) {
5417
- path11 = val.getLiteralValue();
5748
+ path12 = val.getLiteralValue();
5418
5749
  }
5419
5750
  }
5420
5751
  if (propName === pattern.responseExtraction.property) {
@@ -5438,10 +5769,10 @@ function extractEndpointContract(endpointNode, pattern, framework) {
5438
5769
  if (responses.length === 0) {
5439
5770
  return null;
5440
5771
  }
5441
- const boundaryBinding = method !== void 0 || path11 !== void 0 ? (0, import_behavioral_ir.restBinding)({
5772
+ const boundaryBinding = method !== void 0 || path12 !== void 0 ? (0, import_behavioral_ir.restBinding)({
5442
5773
  transport: "http",
5443
5774
  method: method ?? null,
5444
- path: path11 ?? null,
5775
+ path: path12 ?? null,
5445
5776
  recognition: framework
5446
5777
  }) : null;
5447
5778
  return {
@@ -6025,6 +6356,13 @@ function literalIndexOf(index) {
6025
6356
  }
6026
6357
  return null;
6027
6358
  }
6359
+ function asFallbackExpression(expression) {
6360
+ if (!import_ts_morph28.Node.isBinaryExpression(expression)) {
6361
+ return null;
6362
+ }
6363
+ const operator = expression.getOperatorToken().getKind();
6364
+ return operator === import_ts_morph28.SyntaxKind.BarBarToken || operator === import_ts_morph28.SyntaxKind.QuestionQuestionToken ? expression : null;
6365
+ }
6028
6366
  function unwrapExpression(expression) {
6029
6367
  let current2 = expression;
6030
6368
  for (; ; ) {
@@ -6136,6 +6474,12 @@ function emitValue(db, table, value) {
6136
6474
  }
6137
6475
  return id;
6138
6476
  }
6477
+ const fallback = asFallbackExpression(expression);
6478
+ if (fallback !== null) {
6479
+ fact(db, "fallbackBranch", id, emitValue(db, table, fallback.getLeft()));
6480
+ fact(db, "fallbackBranch", id, emitValue(db, table, fallback.getRight()));
6481
+ return id;
6482
+ }
6139
6483
  if (import_ts_morph28.Node.isCallExpression(expression) || import_ts_morph28.Node.isNewExpression(expression)) {
6140
6484
  emitCallFacts(db, table, expression);
6141
6485
  fact(db, "writtenValue", id);
@@ -6600,24 +6944,27 @@ function stampGraphqlClientRefs(summaries, sourceFiles, packs, resolution) {
6600
6944
  const sole = soleGraphqlClientRef(
6601
6945
  collectGraphqlClientRefs(sourceFiles, packs, resolution)
6602
6946
  );
6603
- if (sole === null && scopes.length === 0) {
6604
- return;
6605
- }
6606
6947
  const workspace = sole !== null ? boundWorkspaceFor(sole, packs) : null;
6607
6948
  const soleClient = sole !== null ? workspace !== null ? { ...sole, workspace } : sole : null;
6949
+ let registry = null;
6608
6950
  for (const summary of summaries) {
6609
6951
  if (!(0, import_behavioral_ir3.isGraphqlOperationBinding)(summary.identity.boundaryBinding)) {
6610
6952
  continue;
6611
6953
  }
6954
+ const existing = (0, import_behavioral_ir3.readGraphqlMetadata)(summary) ?? {};
6612
6955
  const scoped = scopedWorkspaceFor(summary.location.file, scopes);
6613
6956
  const client = scoped !== null ? { uri: null, uriRef: null, workspace: scoped } : soleClient;
6614
- if (client === null) {
6957
+ const dangling = (existing.unresolvedFragments?.length ?? 0) > 0;
6958
+ if (client === null && !dangling) {
6615
6959
  continue;
6616
6960
  }
6617
- const existing = (0, import_behavioral_ir3.readGraphqlMetadata)(summary) ?? {};
6961
+ if (dangling && registry === null) {
6962
+ registry = fragmentRegistryStatus(sourceFiles, packs);
6963
+ }
6618
6964
  summary.metadata = (0, import_behavioral_ir3.withGraphqlMetadata)(summary.metadata, {
6619
6965
  ...existing,
6620
- client
6966
+ ...client !== null ? { client } : {},
6967
+ ...dangling && registry !== null ? { fragmentRegistry: registry } : {}
6621
6968
  });
6622
6969
  }
6623
6970
  }
@@ -6683,48 +7030,198 @@ function soleGraphqlClientRef(refs) {
6683
7030
  }
6684
7031
  return [...distinct.values()][0] ?? null;
6685
7032
  }
6686
- function localNamesFor(sourceFile, specs) {
6687
- const names = /* @__PURE__ */ new Map();
6688
- for (const importDecl of sourceFile.getImportDeclarations()) {
6689
- const module2 = importDecl.getModuleSpecifierValue();
6690
- for (const spec of specs) {
6691
- if (module2 !== spec.importModule) {
7033
+ function fragmentRegistryStatus(sourceFiles, packs) {
7034
+ const statuses = [];
7035
+ for (const spec of packs.flatMap((pack) => pack.graphqlClients ?? [])) {
7036
+ const registrySpec = spec.fragmentRegistry;
7037
+ if (registrySpec === void 0) {
7038
+ continue;
7039
+ }
7040
+ for (const sourceFile of sourceFiles) {
7041
+ const local = localImportName(
7042
+ sourceFile,
7043
+ spec.importModule,
7044
+ spec.importName
7045
+ );
7046
+ if (local === null) {
6692
7047
  continue;
6693
7048
  }
6694
- for (const named2 of importDecl.getNamedImports()) {
6695
- if (named2.getName() !== spec.importName) {
6696
- continue;
7049
+ sourceFile.forEachDescendant((node) => {
7050
+ if (isConstructionNamed(node, local)) {
7051
+ statuses.push(constructionRegistryStatus(node, registrySpec));
6697
7052
  }
6698
- const local = named2.getAliasNode()?.getText() ?? named2.getName();
6699
- names.set(local, spec.uriProperty);
6700
- }
7053
+ });
6701
7054
  }
6702
7055
  }
6703
- return names;
7056
+ return combineRegistryStatuses(statuses);
6704
7057
  }
6705
- function constructionRef(node, localNames, resolution) {
6706
- if (!import_ts_morph30.Node.isNewExpression(node) && !import_ts_morph30.Node.isCallExpression(node)) {
6707
- return null;
7058
+ function combineRegistryStatuses(statuses) {
7059
+ if (statuses.includes("configured")) {
7060
+ return "configured";
6708
7061
  }
6709
- const callee = node.getExpression();
6710
- if (callee === void 0 || !import_ts_morph30.Node.isIdentifier(callee)) {
6711
- return null;
7062
+ if (statuses.length === 0 || statuses.includes("unknown")) {
7063
+ return "unknown";
6712
7064
  }
6713
- const uriProperty = localNames.get(callee.getText());
6714
- if (uriProperty === void 0) {
6715
- return null;
7065
+ return "absent";
7066
+ }
7067
+ function constructionRegistryStatus(construction, spec) {
7068
+ const options = firstArgumentObjectLiteral(construction);
7069
+ if (options === null) {
7070
+ return "unknown";
6716
7071
  }
6717
- const optionsArg = node.getArguments()[0];
6718
- if (optionsArg === void 0 || !import_ts_morph30.Node.isObjectLiteralExpression(optionsArg)) {
6719
- return null;
7072
+ const cacheExpr = propertyValueOf2(options, spec.cacheProperty);
7073
+ if (cacheExpr === null) {
7074
+ return "unknown";
6720
7075
  }
6721
- const property = optionsArg.getProperty(uriProperty);
6722
- if (property === void 0 || !import_ts_morph30.Node.isPropertyAssignment(property)) {
6723
- return null;
7076
+ const cacheConstruction = resolveToConstructionOf(
7077
+ cacheExpr,
7078
+ spec.cacheConstructor
7079
+ );
7080
+ if (cacheConstruction === null) {
7081
+ return "unknown";
6724
7082
  }
6725
- const value = property.getInitializer();
6726
- if (value === void 0) {
6727
- return null;
7083
+ if (!import_ts_morph30.Node.isNewExpression(cacheConstruction) && !import_ts_morph30.Node.isCallExpression(cacheConstruction)) {
7084
+ return "unknown";
7085
+ }
7086
+ const args = cacheConstruction.getArguments();
7087
+ if (args.length === 0) {
7088
+ return "absent";
7089
+ }
7090
+ const cacheOptions = firstArgumentObjectLiteral(cacheConstruction);
7091
+ if (cacheOptions === null) {
7092
+ return "unknown";
7093
+ }
7094
+ if (cacheOptions.getProperty(spec.registryProperty) !== void 0) {
7095
+ return "configured";
7096
+ }
7097
+ const hasSpread = cacheOptions.getProperties().some((property) => import_ts_morph30.Node.isSpreadAssignment(property));
7098
+ return hasSpread ? "unknown" : "absent";
7099
+ }
7100
+ function firstArgumentObjectLiteral(construction) {
7101
+ if (!import_ts_morph30.Node.isNewExpression(construction) && !import_ts_morph30.Node.isCallExpression(construction)) {
7102
+ return null;
7103
+ }
7104
+ const arg = construction.getArguments()[0];
7105
+ if (arg === void 0 || !import_ts_morph30.Node.isObjectLiteralExpression(arg)) {
7106
+ return null;
7107
+ }
7108
+ return arg;
7109
+ }
7110
+ function propertyValueOf2(options, name) {
7111
+ const property = options.getProperty(name);
7112
+ if (property === void 0) {
7113
+ return null;
7114
+ }
7115
+ if (import_ts_morph30.Node.isPropertyAssignment(property)) {
7116
+ return property.getInitializer() ?? null;
7117
+ }
7118
+ if (import_ts_morph30.Node.isShorthandPropertyAssignment(property)) {
7119
+ const symbol = property.getProject().getTypeChecker().getShorthandAssignmentValueSymbol(property);
7120
+ return symbol?.getValueDeclaration() ?? null;
7121
+ }
7122
+ return null;
7123
+ }
7124
+ function resolveToConstructionOf(expression, cacheClass) {
7125
+ let current2 = expression;
7126
+ for (let hop = 0; hop < 4 && current2 !== void 0; hop += 1) {
7127
+ current2 = unwrapExpression2(current2);
7128
+ if (import_ts_morph30.Node.isVariableDeclaration(current2)) {
7129
+ current2 = current2.getInitializer();
7130
+ continue;
7131
+ }
7132
+ if (import_ts_morph30.Node.isNewExpression(current2) || import_ts_morph30.Node.isCallExpression(current2)) {
7133
+ const callee = current2.getExpression();
7134
+ if (callee === void 0 || !import_ts_morph30.Node.isIdentifier(callee)) {
7135
+ return null;
7136
+ }
7137
+ const local = localImportName(
7138
+ current2.getSourceFile(),
7139
+ cacheClass.importModule,
7140
+ cacheClass.importName
7141
+ );
7142
+ return local !== null && callee.getText() === local ? current2 : null;
7143
+ }
7144
+ if (!import_ts_morph30.Node.isIdentifier(current2)) {
7145
+ return null;
7146
+ }
7147
+ const declaration = current2.getSymbol()?.getValueDeclaration();
7148
+ if (declaration === void 0 || !import_ts_morph30.Node.isVariableDeclaration(declaration)) {
7149
+ return null;
7150
+ }
7151
+ current2 = declaration.getInitializer();
7152
+ }
7153
+ return null;
7154
+ }
7155
+ function unwrapExpression2(node) {
7156
+ let current2 = node;
7157
+ while (import_ts_morph30.Node.isParenthesizedExpression(current2) || import_ts_morph30.Node.isAsExpression(current2) || import_ts_morph30.Node.isNonNullExpression(current2)) {
7158
+ current2 = current2.getExpression();
7159
+ }
7160
+ return current2;
7161
+ }
7162
+ function isConstructionNamed(node, localName) {
7163
+ if (!import_ts_morph30.Node.isNewExpression(node) && !import_ts_morph30.Node.isCallExpression(node)) {
7164
+ return false;
7165
+ }
7166
+ const callee = node.getExpression();
7167
+ return callee !== void 0 && import_ts_morph30.Node.isIdentifier(callee) && callee.getText() === localName;
7168
+ }
7169
+ function localImportName(sourceFile, importModule, importName) {
7170
+ for (const importDecl of sourceFile.getImportDeclarations()) {
7171
+ const module2 = importDecl.getModuleSpecifierValue();
7172
+ if (module2 !== importModule && !module2.startsWith(`${importModule}/`)) {
7173
+ continue;
7174
+ }
7175
+ for (const named2 of importDecl.getNamedImports()) {
7176
+ if (named2.getName() === importName) {
7177
+ return named2.getAliasNode()?.getText() ?? named2.getName();
7178
+ }
7179
+ }
7180
+ }
7181
+ return null;
7182
+ }
7183
+ function localNamesFor(sourceFile, specs) {
7184
+ const names = /* @__PURE__ */ new Map();
7185
+ for (const importDecl of sourceFile.getImportDeclarations()) {
7186
+ const module2 = importDecl.getModuleSpecifierValue();
7187
+ for (const spec of specs) {
7188
+ if (module2 !== spec.importModule) {
7189
+ continue;
7190
+ }
7191
+ for (const named2 of importDecl.getNamedImports()) {
7192
+ if (named2.getName() !== spec.importName) {
7193
+ continue;
7194
+ }
7195
+ const local = named2.getAliasNode()?.getText() ?? named2.getName();
7196
+ names.set(local, spec.uriProperty);
7197
+ }
7198
+ }
7199
+ }
7200
+ return names;
7201
+ }
7202
+ function constructionRef(node, localNames, resolution) {
7203
+ if (!import_ts_morph30.Node.isNewExpression(node) && !import_ts_morph30.Node.isCallExpression(node)) {
7204
+ return null;
7205
+ }
7206
+ const callee = node.getExpression();
7207
+ if (callee === void 0 || !import_ts_morph30.Node.isIdentifier(callee)) {
7208
+ return null;
7209
+ }
7210
+ const uriProperty = localNames.get(callee.getText());
7211
+ if (uriProperty === void 0) {
7212
+ return null;
7213
+ }
7214
+ const optionsArg = node.getArguments()[0];
7215
+ if (optionsArg === void 0 || !import_ts_morph30.Node.isObjectLiteralExpression(optionsArg)) {
7216
+ return null;
7217
+ }
7218
+ const property = optionsArg.getProperty(uriProperty);
7219
+ if (property === void 0 || !import_ts_morph30.Node.isPropertyAssignment(property)) {
7220
+ return null;
7221
+ }
7222
+ const value = property.getInitializer();
7223
+ if (value === void 0) {
7224
+ return null;
6728
7225
  }
6729
7226
  const literal = stringValueOf(value, resolution);
6730
7227
  if (literal !== null) {
@@ -7010,7 +7507,7 @@ function calleeOf(decorator) {
7010
7507
 
7011
7508
  // src/discovery/decoratedMethod.ts
7012
7509
  function resolverTypeName(args) {
7013
- return args.classTypeName ?? args.typeMap[args.decoratorName] ?? null;
7510
+ return args.typeMap[args.decoratorName] ?? args.classTypeName ?? null;
7014
7511
  }
7015
7512
  function resolveResolverClassTypeName(decoratorArg) {
7016
7513
  if (import_ts_morph34.Node.isArrowFunction(decoratorArg)) {
@@ -7424,32 +7921,163 @@ function innerTemplateText(template) {
7424
7921
  }
7425
7922
  return raw;
7426
7923
  }
7427
- function resolveGraphqlDocument(arg, resolution) {
7428
- const stripped = stripDocumentNodeCasts(arg);
7429
- const inline = documentTextFromExpression(stripped);
7924
+ function startAssembly(resolution) {
7925
+ return {
7926
+ resolution,
7927
+ seen: /* @__PURE__ */ new Set(),
7928
+ unresolvedInterpolations: [],
7929
+ unresolvedInsideSelection: false
7930
+ };
7931
+ }
7932
+ function assembledTemplateText(template, assembly) {
7933
+ if (import_ts_morph37.Node.isNoSubstitutionTemplateLiteral(template)) {
7934
+ return innerTemplateText(template);
7935
+ }
7936
+ if (!import_ts_morph37.Node.isTemplateExpression(template)) {
7937
+ return null;
7938
+ }
7939
+ let text = template.getHead().getLiteralText();
7940
+ for (const span of template.getTemplateSpans()) {
7941
+ const spliced = interpolatedDocumentText(span.getExpression(), assembly);
7942
+ if (spliced === null) {
7943
+ assembly.unresolvedInterpolations.push(
7944
+ singleLine(span.getExpression().getText())
7945
+ );
7946
+ if (openBraceDepth(text) > 0) {
7947
+ assembly.unresolvedInsideSelection = true;
7948
+ }
7949
+ } else {
7950
+ text += `
7951
+ ${spliced}
7952
+ `;
7953
+ }
7954
+ text += span.getLiteral().getLiteralText();
7955
+ }
7956
+ return text;
7957
+ }
7958
+ function openBraceDepth(text) {
7959
+ let depth = 0;
7960
+ for (const char of text) {
7961
+ if (char === "{") {
7962
+ depth += 1;
7963
+ } else if (char === "}") {
7964
+ depth -= 1;
7965
+ }
7966
+ }
7967
+ return depth;
7968
+ }
7969
+ function interpolatedDocumentText(expr, assembly) {
7970
+ const stripped = stripDocumentNodeCasts(expr);
7971
+ const inline = documentTextFromExpression(stripped, assembly);
7430
7972
  if (inline !== null) {
7431
- return { document: inline };
7973
+ return inline;
7432
7974
  }
7433
- const templateText = resolveGqlTemplateText(stripped);
7434
- if (templateText !== null) {
7435
- return { document: templateText };
7975
+ const named2 = resolveGqlTemplateText(stripped, assembly);
7976
+ if (named2 !== null) {
7977
+ return named2;
7436
7978
  }
7437
7979
  const objectDoc = resolveTypedDocumentSource(stripped);
7438
7980
  if (objectDoc !== null) {
7439
- return { document: objectDoc };
7981
+ return objectDoc;
7440
7982
  }
7441
- const throughFacts = resolveThroughFacts(stripped, resolution);
7442
- if (throughFacts !== null) {
7443
- return { document: throughFacts };
7983
+ return resolveThroughFacts(stripped, assembly);
7984
+ }
7985
+ function resolveGraphqlDocument(arg, resolution) {
7986
+ const assembly = startAssembly(resolution);
7987
+ const stripped = stripDocumentNodeCasts(arg);
7988
+ const text = documentTextFromExpression(stripped, assembly) ?? resolveGqlTemplateText(stripped, assembly) ?? resolveTypedDocumentSource(stripped) ?? resolveThroughFacts(stripped, assembly);
7989
+ if (text !== null) {
7990
+ return assembledDocumentResolution(text, stripped, assembly);
7444
7991
  }
7445
7992
  return resolveTypedDocumentHeader(stripped);
7446
7993
  }
7447
- function documentTextFromExpression(node) {
7994
+ function assembledDocumentResolution(text, arg, assembly) {
7995
+ const interpolations = assembly.unresolvedInterpolations;
7996
+ if (assembly.unresolvedInsideSelection) {
7997
+ const header = parseGraphqlOperation(text);
7998
+ return {
7999
+ ...header !== null ? { operationType: header.operationType } : {},
8000
+ ...header?.operationName !== void 0 ? { operationName: header.operationName } : {},
8001
+ unresolved: {
8002
+ reference: singleLine(arg.getText()),
8003
+ reason: `interpolated ${describeInterpolations(interpolations)} inside a selection set did not resolve to a GraphQL document, so what the operation selects is not statically written`
8004
+ }
8005
+ };
8006
+ }
8007
+ const dangling = danglingFragmentSpreads(text);
8008
+ if (dangling === null) {
8009
+ if (interpolations.length === 0) {
8010
+ return { document: text };
8011
+ }
8012
+ return {
8013
+ unresolved: {
8014
+ reference: singleLine(arg.getText()),
8015
+ reason: `the document did not parse after dropping interpolated ${describeInterpolations(interpolations)} that resolved to no GraphQL document`
8016
+ }
8017
+ };
8018
+ }
8019
+ if (dangling.length > 0) {
8020
+ return { document: text, unresolvedFragments: dangling };
8021
+ }
8022
+ if (interpolations.length > 0) {
8023
+ return {
8024
+ document: text,
8025
+ unresolved: {
8026
+ reference: singleLine(arg.getText()),
8027
+ reason: `interpolated ${describeInterpolations(interpolations)} did not resolve to a GraphQL document, so whatever it contributes is not part of the stored document`
8028
+ }
8029
+ };
8030
+ }
8031
+ return { document: text };
8032
+ }
8033
+ function describeInterpolations(interpolations) {
8034
+ const quoted = interpolations.map((text) => `\`${text}\``).join(", ");
8035
+ return interpolations.length === 1 ? `expression ${quoted}` : `expressions ${quoted}`;
8036
+ }
8037
+ function danglingFragmentSpreads(text) {
8038
+ let doc;
8039
+ try {
8040
+ doc = (0, import_graphql.parse)(text);
8041
+ } catch {
8042
+ return null;
8043
+ }
8044
+ const defined = /* @__PURE__ */ new Set();
8045
+ for (const def of doc.definitions) {
8046
+ if (def.kind === import_graphql.Kind.FRAGMENT_DEFINITION) {
8047
+ defined.add(def.name.value);
8048
+ }
8049
+ }
8050
+ const dangling = /* @__PURE__ */ new Set();
8051
+ for (const def of doc.definitions) {
8052
+ if (def.kind === import_graphql.Kind.OPERATION_DEFINITION || def.kind === import_graphql.Kind.FRAGMENT_DEFINITION) {
8053
+ collectDanglingSpreads(def.selectionSet, defined, dangling);
8054
+ }
8055
+ }
8056
+ return [...dangling].sort();
8057
+ }
8058
+ function collectDanglingSpreads(selectionSet, defined, dangling) {
8059
+ for (const selection of selectionSet.selections) {
8060
+ if (selection.kind === import_graphql.Kind.FRAGMENT_SPREAD) {
8061
+ if (!defined.has(selection.name.value)) {
8062
+ dangling.add(selection.name.value);
8063
+ }
8064
+ continue;
8065
+ }
8066
+ if (selection.selectionSet !== void 0) {
8067
+ collectDanglingSpreads(selection.selectionSet, defined, dangling);
8068
+ }
8069
+ }
8070
+ }
8071
+ function documentTextFromExpression(node, assembly) {
7448
8072
  if (import_ts_morph37.Node.isTaggedTemplateExpression(node)) {
7449
8073
  if (!isDocumentTag(node.getTag())) {
7450
8074
  return null;
7451
8075
  }
7452
- return innerTemplateText(node.getTemplate());
8076
+ if (assembly.seen.has(node)) {
8077
+ return "";
8078
+ }
8079
+ assembly.seen.add(node);
8080
+ return assembledTemplateText(node.getTemplate(), assembly);
7453
8081
  }
7454
8082
  if (!import_ts_morph37.Node.isCallExpression(node)) {
7455
8083
  return null;
@@ -7465,7 +8093,11 @@ function documentTextFromExpression(node) {
7465
8093
  if (!import_ts_morph37.Node.isNoSubstitutionTemplateLiteral(template) && !import_ts_morph37.Node.isTemplateExpression(template)) {
7466
8094
  return null;
7467
8095
  }
7468
- return innerTemplateText(template);
8096
+ if (assembly.seen.has(node)) {
8097
+ return "";
8098
+ }
8099
+ assembly.seen.add(node);
8100
+ return assembledTemplateText(template, assembly);
7469
8101
  }
7470
8102
  function isDocumentTag(tag) {
7471
8103
  if (!import_ts_morph37.Node.isIdentifier(tag)) {
@@ -7485,15 +8117,15 @@ function isDocumentTag(tag) {
7485
8117
  }
7486
8118
  return false;
7487
8119
  }
7488
- function resolveThroughFacts(arg, resolution) {
7489
- if (resolution === void 0 || !import_ts_morph37.Node.isIdentifier(arg)) {
8120
+ function resolveThroughFacts(arg, assembly) {
8121
+ if (assembly.resolution === void 0 || !import_ts_morph37.Node.isIdentifier(arg)) {
7490
8122
  return null;
7491
8123
  }
7492
- const written = resolution.resolveWrittenValue(arg);
8124
+ const written = assembly.resolution.resolveWrittenValue(arg);
7493
8125
  if (written === null) {
7494
8126
  return null;
7495
8127
  }
7496
- const text = documentTextFromExpression(written);
8128
+ const text = documentTextFromExpression(written, assembly);
7497
8129
  if (text !== null) {
7498
8130
  return text;
7499
8131
  }
@@ -7506,7 +8138,13 @@ function operationInfoFromResolution(resolution, callOperationType) {
7506
8138
  return null;
7507
8139
  }
7508
8140
  const operationType = operation.operationName !== void 0 ? operation.operationType : callOperationType;
7509
- return { ...operation, operationType, document: resolution.document };
8141
+ return {
8142
+ ...operation,
8143
+ operationType,
8144
+ document: resolution.document,
8145
+ ...resolution.unresolvedFragments !== void 0 ? { unresolvedFragments: resolution.unresolvedFragments } : {},
8146
+ ...resolution.unresolved !== void 0 ? { unresolved: resolution.unresolved } : {}
8147
+ };
7510
8148
  }
7511
8149
  return {
7512
8150
  operationType: resolution.operationType ?? callOperationType,
@@ -7548,8 +8186,8 @@ function importedVariableInitializers(identifier) {
7548
8186
  }
7549
8187
  return inits;
7550
8188
  }
7551
- function resolveGqlTemplateText(arg) {
7552
- const direct = documentTextFromExpression(arg);
8189
+ function resolveGqlTemplateText(arg, assembly) {
8190
+ const direct = documentTextFromExpression(arg, assembly);
7553
8191
  if (direct !== null) {
7554
8192
  return direct;
7555
8193
  }
@@ -7569,7 +8207,10 @@ function resolveGqlTemplateText(arg) {
7569
8207
  }
7570
8208
  }
7571
8209
  for (const init of importedVariableInitializers(arg)) {
7572
- const text = documentTextFromExpression(stripDocumentNodeCasts(init));
8210
+ const text = documentTextFromExpression(
8211
+ stripDocumentNodeCasts(init),
8212
+ assembly
8213
+ );
7573
8214
  if (text !== null) {
7574
8215
  return text;
7575
8216
  }
@@ -8157,14 +8798,14 @@ function unitForJsxRoute(node, match, routeNames, kind, resolution) {
8157
8798
  }
8158
8799
  return routeUnit(node, kind, match.method, resolved.path, target, resolution);
8159
8800
  }
8160
- function routeUnit(at, kind, method, path11, target, resolution) {
8161
- const routeInfo = { method, path: path11 };
8801
+ function routeUnit(at, kind, method, path12, target, resolution) {
8802
+ const routeInfo = { method, path: path12 };
8162
8803
  if (target.kind === "unreadable") {
8163
8804
  return {
8164
8805
  func: null,
8165
8806
  announcedAt: at,
8166
8807
  kind,
8167
- name: path11,
8808
+ name: path12,
8168
8809
  nameKind: "label",
8169
8810
  routeInfo
8170
8811
  };
@@ -9052,9 +9693,9 @@ function withMountPrefix(routeInfo, subjectNode, mountPrefixes) {
9052
9693
  const prefix = mountPrefixes.effectivePrefixFor(subjectNode);
9053
9694
  return prefix === "" ? routeInfo : { ...routeInfo, path: joinMountedPath(prefix, routeInfo.path) };
9054
9695
  }
9055
- function joinMountedPath(prefix, path11) {
9696
+ function joinMountedPath(prefix, path12) {
9056
9697
  const trimmed = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
9057
- return trimmed === "" ? path11 : trimmed + path11;
9698
+ return trimmed === "" ? path12 : trimmed + path12;
9058
9699
  }
9059
9700
  function registrationSubjectsOf(sourceFile, importModule, importName) {
9060
9701
  let importedLocalName = null;
@@ -9218,11 +9859,11 @@ function extractRouteInfoFromBinding(call, methodName, binding, resolution) {
9218
9859
  return null;
9219
9860
  }
9220
9861
  const method2 = stringProperty(routeObject, binding.method.property);
9221
- const path11 = stringProperty(routeObject, binding.path.property);
9222
- if (method2 === null || path11 === null) {
9862
+ const path12 = stringProperty(routeObject, binding.path.property);
9863
+ if (method2 === null || path12 === null) {
9223
9864
  return null;
9224
9865
  }
9225
- return { method: method2.toUpperCase(), path: path11 };
9866
+ return { method: method2.toUpperCase(), path: path12 };
9226
9867
  }
9227
9868
  if (binding.method.type !== "fromRegistration" || binding.path.type !== "fromRegistration") {
9228
9869
  return null;
@@ -9329,7 +9970,7 @@ function bodyReferencesLoopVar(loop, name) {
9329
9970
  }
9330
9971
  function readRouteSpec(element, shape, resolution) {
9331
9972
  let method = null;
9332
- let path11 = null;
9973
+ let path12 = null;
9333
9974
  let handler = null;
9334
9975
  for (const prop of element.getProperties()) {
9335
9976
  if (import_ts_morph46.Node.isMethodDeclaration(prop) && prop.getName() === shape.handlerKey) {
@@ -9347,16 +9988,16 @@ function readRouteSpec(element, shape, resolution) {
9347
9988
  if (name === shape.methodKey) {
9348
9989
  method = readStringLiteralValue(init);
9349
9990
  } else if (name === shape.pathKey) {
9350
- path11 = readStringLiteralValue(init);
9991
+ path12 = readStringLiteralValue(init);
9351
9992
  } else if (name === shape.handlerKey) {
9352
9993
  const func = functionValueOf(init, resolution);
9353
9994
  handler = func === null ? null : { func, name: handlerName(init) };
9354
9995
  }
9355
9996
  }
9356
- if (method === null || path11 === null || handler === null) {
9997
+ if (method === null || path12 === null || handler === null) {
9357
9998
  return null;
9358
9999
  }
9359
- return { method, path: path11, handler: handler.func, handlerName: handler.name };
10000
+ return { method, path: path12, handler: handler.func, handlerName: handler.name };
9360
10001
  }
9361
10002
  function readStringLiteralValue(node) {
9362
10003
  if (import_ts_morph46.Node.isStringLiteral(node) || import_ts_morph46.Node.isNoSubstitutionTemplateLiteral(node)) {
@@ -9385,8 +10026,8 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution) {
9385
10026
  }
9386
10027
  const args = node.getArguments();
9387
10028
  for (const reg of match.registrations) {
9388
- const path11 = substitutePath(reg.pathTemplate, args);
9389
- if (path11 === null) {
10029
+ const path12 = substitutePath(reg.pathTemplate, args);
10030
+ if (path12 === null) {
9390
10031
  continue;
9391
10032
  }
9392
10033
  const handler = resolveHandler(reg.handlerArg, args, resolution);
@@ -9399,7 +10040,7 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution) {
9399
10040
  name: handler.name,
9400
10041
  routeInfo: {
9401
10042
  method: reg.method.toUpperCase(),
9402
- path: path11
10043
+ path: path12
9403
10044
  }
9404
10045
  });
9405
10046
  }
@@ -9568,7 +10209,10 @@ function discoverResolverMaps(sourceFile, match, kind, resolution) {
9568
10209
  resolverInfo: {
9569
10210
  typeName,
9570
10211
  fieldName,
9571
- ...schemaSdl !== null ? { schemaSdl } : {}
10212
+ ...schemaSdl !== null ? {
10213
+ schemaSdl,
10214
+ schemaDocument: config.getSourceFile().getFilePath()
10215
+ } : {}
9572
10216
  }
9573
10217
  });
9574
10218
  }
@@ -9765,7 +10409,15 @@ function discoverUnits(sourceFile, patterns, resolution, mountPrefixes) {
9765
10409
  }
9766
10410
 
9767
10411
  // src/discovery/mountPrefix.ts
9768
- var NO_MOUNTS = { effectivePrefixFor: () => "" };
10412
+ var NO_MOUNTS = {
10413
+ // Recorded even with no mounts anywhere: a mount added later in some
10414
+ // other file changes this router's prefix without touching its file.
10415
+ effectivePrefixFor: (routerNode) => {
10416
+ recordMountPrefix(nodeId(routerNode), "");
10417
+ return "";
10418
+ },
10419
+ prefixForId: () => ""
10420
+ };
9769
10421
  function buildMountPrefixIndex(packsByFile, resolution) {
9770
10422
  const subjectIdsByPack = /* @__PURE__ */ new Map();
9771
10423
  const mountWorkByPack = /* @__PURE__ */ new Map();
@@ -9822,10 +10474,15 @@ function buildMountPrefixIndex(packsByFile, resolution) {
9822
10474
  return NO_MOUNTS;
9823
10475
  }
9824
10476
  const memo = /* @__PURE__ */ new Map();
10477
+ const byId = (childId) => resolvePrefix(edgesByChild, memo, childId, /* @__PURE__ */ new Set()) ?? "";
9825
10478
  return {
9826
10479
  effectivePrefixFor(routerNode) {
9827
- return resolvePrefix(edgesByChild, memo, nodeId(routerNode), /* @__PURE__ */ new Set()) ?? "";
9828
- }
10480
+ const childId = nodeId(routerNode);
10481
+ const prefix = byId(childId);
10482
+ recordMountPrefix(childId, prefix);
10483
+ return prefix;
10484
+ },
10485
+ prefixForId: byId
9829
10486
  };
9830
10487
  }
9831
10488
  function recordEdge(edgesByChild, candidate) {
@@ -10071,7 +10728,8 @@ var JS_RULES = [
10071
10728
  (0, import_datalog2.rule)(
10072
10729
  "stepsTo",
10073
10730
  [(0, import_datalog2.variable)("r"), (0, import_datalog2.variable)("t"), import_resolution.VALUE_STEP],
10074
- [(0, import_datalog2.lit)("bindCall", (0, import_datalog2.variable)("r"), (0, import_datalog2.variable)("t"))]
10731
+ [(0, import_datalog2.lit)("bindCall", (0, import_datalog2.variable)("r"), (0, import_datalog2.variable)("t"))],
10732
+ "bind"
10075
10733
  )
10076
10734
  ];
10077
10735
  var RESOLUTION_PROGRAM = process.env.SUSS_RESOLUTION_ON_DEMAND === "0" ? {
@@ -10081,6 +10739,15 @@ var RESOLUTION_PROGRAM = process.env.SUSS_RESOLUTION_ON_DEMAND === "0" ? {
10081
10739
  [...import_resolution.RESOLUTION_RULES, ...JS_RULES, ...import_resolution.RESOLUTION_QUESTIONS],
10082
10740
  import_resolution.ANSWER_RELATIONS
10083
10741
  );
10742
+ var WITNESS_RULES = [...import_resolution.RESOLUTION_RULES, ...JS_RULES];
10743
+ var NOT_BASE_FACTS = /* @__PURE__ */ new Set([
10744
+ ...[...import_resolution.RESOLUTION_RULES, ...JS_RULES, ...import_resolution.RESOLUTION_QUESTIONS].map(
10745
+ (r) => r.head.relation
10746
+ ),
10747
+ ...RESOLUTION_PROGRAM.rules.map((r) => r.head.relation),
10748
+ "wanted",
10749
+ "wantedOrigin"
10750
+ ]);
10084
10751
  var QUERY_FACTS = RESOLUTION_PROGRAM.demandDriven.length === 0 ? [] : [...RESOLUTION_PROGRAM.demandDriven, "wanted", "wantedOrigin"];
10085
10752
  var MAX_MODULE_HOPS = 6;
10086
10753
  var ResolutionStore = class {
@@ -10089,6 +10756,8 @@ var ResolutionStore = class {
10089
10756
  fullyExtracted = /* @__PURE__ */ new Set();
10090
10757
  seededValues = /* @__PURE__ */ new Set();
10091
10758
  importedNames = /* @__PURE__ */ new Map();
10759
+ /** Files the most recent wave walk entered, for the memo to keep. */
10760
+ lastQueryWalked = [];
10092
10761
  declarations = /* @__PURE__ */ new Map();
10093
10762
  graph = new ModuleGraph();
10094
10763
  stale = true;
@@ -10106,13 +10775,64 @@ var ResolutionStore = class {
10106
10775
  * the walk never reaches on its own.
10107
10776
  */
10108
10777
  resolveCallable(value, alsoFrom) {
10778
+ const sources = this.resolveCallableSources(value, alsoFrom);
10779
+ const only = sources[0];
10780
+ return sources.length === 1 && only !== void 0 ? only : null;
10781
+ }
10782
+ /**
10783
+ * Every function the value comes down to. One is the answer
10784
+ * `resolveCallable` gives; several is a value with more than one
10785
+ * possible source, which a caller says at the site rather than
10786
+ * folding into the same nothing as a chain that went nowhere.
10787
+ */
10788
+ resolveCallableSources(value, alsoFrom) {
10109
10789
  const target = factKeyOf(value);
10110
10790
  return this.resolveByWaves(
10111
10791
  target,
10112
10792
  "wanted",
10113
- () => this.lookup(target),
10793
+ () => this.lookupSources(target),
10114
10794
  alsoFrom
10795
+ ) ?? [];
10796
+ }
10797
+ /**
10798
+ * The witness proof behind `resolveCallable`'s answer. Resolves the
10799
+ * value first, which walks files and extracts facts in the usual
10800
+ * waves, then re-evaluates the rules over those base facts under the
10801
+ * witness algebra and rebuilds the proof of the answer. Null when
10802
+ * the value does not resolve at all.
10803
+ */
10804
+ explainCallable(value, options = {}) {
10805
+ const resolved = this.resolveCallable(value, options.alsoFrom);
10806
+ if (resolved === null) {
10807
+ return null;
10808
+ }
10809
+ const proofDb = new import_datalog2.Database();
10810
+ let baseFacts = 0;
10811
+ for (const relation of this.db.relationNames()) {
10812
+ if (NOT_BASE_FACTS.has(relation)) {
10813
+ continue;
10814
+ }
10815
+ for (const tuple of this.db.facts(relation)) {
10816
+ proofDb.add(relation, tuple);
10817
+ baseFacts++;
10818
+ }
10819
+ }
10820
+ const started = performance.now();
10821
+ (0, import_datalog2.evaluate)(proofDb, WITNESS_RULES, import_datalog2.witnesses);
10822
+ const evaluateMs = performance.now() - started;
10823
+ const derivedFacts = proofDb.relationNames().reduce((count, relation) => count + proofDb.size(relation), 0) - baseFacts;
10824
+ const proof = (0, import_datalog2.proofOf)(
10825
+ proofDb,
10826
+ "resolves",
10827
+ [nodeId(factKeyOf(value)), nodeId(resolved)],
10828
+ options.maxDepth === void 0 ? {} : { maxDepth: options.maxDepth }
10115
10829
  );
10830
+ return {
10831
+ target: resolved,
10832
+ proof,
10833
+ nodeFor: (atom) => this.table.byId.get(String(atom)),
10834
+ stats: { baseFacts, derivedFacts, evaluateMs }
10835
+ };
10116
10836
  }
10117
10837
  resolveObject(value) {
10118
10838
  const target = factKeyOf(value);
@@ -10149,7 +10869,10 @@ var ResolutionStore = class {
10149
10869
  const declaration = `${nodeId(refersTo)}|${modules.join(",")}`;
10150
10870
  const cached = this.importedNames.get(declaration);
10151
10871
  if (cached !== void 0) {
10152
- return cached;
10872
+ for (const walkedPath of cached.walked) {
10873
+ recordFileDependency(walkedPath);
10874
+ }
10875
+ return cached.names;
10153
10876
  }
10154
10877
  const target = factKeyOf(value);
10155
10878
  const found = this.resolveByWaves(
@@ -10157,7 +10880,10 @@ var ResolutionStore = class {
10157
10880
  "wantedOrigin",
10158
10881
  () => this.lookupImportedNames(target, modules)
10159
10882
  ) ?? [];
10160
- this.importedNames.set(declaration, found);
10883
+ this.importedNames.set(declaration, {
10884
+ names: found,
10885
+ walked: [...this.lastQueryWalked]
10886
+ });
10161
10887
  return found;
10162
10888
  }
10163
10889
  /**
@@ -10191,6 +10917,7 @@ var ResolutionStore = class {
10191
10917
  this.wantValue(question, value);
10192
10918
  this.seedValue(value);
10193
10919
  const walked = /* @__PURE__ */ new Set();
10920
+ this.lastQueryWalked = [];
10194
10921
  let frontier = alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom];
10195
10922
  for (let hop = 0; hop <= MAX_MODULE_HOPS; hop++) {
10196
10923
  const next = [];
@@ -10199,6 +10926,8 @@ var ResolutionStore = class {
10199
10926
  continue;
10200
10927
  }
10201
10928
  walked.add(sourceFile.getFilePath());
10929
+ this.lastQueryWalked.push(sourceFile.getFilePath());
10930
+ recordFileDependency(sourceFile.getFilePath());
10202
10931
  this.extractFile(sourceFile);
10203
10932
  next.push(...this.graph.importedFilesOf(sourceFile));
10204
10933
  }
@@ -10226,7 +10955,7 @@ var ResolutionStore = class {
10226
10955
  this.stale = false;
10227
10956
  }
10228
10957
  wantValue(question, value) {
10229
- if (this.db.add(question, [nodeId(value)])) {
10958
+ if (this.db.add(question, [nodeId(value)]) === "added") {
10230
10959
  this.stale = true;
10231
10960
  }
10232
10961
  }
@@ -10290,10 +11019,12 @@ var ResolutionStore = class {
10290
11019
  return this.graph.filesReachingAnyPackage(fileSets);
10291
11020
  }
10292
11021
  /**
10293
- * Two candidates give null. Picking whichever arrived first would make
10294
- * the result depend on the order the facts came in.
11022
+ * Null keeps the wave walk widening; any candidate at all ends it,
11023
+ * since more files can only add candidates, never take one away. The
11024
+ * single-answer policy stays with the callers: picking one of two
11025
+ * would make the result depend on the order the facts came in.
10295
11026
  */
10296
- lookup(value) {
11027
+ lookupSources(value) {
10297
11028
  this.derive();
10298
11029
  const candidates = /* @__PURE__ */ new Set();
10299
11030
  for (const target of this.answersFor("wantedResolves", nodeId(value))) {
@@ -10305,10 +11036,7 @@ var ResolutionStore = class {
10305
11036
  candidates.add(resolved);
10306
11037
  }
10307
11038
  }
10308
- if (candidates.size !== 1) {
10309
- return null;
10310
- }
10311
- return [...candidates][0];
11039
+ return candidates.size === 0 ? null : [...candidates];
10312
11040
  }
10313
11041
  derive() {
10314
11042
  if (!this.stale) {
@@ -10495,6 +11223,27 @@ function moduleInitSummary(sourceFile, effects, options) {
10495
11223
  return (0, import_extractor3.assembleSummary)(raw, options);
10496
11224
  }
10497
11225
 
11226
+ // src/referencedFiles.ts
11227
+ function createReferenceIndex(sourceFiles) {
11228
+ const direct = /* @__PURE__ */ new Map();
11229
+ for (const sourceFile of sourceFiles) {
11230
+ const referenced = /* @__PURE__ */ new Set();
11231
+ for (const target of sourceFile.getReferencedSourceFiles()) {
11232
+ if (target.isInNodeModules() || target.isDeclarationFile()) {
11233
+ continue;
11234
+ }
11235
+ referenced.add(target.getFilePath());
11236
+ }
11237
+ direct.set(sourceFile.getFilePath(), referenced);
11238
+ }
11239
+ const none = /* @__PURE__ */ new Set();
11240
+ return {
11241
+ directOf(filePath) {
11242
+ return direct.get(filePath) ?? none;
11243
+ }
11244
+ };
11245
+ }
11246
+
10498
11247
  // src/resolve/boundaryEffects.ts
10499
11248
  var import_datalog3 = require("@suss/datalog");
10500
11249
  var EFFECT_RULES = [
@@ -10676,6 +11425,11 @@ function classifyStop(declarations) {
10676
11425
  if (declarations.length === 0) {
10677
11426
  return "noDeclaration";
10678
11427
  }
11428
+ for (const declaration of declarations) {
11429
+ if (!isInExternalCode(declaration.getSourceFile())) {
11430
+ recordFileDependency(declaration.getSourceFile().getFilePath());
11431
+ }
11432
+ }
10679
11433
  if (declarations.every(isExternalDeclaration)) {
10680
11434
  return "outsideRun";
10681
11435
  }
@@ -10687,6 +11441,7 @@ function classifyStop(declarations) {
10687
11441
  var RECORDED = {
10688
11442
  noBody: true,
10689
11443
  unsettledValue: true,
11444
+ multipleSources: true,
10690
11445
  outsideRun: false,
10691
11446
  noDeclaration: false
10692
11447
  };
@@ -10696,6 +11451,7 @@ function worthRecording(reason) {
10696
11451
  var STOP_SENTENCE = {
10697
11452
  noBody: (callee) => `The call to ${callee} lands on a declaration with no body, so whatever runs there is missing from this summary`,
10698
11453
  unsettledValue: (callee) => `The call to ${callee} goes through a value this run could not settle, so whatever runs there is missing from this summary`,
11454
+ multipleSources: (callee) => `The call to ${callee} reaches a value with more than one possible source, so whatever runs there is missing from this summary`,
10699
11455
  outsideRun: (callee) => `The call to ${callee} lands in a package whose source is not in this run, so whatever runs there is missing from this summary`,
10700
11456
  noDeclaration: (callee) => `The call to ${callee} has no declaration this run could find, so whatever runs there is missing from this summary`
10701
11457
  };
@@ -10749,24 +11505,6 @@ function resolveDecl(decl, calleeName) {
10749
11505
  }
10750
11506
  return null;
10751
11507
  }
10752
- if (import_ts_morph52.Node.isImportSpecifier(decl)) {
10753
- const importDecl = decl.getImportDeclaration();
10754
- const sf = importDecl.getModuleSpecifierSourceFile();
10755
- if (sf === void 0 || isInExternalCode(sf)) {
10756
- return null;
10757
- }
10758
- const exported = exportedDeclarationsOf(sf).get(decl.getName());
10759
- if (exported === void 0) {
10760
- return null;
10761
- }
10762
- for (const ed of exported) {
10763
- const resolved = resolveDecl(ed, calleeName);
10764
- if (resolved !== null) {
10765
- return resolved;
10766
- }
10767
- }
10768
- return null;
10769
- }
10770
11508
  return null;
10771
11509
  }
10772
11510
  function declName(decl) {
@@ -10794,20 +11532,27 @@ function resolveCallee(call, calleeName, scan) {
10794
11532
  }
10795
11533
  const callee = call.getExpression();
10796
11534
  const symbol = callee.getSymbol();
10797
- const declarations = symbol?.getDeclarations() ?? [];
11535
+ const declarations = declarationsBehind(symbol);
10798
11536
  for (const decl of declarations) {
10799
11537
  const resolved = resolveDecl(decl, calleeName);
10800
11538
  if (resolved !== null) {
10801
11539
  return { kind: "followed", candidate: resolved };
10802
11540
  }
10803
11541
  }
10804
- const resolveCallable = scan.resolveCallable;
10805
- if (resolveCallable !== void 0 && import_ts_morph52.Node.isPropertyAccessExpression(callee) && declarations.some(isDeclaredShape)) {
10806
- const value = resolveCallable(callee, scan.reachedFrom);
10807
- const resolved = value === null ? null : resolveDecl(value, calleeName);
11542
+ const resolveSources = scan.resolveCallableSources;
11543
+ if (resolveSources !== void 0 && import_ts_morph52.Node.isPropertyAccessExpression(callee) && declarations.some(isDeclaredShape)) {
11544
+ const sources = resolveSources(callee, scan.reachedFrom);
11545
+ const only = sources.length === 1 ? sources[0] : void 0;
11546
+ const resolved = only === void 0 ? null : resolveDecl(only, calleeName);
10808
11547
  if (resolved !== null) {
10809
11548
  return { kind: "followed", candidate: resolved };
10810
11549
  }
11550
+ if (sources.length > 1) {
11551
+ return {
11552
+ kind: "stopped",
11553
+ stop: { callee: calleeName, reason: "multipleSources" }
11554
+ };
11555
+ }
10811
11556
  }
10812
11557
  return {
10813
11558
  kind: "stopped",
@@ -10882,7 +11627,7 @@ var REACHABLE_RULES = [
10882
11627
  )
10883
11628
  ];
10884
11629
  var NO_RECOGNIZERS = { invocation: [], access: [] };
10885
- function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS) {
11630
+ function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS, alreadySummarized = []) {
10886
11631
  const lookup = createSourceFileLookup(project);
10887
11632
  const db = facts?.db ?? new import_datalog4.Database();
10888
11633
  const functionByKey = /* @__PURE__ */ new Map();
@@ -10902,6 +11647,15 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10902
11647
  db.add("entry", [key]);
10903
11648
  }
10904
11649
  }
11650
+ const knownKeys = /* @__PURE__ */ new Set();
11651
+ for (const summary of alreadySummarized) {
11652
+ const func = lookup.functionAt(summary.location);
11653
+ if (func !== null) {
11654
+ const key = nodeKey2(func);
11655
+ knownKeys.add(key);
11656
+ facts?.unitKeyBySummary.set(summary, key);
11657
+ }
11658
+ }
10905
11659
  for (; ; ) {
10906
11660
  (0, import_datalog4.evaluate)(db, REACHABLE_RULES);
10907
11661
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
@@ -10916,10 +11670,14 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10916
11670
  }
10917
11671
  const cameFrom = reachedFrom.get(key);
10918
11672
  const scan = {
10919
- ...recognizers.resolveCallable === void 0 ? {} : { resolveCallable: recognizers.resolveCallable },
11673
+ ...recognizers.resolveCallableSources === void 0 ? {} : { resolveCallableSources: recognizers.resolveCallableSources },
10920
11674
  ...cameFrom === void 0 ? {} : { reachedFrom: cameFrom }
10921
11675
  };
10922
- const { candidates, stops } = collectReachable(source.func, scan);
11676
+ const { candidates, stops } = scanWithRecording(
11677
+ key,
11678
+ facts,
11679
+ () => collectReachable(source.func, scan)
11680
+ );
10923
11681
  if (stops.length > 0) {
10924
11682
  stopsByKey.set(key, stops);
10925
11683
  }
@@ -10938,7 +11696,7 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10938
11696
  const reached = [];
10939
11697
  for (const [keyAtom] of db.facts("reachable")) {
10940
11698
  const key = String(keyAtom);
10941
- if (seedKeys.has(key)) {
11699
+ if (seedKeys.has(key) || knownKeys.has(key)) {
10942
11700
  continue;
10943
11701
  }
10944
11702
  const candidate = functionByKey.get(key);
@@ -10952,7 +11710,11 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10952
11710
  candidate.func.getSourceFile().getFilePath()
10953
11711
  );
10954
11712
  }
10955
- const summary = extractReachableSummary(candidate, options, recognizers);
11713
+ const summary = scanWithRecording(
11714
+ key,
11715
+ facts,
11716
+ () => extractReachableSummary(candidate, options, recognizers)
11717
+ );
10956
11718
  facts?.unitKeyBySummary.set(summary, key);
10957
11719
  rememberSummary(summariesByKey, key, summary);
10958
11720
  reached.push(summary);
@@ -10960,6 +11722,20 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10960
11722
  recordStops(stopsByKey, summariesByKey, options);
10961
11723
  return [...seeds, ...reached];
10962
11724
  }
11725
+ function scanWithRecording(key, facts, fn) {
11726
+ const filesByKey = facts?.filesByKey;
11727
+ if (filesByKey === void 0) {
11728
+ return fn();
11729
+ }
11730
+ const sink = createDependencySink();
11731
+ const result = withDependencySink(sink, fn);
11732
+ const bucket = filesByKey.get(key) ?? /* @__PURE__ */ new Set();
11733
+ for (const filePath of sink.files) {
11734
+ bucket.add(filePath);
11735
+ }
11736
+ filesByKey.set(key, bucket);
11737
+ return result;
11738
+ }
10963
11739
  function rememberSummary(summariesByKey, key, summary) {
10964
11740
  const bucket = summariesByKey.get(key) ?? [];
10965
11741
  bucket.push(summary);
@@ -11708,20 +12484,25 @@ function readArrayLiteralText(node) {
11708
12484
  var import_node_fs4 = __toESM(require("fs"), 1);
11709
12485
  var import_node_path7 = __toESM(require("path"), 1);
11710
12486
  var import_behavioral_ir5 = require("@suss/behavioral-ir");
12487
+ function workspaceRootFor(anchor) {
12488
+ return namedPackageDirAbove(anchor) ?? import_node_path7.default.resolve(anchor);
12489
+ }
11711
12490
  function workspaceNameFor(root) {
11712
12491
  if (root === void 0) {
11713
12492
  return null;
11714
12493
  }
11715
- let at = import_node_path7.default.resolve(root);
12494
+ const dir = namedPackageDirAbove(root);
12495
+ if (dir !== null) {
12496
+ return packageNameAt(dir);
12497
+ }
12498
+ const folder = import_node_path7.default.basename(import_node_path7.default.resolve(root));
12499
+ return folder.length > 0 ? folder : null;
12500
+ }
12501
+ function namedPackageDirAbove(start) {
12502
+ let at = import_node_path7.default.resolve(start);
11716
12503
  for (let up = 0; up < 12; up += 1) {
11717
- try {
11718
- const manifest = JSON.parse(
11719
- import_node_fs4.default.readFileSync(import_node_path7.default.join(at, "package.json"), "utf8")
11720
- );
11721
- if (typeof manifest.name === "string" && manifest.name.length > 0) {
11722
- return manifest.name;
11723
- }
11724
- } catch {
12504
+ if (packageNameAt(at) !== null) {
12505
+ return at;
11725
12506
  }
11726
12507
  const parent = import_node_path7.default.dirname(at);
11727
12508
  if (parent === at) {
@@ -11729,8 +12510,19 @@ function workspaceNameFor(root) {
11729
12510
  }
11730
12511
  at = parent;
11731
12512
  }
11732
- const folder = import_node_path7.default.basename(import_node_path7.default.resolve(root));
11733
- return folder.length > 0 ? folder : null;
12513
+ return null;
12514
+ }
12515
+ function packageNameAt(dir) {
12516
+ try {
12517
+ const manifest = JSON.parse(
12518
+ import_node_fs4.default.readFileSync(import_node_path7.default.join(dir, "package.json"), "utf8")
12519
+ );
12520
+ if (typeof manifest.name === "string" && manifest.name.length > 0) {
12521
+ return manifest.name;
12522
+ }
12523
+ } catch {
12524
+ }
12525
+ return null;
11734
12526
  }
11735
12527
  function nameSummaries(summaries, args) {
11736
12528
  for (const summary of summaries) {
@@ -12215,6 +13007,36 @@ function extractCodeStructure(unit, pack, invocationRecognizers = [], accessReco
12215
13007
  function responseAccessorNames(pack, kind) {
12216
13008
  return pack.responseSemantics?.filter((m) => m.semantics.type === kind).map((m) => m.name);
12217
13009
  }
13010
+ var ACCESSOR_FIELD_SEMANTICS = {
13011
+ bodyAccessors: "body",
13012
+ statusAccessors: "statusCode",
13013
+ successAccessors: "statusRange"
13014
+ };
13015
+ function responseAccessors(pack) {
13016
+ const out = {};
13017
+ for (const [field, kind] of Object.entries(ACCESSOR_FIELD_SEMANTICS)) {
13018
+ const names = responseAccessorNames(pack, kind);
13019
+ if (names !== void 0 && names.length > 0) {
13020
+ out[field] = names;
13021
+ }
13022
+ }
13023
+ return out;
13024
+ }
13025
+ var FUNCTION_FALLTHROUGH_TERMINAL = {
13026
+ kind: "return",
13027
+ match: { type: "functionFallthrough" },
13028
+ extraction: {}
13029
+ };
13030
+ function terminalsFor(unit, pack) {
13031
+ const declared = unit.terminals ?? pack.terminals;
13032
+ if (unit.callSite === void 0) {
13033
+ return declared;
13034
+ }
13035
+ if (declared.some((p) => p.match.type === "functionFallthrough")) {
13036
+ return declared;
13037
+ }
13038
+ return [...declared, FUNCTION_FALLTHROUGH_TERMINAL];
13039
+ }
12218
13040
  function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers, barriers, resolveWrittenValue) {
12219
13041
  const { func, kind, name } = unit;
12220
13042
  if (func === null) {
@@ -12226,7 +13048,7 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12226
13048
  );
12227
13049
  const extracted = extractRawBranches(
12228
13050
  func,
12229
- unit.terminals ?? pack.terminals,
13051
+ terminalsFor(unit, pack),
12230
13052
  invocationRecognizers,
12231
13053
  accessRecognizers,
12232
13054
  barriers,
@@ -12262,8 +13084,7 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12262
13084
  }
12263
13085
  }
12264
13086
  }
12265
- const bodyAccessors = unit.callSite !== void 0 ? responseAccessorNames(pack, "body") : void 0;
12266
- const statusAccessors = unit.callSite !== void 0 ? responseAccessorNames(pack, "statusCode") : void 0;
13087
+ const accessors = unit.callSite !== void 0 ? responseAccessors(pack) : {};
12267
13088
  return {
12268
13089
  identity: {
12269
13090
  name,
@@ -12287,8 +13108,8 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12287
13108
  bodyContent: bodyContentOf(func),
12288
13109
  dependencyCalls: depCalls,
12289
13110
  declaredContract: null,
12290
- ...bodyAccessors !== void 0 && bodyAccessors.length > 0 ? { bodyAccessors } : {},
12291
- ...statusAccessors !== void 0 && statusAccessors.length > 0 ? { statusAccessors } : {}
13111
+ ...accessors,
13112
+ ...unit.callSite !== void 0 && pack.failureDelivery !== void 0 ? { failureDelivery: pack.failureDelivery } : {}
12292
13113
  };
12293
13114
  }
12294
13115
  function extractConsumerBinding(unit, pattern, pack, resolution) {
@@ -12301,11 +13122,11 @@ function extractConsumerBinding(unit, pattern, pack, resolution) {
12301
13122
  return null;
12302
13123
  }
12303
13124
  const method = extractBindingMethod(binding, callSite, pack, resolution);
12304
- const path11 = extractBindingPath(binding, callSite, pack, resolution);
13125
+ const path12 = extractBindingPath(binding, callSite, pack, resolution);
12305
13126
  return (0, import_behavioral_ir6.restBinding)({
12306
13127
  transport: pack.protocol,
12307
13128
  method: method ?? null,
12308
- path: path11 ?? null,
13129
+ path: path12 ?? null,
12309
13130
  recognition: pack.name
12310
13131
  });
12311
13132
  }
@@ -12394,12 +13215,12 @@ function originEndOf(flattened) {
12394
13215
  const slash = flattened.indexOf("/", opener[0].length);
12395
13216
  return slash === -1 ? flattened.length : slash;
12396
13217
  }
12397
- function appendPathText(path11, text) {
13218
+ function appendPathText(path12, text) {
12398
13219
  const idx = text.search(/[?#]/);
12399
13220
  if (idx === -1) {
12400
- return { path: path11 + text, stop: false };
13221
+ return { path: path12 + text, stop: false };
12401
13222
  }
12402
- return { path: path11 + text.slice(0, idx), stop: true };
13223
+ return { path: path12 + text.slice(0, idx), stop: true };
12403
13224
  }
12404
13225
  function parseAbsoluteUrl(text) {
12405
13226
  try {
@@ -12423,8 +13244,8 @@ function pathnameOfAbsoluteLiteral(text) {
12423
13244
  return stripQueryAndFragment(stripOriginManually(text));
12424
13245
  }
12425
13246
  function pathFromLiteralUrl(text) {
12426
- const path11 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
12427
- return path11 === "" ? void 0 : path11;
13247
+ const path12 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
13248
+ return path12 === "" ? void 0 : path12;
12428
13249
  }
12429
13250
  function pathFromTemplateUrl(arg) {
12430
13251
  const headText = arg.getHead().getLiteralText();
@@ -12432,7 +13253,7 @@ function pathFromTemplateUrl(arg) {
12432
13253
  const tails = spans.map((span) => span.getLiteral().getLiteralText());
12433
13254
  const originEnd = originEndOf([headText, ...tails].join(SUBSTITUTION));
12434
13255
  const head = appendPathText("", headText.slice(originEnd));
12435
- let path11 = head.path;
13256
+ let path12 = head.path;
12436
13257
  let stop = head.stop;
12437
13258
  let at = headText.length;
12438
13259
  for (let i = 0; i < spans.length && !stop; i++) {
@@ -12442,18 +13263,18 @@ function pathFromTemplateUrl(arg) {
12442
13263
  continue;
12443
13264
  }
12444
13265
  if (at >= originEnd) {
12445
- path11 += `{${placeholderName(span.getExpression())}}`;
13266
+ path12 += `{${placeholderName(span.getExpression())}}`;
12446
13267
  }
12447
13268
  at += SUBSTITUTION.length;
12448
13269
  const appended = appendPathText(
12449
- path11,
13270
+ path12,
12450
13271
  tailText.slice(Math.max(0, originEnd - at))
12451
13272
  );
12452
- path11 = appended.path;
13273
+ path12 = appended.path;
12453
13274
  stop = appended.stop;
12454
13275
  at += tailText.length;
12455
13276
  }
12456
- return path11 === "" ? void 0 : path11;
13277
+ return path12 === "" ? void 0 : path12;
12457
13278
  }
12458
13279
  function extractBindingPath(binding, callSite, pack, resolution) {
12459
13280
  const p = binding.path;
@@ -12642,12 +13463,20 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12642
13463
  if (claimant.pack === pack.name && claimant.file === sourceFile.getFilePath() && tally !== void 0) {
12643
13464
  tally.selfCollisions += 1;
12644
13465
  }
13466
+ if (claimant.file !== sourceFile.getFilePath()) {
13467
+ recordFileDependency(claimant.file);
13468
+ }
12645
13469
  continue;
12646
13470
  }
12647
13471
  claimed.set(claimKey, {
12648
13472
  pack: pack.name,
12649
13473
  file: sourceFile.getFilePath()
12650
13474
  });
13475
+ recordUnitClaim(claimKey, pack.name);
13476
+ const declaringFile = unit.func?.getSourceFile().getFilePath();
13477
+ if (declaringFile !== void 0 && declaringFile !== sourceFile.getFilePath()) {
13478
+ recordFileDependency(declaringFile);
13479
+ }
12651
13480
  unitsWalkedHere += 1;
12652
13481
  if (tally !== void 0) {
12653
13482
  tally.unitsClaimed += 1;
@@ -12686,6 +13515,9 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12686
13515
  }
12687
13516
  if (unit.resolverInfo.schemaSdl !== void 0) {
12688
13517
  raw.graphqlSchemaSdl = unit.resolverInfo.schemaSdl;
13518
+ if (unit.resolverInfo.schemaDocument !== void 0) {
13519
+ raw.sourceDocumentLabel = unit.resolverInfo.schemaDocument;
13520
+ }
12689
13521
  if (unit.resolverInfo.typeName !== null) {
12690
13522
  const contract = deriveGraphqlContract(
12691
13523
  unit.resolverInfo.schemaSdl,
@@ -12721,6 +13553,9 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12721
13553
  if (unit.operationInfo.document !== void 0) {
12722
13554
  raw.graphqlDocument = unit.operationInfo.document;
12723
13555
  }
13556
+ if (unit.operationInfo.unresolvedFragments !== void 0) {
13557
+ raw.graphqlUnresolvedFragments = unit.operationInfo.unresolvedFragments;
13558
+ }
12724
13559
  if (unit.operationInfo.unresolved !== void 0) {
12725
13560
  raw.graphqlUnresolvedDocument = unit.operationInfo.unresolved;
12726
13561
  }
@@ -12874,6 +13709,37 @@ function fileImporting(project, modulePrefix) {
12874
13709
  }
12875
13710
  return void 0;
12876
13711
  }
13712
+ function liftSchemasOntoDocuments(summaries) {
13713
+ const sdlByDocument = /* @__PURE__ */ new Map();
13714
+ for (const summary of summaries) {
13715
+ const label = (0, import_behavioral_ir6.readSourceDocumentMetadata)(summary)?.label;
13716
+ const graphql = (0, import_behavioral_ir6.readGraphqlMetadata)(summary);
13717
+ if (label === void 0 || graphql?.schemaSdl === void 0) {
13718
+ continue;
13719
+ }
13720
+ sdlByDocument.set(label, graphql.schemaSdl);
13721
+ const { schemaSdl: _movedToTheDocument, ...rest } = graphql;
13722
+ summary.metadata = (0, import_behavioral_ir6.withGraphqlMetadata)(summary.metadata, rest);
13723
+ }
13724
+ for (const [label, schemaSdl] of sdlByDocument) {
13725
+ summaries.push(schemaDocumentSummary(label, schemaSdl));
13726
+ }
13727
+ }
13728
+ function schemaDocumentSummary(label, schemaSdl) {
13729
+ return {
13730
+ kind: "library",
13731
+ location: { file: label, range: { start: 0, end: 0 }, exportName: null },
13732
+ identity: { name: label, exportPath: null, boundaryBinding: null },
13733
+ inputs: [],
13734
+ transitions: [],
13735
+ gaps: [],
13736
+ confidence: { source: "declared", level: "high" },
13737
+ metadata: (0, import_behavioral_ir6.withGraphqlMetadata)(
13738
+ (0, import_behavioral_ir6.withSourceDocumentMetadata)(void 0, { label }),
13739
+ { schemaSdl }
13740
+ )
13741
+ };
13742
+ }
12877
13743
  function libraryEnvReadMarker(pack, declared, importer) {
12878
13744
  return {
12879
13745
  kind: "library",
@@ -12991,8 +13857,8 @@ function synthesizeCallerSummaries(wrapper, _project, options) {
12991
13857
  if (pathArg === void 0) {
12992
13858
  continue;
12993
13859
  }
12994
- const path11 = literalOrTemplate(pathArg);
12995
- if (path11 === void 0) {
13860
+ const path12 = literalOrTemplate(pathArg);
13861
+ if (path12 === void 0) {
12996
13862
  continue;
12997
13863
  }
12998
13864
  const callerFunc = enclosingFunction(callExpr);
@@ -13004,7 +13870,7 @@ function synthesizeCallerSummaries(wrapper, _project, options) {
13004
13870
  continue;
13005
13871
  }
13006
13872
  seen.add(dedupKey);
13007
- out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path11, options));
13873
+ out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path12, options));
13008
13874
  }
13009
13875
  return out;
13010
13876
  }
@@ -13056,12 +13922,12 @@ function literalOrTemplate(arg) {
13056
13922
  return arg.getLiteralValue();
13057
13923
  }
13058
13924
  if (import_ts_morph56.Node.isTemplateExpression(arg)) {
13059
- let path11 = arg.getHead().getLiteralText();
13925
+ let path12 = arg.getHead().getLiteralText();
13060
13926
  for (const span of arg.getTemplateSpans()) {
13061
- path11 += `{${placeholderName(span.getExpression())}}`;
13062
- path11 += span.getLiteral().getLiteralText();
13927
+ path12 += `{${placeholderName(span.getExpression())}}`;
13928
+ path12 += span.getLiteral().getLiteralText();
13063
13929
  }
13064
- return path11;
13930
+ return path12;
13065
13931
  }
13066
13932
  return void 0;
13067
13933
  }
@@ -13078,7 +13944,7 @@ function callerName(func) {
13078
13944
  }
13079
13945
  return "anonymous";
13080
13946
  }
13081
- function buildCallerSummary(wrapper, callerFunc, callExpr, path11, options) {
13947
+ function buildCallerSummary(wrapper, callerFunc, callExpr, path12, options) {
13082
13948
  const wrapperBinding = wrapper.summary.identity.boundaryBinding;
13083
13949
  const wrapperRest = wrapperBinding?.semantics.name === "rest" ? wrapperBinding.semantics : null;
13084
13950
  const syntheticPack = {
@@ -13110,7 +13976,7 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path11, options) {
13110
13976
  raw.boundaryBinding = (0, import_behavioral_ir6.restBinding)({
13111
13977
  transport: wrapperBinding?.transport ?? "http",
13112
13978
  method: wrapperRest?.method ?? null,
13113
- path: path11,
13979
+ path: path12,
13114
13980
  recognition: wrapperBinding?.recognition ?? "unknown"
13115
13981
  });
13116
13982
  const summary = (0, import_extractor5.assembleSummary)(raw, options);
@@ -13124,8 +13990,8 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path11, options) {
13124
13990
  summary.confidence = { source: "inferred_static", level: "low" };
13125
13991
  return summary;
13126
13992
  }
13127
- function named(summaries, workspace) {
13128
- const projectRoot = commonDirectoryOf(summaries.map((s) => s.location.file));
13993
+ function named(summaries, workspace, runRoot) {
13994
+ const projectRoot = runRoot ?? commonDirectoryOf(summaries.map((s) => s.location.file));
13129
13995
  nameSummaries(summaries, {
13130
13996
  workspace: workspace ?? workspaceNameFor(projectRoot),
13131
13997
  projectRoot
@@ -13183,6 +14049,7 @@ function createTypeScriptAdapter(config) {
13183
14049
  skipAddingFilesFromTsConfig: true
13184
14050
  } : { skipAddingFilesFromTsConfig: true }
13185
14051
  );
14052
+ const runRoot = config.projectRoot ?? (config.tsConfigFilePath !== void 0 ? workspaceRootFor(import_node_path9.default.dirname(config.tsConfigFilePath)) : void 0);
13186
14053
  let lazyBootstrapped = false;
13187
14054
  let projectFileSet;
13188
14055
  const cacheDir = declineWhenRunFromSource(
@@ -13223,7 +14090,7 @@ function createTypeScriptAdapter(config) {
13223
14090
  )
13224
14091
  );
13225
14092
  }
13226
- return named(summaries, config.workspace);
14093
+ return named(summaries, config.workspace, runRoot);
13227
14094
  },
13228
14095
  async extractAll() {
13229
14096
  const timer = createTimer();
@@ -13256,7 +14123,25 @@ function createTypeScriptAdapter(config) {
13256
14123
  if (config.onTiming !== void 0) {
13257
14124
  config.onTiming(timer.report());
13258
14125
  }
13259
- return named(lookup.summaries, config.workspace);
14126
+ return named(lookup.summaries, config.workspace, runRoot);
14127
+ }
14128
+ const plan = lookup.diagnostic.missReason === "files-changed" ? await timer.timeAsync("cache.plan", () => cache.plan(cacheInput)) : null;
14129
+ if (plan !== null && plan.changed.size === 0 && plan.removed.size === 0) {
14130
+ try {
14131
+ await cache.write(
14132
+ cacheInput,
14133
+ plan.allSummaries(),
14134
+ plan.attribution()
14135
+ );
14136
+ } catch {
14137
+ }
14138
+ if (config.onCacheDiagnostic !== void 0) {
14139
+ config.onCacheDiagnostic({ kind: "hit" });
14140
+ }
14141
+ if (config.onTiming !== void 0) {
14142
+ config.onTiming(timer.report());
14143
+ }
14144
+ return named(plan.allSummaries(), config.workspace, runRoot);
13260
14145
  }
13261
14146
  let candidatePaths = null;
13262
14147
  if (lazyEligible) {
@@ -13287,16 +14172,58 @@ function createTypeScriptAdapter(config) {
13287
14172
  "mountPrefix",
13288
14173
  () => buildMountPrefixIndex(packsByFile, resolution)
13289
14174
  );
14175
+ const caching = cacheDir !== null;
14176
+ const validRoots = /* @__PURE__ */ new Set();
14177
+ if (plan !== null) {
14178
+ const packNamesByPath = /* @__PURE__ */ new Map();
14179
+ for (const [sf, packs] of packsByFile) {
14180
+ packNamesByPath.set(
14181
+ sf.getFilePath(),
14182
+ packs.map((p) => p.name).sort().join(",")
14183
+ );
14184
+ }
14185
+ for (const rootPath of plan.validRoots) {
14186
+ const record = plan.roots.get(rootPath);
14187
+ if (record === void 0) {
14188
+ continue;
14189
+ }
14190
+ if (!mountAssumptionsAgree(record, mountPrefixes)) {
14191
+ continue;
14192
+ }
14193
+ const stored = [...record.packs].sort().join(",");
14194
+ if ((packNamesByPath.get(rootPath) ?? "") !== stored) {
14195
+ continue;
14196
+ }
14197
+ validRoots.add(rootPath);
14198
+ }
14199
+ }
14200
+ const reused = plan !== null && validRoots.size > 0 ? plan.reuse(validRoots) : null;
13290
14201
  const claimedUnits = /* @__PURE__ */ new Map();
14202
+ if (reused !== null && plan !== null) {
14203
+ for (const rootPath of validRoots) {
14204
+ for (const claim of plan.roots.get(rootPath)?.claims ?? []) {
14205
+ claimedUnits.set(claim.key, { pack: claim.pack, file: rootPath });
14206
+ }
14207
+ }
14208
+ }
14209
+ const walkList = reused === null ? sourceFiles : sourceFiles.filter((sf) => !validRoots.has(sf.getFilePath()));
14210
+ const sinkByRoot = /* @__PURE__ */ new Map();
14211
+ const ownersBySummary = /* @__PURE__ */ new Map();
13291
14212
  timer.time("extract per-file", () => {
13292
- for (const sourceFile of sourceFiles) {
14213
+ for (const sourceFile of walkList) {
14214
+ const rootPath = sourceFile.getFilePath();
14215
+ const sink = createDependencySink();
14216
+ if (caching) {
14217
+ sinkByRoot.set(rootPath, sink);
14218
+ }
13293
14219
  const applicablePacks = packsByFile.get(sourceFile);
13294
14220
  if (applicablePacks === void 0) {
13295
14221
  continue;
13296
14222
  }
13297
14223
  try {
13298
- summaries.push(
13299
- ...extractFromSourceFile(
14224
+ const walked = withDependencySink(
14225
+ sink,
14226
+ () => extractFromSourceFile(
13300
14227
  sourceFile,
13301
14228
  applicablePacks,
13302
14229
  claimedUnits,
@@ -13306,6 +14233,10 @@ function createTypeScriptAdapter(config) {
13306
14233
  mountPrefixes
13307
14234
  )
13308
14235
  );
14236
+ for (const summary of walked) {
14237
+ ownersBySummary.set(summary, /* @__PURE__ */ new Set([rootPath]));
14238
+ }
14239
+ summaries.push(...walked);
13309
14240
  } catch (error) {
13310
14241
  if (!(error instanceof RangeError)) {
13311
14242
  throw error;
@@ -13314,23 +14245,32 @@ function createTypeScriptAdapter(config) {
13314
14245
  }
13315
14246
  }
13316
14247
  });
14248
+ const wrapperInput = reused === null ? summaries : [...summaries, ...reused.summaries];
13317
14249
  const withWrappers = timer.time(
13318
14250
  "expandWrapperCallers",
13319
- () => expandWrapperCallers(summaries, project, config.extractorOptions)
14251
+ () => expandWrapperCallers(wrapperInput, project, config.extractorOptions)
13320
14252
  );
14253
+ const pipeline = reused === null ? withWrappers : [...summaries, ...withWrappers.slice(wrapperInput.length)];
13321
14254
  const withSubUnits = timer.time(
13322
14255
  "synthesizeSubUnits",
13323
14256
  () => synthesizeSubUnits(
13324
- withWrappers,
14257
+ pipeline,
13325
14258
  project,
13326
14259
  config.frameworks,
13327
14260
  config.extractorOptions,
13328
- tallies
14261
+ tallies,
14262
+ (created, parent) => {
14263
+ const parentOwners = ownersBySummary.get(parent);
14264
+ if (parentOwners !== void 0) {
14265
+ ownersBySummary.set(created, new Set(parentOwners));
14266
+ }
14267
+ }
13329
14268
  )
13330
14269
  );
13331
14270
  const closureFacts = {
13332
14271
  db: new import_datalog6.Database(),
13333
- unitKeyBySummary: /* @__PURE__ */ new Map()
14272
+ unitKeyBySummary: /* @__PURE__ */ new Map(),
14273
+ ...caching ? { filesByKey: /* @__PURE__ */ new Map() } : {}
13334
14274
  };
13335
14275
  const withClosure = config.includeReachable !== false ? timer.time(
13336
14276
  "expandReachableClosure",
@@ -13347,13 +14287,29 @@ function createTypeScriptAdapter(config) {
13347
14287
  ),
13348
14288
  access: collectAccessRecognizers(config.frameworks, tallies),
13349
14289
  resolveWrittenValue: (value) => resolution.resolveWrittenValue(value),
13350
- resolveCallable: (value, alsoFrom) => resolution.resolveCallable(value, alsoFrom)
13351
- }
14290
+ resolveCallableSources: (value, alsoFrom) => resolution.resolveCallableSources(value, alsoFrom)
14291
+ },
14292
+ // Reached units the cache already serves emit nothing,
14293
+ // the way a cold run's seeds do not.
14294
+ reused?.summaries ?? []
13352
14295
  )
13353
14296
  ) : withSubUnits;
14297
+ const closureOwnership = caching && config.includeReachable !== false ? timer.time(
14298
+ "cache.attributeClosure",
14299
+ () => attributeReachedSummaries(
14300
+ withSubUnits,
14301
+ withClosure.slice(withSubUnits.length),
14302
+ closureFacts,
14303
+ ownersBySummary
14304
+ )
14305
+ ) : null;
14306
+ const merged = timer.time(
14307
+ "cache.merge",
14308
+ () => mergeWithReused(reused, withClosure, ownersBySummary)
14309
+ );
13354
14310
  const enriched = timer.time(
13355
14311
  "enrichRethrows",
13356
- () => enrichRethrows(withClosure, project, closureFacts)
14312
+ () => enrichRethrows(merged.summaries, project, closureFacts)
13357
14313
  );
13358
14314
  if (config.includeReachable !== false) {
13359
14315
  timer.time(
@@ -13369,6 +14325,10 @@ function createTypeScriptAdapter(config) {
13369
14325
  "emitLibraryEnvReadMarkers",
13370
14326
  () => emitLibraryEnvReadMarkers(enriched, project, config.frameworks)
13371
14327
  );
14328
+ timer.time(
14329
+ "liftSchemasOntoDocuments",
14330
+ () => liftSchemasOntoDocuments(enriched)
14331
+ );
13372
14332
  timer.time(
13373
14333
  "stampGraphqlClientRefs",
13374
14334
  () => stampGraphqlClientRefs(
@@ -13378,12 +14338,41 @@ function createTypeScriptAdapter(config) {
13378
14338
  resolution
13379
14339
  )
13380
14340
  );
14341
+ if (config.onCacheDiagnostic !== void 0 && plan !== null) {
14342
+ config.onCacheDiagnostic({
14343
+ kind: "partial",
14344
+ partial: {
14345
+ filesChanged: plan.changed.size,
14346
+ filesRemoved: plan.removed.size,
14347
+ rootsReused: validRoots.size,
14348
+ rootsReextracted: walkList.length,
14349
+ rootsDeclined: plan.rootsDeclined,
14350
+ summariesReused: merged.reusedKept.length
14351
+ }
14352
+ });
14353
+ }
13381
14354
  await timer.timeAsync("cache.write", async () => {
13382
- if (enriched.length === 0) {
14355
+ if (!caching || enriched.length === 0) {
13383
14356
  return;
13384
14357
  }
13385
14358
  try {
13386
- await cache.write(cacheInput, enriched);
14359
+ const attribution = buildCacheAttribution({
14360
+ project,
14361
+ plan,
14362
+ validRoots,
14363
+ sinkByRoot,
14364
+ ownersBySummary,
14365
+ closureOwnership,
14366
+ unitKeyBySummary: closureFacts.unitKeyBySummary,
14367
+ filesByKey: closureFacts.filesByKey,
14368
+ packsByFile,
14369
+ reusedKept: merged.reusedKept,
14370
+ reusedOwners: merged.reusedOwners,
14371
+ // Everything after the reused prefix, the passes' own
14372
+ // additions (markers, schema documents) included.
14373
+ fresh: enriched.slice(merged.reusedKept.length)
14374
+ });
14375
+ await cache.write(cacheInput, enriched, attribution);
13387
14376
  } catch {
13388
14377
  }
13389
14378
  });
@@ -13396,7 +14385,7 @@ function createTypeScriptAdapter(config) {
13396
14385
  packs: config.frameworks,
13397
14386
  tallies,
13398
14387
  filesInProject: tsconfigFileList?.length ?? null,
13399
- filesWalked: sourceFiles.length,
14388
+ filesWalked: walkList.length,
13400
14389
  summaries: enriched,
13401
14390
  tsConfigFilePath: config.tsConfigFilePath,
13402
14391
  projectRoot: commonDirectoryOf(
@@ -13406,17 +14395,204 @@ function createTypeScriptAdapter(config) {
13406
14395
  })
13407
14396
  );
13408
14397
  }
13409
- nameSummaries(enriched, {
13410
- workspace: config.workspace ?? workspaceNameFor(
13411
- commonDirectoryOf(sourceFiles.map((f) => f.getFilePath()))
13412
- ),
13413
- projectRoot: commonDirectoryOf(sourceFiles.map((f) => f.getFilePath()))
13414
- });
13415
- return enriched;
14398
+ return named(
14399
+ enriched,
14400
+ config.workspace,
14401
+ runRoot ?? commonDirectoryOf(sourceFiles.map((f) => f.getFilePath()))
14402
+ );
13416
14403
  }
13417
14404
  };
13418
14405
  }
13419
- function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
14406
+ function mountAssumptionsAgree(record, index) {
14407
+ return Object.entries(record.mountPrefixes).every(
14408
+ ([childId, prefix]) => (index.prefixForId?.(childId) ?? "") === prefix
14409
+ );
14410
+ }
14411
+ function attributeReachedSummaries(seeds, reached, facts, owners) {
14412
+ const adjacency = /* @__PURE__ */ new Map();
14413
+ for (const [from, to] of facts.db.facts("calls")) {
14414
+ const key = String(from);
14415
+ const bucket = adjacency.get(key) ?? [];
14416
+ bucket.push(String(to));
14417
+ adjacency.set(key, bucket);
14418
+ }
14419
+ const seedKeysByRoot = /* @__PURE__ */ new Map();
14420
+ for (const seed of seeds) {
14421
+ const key = facts.unitKeyBySummary.get(seed);
14422
+ if (key === void 0) {
14423
+ continue;
14424
+ }
14425
+ for (const root of owners.get(seed) ?? []) {
14426
+ const bucket = seedKeysByRoot.get(root) ?? [];
14427
+ bucket.push(key);
14428
+ seedKeysByRoot.set(root, bucket);
14429
+ }
14430
+ }
14431
+ const reachableByRoot = /* @__PURE__ */ new Map();
14432
+ const rootsByKey = /* @__PURE__ */ new Map();
14433
+ for (const [root, seedKeys] of seedKeysByRoot) {
14434
+ const seen = new Set(seedKeys);
14435
+ const queue = [...seedKeys];
14436
+ while (queue.length > 0) {
14437
+ const key = queue.pop();
14438
+ if (key === void 0) {
14439
+ continue;
14440
+ }
14441
+ for (const next of adjacency.get(key) ?? []) {
14442
+ if (!seen.has(next)) {
14443
+ seen.add(next);
14444
+ queue.push(next);
14445
+ }
14446
+ }
14447
+ const keyRoots = rootsByKey.get(key) ?? /* @__PURE__ */ new Set();
14448
+ keyRoots.add(root);
14449
+ rootsByKey.set(key, keyRoots);
14450
+ }
14451
+ reachableByRoot.set(root, seen);
14452
+ }
14453
+ for (const summary of reached) {
14454
+ const key = facts.unitKeyBySummary.get(summary);
14455
+ if (key === void 0) {
14456
+ continue;
14457
+ }
14458
+ const keyRoots = rootsByKey.get(key);
14459
+ if (keyRoots !== void 0 && keyRoots.size > 0) {
14460
+ owners.set(summary, new Set(keyRoots));
14461
+ }
14462
+ }
14463
+ return { reachableByRoot, rootsByKey };
14464
+ }
14465
+ function fileOfNodeKey(key) {
14466
+ const cut = key.lastIndexOf(":");
14467
+ return cut <= 0 ? null : key.slice(0, cut);
14468
+ }
14469
+ function summaryMergeKey(summary) {
14470
+ return JSON.stringify([
14471
+ summary.location.file,
14472
+ summary.location.range.start,
14473
+ summary.location.range.end,
14474
+ summary.kind,
14475
+ summary.identity.name,
14476
+ summary.identity.exportPath,
14477
+ summary.identity.boundaryBinding
14478
+ ]);
14479
+ }
14480
+ function mergeWithReused(reused, fresh, ownersBySummary) {
14481
+ if (reused === null) {
14482
+ return { summaries: fresh, reusedKept: [], reusedOwners: [] };
14483
+ }
14484
+ const freshByKey = /* @__PURE__ */ new Map();
14485
+ for (const summary of fresh) {
14486
+ freshByKey.set(summaryMergeKey(summary), summary);
14487
+ }
14488
+ const reusedKept = [];
14489
+ const reusedOwners = [];
14490
+ reused.summaries.forEach((summary, i) => {
14491
+ const owners = reused.owners[i] ?? [];
14492
+ const supersededBy = freshByKey.get(summaryMergeKey(summary));
14493
+ if (supersededBy !== void 0) {
14494
+ const freshOwners = ownersBySummary.get(supersededBy) ?? /* @__PURE__ */ new Set();
14495
+ for (const owner of owners) {
14496
+ freshOwners.add(owner);
14497
+ }
14498
+ ownersBySummary.set(supersededBy, freshOwners);
14499
+ return;
14500
+ }
14501
+ reusedKept.push(summary);
14502
+ reusedOwners.push(owners);
14503
+ });
14504
+ return { summaries: [...reusedKept, ...fresh], reusedKept, reusedOwners };
14505
+ }
14506
+ function readsRunLevelJoins(summary) {
14507
+ if ((0, import_behavioral_ir6.readSourceDocumentMetadata)(summary) !== void 0) {
14508
+ return true;
14509
+ }
14510
+ if ((0, import_behavioral_ir6.readGraphqlMetadata)(summary)?.client !== void 0) {
14511
+ return true;
14512
+ }
14513
+ const semantics = summary.identity.boundaryBinding?.semantics;
14514
+ return semantics !== void 0 && "operationType" in semantics;
14515
+ }
14516
+ function buildCacheAttribution(args) {
14517
+ const references = createReferenceIndex(
14518
+ args.project.getSourceFiles().filter((sf) => !sf.isDeclarationFile())
14519
+ );
14520
+ const packNamesByPath = /* @__PURE__ */ new Map();
14521
+ for (const [sf, packs] of args.packsByFile) {
14522
+ packNamesByPath.set(
14523
+ sf.getFilePath(),
14524
+ packs.map((p) => p.name)
14525
+ );
14526
+ }
14527
+ const summariesByRoot = /* @__PURE__ */ new Map();
14528
+ for (const summary of args.fresh) {
14529
+ for (const root of args.ownersBySummary.get(summary) ?? []) {
14530
+ const bucket = summariesByRoot.get(root) ?? [];
14531
+ bucket.push(summary);
14532
+ summariesByRoot.set(root, bucket);
14533
+ }
14534
+ }
14535
+ const roots = [];
14536
+ for (const [rootPath, sink] of args.sinkByRoot) {
14537
+ const deps = new Set(sink.files);
14538
+ for (const dep of references.directOf(rootPath)) {
14539
+ deps.add(dep);
14540
+ }
14541
+ let cacheable = true;
14542
+ for (const summary of summariesByRoot.get(rootPath) ?? []) {
14543
+ deps.add(summary.location.file);
14544
+ if (readsRunLevelJoins(summary)) {
14545
+ cacheable = false;
14546
+ }
14547
+ }
14548
+ const reachable = args.closureOwnership?.reachableByRoot.get(rootPath) ?? [];
14549
+ for (const key of reachable) {
14550
+ const keyFile = fileOfNodeKey(key);
14551
+ if (keyFile !== null) {
14552
+ deps.add(keyFile);
14553
+ for (const hop of references.directOf(keyFile)) {
14554
+ deps.add(hop);
14555
+ }
14556
+ }
14557
+ for (const filePath of args.filesByKey?.get(key) ?? []) {
14558
+ deps.add(filePath);
14559
+ }
14560
+ }
14561
+ deps.delete(rootPath);
14562
+ roots.push({
14563
+ path: rootPath,
14564
+ cacheable,
14565
+ deps: [...deps].sort(),
14566
+ claims: sink.claims,
14567
+ mountPrefixes: Object.fromEntries(sink.mountPrefixes),
14568
+ packs: packNamesByPath.get(rootPath) ?? []
14569
+ });
14570
+ }
14571
+ if (args.plan !== null) {
14572
+ for (const rootPath of args.validRoots) {
14573
+ const record = args.plan.roots.get(rootPath);
14574
+ if (record !== void 0) {
14575
+ roots.push(record);
14576
+ }
14577
+ }
14578
+ }
14579
+ const reusedOwners = args.reusedKept.map((summary, i) => {
14580
+ const combined = new Set(args.reusedOwners[i] ?? []);
14581
+ const key = args.unitKeyBySummary.get(summary);
14582
+ if (key !== void 0) {
14583
+ for (const root of args.closureOwnership?.rootsByKey.get(key) ?? []) {
14584
+ combined.add(root);
14585
+ }
14586
+ }
14587
+ return [...combined];
14588
+ });
14589
+ const owners = [
14590
+ ...reusedOwners,
14591
+ ...args.fresh.map((s) => [...args.ownersBySummary.get(s) ?? []])
14592
+ ];
14593
+ return { roots, owners };
14594
+ }
14595
+ function synthesizeSubUnits(summaries, project, frameworks, options, tallies, onCreated) {
13420
14596
  const packByRecognition = /* @__PURE__ */ new Map();
13421
14597
  for (const pack of frameworks) {
13422
14598
  packByRecognition.set(pack.name, pack);
@@ -13459,6 +14635,7 @@ function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13459
14635
  );
13460
14636
  if (summary !== null) {
13461
14637
  synthesized.push(summary);
14638
+ onCreated?.(summary, parent);
13462
14639
  }
13463
14640
  }
13464
14641
  }
@@ -13467,14 +14644,7 @@ function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13467
14644
  var DEFAULT_SUB_UNIT_TERMINALS = [
13468
14645
  { kind: "return", match: { type: "returnStatement" }, extraction: {} },
13469
14646
  { kind: "throw", match: { type: "throwExpression" }, extraction: {} },
13470
- // A callback routinely falls off the end returning `undefined`, so
13471
- // fall-through gives it a default transition. HTTP packs do not opt in:
13472
- // a handler that sends no response should come out with none.
13473
- {
13474
- kind: "return",
13475
- match: { type: "functionFallthrough" },
13476
- extraction: {}
13477
- }
14647
+ FUNCTION_FALLTHROUGH_TERMINAL
13478
14648
  ];
13479
14649
  var DEFAULT_SUB_UNIT_INPUT_MAPPING = {
13480
14650
  type: "positionalParams",
@@ -13659,7 +14829,7 @@ function readStringArg(arg) {
13659
14829
  }
13660
14830
  return candidate.value;
13661
14831
  }
13662
- function rootIdentifier(node) {
14832
+ function rootIdentifier2(node) {
13663
14833
  let current2 = node;
13664
14834
  while (import_ts_morph58.Node.isPropertyAccessExpression(current2)) {
13665
14835
  current2 = current2.getExpression();
@@ -13667,10 +14837,165 @@ function rootIdentifier(node) {
13667
14837
  return import_ts_morph58.Node.isIdentifier(current2) ? current2 : null;
13668
14838
  }
13669
14839
 
14840
+ // src/facts/why.ts
14841
+ var import_node_path11 = __toESM(require("path"), 1);
14842
+ var import_ts_morph59 = require("ts-morph");
14843
+ var import_resolution2 = require("@suss/resolution");
14844
+ var JS_PHRASES = {
14845
+ bind: ({ tuple, describe }) => ({
14846
+ reason: `${describe(tuple[0])} is a .bind of ${describe(tuple[1])}, which runs the same function`
14847
+ })
14848
+ };
14849
+ var WhySession = class {
14850
+ project;
14851
+ store;
14852
+ root;
14853
+ constructor(options) {
14854
+ this.root = import_node_path11.default.resolve(options.dir);
14855
+ const tsconfig = findNearestTsconfig(this.root);
14856
+ this.project = tsconfig === null ? createProjectWithoutTsconfig(this.root).project : new import_ts_morph59.Project({ tsConfigFilePath: tsconfig });
14857
+ this.store = new ResolutionStore();
14858
+ }
14859
+ /**
14860
+ * The smallest expression on `line` of `file` whose text is exactly
14861
+ * `text`, or null. Spelled from the person's side: the name as
14862
+ * written, not a node id.
14863
+ */
14864
+ findExpression(file, line, text) {
14865
+ const sourceFile = this.sourceFileAt(file);
14866
+ if (sourceFile === null) {
14867
+ return null;
14868
+ }
14869
+ let found = null;
14870
+ sourceFile.forEachDescendant((node) => {
14871
+ if (import_ts_morph59.Node.isExpression(node) && node.getStartLineNumber() === line && node.getText() === text && (found === null || node.getWidth() <= found.getWidth())) {
14872
+ found = node;
14873
+ }
14874
+ });
14875
+ return found;
14876
+ }
14877
+ /**
14878
+ * The callee of the call written as `calleeText` between `startLine`
14879
+ * and `endLine` of `file`, or null. This is how a summary's recorded
14880
+ * callee gets back to its node: the summary keeps source text and a
14881
+ * range, not ids.
14882
+ */
14883
+ findCallee(file, startLine, endLine, calleeText) {
14884
+ const sourceFile = this.sourceFileAt(file);
14885
+ if (sourceFile === null) {
14886
+ return null;
14887
+ }
14888
+ for (const call of sourceFile.getDescendantsOfKind(
14889
+ import_ts_morph59.SyntaxKind.CallExpression
14890
+ )) {
14891
+ const line = call.getStartLineNumber();
14892
+ if (line < startLine || line > endLine) {
14893
+ continue;
14894
+ }
14895
+ if (call.getExpression().getText() === calleeText) {
14896
+ return call.getExpression();
14897
+ }
14898
+ }
14899
+ return null;
14900
+ }
14901
+ /**
14902
+ * Why `value` resolves to what it does: the witness proof, flattened
14903
+ * to the chain and rendered. Null when the value does not resolve,
14904
+ * which the caller says in its own words.
14905
+ */
14906
+ explain(value, options = {}) {
14907
+ const explained = this.store.explainCallable(value, options);
14908
+ if (explained === null) {
14909
+ return null;
14910
+ }
14911
+ const describe = (atom) => {
14912
+ const node = explained.nodeFor(atom);
14913
+ if (node === void 0) {
14914
+ return this.displayPath(String(atom));
14915
+ }
14916
+ const at = this.locate(node);
14917
+ return `${at.name} (${at.file}:${at.line})`;
14918
+ };
14919
+ const explanation = (0, import_resolution2.explainResolutionProof)(explained.proof, {
14920
+ describe,
14921
+ phrases: JS_PHRASES
14922
+ });
14923
+ if (explanation === null) {
14924
+ return null;
14925
+ }
14926
+ return {
14927
+ explanation,
14928
+ chain: explanation.atoms.map((atom) => describe(atom)),
14929
+ lines: (0, import_resolution2.renderExplanation)(explanation, describe),
14930
+ target: this.locate(explained.target),
14931
+ stats: explained.stats
14932
+ };
14933
+ }
14934
+ /** Where a found node is, in the terms an answer prints. */
14935
+ locate(node) {
14936
+ return {
14937
+ name: displayNameOf(node),
14938
+ file: this.displayPath(node.getSourceFile().getFilePath()),
14939
+ line: node.getStartLineNumber()
14940
+ };
14941
+ }
14942
+ sourceFileAt(file) {
14943
+ const absolute = import_node_path11.default.resolve(this.root, file);
14944
+ const loaded = this.project.getSourceFile(absolute);
14945
+ if (loaded !== void 0) {
14946
+ return loaded;
14947
+ }
14948
+ try {
14949
+ return this.project.addSourceFileAtPath(absolute);
14950
+ } catch {
14951
+ return null;
14952
+ }
14953
+ }
14954
+ /** A module key: a file path said relative to the root, or a package name. */
14955
+ displayPath(key) {
14956
+ if (key.startsWith(this.root)) {
14957
+ return import_node_path11.default.relative(this.root, key);
14958
+ }
14959
+ return key;
14960
+ }
14961
+ };
14962
+ function displayNameOf(node) {
14963
+ if (import_ts_morph59.Node.isIdentifier(node) || import_ts_morph59.Node.isPropertyAccessExpression(node)) {
14964
+ return node.getText();
14965
+ }
14966
+ if (import_ts_morph59.Node.isCallExpression(node) || import_ts_morph59.Node.isNewExpression(node)) {
14967
+ return `${node.getExpression()?.getText() ?? "call"}(...)`;
14968
+ }
14969
+ if (import_ts_morph59.Node.isFunctionDeclaration(node) || import_ts_morph59.Node.isClassDeclaration(node) || import_ts_morph59.Node.isMethodDeclaration(node) || import_ts_morph59.Node.isVariableDeclaration(node)) {
14970
+ const name = node.getName();
14971
+ if (name !== void 0) {
14972
+ return name;
14973
+ }
14974
+ }
14975
+ if (import_ts_morph59.Node.isArrowFunction(node) || import_ts_morph59.Node.isFunctionExpression(node)) {
14976
+ const parent = node.getParent();
14977
+ if (parent !== void 0 && import_ts_morph59.Node.isVariableDeclaration(parent)) {
14978
+ return parent.getName();
14979
+ }
14980
+ }
14981
+ const text = node.getText().split("\n")[0].trim();
14982
+ return text.length > 40 ? `${text.slice(0, 40)}...` : text;
14983
+ }
14984
+
13670
14985
  // src/packHealth.ts
13671
14986
  function stagesOf(funnel) {
13672
14987
  const stages = [];
13673
14988
  const gateSaysSomething = funnel.gates.length > 0 && funnel.unresolvedGates.length === 0;
14989
+ if (funnel.recognizes && !funnel.discovers && gateSaysSomething) {
14990
+ stages.push({
14991
+ from: {
14992
+ name: "unit bodies to look inside",
14993
+ count: funnel.unitsInGatedFiles
14994
+ },
14995
+ to: { name: "effects recognized", count: funnel.effectsRecognized },
14996
+ meaning: "its import gate found the library and it matched nothing in the bodies it saw"
14997
+ });
14998
+ }
13674
14999
  if (funnel.discovers && gateSaysSomething) {
13675
15000
  stages.push({
13676
15001
  from: { name: "candidate files", count: funnel.candidateFiles },
@@ -13780,7 +15105,8 @@ function formatPackHealth(checks, audiences) {
13780
15105
  }
13781
15106
 
13782
15107
  // src/resolve/readName.ts
13783
- var import_ts_morph59 = require("ts-morph");
15108
+ var import_ts_morph60 = require("ts-morph");
15109
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
13784
15110
  var MAX_HELPER_HOPS = 2;
13785
15111
  var UNNAMED_HOLE = "param";
13786
15112
  function readName(expr, options = {}) {
@@ -13794,6 +15120,14 @@ function readName(expr, options = {}) {
13794
15120
  if (name !== null || options.unsettled !== "reference") {
13795
15121
  return name;
13796
15122
  }
15123
+ const reference = referenceIn(expr);
15124
+ return reference === null ? null : (0, import_behavioral_ir7.referenceName)(reference);
15125
+ }
15126
+ function referenceIn(expr) {
15127
+ const fromParameter = parameterPath(expr);
15128
+ if (fromParameter !== null) {
15129
+ return fromParameter;
15130
+ }
13797
15131
  const asked = holeName(expr, {
13798
15132
  resolve: () => null,
13799
15133
  bindings: /* @__PURE__ */ new Map(),
@@ -13801,7 +15135,25 @@ function readName(expr, options = {}) {
13801
15135
  seen: /* @__PURE__ */ new Set(),
13802
15136
  insideHole: true
13803
15137
  });
13804
- return asked === UNNAMED_HOLE ? null : `{${asked}}`;
15138
+ return asked === UNNAMED_HOLE ? null : { root: asked, fields: [] };
15139
+ }
15140
+ function parameterPath(expr) {
15141
+ const fields = [];
15142
+ let step = expr;
15143
+ while (import_ts_morph60.Node.isPropertyAccessExpression(step)) {
15144
+ fields.unshift(step.getName());
15145
+ step = step.getExpression();
15146
+ }
15147
+ if (!import_ts_morph60.Node.isIdentifier(step) || !isParameter(step)) {
15148
+ return null;
15149
+ }
15150
+ return { root: step.getText(), fields };
15151
+ }
15152
+ function isParameter(identifier) {
15153
+ const declarations = identifier.getSymbol()?.getDeclarations() ?? [];
15154
+ return declarations.some(
15155
+ (declaration) => import_ts_morph60.Node.isParameterDeclaration(declaration)
15156
+ );
13805
15157
  }
13806
15158
  function read(expr, ctx) {
13807
15159
  if (ctx.seen.has(expr)) {
@@ -13809,35 +15161,52 @@ function read(expr, ctx) {
13809
15161
  }
13810
15162
  const seen = new Set(ctx.seen).add(expr);
13811
15163
  const inner = { ...ctx, seen };
13812
- if (import_ts_morph59.Node.isStringLiteral(expr) || import_ts_morph59.Node.isNoSubstitutionTemplateLiteral(expr)) {
15164
+ if (import_ts_morph60.Node.isStringLiteral(expr) || import_ts_morph60.Node.isNoSubstitutionTemplateLiteral(expr)) {
13813
15165
  return expr.getLiteralValue();
13814
15166
  }
13815
- if (import_ts_morph59.Node.isNumericLiteral(expr)) {
15167
+ if (import_ts_morph60.Node.isNumericLiteral(expr)) {
13816
15168
  return expr.getLiteralText();
13817
15169
  }
13818
- if (import_ts_morph59.Node.isTemplateExpression(expr)) {
15170
+ if (import_ts_morph60.Node.isTemplateExpression(expr)) {
13819
15171
  return fromTemplate(expr, inner);
13820
15172
  }
13821
- if (import_ts_morph59.Node.isParenthesizedExpression(expr)) {
15173
+ if (import_ts_morph60.Node.isParenthesizedExpression(expr)) {
13822
15174
  return read(expr.getExpression(), inner);
13823
15175
  }
13824
- if (import_ts_morph59.Node.isBinaryExpression(expr)) {
15176
+ if (import_ts_morph60.Node.isBinaryExpression(expr)) {
13825
15177
  return fromBinary(expr, inner);
13826
15178
  }
13827
- if (import_ts_morph59.Node.isCallExpression(expr)) {
15179
+ if (import_ts_morph60.Node.isCallExpression(expr)) {
13828
15180
  return fromCall(expr, inner);
13829
15181
  }
13830
- if (import_ts_morph59.Node.isIdentifier(expr)) {
15182
+ if (import_ts_morph60.Node.isIdentifier(expr)) {
13831
15183
  const only = boundOnce(expr, ctx);
13832
15184
  if (only !== null) {
13833
15185
  return read(only.node, { ...inner, bindings: only.scope });
13834
15186
  }
13835
15187
  }
13836
15188
  const written = ctx.resolve(expr);
13837
- return written === null || written === expr ? null : read(written, inner);
15189
+ if (written === null || written === expr) {
15190
+ return null;
15191
+ }
15192
+ if (ctx.insideHole && isFallbackBranch(written)) {
15193
+ return null;
15194
+ }
15195
+ return read(written, inner);
15196
+ }
15197
+ function isFallbackBranch(node) {
15198
+ let step = node.getParent();
15199
+ while (step !== void 0 && import_ts_morph60.Node.isParenthesizedExpression(step)) {
15200
+ step = step.getParent();
15201
+ }
15202
+ if (step === void 0 || !import_ts_morph60.Node.isBinaryExpression(step)) {
15203
+ return false;
15204
+ }
15205
+ const operator = step.getOperatorToken().getKind();
15206
+ return operator === import_ts_morph60.SyntaxKind.BarBarToken || operator === import_ts_morph60.SyntaxKind.QuestionQuestionToken;
13838
15207
  }
13839
15208
  function fromTemplate(expr, ctx) {
13840
- if (!import_ts_morph59.Node.isTemplateExpression(expr)) {
15209
+ if (!import_ts_morph60.Node.isTemplateExpression(expr)) {
13841
15210
  return null;
13842
15211
  }
13843
15212
  let name = expr.getHead().getLiteralText();
@@ -13848,28 +15217,28 @@ function fromTemplate(expr, ctx) {
13848
15217
  return name;
13849
15218
  }
13850
15219
  function fromBinary(expr, ctx) {
13851
- if (!import_ts_morph59.Node.isBinaryExpression(expr)) {
15220
+ if (!import_ts_morph60.Node.isBinaryExpression(expr)) {
13852
15221
  return null;
13853
15222
  }
13854
15223
  const operator = expr.getOperatorToken().getKind();
13855
- if (operator === import_ts_morph59.SyntaxKind.BarBarToken || operator === import_ts_morph59.SyntaxKind.QuestionQuestionToken) {
15224
+ if (operator === import_ts_morph60.SyntaxKind.BarBarToken || operator === import_ts_morph60.SyntaxKind.QuestionQuestionToken) {
13856
15225
  return ctx.insideHole ? null : read(expr.getRight(), ctx);
13857
15226
  }
13858
- if (operator !== import_ts_morph59.SyntaxKind.PlusToken) {
15227
+ if (operator !== import_ts_morph60.SyntaxKind.PlusToken) {
13859
15228
  return null;
13860
15229
  }
13861
15230
  const parts = { ...ctx, insideHole: true };
13862
15231
  return readOrHole(expr.getLeft(), parts) + readOrHole(expr.getRight(), parts);
13863
15232
  }
13864
15233
  function fromCall(expr, ctx) {
13865
- if (!import_ts_morph59.Node.isCallExpression(expr)) {
15234
+ if (!import_ts_morph60.Node.isCallExpression(expr)) {
13866
15235
  return null;
13867
15236
  }
13868
15237
  const callee = expr.getExpression();
13869
- if (import_ts_morph59.Node.isPropertyAccessExpression(callee) && callee.getName() === "join") {
15238
+ if (import_ts_morph60.Node.isPropertyAccessExpression(callee) && callee.getName() === "join") {
13870
15239
  return fromJoin(callee.getExpression(), expr.getArguments(), ctx);
13871
15240
  }
13872
- if (import_ts_morph59.Node.isIdentifier(callee) && callee.getText() === "String") {
15241
+ if (import_ts_morph60.Node.isIdentifier(callee) && callee.getText() === "String") {
13873
15242
  const only = expr.getArguments()[0];
13874
15243
  return only === void 0 ? null : readOrHole(only, ctx);
13875
15244
  }
@@ -13888,7 +15257,7 @@ function fromCall(expr, ctx) {
13888
15257
  }
13889
15258
  function fromJoin(subject, args, ctx) {
13890
15259
  const separator = args[0];
13891
- if (separator === void 0 || !import_ts_morph59.Node.isStringLiteral(separator)) {
15260
+ if (separator === void 0 || !import_ts_morph60.Node.isStringLiteral(separator)) {
13892
15261
  return null;
13893
15262
  }
13894
15263
  const parts = elementsOf(subject, ctx);
@@ -13904,17 +15273,17 @@ function fromJoin(subject, args, ctx) {
13904
15273
  ).join(separator.getLiteralValue());
13905
15274
  }
13906
15275
  function elementsOf(subject, ctx) {
13907
- if (import_ts_morph59.Node.isArrayLiteralExpression(subject)) {
15276
+ if (import_ts_morph60.Node.isArrayLiteralExpression(subject)) {
13908
15277
  return subject.getElements().map((element) => here(element, ctx));
13909
15278
  }
13910
- if (import_ts_morph59.Node.isCallExpression(subject)) {
15279
+ if (import_ts_morph60.Node.isCallExpression(subject)) {
13911
15280
  const callee = subject.getExpression();
13912
- if (import_ts_morph59.Node.isPropertyAccessExpression(callee) && callee.getName() === "map") {
15281
+ if (import_ts_morph60.Node.isPropertyAccessExpression(callee) && callee.getName() === "map") {
13913
15282
  return elementsOf(callee.getExpression(), ctx);
13914
15283
  }
13915
15284
  return null;
13916
15285
  }
13917
- if (!import_ts_morph59.Node.isIdentifier(subject)) {
15286
+ if (!import_ts_morph60.Node.isIdentifier(subject)) {
13918
15287
  return null;
13919
15288
  }
13920
15289
  const bound = ctx.bindings.get(subject.getText());
@@ -13922,7 +15291,7 @@ function elementsOf(subject, ctx) {
13922
15291
  return null;
13923
15292
  }
13924
15293
  const only = bound.length === 1 ? bound[0] : void 0;
13925
- return only !== void 0 && import_ts_morph59.Node.isArrayLiteralExpression(only.node) ? only.node.getElements().map((element) => here(element, ctx)) : bound;
15294
+ return only !== void 0 && import_ts_morph60.Node.isArrayLiteralExpression(only.node) ? only.node.getElements().map((element) => here(element, ctx)) : bound;
13926
15295
  }
13927
15296
  function singleReturnBody(callee, ctx) {
13928
15297
  const declaration = declarationOf2(callee, ctx);
@@ -13937,22 +15306,22 @@ function singleReturnBody(callee, ctx) {
13937
15306
  if (body === void 0) {
13938
15307
  return null;
13939
15308
  }
13940
- if (import_ts_morph59.Node.isExpression(body)) {
15309
+ if (import_ts_morph60.Node.isExpression(body)) {
13941
15310
  return { parameters, returned: body };
13942
15311
  }
13943
- if (!import_ts_morph59.Node.isBlock(body)) {
15312
+ if (!import_ts_morph60.Node.isBlock(body)) {
13944
15313
  return null;
13945
15314
  }
13946
15315
  const statements = body.getStatements();
13947
15316
  const only = statements[0];
13948
- if (statements.length !== 1 || only === void 0 || !import_ts_morph59.Node.isReturnStatement(only)) {
15317
+ if (statements.length !== 1 || only === void 0 || !import_ts_morph60.Node.isReturnStatement(only)) {
13949
15318
  return null;
13950
15319
  }
13951
15320
  const returned = only.getExpression();
13952
15321
  return returned === void 0 ? null : { parameters, returned };
13953
15322
  }
13954
15323
  function declarationOf2(callee, ctx) {
13955
- const symbol = import_ts_morph59.Node.isPropertyAccessExpression(callee) ? callee.getNameNode().getSymbol() : callee.getSymbol();
15324
+ const symbol = import_ts_morph60.Node.isPropertyAccessExpression(callee) ? callee.getNameNode().getSymbol() : callee.getSymbol();
13956
15325
  const declarations = symbol?.getAliasedSymbol()?.getDeclarations() ?? symbol?.getDeclarations() ?? [];
13957
15326
  for (const declaration of declarations) {
13958
15327
  const callable = asCallable(declaration);
@@ -13964,14 +15333,14 @@ function declarationOf2(callee, ctx) {
13964
15333
  return written === null || written === callee ? null : declarationOf2(written, { ...ctx, resolve: () => null });
13965
15334
  }
13966
15335
  function asCallable(declaration) {
13967
- if (import_ts_morph59.Node.isFunctionDeclaration(declaration) || import_ts_morph59.Node.isMethodDeclaration(declaration) || import_ts_morph59.Node.isArrowFunction(declaration) || import_ts_morph59.Node.isFunctionExpression(declaration)) {
15336
+ if (import_ts_morph60.Node.isFunctionDeclaration(declaration) || import_ts_morph60.Node.isMethodDeclaration(declaration) || import_ts_morph60.Node.isArrowFunction(declaration) || import_ts_morph60.Node.isFunctionExpression(declaration)) {
13968
15337
  return declaration;
13969
15338
  }
13970
- if (!import_ts_morph59.Node.isVariableDeclaration(declaration)) {
15339
+ if (!import_ts_morph60.Node.isVariableDeclaration(declaration)) {
13971
15340
  return null;
13972
15341
  }
13973
15342
  const initializer = declaration.getInitializer();
13974
- if (initializer !== void 0 && (import_ts_morph59.Node.isArrowFunction(initializer) || import_ts_morph59.Node.isFunctionExpression(initializer))) {
15343
+ if (initializer !== void 0 && (import_ts_morph60.Node.isArrowFunction(initializer) || import_ts_morph60.Node.isFunctionExpression(initializer))) {
13975
15344
  return initializer;
13976
15345
  }
13977
15346
  return null;
@@ -13993,22 +15362,22 @@ function here(node, ctx) {
13993
15362
  return { node, scope: ctx.bindings };
13994
15363
  }
13995
15364
  function boundOnce(expr, ctx) {
13996
- const bound = import_ts_morph59.Node.isIdentifier(expr) ? ctx.bindings.get(expr.getText()) : void 0;
15365
+ const bound = import_ts_morph60.Node.isIdentifier(expr) ? ctx.bindings.get(expr.getText()) : void 0;
13997
15366
  return bound !== void 0 && bound.length === 1 ? bound[0] : null;
13998
15367
  }
13999
15368
  function readOrHole(expr, ctx) {
14000
15369
  const name = read(expr, ctx);
14001
- return name ?? `{${holeName(expr, ctx)}}`;
15370
+ return name ?? (0, import_behavioral_ir7.patternHole)(holeName(expr, ctx));
14002
15371
  }
14003
15372
  function holeName(expr, ctx) {
14004
15373
  const bound = boundOnce(expr, ctx);
14005
15374
  if (bound !== null) {
14006
15375
  return holeName(bound.node, { ...ctx, bindings: bound.scope });
14007
15376
  }
14008
- if (import_ts_morph59.Node.isIdentifier(expr)) {
15377
+ if (import_ts_morph60.Node.isIdentifier(expr)) {
14009
15378
  return expr.getText();
14010
15379
  }
14011
- if (import_ts_morph59.Node.isPropertyAccessExpression(expr)) {
15380
+ if (import_ts_morph60.Node.isPropertyAccessExpression(expr)) {
14012
15381
  return expr.getName();
14013
15382
  }
14014
15383
  return UNNAMED_HOLE;
@@ -14017,6 +15386,7 @@ function holeName(expr, ctx) {
14017
15386
  0 && (module.exports = {
14018
15387
  ADAPTER_VERSION,
14019
15388
  ResolutionStore,
15389
+ WhySession,
14020
15390
  collectAncestorBranches,
14021
15391
  collectClientFieldAccesses,
14022
15392
  computeAdapterPacksDigest,
@@ -14042,5 +15412,6 @@ function holeName(expr, ctx) {
14042
15412
  readName,
14043
15413
  resolveAliasedSymbol,
14044
15414
  resolveSubject,
14045
- rootIdentifier
15415
+ rootIdentifier,
15416
+ workspaceRootFor
14046
15417
  });