@saptools/service-flow 0.1.70 → 0.1.73

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +23 -6
  3. package/TECHNICAL-NOTE.md +24 -2
  4. package/dist/{chunk-GSLFY6J2.js → chunk-32WOTGTS.js} +12061 -9288
  5. package/dist/chunk-32WOTGTS.js.map +1 -0
  6. package/dist/cli.js +854 -161
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +54 -14
  9. package/dist/index.js +2 -23
  10. package/dist/index.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/cli/doctor-event-quality.ts +446 -0
  13. package/src/cli/doctor.ts +4 -6
  14. package/src/cli.ts +35 -13
  15. package/src/config/workspace-config.ts +15 -0
  16. package/src/db/call-fact-repository.ts +6 -3
  17. package/src/db/current-fact-semantics.ts +5 -29
  18. package/src/db/event-fact-semantics.ts +546 -0
  19. package/src/db/event-site-semantics.ts +62 -0
  20. package/src/db/event-surface-invalidation.ts +38 -0
  21. package/src/db/fact-json-inventory.ts +16 -0
  22. package/src/db/fact-lifecycle.ts +70 -12
  23. package/src/db/migrations.ts +15 -1
  24. package/src/db/package-target-invalidation.ts +80 -0
  25. package/src/db/repositories.ts +28 -2
  26. package/src/db/schema-structure.ts +41 -1
  27. package/src/db/schema.ts +6 -2
  28. package/src/indexer/repository-indexer.ts +93 -10
  29. package/src/indexer/workspace-indexer.ts +13 -3
  30. package/src/linker/cross-repo-linker.ts +27 -3
  31. package/src/linker/event-environment-link.ts +211 -0
  32. package/src/linker/event-shape-candidate-linker.ts +204 -0
  33. package/src/linker/event-subscription-handler-linker.ts +220 -25
  34. package/src/linker/event-template-link.ts +40 -6
  35. package/src/linker/package-event-constant-resolver.ts +302 -0
  36. package/src/output/repository-inspection.ts +11 -0
  37. package/src/output/table-output.ts +13 -1
  38. package/src/parsers/decorator-parser.ts +9 -53
  39. package/src/parsers/environment-declarations.ts +404 -0
  40. package/src/parsers/event-call-analysis.ts +370 -0
  41. package/src/parsers/event-environment-reference.ts +242 -0
  42. package/src/parsers/event-loop-registration.ts +132 -0
  43. package/src/parsers/event-name-import-resolution.ts +243 -0
  44. package/src/parsers/event-receiver-analysis.ts +394 -0
  45. package/src/parsers/event-subscription-facts.ts +4 -0
  46. package/src/parsers/generated-constants-parser.ts +80 -14
  47. package/src/parsers/outbound-call-classifier.ts +27 -124
  48. package/src/parsers/outbound-call-parser.ts +13 -1
  49. package/src/parsers/outbound-expression-analysis.ts +2 -2
  50. package/src/parsers/stable-local-value.ts +42 -10
  51. package/src/parsers/string-constant-lookups.ts +408 -0
  52. package/src/trace/edge-target.ts +65 -0
  53. package/src/trace/event-runtime-resolution.ts +24 -3
  54. package/src/trace/event-shape-candidate-trace.ts +172 -0
  55. package/src/trace/event-subscriber-traversal.ts +90 -8
  56. package/src/trace/evidence.ts +7 -28
  57. package/src/trace/trace-scope-execution.ts +22 -30
  58. package/src/types.ts +19 -1
  59. package/src/utils/event-skeleton.ts +207 -0
  60. package/src/version.ts +1 -1
  61. package/dist/chunk-GSLFY6J2.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ANALYZER_VERSION,
4
+ DEFAULT_EVENT_ENVIRONMENT_KEYS,
5
+ EVENT_ENVIRONMENT_KEY_CAP,
4
6
  PreparedRepositorySnapshotError,
5
7
  VERSION,
6
8
  analyzePackagePublicSurface,
@@ -8,14 +10,20 @@ import {
8
10
  bindingSite,
9
11
  classifyODataPathIntent,
10
12
  classifyOutboundCallsInSource,
13
+ collectEnvironmentDeclarations,
14
+ collectStringConstantLookups,
11
15
  collectSymbolImportBindings,
12
16
  compactTrace,
13
17
  containsSupportedOutboundCall,
14
18
  createBindingLexicalIndex,
19
+ createEventEnvironmentReferenceResolver,
20
+ createImportedEventNameResolver,
15
21
  derivedMemberImportReference,
16
22
  discoverRepositories,
23
+ emptyEnvironmentDeclarations,
17
24
  executableSymbolCandidates,
18
25
  factLifecycleDiagnostic,
26
+ generatedConstantFacts,
19
27
  identifierMatchesDeclaration,
20
28
  implementationHintSuggestions,
21
29
  insertCalls,
@@ -28,6 +36,7 @@ import {
28
36
  loadPackageJsonSnapshot,
29
37
  loadRepositorySourceContext,
30
38
  migrate,
39
+ normalizeEventEnvironmentKeys,
31
40
  normalizeODataOperationInvocationPath,
32
41
  normalizePath,
33
42
  parseCdsFile,
@@ -42,13 +51,16 @@ import {
42
51
  projectBoundedInOrder,
43
52
  recordPreparedSnapshotFailure,
44
53
  redactValue,
54
+ resolveBinding,
45
55
  selectCallOwner,
46
56
  selectVisibleBinding,
47
57
  selectorRepoAmbiguousDiagnostic,
58
+ sha256Text,
48
59
  stableLocalValueReference,
49
60
  symbolImportReference,
50
- trace
51
- } from "./chunk-GSLFY6J2.js";
61
+ trace,
62
+ validEventEnvironmentKey
63
+ } from "./chunk-32WOTGTS.js";
52
64
 
53
65
  // src/cli.ts
54
66
  import { Command, Option } from "commander";
@@ -73,10 +85,18 @@ var DEFAULT_DB_FILE = "service-flow.db";
73
85
  import fs from "fs/promises";
74
86
  import path from "path";
75
87
  import { z } from "zod";
88
+ var environmentKeys = z.array(
89
+ z.string().refine(validEventEnvironmentKey)
90
+ ).min(1).max(EVENT_ENVIRONMENT_KEY_CAP).transform(
91
+ normalizeEventEnvironmentKeys
92
+ );
76
93
  var schema = z.object({
77
94
  rootPath: z.string(),
78
95
  dbPath: z.string(),
79
96
  ignore: z.array(z.string()),
97
+ eventEnvironmentKeys: environmentKeys.default(
98
+ [...DEFAULT_EVENT_ENVIRONMENT_KEYS]
99
+ ),
80
100
  createdAt: z.string(),
81
101
  updatedAt: z.string()
82
102
  });
@@ -113,6 +133,7 @@ function createWorkspaceConfig(rootPath, dbPath, ignore = [...DEFAULT_IGNORES])
113
133
  rootPath: root,
114
134
  dbPath: path.resolve(dbPath ?? defaultDbPath(root)),
115
135
  ignore,
136
+ eventEnvironmentKeys: [...DEFAULT_EVENT_ENVIRONMENT_KEYS],
116
137
  createdAt: now,
117
138
  updatedAt: now
118
139
  };
@@ -250,8 +271,8 @@ function upsertRepository(db, workspaceId, r) {
250
271
  db.prepare(
251
272
  `INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
252
273
  package_name,package_version,dependencies_json,
253
- package_public_surface_json,kind,is_git_repo)
254
- VALUES(?,?,?,?,?,?,?,?,?,?)
274
+ package_public_surface_json,environment_declarations_json,kind,is_git_repo)
275
+ VALUES(?,?,?,?,?,?,?,?,?,?,?)
255
276
  ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET
256
277
  name=excluded.name,relative_path=excluded.relative_path,
257
278
  package_name=excluded.package_name,
@@ -266,6 +287,7 @@ function upsertRepository(db, workspaceId, r) {
266
287
  r.packageVersion,
267
288
  JSON.stringify(r.dependencies ?? {}),
268
289
  initialPackagePublicSurface(r.packageName),
290
+ JSON.stringify(emptyEnvironmentDeclarations()),
269
291
  r.kind ?? "unknown",
270
292
  r.isGitRepo ? 1 : 0
271
293
  );
@@ -292,6 +314,7 @@ function clearRepoFacts(db, repoId) {
292
314
  "symbol_calls",
293
315
  "handler_registrations",
294
316
  "service_bindings",
317
+ "generated_constants",
295
318
  "symbols",
296
319
  "diagnostics",
297
320
  "files"
@@ -299,6 +322,32 @@ function clearRepoFacts(db, repoId) {
299
322
  db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
300
323
  db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
301
324
  }
325
+ function insertGeneratedConstants(db, repoId, rows) {
326
+ const stmt = db.prepare(`INSERT INTO generated_constants(
327
+ repo_id,source_file,source_line,name,container_name,member_name,value,
328
+ constant_kind,exported,stable,resolution_status,unresolved_reason,
329
+ declaration_start_offset,declaration_end_offset,
330
+ value_start_offset,value_end_offset
331
+ ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
332
+ for (const row of rows) stmt.run(
333
+ repoId,
334
+ row.sourceFile,
335
+ row.sourceLine,
336
+ row.name,
337
+ row.containerName ?? null,
338
+ row.memberName ?? null,
339
+ row.value ?? null,
340
+ row.constantKind,
341
+ row.exported ? 1 : 0,
342
+ row.stable ? 1 : 0,
343
+ row.resolutionStatus,
344
+ row.unresolvedReason ?? null,
345
+ row.declarationStartOffset,
346
+ row.declarationEndOffset,
347
+ row.valueStartOffset,
348
+ row.valueEndOffset
349
+ );
350
+ }
302
351
  function insertRequires(db, repoId, rows) {
303
352
  const stmt = db.prepare(
304
353
  "INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
@@ -612,10 +661,10 @@ import path5 from "path";
612
661
  // src/parsers/symbol-parser.ts
613
662
  import fs4 from "fs/promises";
614
663
  import path4 from "path";
615
- import ts8 from "typescript";
664
+ import ts9 from "typescript";
616
665
 
617
666
  // src/parsers/event-subscription-facts.ts
618
- import ts2 from "typescript";
667
+ import ts3 from "typescript";
619
668
 
620
669
  // src/parsers/local-symbol-reference.ts
621
670
  import ts from "typescript";
@@ -752,6 +801,89 @@ function localSymbolTarget(expression, source, symbols) {
752
801
  return declaration ? exactPropertyTarget(expression, declaration, symbols, source) : void 0;
753
802
  }
754
803
 
804
+ // src/parsers/event-loop-registration.ts
805
+ import ts2 from "typescript";
806
+ var loopValueCap = 32;
807
+ var loopExpressionLimit = 256;
808
+ function unwrap(expression) {
809
+ if (ts2.isParenthesizedExpression(expression) || ts2.isAsExpression(expression) || ts2.isSatisfiesExpression(expression) || ts2.isTypeAssertionExpression(expression))
810
+ return unwrap(expression.expression);
811
+ return expression;
812
+ }
813
+ function stringArray(expression) {
814
+ const value = unwrap(expression);
815
+ if (!ts2.isArrayLiteralExpression(value)) return void 0;
816
+ const strings = value.elements.flatMap((element) => {
817
+ const item = ts2.isSpreadElement(element) ? void 0 : unwrap(element);
818
+ return item && ts2.isStringLiteralLike(item) ? [item.text] : [];
819
+ });
820
+ return strings.length === value.elements.length ? strings : void 0;
821
+ }
822
+ function identifierArray(identifier) {
823
+ const binding = resolveBinding(identifier, identifier);
824
+ return binding.immutable && binding.initializer ? stringArray(binding.initializer) : void 0;
825
+ }
826
+ function orderedValues(values) {
827
+ return [...values].sort((left, right) => left.declarationStartOffset - right.declarationStartOffset).map((item) => item.value);
828
+ }
829
+ function objectValues(expression, source) {
830
+ if (!ts2.isPropertyAccessExpression(expression.expression))
831
+ return void 0;
832
+ const root = expression.expression.expression;
833
+ if (!ts2.isIdentifier(root) || root.text !== "Object" || lexicalIdentifierDeclaration(root) || expression.expression.name.text !== "values" || expression.arguments.length !== 1) return void 0;
834
+ const argument = expression.arguments[0];
835
+ if (!argument || !ts2.isIdentifier(argument)) return void 0;
836
+ const lookups = collectStringConstantLookups(source);
837
+ const prefix = `${argument.text}.`;
838
+ const refused = [...lookups.refusedMembers.keys()].some((key) => key.startsWith(prefix));
839
+ const values = [
840
+ ...lookups.enumMembers.values(),
841
+ ...lookups.objectProperties.values()
842
+ ].filter((item) => item.key.startsWith(prefix));
843
+ return !refused && values.length > 0 ? orderedValues(values) : void 0;
844
+ }
845
+ function staticCollectionValues(expression, source) {
846
+ const value = unwrap(expression);
847
+ if (ts2.isArrayLiteralExpression(value)) return stringArray(value);
848
+ if (ts2.isIdentifier(value)) return identifierArray(value);
849
+ return ts2.isCallExpression(value) ? objectValues(value, source) : void 0;
850
+ }
851
+ function enclosingForEach(node) {
852
+ let current = node.parent;
853
+ while (current && !ts2.isSourceFile(current)) {
854
+ if ((ts2.isArrowFunction(current) || ts2.isFunctionExpression(current)) && ts2.isCallExpression(current.parent) && current.parent.arguments.includes(current) && ts2.isPropertyAccessExpression(current.parent.expression) && current.parent.expression.name.text === "forEach")
855
+ return current.parent;
856
+ if (ts2.isFunctionLike(current)) return void 0;
857
+ current = current.parent;
858
+ }
859
+ return void 0;
860
+ }
861
+ function boundedExpression(expression, source) {
862
+ return expression.getText(source).slice(0, loopExpressionLimit);
863
+ }
864
+ function eventLoopRegistrationEvidence(node, source) {
865
+ const loop = enclosingForEach(node);
866
+ if (!loop || !ts2.isPropertyAccessExpression(loop.expression)) return {};
867
+ const collection = loop.expression.expression;
868
+ const values = staticCollectionValues(collection, source);
869
+ if (!values) return {
870
+ subscriptionRegisteredInLoop: true,
871
+ subscriptionLoopRegistrationStatus: "unresolved",
872
+ subscriptionLoopUnresolvedReason: "subscription_loop_collection_not_statically_enumerable",
873
+ subscriptionLoopCollectionExpression: boundedExpression(collection, source)
874
+ };
875
+ const shown = values.slice(0, loopValueCap);
876
+ return {
877
+ subscriptionRegisteredInLoop: true,
878
+ subscriptionLoopRegistrationStatus: "enumerated",
879
+ subscriptionLoopCollectionExpression: boundedExpression(collection, source),
880
+ subscriptionLoopRegistrationCount: values.length,
881
+ subscriptionLoopValues: shown,
882
+ shownSubscriptionLoopValueCount: shown.length,
883
+ omittedSubscriptionLoopValueCount: Math.max(0, values.length - shown.length)
884
+ };
885
+ }
886
+
755
887
  // src/parsers/event-subscription-facts.ts
756
888
  function lineOf(source, node) {
757
889
  return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
@@ -766,7 +898,7 @@ function importRelation(binding) {
766
898
  function directTarget(expression, source, imports, symbols) {
767
899
  const imported = symbolImportReference(expression, imports);
768
900
  if (imported) return importedTarget(expression, source, imported);
769
- if (ts2.isIdentifier(expression)) {
901
+ if (ts3.isIdentifier(expression)) {
770
902
  const exact = localSymbolTarget(expression, source, symbols);
771
903
  return {
772
904
  calleeExpression: expression.text,
@@ -789,7 +921,7 @@ function importedTarget(expression, source, binding) {
789
921
  };
790
922
  }
791
923
  function propertyTarget(expression, source, symbols) {
792
- if (!ts2.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts2.isIdentifier(expression.expression)) return void 0;
924
+ if (!ts3.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts3.isIdentifier(expression.expression)) return void 0;
793
925
  const exact = localSymbolTarget(expression, source, symbols);
794
926
  return {
795
927
  calleeExpression: expression.getText(source),
@@ -809,7 +941,7 @@ function classifyHandlerReference(expression, source, imports = collectSymbolImp
809
941
  "handler_argument_missing",
810
942
  "missing"
811
943
  );
812
- if (ts2.isArrowFunction(expression) || ts2.isFunctionExpression(expression))
944
+ if (ts3.isArrowFunction(expression) || ts3.isFunctionExpression(expression))
813
945
  return unsupportedClassification(
814
946
  "unsupported_inline",
815
947
  "inline_handler_body_not_indexed",
@@ -818,7 +950,7 @@ function classifyHandlerReference(expression, source, imports = collectSymbolImp
818
950
  const direct = directTarget(expression, source, imports, symbols);
819
951
  if (direct)
820
952
  return { status: "role_required", referenceShape: direct.referenceShape, target: direct };
821
- if (!ts2.isCallExpression(expression))
953
+ if (!ts3.isCallExpression(expression))
822
954
  return unsupportedClassification(
823
955
  "unsupported_reference_shape",
824
956
  "handler_reference_shape_unsupported",
@@ -906,6 +1038,7 @@ function enrichSubscription(source, classified, symbols) {
906
1038
  );
907
1039
  const evidence = {
908
1040
  ...classified.fact.evidence ?? {},
1041
+ ...eventLoopRegistrationEvidence(classified.node, source),
909
1042
  handlerReferenceStatus: classification.status,
910
1043
  ...classification.reason ? { handlerReferenceReason: classification.reason } : {},
911
1044
  handlerReferenceShape: classification.referenceShape
@@ -939,7 +1072,7 @@ function reconcileEventSubscriptions(source, classifications, symbols, calls) {
939
1072
  import "typescript";
940
1073
 
941
1074
  // src/parsers/binding-identity.ts
942
- import ts3 from "typescript";
1075
+ import ts4 from "typescript";
943
1076
  var scopeChainCap = 16;
944
1077
  function matchFactsToSites(index, facts) {
945
1078
  return facts.map((fact) => {
@@ -1059,9 +1192,9 @@ function assertUniqueSites(bindings) {
1059
1192
  function callNodeMap(source) {
1060
1193
  const calls = /* @__PURE__ */ new Map();
1061
1194
  const visit = (node) => {
1062
- if (ts3.isCallExpression(node))
1195
+ if (ts4.isCallExpression(node))
1063
1196
  calls.set(`${node.getStart(source)}:${node.getEnd()}`, node);
1064
- ts3.forEachChild(node, visit);
1197
+ ts4.forEachChild(node, visit);
1065
1198
  };
1066
1199
  visit(source);
1067
1200
  return calls;
@@ -1254,7 +1387,7 @@ function reconcileSourceFacts(source, classifications, bindings, outboundCalls,
1254
1387
  }
1255
1388
 
1256
1389
  // src/parsers/symbol-call-facts.ts
1257
- import ts5 from "typescript";
1390
+ import ts6 from "typescript";
1258
1391
  var commonTerminalMembers = /* @__PURE__ */ new Set([
1259
1392
  "push",
1260
1393
  "includes",
@@ -1322,8 +1455,8 @@ var cdsFrameworkPrefixes = [
1322
1455
  "cds.parse."
1323
1456
  ];
1324
1457
  function symbolCallName(expr) {
1325
- if (ts5.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
1326
- if (!ts5.isPropertyAccessExpression(expr))
1458
+ if (ts6.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
1459
+ if (!ts6.isPropertyAccessExpression(expr))
1327
1460
  return { expression: expr.getText() };
1328
1461
  const left = expr.expression.getText();
1329
1462
  return {
@@ -1355,25 +1488,25 @@ function ignoredFrameworkCall(callee) {
1355
1488
  }
1356
1489
  function argumentEvidence(args) {
1357
1490
  return args.map((arg) => {
1358
- if (ts5.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
1359
- if (ts5.isArrayLiteralExpression(arg)) return {
1491
+ if (ts6.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
1492
+ if (ts6.isArrayLiteralExpression(arg)) return {
1360
1493
  kind: "array_literal",
1361
- elements: arg.elements.flatMap((item, index) => ts5.isIdentifier(item) ? [{ index, kind: "identifier", name: item.text }] : [])
1494
+ elements: arg.elements.flatMap((item, index) => ts6.isIdentifier(item) ? [{ index, kind: "identifier", name: item.text }] : [])
1362
1495
  };
1363
- if (ts5.isObjectLiteralExpression(arg))
1496
+ if (ts6.isObjectLiteralExpression(arg))
1364
1497
  return objectArgumentEvidence(arg);
1365
1498
  return { kind: "unsupported", expression: arg.getText() };
1366
1499
  });
1367
1500
  }
1368
1501
  function objectArgumentEvidence(argument) {
1369
1502
  const properties = argument.properties.flatMap((property) => {
1370
- if (ts5.isShorthandPropertyAssignment(property))
1503
+ if (ts6.isShorthandPropertyAssignment(property))
1371
1504
  return [{
1372
1505
  kind: "shorthand",
1373
1506
  property: property.name.text,
1374
1507
  argument: property.name.text
1375
1508
  }];
1376
- if (!ts5.isPropertyAssignment(property) || !ts5.isIdentifier(property.initializer)) return [];
1509
+ if (!ts6.isPropertyAssignment(property) || !ts6.isIdentifier(property.initializer)) return [];
1377
1510
  const name = propertyName(property.name);
1378
1511
  return name ? [{
1379
1512
  kind: "property_assignment",
@@ -1384,7 +1517,7 @@ function objectArgumentEvidence(argument) {
1384
1517
  return { kind: "object_literal", properties };
1385
1518
  }
1386
1519
  function propertyName(name) {
1387
- return ts5.isIdentifier(name) || ts5.isStringLiteralLike(name) || ts5.isNumericLiteral(name) ? name.text : void 0;
1520
+ return ts6.isIdentifier(name) || ts6.isStringLiteralLike(name) || ts6.isNumericLiteral(name) ? name.text : void 0;
1388
1521
  }
1389
1522
  function exactCaller(collection, node) {
1390
1523
  const selected = selectCallOwner(
@@ -1445,9 +1578,9 @@ function callFact(collection, node) {
1445
1578
  }
1446
1579
  function expressionRoot(expression) {
1447
1580
  let current = expression;
1448
- while (ts5.isPropertyAccessExpression(current))
1581
+ while (ts6.isPropertyAccessExpression(current))
1449
1582
  current = current.expression;
1450
- return ts5.isIdentifier(current) ? current : void 0;
1583
+ return ts6.isIdentifier(current) ? current : void 0;
1451
1584
  }
1452
1585
  function exactDeclaredContext(identifier, candidates) {
1453
1586
  const matches = candidates.filter((candidate) => identifierMatchesDeclaration(
@@ -1478,8 +1611,8 @@ function callInstance(collection, node, callee) {
1478
1611
  }
1479
1612
  function enclosingClass(node) {
1480
1613
  let current = node.parent;
1481
- while (current && !ts5.isSourceFile(current)) {
1482
- if (ts5.isClassLike(current)) return current;
1614
+ while (current && !ts6.isSourceFile(current)) {
1615
+ if (ts6.isClassLike(current)) return current;
1483
1616
  current = current.parent;
1484
1617
  }
1485
1618
  return void 0;
@@ -1619,26 +1752,26 @@ function parserCandidateStrategy(context) {
1619
1752
  function collectSymbolCallFacts(collection) {
1620
1753
  const calls = [];
1621
1754
  const visit = (node) => {
1622
- if (ts5.isCallExpression(node)) {
1755
+ if (ts6.isCallExpression(node)) {
1623
1756
  const call = callFact(collection, node);
1624
1757
  if (call) calls.push(call);
1625
1758
  }
1626
- ts5.forEachChild(node, visit);
1759
+ ts6.forEachChild(node, visit);
1627
1760
  };
1628
1761
  visit(collection.source);
1629
1762
  return calls;
1630
1763
  }
1631
1764
 
1632
1765
  // src/parsers/executable-body-eligibility.ts
1633
- import ts6 from "typescript";
1766
+ import ts7 from "typescript";
1634
1767
  function hasModifier(node, kind) {
1635
- return ts6.canHaveModifiers(node) && Boolean(ts6.getModifiers(node)?.some((item) => item.kind === kind));
1768
+ return ts7.canHaveModifiers(node) && Boolean(ts7.getModifiers(node)?.some((item) => item.kind === kind));
1636
1769
  }
1637
1770
  function executableBodyEligibility(node, source) {
1638
1771
  if (node.body) return { eligible: true, reason: "body_present" };
1639
- if (hasModifier(node, ts6.SyntaxKind.AbstractKeyword))
1772
+ if (hasModifier(node, ts7.SyntaxKind.AbstractKeyword))
1640
1773
  return { eligible: false, reason: "abstract_bodyless" };
1641
- if (hasModifier(node, ts6.SyntaxKind.DeclareKeyword))
1774
+ if (hasModifier(node, ts7.SyntaxKind.DeclareKeyword))
1642
1775
  return { eligible: false, reason: "ambient_declaration" };
1643
1776
  return {
1644
1777
  eligible: false,
@@ -1647,7 +1780,7 @@ function executableBodyEligibility(node, source) {
1647
1780
  }
1648
1781
 
1649
1782
  // src/parsers/symbol-derived-contexts.ts
1650
- import ts7 from "typescript";
1783
+ import ts8 from "typescript";
1651
1784
  var builtInConstructors = /* @__PURE__ */ new Set([
1652
1785
  "Set",
1653
1786
  "Map",
@@ -1688,23 +1821,23 @@ function appendNamed(values, name, value) {
1688
1821
  values.set(name, matches);
1689
1822
  }
1690
1823
  function assignmentOperator2(kind) {
1691
- return kind >= ts7.SyntaxKind.FirstAssignment && kind <= ts7.SyntaxKind.LastAssignment;
1824
+ return kind >= ts8.SyntaxKind.FirstAssignment && kind <= ts8.SyntaxKind.LastAssignment;
1692
1825
  }
1693
1826
  function mutationUnary2(node) {
1694
- return node.operator === ts7.SyntaxKind.PlusPlusToken || node.operator === ts7.SyntaxKind.MinusMinusToken;
1827
+ return node.operator === ts8.SyntaxKind.PlusPlusToken || node.operator === ts8.SyntaxKind.MinusMinusToken;
1695
1828
  }
1696
1829
  function receiverIdentifier(expression) {
1697
1830
  let current = expression;
1698
- while (ts7.isPropertyAccessExpression(current) || ts7.isElementAccessExpression(current))
1831
+ while (ts8.isPropertyAccessExpression(current) || ts8.isElementAccessExpression(current))
1699
1832
  current = current.expression;
1700
- return ts7.isIdentifier(current) ? current : void 0;
1833
+ return ts8.isIdentifier(current) ? current : void 0;
1701
1834
  }
1702
1835
  function nodeWritesMember(node, matches) {
1703
- if (ts7.isBinaryExpression(node))
1836
+ if (ts8.isBinaryExpression(node))
1704
1837
  return assignmentOperator2(node.operatorToken.kind) && matches(node.left);
1705
- if (ts7.isPrefixUnaryExpression(node) || ts7.isPostfixUnaryExpression(node))
1838
+ if (ts8.isPrefixUnaryExpression(node) || ts8.isPostfixUnaryExpression(node))
1706
1839
  return mutationUnary2(node) && matches(node.operand);
1707
- return ts7.isDeleteExpression(node) && matches(node.expression);
1840
+ return ts8.isDeleteExpression(node) && matches(node.expression);
1708
1841
  }
1709
1842
  function memberWrite(source, declaration) {
1710
1843
  const start = declaration.getStart(source);
@@ -1717,16 +1850,16 @@ function memberWrite(source, declaration) {
1717
1850
  const visit = (node) => {
1718
1851
  if (written) return;
1719
1852
  written = nodeWritesMember(node, matches);
1720
- if (!written) ts7.forEachChild(node, visit);
1853
+ if (!written) ts8.forEachChild(node, visit);
1721
1854
  };
1722
1855
  visit(source);
1723
1856
  return written;
1724
1857
  }
1725
1858
  function stableVariable(collection, node) {
1726
- return ts7.isIdentifier(node.name) && ts7.isVariableDeclarationList(node.parent) && (node.parent.flags & ts7.NodeFlags.Const) !== 0 && !memberWrite(collection.source, node.name);
1859
+ return ts8.isIdentifier(node.name) && ts8.isVariableDeclarationList(node.parent) && (node.parent.flags & ts8.NodeFlags.Const) !== 0 && !memberWrite(collection.source, node.name);
1727
1860
  }
1728
1861
  function collectProxy(collection, node) {
1729
- if (!ts7.isVariableDeclaration(node) || !stableVariable(collection, node) || !node.initializer || !ts7.isCallExpression(node.initializer) || !ts7.isPropertyAccessExpression(node.initializer.expression)) return;
1862
+ if (!ts8.isVariableDeclaration(node) || !stableVariable(collection, node) || !node.initializer || !ts8.isCallExpression(node.initializer) || !ts8.isPropertyAccessExpression(node.initializer.expression)) return;
1730
1863
  const callee = symbolCallName(node.initializer.expression);
1731
1864
  const binding = symbolImportReference(
1732
1865
  node.initializer.expression,
@@ -1743,8 +1876,8 @@ function collectProxy(collection, node) {
1743
1876
  });
1744
1877
  }
1745
1878
  function propertyContainer2(declaration, propertyName3) {
1746
- const property = propertyName3 && ts7.isPropertyDeclaration(declaration.parent) ? declaration.parent : void 0;
1747
- return property && ts7.isClassLike(property.parent) ? property.parent : void 0;
1879
+ const property = propertyName3 && ts8.isPropertyDeclaration(declaration.parent) ? declaration.parent : void 0;
1880
+ return property && ts8.isClassLike(property.parent) ? property.parent : void 0;
1748
1881
  }
1749
1882
  function eligibleClassName(collection, className, imported) {
1750
1883
  if (builtInConstructors.has(className)) return false;
@@ -1775,43 +1908,43 @@ function rememberInstance(collection, declaration, classExpression, propertyName
1775
1908
  });
1776
1909
  }
1777
1910
  function collectVariableInstance(collection, node) {
1778
- if (!ts7.isVariableDeclaration(node) || !stableVariable(collection, node))
1911
+ if (!ts8.isVariableDeclaration(node) || !stableVariable(collection, node))
1779
1912
  return;
1780
1913
  const initializer = node.initializer;
1781
- if (initializer && ts7.isNewExpression(initializer) && ts7.isIdentifier(initializer.expression))
1914
+ if (initializer && ts8.isNewExpression(initializer) && ts8.isIdentifier(initializer.expression))
1782
1915
  rememberInstance(collection, node.name, initializer.expression);
1783
1916
  }
1784
1917
  function propertyName2(name) {
1785
- return ts7.isIdentifier(name) || ts7.isStringLiteralLike(name) || ts7.isNumericLiteral(name) ? name.text : void 0;
1918
+ return ts8.isIdentifier(name) || ts8.isStringLiteralLike(name) || ts8.isNumericLiteral(name) ? name.text : void 0;
1786
1919
  }
1787
1920
  function thisPropertyWrite(expression, name) {
1788
- if (ts7.isPropertyAccessExpression(expression))
1789
- return expression.expression.kind === ts7.SyntaxKind.ThisKeyword && expression.name.text === name;
1790
- return ts7.isElementAccessExpression(expression) && expression.expression.kind === ts7.SyntaxKind.ThisKeyword && Boolean(expression.argumentExpression && ts7.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === name);
1921
+ if (ts8.isPropertyAccessExpression(expression))
1922
+ return expression.expression.kind === ts8.SyntaxKind.ThisKeyword && expression.name.text === name;
1923
+ return ts8.isElementAccessExpression(expression) && expression.expression.kind === ts8.SyntaxKind.ThisKeyword && Boolean(expression.argumentExpression && ts8.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === name);
1791
1924
  }
1792
1925
  function nodeWritesThisProperty(node, name) {
1793
- if (ts7.isBinaryExpression(node))
1926
+ if (ts8.isBinaryExpression(node))
1794
1927
  return assignmentOperator2(node.operatorToken.kind) && thisPropertyWrite(node.left, name);
1795
- if (ts7.isPrefixUnaryExpression(node) || ts7.isPostfixUnaryExpression(node))
1928
+ if (ts8.isPrefixUnaryExpression(node) || ts8.isPostfixUnaryExpression(node))
1796
1929
  return mutationUnary2(node) && thisPropertyWrite(node.operand, name);
1797
- return ts7.isDeleteExpression(node) && thisPropertyWrite(node.expression, name);
1930
+ return ts8.isDeleteExpression(node) && thisPropertyWrite(node.expression, name);
1798
1931
  }
1799
1932
  function stableProperty(node, name) {
1800
- const flags = ts7.getCombinedModifierFlags(node);
1801
- if ((flags & (ts7.ModifierFlags.Private | ts7.ModifierFlags.Readonly)) === 0 || !ts7.isClassLike(node.parent)) return false;
1933
+ const flags = ts8.getCombinedModifierFlags(node);
1934
+ if ((flags & (ts8.ModifierFlags.Private | ts8.ModifierFlags.Readonly)) === 0 || !ts8.isClassLike(node.parent)) return false;
1802
1935
  let written = false;
1803
1936
  const visit = (child) => {
1804
1937
  if (written || child === node) return;
1805
1938
  written = nodeWritesThisProperty(child, name);
1806
- if (!written) ts7.forEachChild(child, visit);
1939
+ if (!written) ts8.forEachChild(child, visit);
1807
1940
  };
1808
1941
  visit(node.parent);
1809
1942
  return !written;
1810
1943
  }
1811
1944
  function collectPropertyInstance(collection, node) {
1812
- if (!ts7.isPropertyDeclaration(node)) return;
1945
+ if (!ts8.isPropertyDeclaration(node)) return;
1813
1946
  const initializer = node.initializer;
1814
- if (!initializer || !ts7.isNewExpression(initializer) || !ts7.isIdentifier(initializer.expression)) return;
1947
+ if (!initializer || !ts8.isNewExpression(initializer) || !ts8.isIdentifier(initializer.expression)) return;
1815
1948
  const name = propertyName2(node.name);
1816
1949
  if (name && stableProperty(node, name)) rememberInstance(
1817
1950
  collection,
@@ -1825,7 +1958,7 @@ function collectDerivedSymbolContexts(collection) {
1825
1958
  collectProxy(collection, node);
1826
1959
  collectVariableInstance(collection, node);
1827
1960
  collectPropertyInstance(collection, node);
1828
- ts7.forEachChild(node, visit);
1961
+ ts8.forEachChild(node, visit);
1829
1962
  };
1830
1963
  visit(collection.source);
1831
1964
  }
@@ -1836,51 +1969,51 @@ function lineOf2(source, pos) {
1836
1969
  }
1837
1970
  function nameOf(node) {
1838
1971
  if (!node) return void 0;
1839
- if (ts8.isIdentifier(node) || ts8.isStringLiteral(node) || ts8.isNumericLiteral(node)) return node.text;
1972
+ if (ts9.isIdentifier(node) || ts9.isStringLiteral(node) || ts9.isNumericLiteral(node)) return node.text;
1840
1973
  return void 0;
1841
1974
  }
1842
1975
  function isFunctionLike(node) {
1843
- return ts8.isFunctionDeclaration(node) || ts8.isMethodDeclaration(node) || ts8.isFunctionExpression(node) || ts8.isArrowFunction(node);
1976
+ return ts9.isFunctionDeclaration(node) || ts9.isMethodDeclaration(node) || ts9.isFunctionExpression(node) || ts9.isArrowFunction(node);
1844
1977
  }
1845
1978
  function exported(node) {
1846
- return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Export);
1979
+ return Boolean(ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Export);
1847
1980
  }
1848
1981
  function defaultExported(node) {
1849
1982
  return Boolean(
1850
- ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Default
1983
+ ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Default
1851
1984
  );
1852
1985
  }
1853
1986
  function isPublicClassMethod(node) {
1854
- const flags = ts8.getCombinedModifierFlags(node);
1855
- return (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
1987
+ const flags = ts9.getCombinedModifierFlags(node);
1988
+ return (flags & ts9.ModifierFlags.Private) === 0 && (flags & ts9.ModifierFlags.Protected) === 0;
1856
1989
  }
1857
1990
  function exportDeclarations(source) {
1858
1991
  const exports = /* @__PURE__ */ new Map();
1859
1992
  const visit = (node) => {
1860
- if (ts8.isExportDeclaration(node) && node.exportClause && ts8.isNamedExports(node.exportClause)) {
1993
+ if (ts9.isExportDeclaration(node) && node.exportClause && ts9.isNamedExports(node.exportClause)) {
1861
1994
  for (const el of node.exportClause.elements) exports.set((el.propertyName ?? el.name).text, el.name.text);
1862
1995
  }
1863
- ts8.forEachChild(node, visit);
1996
+ ts9.forEachChild(node, visit);
1864
1997
  };
1865
1998
  visit(source);
1866
1999
  return exports;
1867
2000
  }
1868
2001
  function isObjectFunction(node) {
1869
- return ts8.isFunctionExpression(node) || ts8.isArrowFunction(node) || ts8.isMethodDeclaration(node);
2002
+ return ts9.isFunctionExpression(node) || ts9.isArrowFunction(node) || ts9.isMethodDeclaration(node);
1870
2003
  }
1871
2004
  function requireSource(expr) {
1872
- if (!ts8.isCallExpression(expr) || !ts8.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
2005
+ if (!ts9.isCallExpression(expr) || !ts9.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
1873
2006
  const first = expr.arguments[0];
1874
- return first && ts8.isStringLiteral(first) ? first.text : void 0;
2007
+ return first && ts9.isStringLiteral(first) ? first.text : void 0;
1875
2008
  }
1876
2009
  function bindingLocalName(name, initializer) {
1877
- if (ts8.isIdentifier(name)) return name.text;
1878
- if (initializer && ts8.isIdentifier(initializer)) return initializer.text;
2010
+ if (ts9.isIdentifier(name)) return name.text;
2011
+ if (initializer && ts9.isIdentifier(initializer)) return initializer.text;
1879
2012
  return void 0;
1880
2013
  }
1881
2014
  function objectPatternAliases(pattern, parameter, source, lineNode) {
1882
2015
  return pattern.elements.flatMap((element) => {
1883
- if (element.dotDotDotToken || ts8.isObjectBindingPattern(element.name) || ts8.isArrayBindingPattern(element.name)) return [];
2016
+ if (element.dotDotDotToken || ts9.isObjectBindingPattern(element.name) || ts9.isArrayBindingPattern(element.name)) return [];
1884
2017
  const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
1885
2018
  if (!property) return [];
1886
2019
  const local = bindingLocalName(element.name, element.initializer);
@@ -1888,21 +2021,21 @@ function objectPatternAliases(pattern, parameter, source, lineNode) {
1888
2021
  });
1889
2022
  }
1890
2023
  function parameterPropertyAliases(fn, source) {
1891
- const parameterNames = new Set(fn.parameters.flatMap((param) => ts8.isIdentifier(param.name) ? [param.name.text] : []));
2024
+ const parameterNames = new Set(fn.parameters.flatMap((param) => ts9.isIdentifier(param.name) ? [param.name.text] : []));
1892
2025
  if (!fn.body || parameterNames.size === 0) return [];
1893
2026
  const aliases = [];
1894
2027
  const addFromAssignment = (left, right, node) => {
1895
- if (!ts8.isObjectLiteralExpression(left) || !ts8.isIdentifier(right) || !parameterNames.has(right.text)) return;
2028
+ if (!ts9.isObjectLiteralExpression(left) || !ts9.isIdentifier(right) || !parameterNames.has(right.text)) return;
1896
2029
  for (const prop of left.properties) {
1897
- if (!ts8.isPropertyAssignment(prop)) continue;
2030
+ if (!ts9.isPropertyAssignment(prop)) continue;
1898
2031
  const property = nameOf(prop.name);
1899
- if (property && ts8.isIdentifier(prop.initializer)) aliases.push({ parameter: right.text, property, local: prop.initializer.text, kind: "object_parameter_destructure", line: lineOf2(source, node.getStart(source)) });
2032
+ if (property && ts9.isIdentifier(prop.initializer)) aliases.push({ parameter: right.text, property, local: prop.initializer.text, kind: "object_parameter_destructure", line: lineOf2(source, node.getStart(source)) });
1900
2033
  }
1901
2034
  };
1902
2035
  const visit = (node) => {
1903
- if (ts8.isVariableDeclaration(node) && ts8.isObjectBindingPattern(node.name) && node.initializer && ts8.isIdentifier(node.initializer) && parameterNames.has(node.initializer.text)) aliases.push(...objectPatternAliases(node.name, node.initializer.text, source, node));
1904
- if (ts8.isBinaryExpression(node) && node.operatorToken.kind === ts8.SyntaxKind.EqualsToken) addFromAssignment(ts8.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
1905
- ts8.forEachChild(node, visit);
2036
+ if (ts9.isVariableDeclaration(node) && ts9.isObjectBindingPattern(node.name) && node.initializer && ts9.isIdentifier(node.initializer) && parameterNames.has(node.initializer.text)) aliases.push(...objectPatternAliases(node.name, node.initializer.text, source, node));
2037
+ if (ts9.isBinaryExpression(node) && node.operatorToken.kind === ts9.SyntaxKind.EqualsToken) addFromAssignment(ts9.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
2038
+ ts9.forEachChild(node, visit);
1906
2039
  };
1907
2040
  visit(fn.body);
1908
2041
  const seen = /* @__PURE__ */ new Set();
@@ -1915,10 +2048,10 @@ function parameterPropertyAliases(fn, source) {
1915
2048
  }
1916
2049
  function parameterBindings(params) {
1917
2050
  return params.flatMap((param, index) => {
1918
- if (ts8.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
1919
- if (ts8.isObjectBindingPattern(param.name)) {
2051
+ if (ts9.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
2052
+ if (ts9.isObjectBindingPattern(param.name)) {
1920
2053
  const properties = param.name.elements.flatMap((element) => {
1921
- if (element.dotDotDotToken || ts8.isObjectBindingPattern(element.name) || ts8.isArrayBindingPattern(element.name)) return [];
2054
+ if (element.dotDotDotToken || ts9.isObjectBindingPattern(element.name) || ts9.isArrayBindingPattern(element.name)) return [];
1922
2055
  const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
1923
2056
  if (!property) return [];
1924
2057
  const local = bindingLocalName(element.name, element.initializer);
@@ -1926,8 +2059,8 @@ function parameterBindings(params) {
1926
2059
  });
1927
2060
  return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
1928
2061
  }
1929
- if (ts8.isArrayBindingPattern(param.name)) {
1930
- const elements = param.name.elements.flatMap((element, elementIndex) => ts8.isBindingElement(element) && !element.dotDotDotToken && ts8.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
2062
+ if (ts9.isArrayBindingPattern(param.name)) {
2063
+ const elements = param.name.elements.flatMap((element, elementIndex) => ts9.isBindingElement(element) && !element.dotDotDotToken && ts9.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
1931
2064
  return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
1932
2065
  }
1933
2066
  return [];
@@ -1938,9 +2071,9 @@ function symbolSourceEvidence(collection, node, options) {
1938
2071
  if (options.classMemberExported) return {
1939
2072
  source: "exported_class_member",
1940
2073
  exportedClass: options.parentRoot,
1941
- memberKind: ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static ? "static_method" : "class_method"
2074
+ memberKind: ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Static ? "static_method" : "class_method"
1942
2075
  };
1943
- if (options.classContainerExported && ts8.isMethodDeclaration(node) && isPublicClassMethod(node)) return {
2076
+ if (options.classContainerExported && ts9.isMethodDeclaration(node) && isPublicClassMethod(node)) return {
1944
2077
  source: "exported_class_instance_member",
1945
2078
  exportedClass: options.parentRoot,
1946
2079
  exportedClassExportKind: options.classContainerDefaultExported ? "default" : "named",
@@ -1964,8 +2097,8 @@ function executableEvidence(node, source) {
1964
2097
  };
1965
2098
  }
1966
2099
  function exportedClassMember(collection, kind, parentName, parentRoot, node) {
1967
- if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !ts8.isMethodDeclaration(node)) return false;
1968
- return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static) && isPublicClassMethod(node);
2100
+ if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !ts9.isMethodDeclaration(node)) return false;
2101
+ return Boolean(ts9.getCombinedModifierFlags(node) & ts9.ModifierFlags.Static) && isPublicClassMethod(node);
1969
2102
  }
1970
2103
  function classExportState(collection, parentName, parentRoot) {
1971
2104
  return {
@@ -2062,41 +2195,41 @@ function addAliasSymbol(collection, objectName, propertyName3, node) {
2062
2195
  }
2063
2196
  function collectImportSources(collection) {
2064
2197
  const visit = (node) => {
2065
- if (ts8.isImportDeclaration(node) && ts8.isStringLiteral(node.moduleSpecifier))
2198
+ if (ts9.isImportDeclaration(node) && ts9.isStringLiteral(node.moduleSpecifier))
2066
2199
  collectEsmImportSources(collection.imports, node);
2067
- if (ts8.isVariableStatement(node))
2200
+ if (ts9.isVariableStatement(node))
2068
2201
  collectCjsImportSources(collection.imports, node);
2069
- ts8.forEachChild(node, visit);
2202
+ ts9.forEachChild(node, visit);
2070
2203
  };
2071
2204
  visit(collection.source);
2072
2205
  }
2073
2206
  function collectEsmImportSources(imports, node) {
2074
- if (!ts8.isStringLiteral(node.moduleSpecifier)) return;
2207
+ if (!ts9.isStringLiteral(node.moduleSpecifier)) return;
2075
2208
  const source = node.moduleSpecifier.text;
2076
2209
  const clause = node.importClause;
2077
2210
  if (clause?.name) imports.set(clause.name.text, source);
2078
2211
  const named = clause?.namedBindings;
2079
- if (named && ts8.isNamedImports(named))
2212
+ if (named && ts9.isNamedImports(named))
2080
2213
  for (const item of named.elements) imports.set(item.name.text, source);
2081
- if (named && ts8.isNamespaceImport(named))
2214
+ if (named && ts9.isNamespaceImport(named))
2082
2215
  imports.set(named.name.text, source);
2083
2216
  }
2084
2217
  function collectCjsImportSources(imports, node) {
2085
2218
  for (const declaration of node.declarationList.declarations) {
2086
2219
  const source = declaration.initializer ? requireSource(declaration.initializer) : void 0;
2087
2220
  if (!source) continue;
2088
- if (ts8.isIdentifier(declaration.name))
2221
+ if (ts9.isIdentifier(declaration.name))
2089
2222
  imports.set(declaration.name.text, source);
2090
- if (ts8.isObjectBindingPattern(declaration.name)) {
2223
+ if (ts9.isObjectBindingPattern(declaration.name)) {
2091
2224
  for (const item of declaration.name.elements)
2092
- if (ts8.isIdentifier(item.name)) imports.set(item.name.text, source);
2225
+ if (ts9.isIdentifier(item.name)) imports.set(item.name.text, source);
2093
2226
  }
2094
2227
  }
2095
2228
  }
2096
2229
  function classPropertySymbol(collection, node, parentClass) {
2097
2230
  const initializer = node.initializer;
2098
2231
  const localName = nameOf(node.name);
2099
- if (!localName || !initializer || !ts8.isArrowFunction(initializer) && !ts8.isFunctionExpression(initializer)) return;
2232
+ if (!localName || !initializer || !ts9.isArrowFunction(initializer) && !ts9.isFunctionExpression(initializer)) return;
2100
2233
  const staticPublic = publicStaticProperty(collection, node, parentClass);
2101
2234
  const memberKind = propertyMemberKind(initializer, staticPublic);
2102
2235
  addExecutableSymbol(
@@ -2110,22 +2243,22 @@ function classPropertySymbol(collection, node, parentClass) {
2110
2243
  );
2111
2244
  }
2112
2245
  function publicStaticProperty(collection, node, parentClass) {
2113
- const flags = ts8.getCombinedModifierFlags(node);
2114
- return collection.exportedClasses.has(parentClass) && Boolean(flags & ts8.ModifierFlags.Static) && (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
2246
+ const flags = ts9.getCombinedModifierFlags(node);
2247
+ return collection.exportedClasses.has(parentClass) && Boolean(flags & ts9.ModifierFlags.Static) && (flags & ts9.ModifierFlags.Private) === 0 && (flags & ts9.ModifierFlags.Protected) === 0;
2115
2248
  }
2116
2249
  function propertyMemberKind(initializer, staticPublic) {
2117
- if (ts8.isArrowFunction(initializer))
2250
+ if (ts9.isArrowFunction(initializer))
2118
2251
  return staticPublic ? "static_arrow_function" : "arrow_function_property";
2119
2252
  return staticPublic ? "static_function_expression" : "function_expression_property";
2120
2253
  }
2121
2254
  function objectCallable(property) {
2122
- if (ts8.isMethodDeclaration(property)) return property;
2123
- return ts8.isPropertyAssignment(property) && isObjectFunction(property.initializer) ? property.initializer : void 0;
2255
+ if (ts9.isMethodDeclaration(property)) return property;
2256
+ return ts9.isPropertyAssignment(property) && isObjectFunction(property.initializer) ? property.initializer : void 0;
2124
2257
  }
2125
2258
  function objectLiteralSymbols(collection, objectName, object, objectIsExported) {
2126
2259
  if (objectIsExported) collection.objectExports.add(objectName);
2127
2260
  for (const property of object.properties) {
2128
- if (objectIsExported && ts8.isShorthandPropertyAssignment(property))
2261
+ if (objectIsExported && ts9.isShorthandPropertyAssignment(property))
2129
2262
  addAliasSymbol(collection, objectName, property.name.text, property.name);
2130
2263
  const callable = objectCallable(property);
2131
2264
  const propertyName3 = callable ? nameOf(property.name) : void 0;
@@ -2152,7 +2285,7 @@ function variableSymbols(collection, node) {
2152
2285
  void 0,
2153
2286
  exported(node) ? localName : collection.exportNames.get(localName)
2154
2287
  );
2155
- if (ts8.isObjectLiteralExpression(initializer))
2288
+ if (ts9.isObjectLiteralExpression(initializer))
2156
2289
  objectLiteralSymbols(
2157
2290
  collection,
2158
2291
  localName,
@@ -2162,7 +2295,7 @@ function variableSymbols(collection, node) {
2162
2295
  }
2163
2296
  }
2164
2297
  function collectClassDeclaration(collection, node) {
2165
- if (!ts8.isClassDeclaration(node) || !node.name) return false;
2298
+ if (!ts9.isClassDeclaration(node) || !node.name) return false;
2166
2299
  collection.declaredClasses.add(node.name.text);
2167
2300
  if (exported(node) || collection.exportNames.has(node.name.text))
2168
2301
  collection.exportedClasses.add(node.name.text);
@@ -2173,7 +2306,7 @@ function collectClassDeclaration(collection, node) {
2173
2306
  return true;
2174
2307
  }
2175
2308
  function collectMethodDeclaration(collection, node, parentClass) {
2176
- if (!ts8.isMethodDeclaration(node)) return false;
2309
+ if (!ts9.isMethodDeclaration(node)) return false;
2177
2310
  const localName = nameOf(node.name);
2178
2311
  if (localName)
2179
2312
  addExecutableSymbol(collection, "method", localName, node, parentClass);
@@ -2182,11 +2315,11 @@ function collectMethodDeclaration(collection, node, parentClass) {
2182
2315
  function visitDeclaredSymbol(collection, node, parentClass) {
2183
2316
  if (collectClassDeclaration(collection, node)) return;
2184
2317
  if (collectMethodDeclaration(collection, node, parentClass)) return;
2185
- if (ts8.isPropertyDeclaration(node)) {
2318
+ if (ts9.isPropertyDeclaration(node)) {
2186
2319
  if (parentClass) classPropertySymbol(collection, node, parentClass);
2187
2320
  return;
2188
2321
  }
2189
- if (ts8.isFunctionDeclaration(node) && node.name) {
2322
+ if (ts9.isFunctionDeclaration(node) && node.name) {
2190
2323
  addExecutableSymbol(
2191
2324
  collection,
2192
2325
  "function",
@@ -2197,17 +2330,17 @@ function visitDeclaredSymbol(collection, node, parentClass) {
2197
2330
  );
2198
2331
  return;
2199
2332
  }
2200
- if (ts8.isVariableStatement(node)) {
2333
+ if (ts9.isVariableStatement(node)) {
2201
2334
  variableSymbols(collection, node);
2202
2335
  return;
2203
2336
  }
2204
- ts8.forEachChild(node, (child) => visitDeclaredSymbol(collection, child, parentClass));
2337
+ ts9.forEachChild(node, (child) => visitDeclaredSymbol(collection, child, parentClass));
2205
2338
  }
2206
2339
  function collectDeclaredSymbols(collection) {
2207
2340
  visitDeclaredSymbol(collection, collection.source);
2208
2341
  }
2209
2342
  function isTopLevelCallback(node) {
2210
- if (!ts8.isArrowFunction(node) && !ts8.isFunctionExpression(node) || !ts8.isCallExpression(node.parent)) return false;
2343
+ if (!ts9.isArrowFunction(node) && !ts9.isFunctionExpression(node) || !ts9.isCallExpression(node.parent)) return false;
2211
2344
  const callee = symbolCallName(node.parent.expression);
2212
2345
  const member = callee.member ?? callee.local;
2213
2346
  return Boolean(member && [
@@ -2249,7 +2382,7 @@ function collectCallbackSymbols(collection) {
2249
2382
  }
2250
2383
  });
2251
2384
  }
2252
- ts8.forEachChild(node, visit);
2385
+ ts9.forEachChild(node, visit);
2253
2386
  };
2254
2387
  visit(collection.source);
2255
2388
  }
@@ -2279,12 +2412,12 @@ function populateCollection(collection) {
2279
2412
  async function sourceFile(repoPath, filePath, context) {
2280
2413
  const snapshot = context?.get(filePath);
2281
2414
  const text = snapshot?.text ?? await fs4.readFile(path4.join(repoPath, filePath), "utf8");
2282
- return snapshot?.sourceFile() ?? ts8.createSourceFile(
2415
+ return snapshot?.sourceFile() ?? ts9.createSourceFile(
2283
2416
  filePath,
2284
2417
  text,
2285
- ts8.ScriptTarget.Latest,
2418
+ ts9.ScriptTarget.Latest,
2286
2419
  true,
2287
- filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
2420
+ filePath.endsWith(".ts") ? ts9.ScriptKind.TS : ts9.ScriptKind.JS
2288
2421
  );
2289
2422
  }
2290
2423
  async function parseExecutableSymbols(repoPath, filePath, context, preparedOutboundCalls) {
@@ -2366,13 +2499,6 @@ function mergePackageSymbolEvidence(symbols, analysis) {
2366
2499
  });
2367
2500
  }
2368
2501
 
2369
- // src/utils/hashing.ts
2370
- import { createHash } from "crypto";
2371
- import { readFile } from "fs/promises";
2372
- function sha256Text(text) {
2373
- return createHash("sha256").update(text).digest("hex");
2374
- }
2375
-
2376
2502
  // src/db/package-target-invalidation.ts
2377
2503
  var resolverKeys = /* @__PURE__ */ new Set([
2378
2504
  "candidateStrategy",
@@ -2403,6 +2529,11 @@ function parsedEvidence(value) {
2403
2529
  function packageName(evidence) {
2404
2530
  return parsePackageImportReference(evidence.importBinding)?.requestedPackageName ?? void 0;
2405
2531
  }
2532
+ function eventPackageName(evidence) {
2533
+ return parsePackageImportReference(
2534
+ evidence.eventNameConstantImportBinding
2535
+ )?.requestedPackageName ?? void 0;
2536
+ }
2406
2537
  function packageCallEvidenceValid(evidence) {
2407
2538
  const binding = record(evidence.importBinding);
2408
2539
  const classified = evidence.relation === "package_import" || binding?.moduleKind === "package";
@@ -2439,6 +2570,54 @@ function pendingEvidence(evidence) {
2439
2570
  unresolvedReason: "package_resolution_pending"
2440
2571
  });
2441
2572
  }
2573
+ function currentEventCalls(db, workspaceId, targetRepoId) {
2574
+ const rows = db.prepare(`SELECT c.id,c.repo_id repoId,
2575
+ c.unresolved_reason unresolvedReason,c.evidence_json evidenceJson
2576
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
2577
+ WHERE r.workspace_id=? AND r.id<>? AND r.fact_analyzer_version=?
2578
+ AND json_extract(c.evidence_json,
2579
+ '$.eventNameConstantImportBinding.moduleKind')='package'
2580
+ ORDER BY c.id`).all(workspaceId, targetRepoId, ANALYZER_VERSION);
2581
+ return rows.flatMap((row) => {
2582
+ const evidence = parsedEvidence(row.evidenceJson);
2583
+ return evidence && eventPackageName(evidence) && typeof row.id === "number" && typeof row.repoId === "number" ? [{
2584
+ id: row.id,
2585
+ repoId: row.repoId,
2586
+ evidence,
2587
+ unresolvedReason: typeof row.unresolvedReason === "string" ? row.unresolvedReason : null
2588
+ }] : [];
2589
+ });
2590
+ }
2591
+ function pendingEventEvidence(evidence) {
2592
+ const parser = { ...evidence };
2593
+ delete parser.eventNameConstant;
2594
+ delete parser.eventNamePackageConstantResolution;
2595
+ parser.eventNameUnresolvedReason = "event_name_constant_resolution_pending";
2596
+ parser.eventNameStatus = "dynamic";
2597
+ parser.eventNameSourceKind = "dynamic_expression";
2598
+ parser.eventNamePlaceholderKeys = [];
2599
+ return JSON.stringify(parser);
2600
+ }
2601
+ function resetPackageEventCalls(db, workspaceId, targetRepoId, names, batch) {
2602
+ const update = db.prepare(`UPDATE outbound_calls SET event_name_expr=?,
2603
+ unresolved_reason=?,evidence_json=? WHERE id=?`);
2604
+ let matched = false;
2605
+ for (const call of currentEventCalls(db, workspaceId, targetRepoId)) {
2606
+ if (!names.has(eventPackageName(call.evidence) ?? "")) continue;
2607
+ const source = call.evidence.eventNameConstantSourceExpression;
2608
+ if (typeof source !== "string" || source.length === 0)
2609
+ throw new Error("invalid_current_package_event_constant_evidence");
2610
+ update.run(
2611
+ source,
2612
+ "event_name_constant_resolution_pending",
2613
+ pendingEventEvidence(call.evidence),
2614
+ call.id
2615
+ );
2616
+ batch.affectedCallerRepoIds.add(call.repoId);
2617
+ matched = true;
2618
+ }
2619
+ return matched;
2620
+ }
2442
2621
  function targetWorkspace(db, repoId) {
2443
2622
  const row = db.prepare(`SELECT workspace_id workspaceId,
2444
2623
  package_name packageName FROM repositories WHERE id=?`).get(repoId);
@@ -2473,6 +2652,13 @@ function invalidatePackageTargetFacts(db, targetRepoId, newPackageName, batch) {
2473
2652
  batch.affectedCallerRepoIds.add(call.repoId);
2474
2653
  matched = true;
2475
2654
  }
2655
+ matched = resetPackageEventCalls(
2656
+ db,
2657
+ target.workspaceId,
2658
+ targetRepoId,
2659
+ names,
2660
+ batch
2661
+ ) || matched;
2476
2662
  if (matched || packageIdentityChanged(
2477
2663
  target.packageName,
2478
2664
  newPackageName
@@ -2503,8 +2689,31 @@ function finalizePackageTargetInvalidations(db, batch) {
2503
2689
  stale.run(workspaceId);
2504
2690
  }
2505
2691
 
2692
+ // src/db/event-surface-invalidation.ts
2693
+ function priorEventFactCount(db, repoId) {
2694
+ const row = db.prepare(`SELECT COUNT(*) count FROM outbound_calls
2695
+ WHERE repo_id=? AND call_type IN ('async_emit','async_subscribe')`).get(
2696
+ repoId
2697
+ );
2698
+ return Number(row?.count ?? 0);
2699
+ }
2700
+ function repositoryEnvironment(db, repoId) {
2701
+ const row = db.prepare(`SELECT environment_declarations_json value
2702
+ FROM repositories WHERE id=?`).get(repoId);
2703
+ return typeof row?.value === "string" ? row.value : null;
2704
+ }
2705
+ function invalidateEventSurfaceFacts(db, repoId, calls, nextEnvironmentJson) {
2706
+ const hasNewEvents = calls.some((call) => call.callType === "async_emit" || call.callType === "async_subscribe");
2707
+ const environmentChanged = repositoryEnvironment(db, repoId) !== nextEnvironmentJson;
2708
+ if (!hasNewEvents && priorEventFactCount(db, repoId) === 0 && !environmentChanged) return;
2709
+ db.prepare(`UPDATE repositories SET
2710
+ graph_stale_reason='event_surface_facts_changed',
2711
+ graph_stale_at=datetime('now')
2712
+ WHERE workspace_id=(SELECT workspace_id FROM repositories WHERE id=?)`).run(repoId);
2713
+ }
2714
+
2506
2715
  // src/indexer/repository-indexer.ts
2507
- async function prepareRepositoryIndex(repo, force, instrumentation) {
2716
+ async function prepareRepositoryIndex(repo, force, instrumentation, eventEnvironmentKeys) {
2508
2717
  const sourceFiles = await findSourceFiles(repo.absolute_path);
2509
2718
  const packageSnapshot = await loadPackageJsonSnapshot(repo.absolute_path, {
2510
2719
  strict: true,
@@ -2516,13 +2725,23 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
2516
2725
  sourceFiles,
2517
2726
  instrumentation
2518
2727
  );
2728
+ const environmentDeclarations = collectEnvironmentDeclarations(
2729
+ sources,
2730
+ eventEnvironmentKeys
2731
+ );
2519
2732
  const fingerprint = repositoryFingerprint(
2520
2733
  sources,
2521
2734
  packageFacts,
2522
- packageSnapshot.rawText
2735
+ packageSnapshot.rawText,
2736
+ environmentDeclarations
2523
2737
  );
2524
2738
  if (!force && repo.fingerprint === fingerprint) return { repo, fileCount: 0, diagnosticCount: 0, skipped: true };
2525
- const parsedFacts = await parseAllSourceFacts(repo.absolute_path, sources);
2739
+ const parsedFacts = await parseAllSourceFacts(
2740
+ repo.absolute_path,
2741
+ sources,
2742
+ environmentDeclarations,
2743
+ eventEnvironmentKeys
2744
+ );
2526
2745
  const packageSurface = analyzeRepositoryPackageSurface(
2527
2746
  packageFacts,
2528
2747
  packageSnapshot.manifest,
@@ -2539,6 +2758,7 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
2539
2758
  kind: await classifyRepository(repo.absolute_path, packageFacts),
2540
2759
  parsed,
2541
2760
  packagePublicSurface: packageSurface.surface,
2761
+ environmentDeclarations,
2542
2762
  fileCount: sourceFiles.length,
2543
2763
  diagnosticCount: parsed.handlers.filter((handler) => handler.hasHandlerDecorator && (handler.methods.length === 0 || handler.methods.some((method) => !handlerMethodIsExecutable(method)))).length,
2544
2764
  skipped: false
@@ -2546,23 +2766,32 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
2546
2766
  }
2547
2767
  function publishPreparedRepositoryIndex(db, prepared, invalidations) {
2548
2768
  if (prepared.skipped) return;
2549
- if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface)
2769
+ if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface || !prepared.environmentDeclarations)
2550
2770
  throw new Error("Prepared repository index is missing publication facts");
2551
2771
  const now = (/* @__PURE__ */ new Date()).toISOString();
2552
2772
  const repoId = prepared.repo.id;
2773
+ const environmentJson = JSON.stringify(prepared.environmentDeclarations);
2553
2774
  invalidatePackageTargetFacts(
2554
2775
  db,
2555
2776
  repoId,
2556
2777
  prepared.packageFacts.packageName,
2557
2778
  invalidations
2558
2779
  );
2780
+ invalidateEventSurfaceFacts(
2781
+ db,
2782
+ repoId,
2783
+ prepared.parsed.calls,
2784
+ environmentJson
2785
+ );
2559
2786
  db.prepare(`UPDATE repositories SET package_name=?, package_version=?,
2560
- dependencies_json=?,package_public_surface_json=?,kind=?,index_status=?
2787
+ dependencies_json=?,package_public_surface_json=?,
2788
+ environment_declarations_json=?,kind=?,index_status=?
2561
2789
  WHERE id=?`).run(
2562
2790
  prepared.packageFacts.packageName,
2563
2791
  prepared.packageFacts.packageVersion,
2564
2792
  JSON.stringify(prepared.packageFacts.dependencies),
2565
2793
  JSON.stringify(prepared.packagePublicSurface),
2794
+ environmentJson,
2566
2795
  prepared.kind,
2567
2796
  "indexing",
2568
2797
  repoId
@@ -2578,6 +2807,7 @@ function publishPreparedRepositoryIndex(db, prepared, invalidations) {
2578
2807
  insertRegistrations(db, repoId, prepared.parsed.registrations);
2579
2808
  insertBindings(db, repoId, prepared.parsed.bindings);
2580
2809
  insertCalls(db, repoId, prepared.parsed.calls);
2810
+ insertGeneratedConstants(db, repoId, prepared.parsed.generatedConstants);
2581
2811
  db.prepare("UPDATE repositories SET last_indexed_at=?, index_status='indexed', error_count=0, fingerprint=?, fact_generation=COALESCE(fact_generation,0)+1, graph_stale_reason='facts_changed', graph_stale_at=?, fact_analyzer_version=? WHERE id=?").run(now, prepared.fingerprint, now, ANALYZER_VERSION, repoId);
2582
2812
  }
2583
2813
  function publishOneRepository(db, prepared, invalidations) {
@@ -2619,15 +2849,33 @@ function recordIndexFailure(db, repoId, error) {
2619
2849
  )`).run(repoId);
2620
2850
  db.prepare("INSERT INTO diagnostics(repo_id,severity,code,message) VALUES(?,?,?,?)").run(repoId, "error", "source_read_failed", `Index failed before publication; previous facts and fingerprint were preserved. ${message}`);
2621
2851
  }
2622
- async function parseAllSourceFacts(root, sources) {
2623
- const facts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], fileRecords: [] };
2852
+ async function parseAllSourceFacts(root, sources, environmentDeclarations, eventEnvironmentKeys) {
2853
+ const facts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], generatedConstants: [], fileRecords: [] };
2624
2854
  for (const snapshot of sources.entries()) {
2625
2855
  const file = snapshot.filePath;
2626
- facts.fileRecords.push({ relativePath: normalizePath(file), extension: path5.extname(file), sha256: sha256Text(snapshot.text), sizeBytes: snapshot.sizeBytes });
2856
+ facts.fileRecords.push({
2857
+ relativePath: normalizePath(file),
2858
+ extension: path5.extname(file),
2859
+ sha256: sourceFactHash(snapshot, environmentDeclarations),
2860
+ sizeBytes: snapshot.sizeBytes
2861
+ });
2627
2862
  if (file.endsWith(".cds")) facts.services.push(...await parseCdsFile(root, file, sources));
2628
2863
  if (/\.[jt]s$/.test(file)) {
2629
2864
  const source = snapshot.sourceFile();
2630
- const classified = classifyOutboundCallsInSource(source, file);
2865
+ facts.generatedConstants.push(...generatedConstantFacts(source, file));
2866
+ const classified = classifyOutboundCallsInSource(source, file, {
2867
+ importedEventNameResolver: createImportedEventNameResolver(
2868
+ sources,
2869
+ source,
2870
+ file
2871
+ ),
2872
+ eventEnvironmentReferenceResolver: createEventEnvironmentReferenceResolver(
2873
+ sources,
2874
+ source,
2875
+ file,
2876
+ eventEnvironmentKeys
2877
+ )
2878
+ });
2631
2879
  facts.handlers.push(...await parseDecorators(root, file, sources));
2632
2880
  facts.registrations.push(...await parseHandlerRegistrations(root, file, sources));
2633
2881
  const bindings = await parseServiceBindings(root, file, sources);
@@ -2668,18 +2916,21 @@ async function findSourceFiles(root) {
2668
2916
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
2669
2917
  if (e.isDirectory()) {
2670
2918
  if (!["node_modules", "dist", "gen", "coverage", ".git"].includes(e.name)) await walk(path5.join(dir, e.name), rel);
2671
- } else if (/\.(cds|ts|js)$/.test(e.name) && !isDefaultTestFile(rel)) out.push(rel);
2919
+ } else if (isRepositoryFactInput(e.name) && !isDefaultTestFile(rel)) out.push(rel);
2672
2920
  }
2673
2921
  }
2674
2922
  await walk(root);
2675
2923
  return out.sort();
2676
2924
  }
2925
+ function isRepositoryFactInput(name) {
2926
+ return /\.(cds|ts|js)$/.test(name) || ["nodemon.json", ".env", "mta.yaml", "manifest.yml"].includes(name);
2927
+ }
2677
2928
  function isDefaultTestFile(relativeFile) {
2678
2929
  const parts = relativeFile.split("/");
2679
2930
  if (parts.some((part) => ["test", "tests", "__tests__"].includes(part))) return true;
2680
2931
  return /\.(test|spec)\.[jt]s$/.test(parts.at(-1) ?? "");
2681
2932
  }
2682
- function repositoryFingerprint(sources, facts, packageJsonText) {
2933
+ function repositoryFingerprint(sources, facts, packageJsonText, environmentDeclarations) {
2683
2934
  const normalizedFacts = {
2684
2935
  analyzerVersion: ANALYZER_VERSION,
2685
2936
  packageName: facts.packageName,
@@ -2688,13 +2939,33 @@ function repositoryFingerprint(sources, facts, packageJsonText) {
2688
2939
  cdsRequires: [...facts.cdsRequires].sort((a, b) => a.alias.localeCompare(b.alias)),
2689
2940
  scripts: Object.fromEntries(Object.entries(facts.scripts).sort()),
2690
2941
  includeTests: false,
2942
+ eventEnvironmentKeys: environmentDeclarations.allowedKeys,
2691
2943
  packageJsonHash: sha256Text(packageJsonText)
2692
2944
  };
2693
2945
  const entries = [`facts:${JSON.stringify(normalizedFacts)}`];
2694
2946
  for (const snapshot of sources.entries())
2695
- entries.push(`${snapshot.filePath}:${sha256Text(snapshot.text)}`);
2947
+ entries.push(
2948
+ `${snapshot.filePath}:${sourceFactHash(snapshot, environmentDeclarations)}`
2949
+ );
2696
2950
  return sha256Text(entries.join("\n"));
2697
2951
  }
2952
+ function sourceFactHash(snapshot, environmentDeclarations) {
2953
+ if (!isEnvironmentFactInput(snapshot.filePath))
2954
+ return sha256Text(snapshot.text);
2955
+ const declarations = environmentDeclarations.declarations.filter(
2956
+ (item) => item.sourceFile === snapshot.filePath
2957
+ );
2958
+ return sha256Text(JSON.stringify({
2959
+ allowedKeys: environmentDeclarations.allowedKeys,
2960
+ declarations
2961
+ }));
2962
+ }
2963
+ function isEnvironmentFactInput(filePath) {
2964
+ const name = filePath.split("/").at(-1);
2965
+ return ["nodemon.json", ".env", "mta.yaml", "manifest.yml"].includes(
2966
+ name ?? ""
2967
+ );
2968
+ }
2698
2969
 
2699
2970
  // src/indexer/cds-extension-resolver.ts
2700
2971
  function materializeCdsExtensionOperations(db, workspaceId, excludedRepoIds = /* @__PURE__ */ new Set()) {
@@ -2874,7 +3145,12 @@ async function indexWorkspace(db, workspaceId, options) {
2874
3145
  rows: []
2875
3146
  };
2876
3147
  try {
2877
- await prepareRepositories(repos, options.force, state);
3148
+ await prepareRepositories(
3149
+ repos,
3150
+ options.force,
3151
+ state,
3152
+ options.eventEnvironmentKeys
3153
+ );
2878
3154
  return publishPreparedWorkspaceRows(
2879
3155
  db,
2880
3156
  workspaceId,
@@ -2901,10 +3177,15 @@ function selectedRepositories(db, workspaceId, repoName) {
2901
3177
  );
2902
3178
  return repos;
2903
3179
  }
2904
- async function prepareRepositories(repos, force, state) {
3180
+ async function prepareRepositories(repos, force, state, eventEnvironmentKeys) {
2905
3181
  for (const repo of repos) {
2906
3182
  state.activeRepoId = repo.id;
2907
- const result = await prepareRepositoryIndex(repo, force);
3183
+ const result = await prepareRepositoryIndex(
3184
+ repo,
3185
+ force,
3186
+ void 0,
3187
+ eventEnvironmentKeys
3188
+ );
2908
3189
  state.rows.push(result);
2909
3190
  state.fileCount += result.fileCount;
2910
3191
  state.diagnosticCount += result.diagnosticCount;
@@ -3249,6 +3530,392 @@ function symbolCallQuality(db) {
3249
3530
  };
3250
3531
  }
3251
3532
 
3533
+ // src/cli/doctor-event-quality.ts
3534
+ var eventNameReason = `COALESCE(
3535
+ json_extract(c.evidence_json,'$.eventNameUnresolvedReason'),
3536
+ CASE WHEN c.unresolved_reason GLOB 'dynamic_event_name_*'
3537
+ OR c.unresolved_reason GLOB 'event_name_*'
3538
+ THEN c.unresolved_reason END
3539
+ )`;
3540
+ var receiverReason = `CASE
3541
+ WHEN json_extract(c.evidence_json,
3542
+ '$.receiverClassification')='name_fallback'
3543
+ THEN COALESCE(json_extract(c.evidence_json,
3544
+ '$.receiverFallbackRefusedReason'),'name_fallback')
3545
+ WHEN json_extract(c.evidence_json,
3546
+ '$.receiverClassification')='unproven'
3547
+ THEN COALESCE(json_extract(c.evidence_json,
3548
+ '$.receiverUnresolvedReason'),'unproven')
3549
+ ELSE 'missing' END`;
3550
+ function workspacePredicate(alias) {
3551
+ return `(? IS NULL OR ${alias}.workspace_id=?)`;
3552
+ }
3553
+ function count2(value) {
3554
+ return Number(value ?? 0);
3555
+ }
3556
+ function unresolvedEventNameExamples(db, workspaceId) {
3557
+ return db.prepare(`SELECT r.name repositoryName,
3558
+ c.source_file sourceFile,c.source_line sourceLine,
3559
+ c.event_name_expr eventName,${eventNameReason} reason
3560
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
3561
+ WHERE c.call_type='async_emit' AND ${workspacePredicate("r")}
3562
+ AND ${eventNameReason} IS NOT NULL
3563
+ ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
3564
+ c.source_line,c.id LIMIT 5`).all(
3565
+ workspaceId,
3566
+ workspaceId
3567
+ );
3568
+ }
3569
+ function eventNameResolutionQuality(db, workspaceId) {
3570
+ const aggregate = db.prepare(`SELECT COUNT(*) total,
3571
+ SUM(CASE WHEN reason IS NOT NULL THEN 1 ELSE 0 END) unresolved
3572
+ FROM (SELECT ${eventNameReason} reason
3573
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
3574
+ WHERE c.call_type='async_emit' AND ${workspacePredicate("r")})`).get(
3575
+ workspaceId,
3576
+ workspaceId
3577
+ );
3578
+ const reasons = db.prepare(`SELECT ${eventNameReason} reason,COUNT(*) count
3579
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
3580
+ WHERE c.call_type='async_emit' AND ${workspacePredicate("r")}
3581
+ AND ${eventNameReason} IS NOT NULL
3582
+ GROUP BY reason ORDER BY count DESC,reason COLLATE BINARY LIMIT 16`).all(
3583
+ workspaceId,
3584
+ workspaceId
3585
+ );
3586
+ const unresolved = count2(aggregate?.unresolved);
3587
+ const reasonCount = count2(db.prepare(`SELECT COUNT(DISTINCT reason) count
3588
+ FROM (SELECT ${eventNameReason} reason
3589
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
3590
+ WHERE c.call_type='async_emit' AND ${workspacePredicate("r")}
3591
+ AND ${eventNameReason} IS NOT NULL)`).get(
3592
+ workspaceId,
3593
+ workspaceId
3594
+ )?.count);
3595
+ return {
3596
+ severity: unresolved > 0 ? "warning" : "info",
3597
+ code: "strict_event_name_resolution_quality",
3598
+ message: "Event publication name-resolution aggregate",
3599
+ publicationTotal: count2(aggregate?.total),
3600
+ unresolvedPublicationCount: unresolved,
3601
+ reasonBuckets: reasons,
3602
+ reasonBucketCount: reasonCount,
3603
+ shownReasonBucketCount: reasons.length,
3604
+ omittedReasonBucketCount: Math.max(0, reasonCount - reasons.length),
3605
+ examples: unresolvedEventNameExamples(db, workspaceId),
3606
+ exampleCount: unresolved
3607
+ };
3608
+ }
3609
+ function dynamicEventExamples(db, workspaceId) {
3610
+ return db.prepare(`SELECT r.name repositoryName,c.call_type callType,
3611
+ c.source_file sourceFile,c.source_line sourceLine,
3612
+ e.to_kind targetKind,e.to_id targetId,e.unresolved_reason reason
3613
+ FROM graph_edges e JOIN outbound_calls c
3614
+ ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
3615
+ JOIN repositories r ON r.id=c.repo_id
3616
+ WHERE c.call_type IN ('async_emit','async_subscribe')
3617
+ AND ${workspacePredicate("r")}
3618
+ AND (e.to_kind='event_candidate'
3619
+ OR e.edge_type='DYNAMIC_EDGE_CANDIDATE')
3620
+ ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
3621
+ c.source_line,c.id LIMIT 5`).all(
3622
+ workspaceId,
3623
+ workspaceId
3624
+ );
3625
+ }
3626
+ function dynamicEventQuality(db, workspaceId) {
3627
+ const row = db.prepare(`SELECT
3628
+ COUNT(DISTINCT CASE WHEN e.to_kind='event_candidate'
3629
+ THEN e.workspace_id || ':' || e.to_id END) eventCandidateNodeCount,
3630
+ COUNT(*) dynamicEventEdgeCount,
3631
+ SUM(CASE WHEN json_array_length(e.evidence_json,
3632
+ '$.eventTemplateResolution.placeholders')>0 THEN 1 ELSE 0 END)
3633
+ variableRecoverableCount
3634
+ FROM graph_edges e JOIN outbound_calls c
3635
+ ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
3636
+ JOIN repositories r ON r.id=c.repo_id
3637
+ WHERE c.call_type IN ('async_emit','async_subscribe')
3638
+ AND ${workspacePredicate("r")}
3639
+ AND (e.to_kind='event_candidate'
3640
+ OR e.edge_type='DYNAMIC_EDGE_CANDIDATE')`).get(
3641
+ workspaceId,
3642
+ workspaceId
3643
+ );
3644
+ const total = count2(row?.dynamicEventEdgeCount);
3645
+ const recoverable = count2(row?.variableRecoverableCount);
3646
+ return {
3647
+ severity: total > 0 ? "warning" : "info",
3648
+ code: "strict_event_dynamic_candidate_quality",
3649
+ message: "Dynamic event candidate aggregate",
3650
+ eventCandidateNodeCount: count2(row?.eventCandidateNodeCount),
3651
+ dynamicEventEdgeCount: total,
3652
+ eventCandidateCount: total,
3653
+ variableRecoverableCount: recoverable,
3654
+ nonVariableRecoverableCount: Math.max(0, total - recoverable),
3655
+ examples: dynamicEventExamples(db, workspaceId),
3656
+ exampleCount: total
3657
+ };
3658
+ }
3659
+ function unmatchedPublicationRows(db, workspaceId) {
3660
+ return db.prepare(`SELECT r.name repositoryName,c.source_file sourceFile,
3661
+ c.source_line sourceLine,e.to_id eventName
3662
+ FROM graph_edges e JOIN outbound_calls c
3663
+ ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
3664
+ JOIN repositories r ON r.id=c.repo_id
3665
+ WHERE c.call_type='async_emit' AND e.edge_type='HANDLER_EMITS_EVENT'
3666
+ AND e.to_kind='event' AND ${workspacePredicate("r")}
3667
+ AND NOT EXISTS (SELECT 1 FROM graph_edges subscription
3668
+ WHERE subscription.workspace_id=e.workspace_id
3669
+ AND subscription.generation=e.generation
3670
+ AND subscription.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
3671
+ AND subscription.from_kind='event'
3672
+ AND subscription.from_id=e.to_id)
3673
+ ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
3674
+ c.source_line,c.id LIMIT 5`).all(
3675
+ workspaceId,
3676
+ workspaceId
3677
+ );
3678
+ }
3679
+ function unmatchedPublicationQuality(db, workspaceId) {
3680
+ const row = db.prepare(`SELECT COUNT(*) count
3681
+ FROM graph_edges e JOIN outbound_calls c
3682
+ ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER)
3683
+ JOIN repositories r ON r.id=c.repo_id
3684
+ WHERE c.call_type='async_emit' AND e.edge_type='HANDLER_EMITS_EVENT'
3685
+ AND e.to_kind='event' AND ${workspacePredicate("r")}
3686
+ AND NOT EXISTS (SELECT 1 FROM graph_edges subscription
3687
+ WHERE subscription.workspace_id=e.workspace_id
3688
+ AND subscription.generation=e.generation
3689
+ AND subscription.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
3690
+ AND subscription.from_kind='event'
3691
+ AND subscription.from_id=e.to_id)`).get(workspaceId, workspaceId);
3692
+ const total = count2(row?.count);
3693
+ return {
3694
+ severity: total > 0 ? "warning" : "info",
3695
+ code: "strict_event_publication_without_subscription_quality",
3696
+ message: "Resolved event publications without a matching subscription",
3697
+ unmatchedPublicationCount: total,
3698
+ examples: unmatchedPublicationRows(db, workspaceId),
3699
+ exampleCount: total
3700
+ };
3701
+ }
3702
+ function unmatchedSubscriptionRows(db, workspaceId) {
3703
+ return db.prepare(`SELECT COALESCE(json_extract(e.evidence_json,
3704
+ '$.subscriptionConsumerRepositoryName'),
3705
+ json_extract(e.evidence_json,'$.repositoryName')) repositoryName,
3706
+ json_extract(e.evidence_json,'$.sourceFile') sourceFile,
3707
+ json_extract(e.evidence_json,'$.sourceLine') sourceLine,
3708
+ e.from_id eventName,
3709
+ json_extract(c.evidence_json,'$.subscriptionLoopRegistrationStatus')
3710
+ loopRegistrationStatus,
3711
+ json_extract(c.evidence_json,'$.subscriptionLoopRegistrationCount')
3712
+ loopRegistrationCount
3713
+ FROM graph_edges e LEFT JOIN outbound_calls c
3714
+ ON c.id=CAST(json_extract(e.evidence_json,'$.subscribeCallId') AS INTEGER)
3715
+ WHERE e.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
3716
+ AND ${workspacePredicate("e")}
3717
+ AND NOT EXISTS (SELECT 1 FROM graph_edges publication
3718
+ WHERE publication.workspace_id=e.workspace_id
3719
+ AND publication.generation=e.generation
3720
+ AND publication.edge_type='HANDLER_EMITS_EVENT'
3721
+ AND publication.to_kind='event'
3722
+ AND publication.to_id=e.from_id)
3723
+ ORDER BY repositoryName COLLATE BINARY,sourceFile COLLATE BINARY,
3724
+ sourceLine,e.id LIMIT 5`).all(
3725
+ workspaceId,
3726
+ workspaceId
3727
+ );
3728
+ }
3729
+ function unmatchedSubscriptionQuality(db, workspaceId) {
3730
+ const row = db.prepare(`SELECT
3731
+ COUNT(DISTINCT json_extract(e.evidence_json,'$.subscribeCallId'))
3732
+ siteCount,
3733
+ SUM(CASE
3734
+ WHEN json_extract(e.evidence_json,'$.materializedLoopEventName')
3735
+ IS NOT NULL THEN 1
3736
+ WHEN json_extract(c.evidence_json,
3737
+ '$.subscriptionLoopRegistrationStatus')='enumerated'
3738
+ THEN CAST(json_extract(c.evidence_json,
3739
+ '$.subscriptionLoopRegistrationCount') AS INTEGER)
3740
+ WHEN json_extract(c.evidence_json,
3741
+ '$.subscriptionLoopRegistrationStatus')='unresolved' THEN 0
3742
+ ELSE 1 END) count,
3743
+ SUM(CASE WHEN json_extract(c.evidence_json,
3744
+ '$.subscriptionLoopRegistrationStatus')='unresolved'
3745
+ THEN 1 ELSE 0 END) unknownMultiplicityEdgeCount
3746
+ FROM graph_edges e LEFT JOIN outbound_calls c
3747
+ ON c.id=CAST(json_extract(e.evidence_json,'$.subscribeCallId') AS INTEGER)
3748
+ WHERE e.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
3749
+ AND ${workspacePredicate("e")}
3750
+ AND NOT EXISTS (SELECT 1 FROM graph_edges publication
3751
+ WHERE publication.workspace_id=e.workspace_id
3752
+ AND publication.generation=e.generation
3753
+ AND publication.edge_type='HANDLER_EMITS_EVENT'
3754
+ AND publication.to_kind='event'
3755
+ AND publication.to_id=e.from_id)`).get(workspaceId, workspaceId);
3756
+ const total = count2(row?.count);
3757
+ const siteCount = count2(row?.siteCount);
3758
+ const unknownSites = unknownMultiplicitySiteCount(db, workspaceId);
3759
+ return {
3760
+ severity: siteCount > 0 ? "warning" : "info",
3761
+ code: "strict_event_subscription_without_publication_quality",
3762
+ message: "Event subscriptions without a matching publication",
3763
+ unmatchedSubscriptionCount: total,
3764
+ unmatchedSubscriptionSiteCount: siteCount,
3765
+ unknownMultiplicitySiteCount: unknownSites,
3766
+ examples: unmatchedSubscriptionRows(db, workspaceId),
3767
+ exampleCount: siteCount
3768
+ };
3769
+ }
3770
+ function unknownMultiplicitySiteCount(db, workspaceId) {
3771
+ const row = db.prepare(`SELECT COUNT(DISTINCT
3772
+ json_extract(e.evidence_json,'$.subscribeCallId')) count
3773
+ FROM graph_edges e LEFT JOIN outbound_calls c
3774
+ ON c.id=CAST(json_extract(e.evidence_json,'$.subscribeCallId') AS INTEGER)
3775
+ WHERE e.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
3776
+ AND ${workspacePredicate("e")}
3777
+ AND json_extract(c.evidence_json,
3778
+ '$.subscriptionLoopRegistrationStatus')='unresolved'
3779
+ AND NOT EXISTS (SELECT 1 FROM graph_edges publication
3780
+ WHERE publication.workspace_id=e.workspace_id
3781
+ AND publication.generation=e.generation
3782
+ AND publication.edge_type='HANDLER_EMITS_EVENT'
3783
+ AND publication.to_kind='event'
3784
+ AND publication.to_id=e.from_id)`).get(workspaceId, workspaceId);
3785
+ return count2(row?.count);
3786
+ }
3787
+ function receiverProofAggregate(db, workspaceId) {
3788
+ return db.prepare(`SELECT COUNT(*) eventTotal,
3789
+ SUM(CASE WHEN json_extract(c.evidence_json,
3790
+ '$.receiverClassification')='cap_evidence' THEN 1 ELSE 0 END) proven,
3791
+ SUM(CASE WHEN json_extract(c.evidence_json,
3792
+ '$.receiverClassification')='name_fallback' THEN 1 ELSE 0 END)
3793
+ nameFallback,
3794
+ SUM(CASE WHEN json_extract(c.evidence_json,
3795
+ '$.receiverClassification')='unproven' THEN 1 ELSE 0 END) unproven
3796
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
3797
+ WHERE c.call_type IN ('async_emit','async_subscribe')
3798
+ AND ${workspacePredicate("r")}`).get(workspaceId, workspaceId);
3799
+ }
3800
+ function receiverReasonBuckets(db, workspaceId) {
3801
+ return db.prepare(`SELECT ${receiverReason} reason,COUNT(*) count
3802
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
3803
+ WHERE c.call_type IN ('async_emit','async_subscribe')
3804
+ AND ${workspacePredicate("r")}
3805
+ AND json_extract(c.evidence_json,'$.receiverClassification')
3806
+ <>'cap_evidence'
3807
+ GROUP BY reason ORDER BY count DESC,reason COLLATE BINARY LIMIT 16`).all(
3808
+ workspaceId,
3809
+ workspaceId
3810
+ );
3811
+ }
3812
+ function receiverReasonBucketCount(db, workspaceId) {
3813
+ const row = db.prepare(`SELECT COUNT(DISTINCT reason) count
3814
+ FROM (SELECT ${receiverReason} reason
3815
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
3816
+ WHERE c.call_type IN ('async_emit','async_subscribe')
3817
+ AND ${workspacePredicate("r")}
3818
+ AND json_extract(c.evidence_json,'$.receiverClassification')
3819
+ <>'cap_evidence')`).get(workspaceId, workspaceId);
3820
+ return count2(row?.count);
3821
+ }
3822
+ function receiverProofQuality(db, workspaceId) {
3823
+ const row = receiverProofAggregate(db, workspaceId);
3824
+ const buckets = receiverReasonBuckets(db, workspaceId);
3825
+ const bucketCount = receiverReasonBucketCount(db, workspaceId);
3826
+ const questionable = count2(row?.nameFallback) + count2(row?.unproven);
3827
+ return {
3828
+ severity: questionable > 0 ? "warning" : "info",
3829
+ code: "strict_event_receiver_classification_quality",
3830
+ message: "CAP event receiver proof aggregate",
3831
+ eventTotal: count2(row?.eventTotal),
3832
+ proven: count2(row?.proven),
3833
+ nameFallback: count2(row?.nameFallback),
3834
+ unproven: count2(row?.unproven),
3835
+ questionable,
3836
+ reasonBuckets: buckets,
3837
+ reasonBucketCount: bucketCount,
3838
+ shownReasonBucketCount: buckets.length,
3839
+ omittedReasonBucketCount: Math.max(0, bucketCount - buckets.length),
3840
+ examples: receiverProofExamples(db, workspaceId),
3841
+ exampleCount: questionable
3842
+ };
3843
+ }
3844
+ function receiverProofExamples(db, workspaceId) {
3845
+ return db.prepare(`SELECT r.name repositoryName,c.call_type callType,
3846
+ c.source_file sourceFile,c.source_line sourceLine,
3847
+ json_extract(c.evidence_json,'$.receiverClassification')
3848
+ receiverClassification,
3849
+ COALESCE(json_extract(c.evidence_json,'$.receiverUnresolvedReason'),
3850
+ json_extract(c.evidence_json,'$.receiverFallbackRefusedReason')) reason,
3851
+ json_array_length(json_extract(c.evidence_json,
3852
+ '$.consideredBindingSites')) consideredBindingSiteCount
3853
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
3854
+ WHERE c.call_type IN ('async_emit','async_subscribe')
3855
+ AND ${workspacePredicate("r")}
3856
+ AND json_extract(c.evidence_json,'$.receiverClassification')
3857
+ <>'cap_evidence'
3858
+ ORDER BY r.name COLLATE BINARY,c.source_file COLLATE BINARY,
3859
+ c.source_line,c.id LIMIT 5`).all(
3860
+ workspaceId,
3861
+ workspaceId
3862
+ );
3863
+ }
3864
+ function shapeEnvironmentExamples(db, workspaceId) {
3865
+ return db.prepare(`SELECT e.edge_type edgeType,e.from_id fromId,
3866
+ e.to_kind toKind,e.to_id toId,
3867
+ json_extract(e.evidence_json,
3868
+ '$.eventEnvironmentResolution.status') environmentStatus,
3869
+ json_extract(e.evidence_json,
3870
+ '$.eventEnvironmentResolution.collisionCount') collisionCount
3871
+ FROM graph_edges e WHERE ${workspacePredicate("e")}
3872
+ AND (e.edge_type='EVENT_SHAPE_CANDIDATE_SUBSCRIBER'
3873
+ OR json_extract(e.evidence_json,
3874
+ '$.eventEnvironmentResolution.status')='ambiguous'
3875
+ OR CAST(json_extract(e.evidence_json,
3876
+ '$.eventEnvironmentResolution.collisionCount') AS INTEGER)>1)
3877
+ ORDER BY e.edge_type COLLATE BINARY,e.from_id COLLATE BINARY,
3878
+ e.to_id COLLATE BINARY,e.id LIMIT 5`).all(
3879
+ workspaceId,
3880
+ workspaceId
3881
+ );
3882
+ }
3883
+ function shapeEnvironmentQuality(db, workspaceId) {
3884
+ const row = db.prepare(`SELECT
3885
+ SUM(CASE WHEN e.edge_type='EVENT_SHAPE_CANDIDATE_SUBSCRIBER'
3886
+ THEN 1 ELSE 0 END) shapeCandidates,
3887
+ SUM(CASE WHEN json_extract(e.evidence_json,
3888
+ '$.eventEnvironmentResolution.status')='ambiguous'
3889
+ OR CAST(json_extract(e.evidence_json,
3890
+ '$.eventEnvironmentResolution.collisionCount') AS INTEGER)>1
3891
+ THEN 1 ELSE 0 END) environmentAmbiguities
3892
+ FROM graph_edges e WHERE ${workspacePredicate("e")}`).get(
3893
+ workspaceId,
3894
+ workspaceId
3895
+ );
3896
+ const shapes = count2(row?.shapeCandidates);
3897
+ const ambiguous = count2(row?.environmentAmbiguities);
3898
+ return {
3899
+ severity: shapes + ambiguous > 0 ? "warning" : "info",
3900
+ code: "strict_event_shape_environment_quality",
3901
+ message: "Event skeleton candidate and environment ambiguity aggregate",
3902
+ skeletonCandidateCount: shapes,
3903
+ environmentBindingAmbiguityCount: ambiguous,
3904
+ examples: shapeEnvironmentExamples(db, workspaceId),
3905
+ exampleCount: shapes + ambiguous
3906
+ };
3907
+ }
3908
+ function eventSurfaceQualityDiagnostics(db, workspaceId) {
3909
+ return [
3910
+ eventNameResolutionQuality(db, workspaceId),
3911
+ dynamicEventQuality(db, workspaceId),
3912
+ unmatchedPublicationQuality(db, workspaceId),
3913
+ unmatchedSubscriptionQuality(db, workspaceId),
3914
+ receiverProofQuality(db, workspaceId),
3915
+ shapeEnvironmentQuality(db, workspaceId)
3916
+ ];
3917
+ }
3918
+
3252
3919
  // src/cli/doctor.ts
3253
3920
  function doctorDiagnostics(db, strict, options = {}) {
3254
3921
  const lifecycle = factLifecycleDiagnostic(db, options.workspaceId);
@@ -3387,7 +4054,7 @@ function parserQualityDiagnostics(db, strict, options) {
3387
4054
  externalHttpTargetQuality(db),
3388
4055
  odataInvocationResolutionQuality(db),
3389
4056
  ...jsonEvidenceQuality(db),
3390
- eventReceiverQuality(db),
4057
+ ...eventSurfaceQualityDiagnostics(db, options.workspaceId),
3391
4058
  graphDynamicFlagQuality(db),
3392
4059
  symbol,
3393
4060
  dbq,
@@ -3422,10 +4089,6 @@ function outboundOwnershipQuality(db) {
3422
4089
  const ratio = total === 0 ? 0 : Number((without / total).toFixed(4));
3423
4090
  return { severity: ratio > 0.01 ? "warning" : "info", code: "strict_outbound_source_ownership_quality", message: "Outbound call source-symbol ownership aggregate", total, withoutOwnership: without, withoutOwnershipRatio: ratio, withoutOwnershipRatioThreshold: 0.01, ownerlessByType: byType, ownerlessExamples: examples };
3424
4091
  }
3425
- function eventReceiverQuality(db) {
3426
- const row = db.prepare("SELECT SUM(CASE WHEN call_type IN ('async_emit','async_subscribe') THEN 1 ELSE 0 END) eventTotal, SUM(CASE WHEN call_type IN ('async_emit','async_subscribe') AND (json_extract(evidence_json,'$.receiverClassification') IS NULL OR json_extract(evidence_json,'$.receiverClassification') <> 'cap_evidence') THEN 1 ELSE 0 END) questionable FROM outbound_calls").get();
3427
- return { severity: Number(row.questionable ?? 0) > 0 ? "warning" : "info", code: "strict_event_receiver_classification_quality", message: "CAP event receiver classification aggregate", eventTotal: Number(row.eventTotal ?? 0), questionable: Number(row.questionable ?? 0) };
3428
- }
3429
4092
  function graphDynamicFlagQuality(db) {
3430
4093
  const row = db.prepare("SELECT COUNT(*) count FROM graph_edges WHERE status='terminal' AND is_dynamic=1").get();
3431
4094
  return { severity: Number(row.count ?? 0) > 0 ? "warning" : "info", code: "strict_graph_dynamic_flag_consistency", message: "Graph dynamic flag consistency aggregate", dynamicTerminalEdges: Number(row.count ?? 0) };
@@ -3861,7 +4524,7 @@ function location(evidence) {
3861
4524
  function renderTraceTable(result) {
3862
4525
  const lines = ["Step Type From To Evidence"];
3863
4526
  for (const e of result.edges) {
3864
- lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${e.to.slice(0, 35).padEnd(36)} ${location(e.evidence)}`);
4527
+ lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${e.to.slice(0, 35).padEnd(36)} ${evidenceSummary(e.evidence)}`);
3865
4528
  if (e.unresolvedReason)
3866
4529
  lines.push(...hintLines(e.evidence).map((hint) => ` ${hint}`));
3867
4530
  }
@@ -3869,6 +4532,13 @@ function renderTraceTable(result) {
3869
4532
  return `${lines.join("\n")}
3870
4533
  `;
3871
4534
  }
4535
+ function evidenceSummary(evidence) {
4536
+ const labels = [
4537
+ typeof evidence.dispatchScope === "string" ? `scope=${evidence.dispatchScope}` : void 0,
4538
+ typeof evidence.dispatchCertainty === "string" ? `certainty=${evidence.dispatchCertainty}` : void 0
4539
+ ].filter((value) => Boolean(value));
4540
+ return labels.length > 0 ? `${location(evidence)} [${labels.join(",")}]` : location(evidence);
4541
+ }
3872
4542
  function diagnosticLines(diagnostic) {
3873
4543
  const first = `${String(diagnostic.severity ?? "info")} ${String(diagnostic.code ?? "diagnostic")} ${String(diagnostic.message ?? "")}`;
3874
4544
  const details = diagnosticDetailLines(diagnostic);
@@ -3906,8 +4576,8 @@ function hintLines(evidence) {
3906
4576
  }
3907
4577
  function dynamicHintLines(evidence) {
3908
4578
  const exploration = isRecord(evidence.dynamicTargetExploration) ? evidence.dynamicTargetExploration : evidence;
3909
- const count2 = numberValue(exploration.candidateCount);
3910
- if (count2 === 0) return [];
4579
+ const count3 = numberValue(exploration.candidateCount);
4580
+ if (count3 === 0) return [];
3911
4581
  const shown = numberValue(exploration.shownCandidateCount);
3912
4582
  const omitted = numberValue(exploration.omittedCandidateCount);
3913
4583
  const rejected = numberValue(exploration.rejectedCandidateCount);
@@ -3915,7 +4585,7 @@ function dynamicHintLines(evidence) {
3915
4585
  `viable candidates: ${shown} shown, ${omitted} omitted; rejected: ${rejected}`
3916
4586
  ];
3917
4587
  lines.push(...varSetHints(exploration.suggestedVarSets));
3918
- if (omitted > 0 || rejected > 0 || shown < count2)
4588
+ if (omitted > 0 || rejected > 0 || shown < count3)
3919
4589
  lines.push("use --dynamic-mode candidates --max-dynamic-candidates 20 to explore candidate branches");
3920
4590
  return lines;
3921
4591
  }
@@ -3985,9 +4655,9 @@ function diagnosticLocation(diagnostic) {
3985
4655
  }
3986
4656
  function compactMessage(diagnostic) {
3987
4657
  const message = String(diagnostic.message ?? "");
3988
- const count2 = typeof diagnostic.count === "number" ? ` count=${diagnostic.count}` : "";
4658
+ const count3 = typeof diagnostic.count === "number" ? ` count=${diagnostic.count}` : "";
3989
4659
  const total = typeof diagnostic.total === "number" ? ` total=${diagnostic.total}` : "";
3990
- return `${message}${count2}${total}`.trim();
4660
+ return `${message}${count3}${total}`.trim();
3991
4661
  }
3992
4662
  function suggestedHintLines(diagnostic) {
3993
4663
  const direct = cliHints(diagnostic.suggestedHints);
@@ -4102,6 +4772,15 @@ function renderCompactJson(value) {
4102
4772
  `;
4103
4773
  }
4104
4774
 
4775
+ // src/output/repository-inspection.ts
4776
+ function projectRepositoryInspection(repository) {
4777
+ return Object.fromEntries(
4778
+ Object.entries(repository).filter(
4779
+ ([key]) => key !== "environment_declarations_json"
4780
+ )
4781
+ );
4782
+ }
4783
+
4105
4784
  // src/cli/clean.ts
4106
4785
  import fs6 from "fs/promises";
4107
4786
  import path6 from "path";
@@ -4215,7 +4894,7 @@ async function withWorkspace(workspace, fn) {
4215
4894
  try {
4216
4895
  const row = getWorkspace(db, config.rootPath);
4217
4896
  const workspaceId = row?.id ?? upsertWorkspace(db, config.rootPath, config.dbPath);
4218
- return await fn(db, workspaceId, config.rootPath);
4897
+ return await fn(db, workspaceId, config.rootPath, config);
4219
4898
  } finally {
4220
4899
  db.close();
4221
4900
  }
@@ -4330,10 +5009,11 @@ function registerInitCommand(program) {
4330
5009
  }
4331
5010
  function registerIndexCommand(program) {
4332
5011
  program.command("index").option("--workspace <path>").option("--repo <name>").option("--force").action(
4333
- (opts) => void withWorkspace(opts.workspace, async (db, workspaceId) => {
5012
+ (opts) => void withWorkspace(opts.workspace, async (db, workspaceId, _root, config) => {
4334
5013
  const r = await indexWorkspace(db, workspaceId, {
4335
5014
  repo: opts.repo,
4336
- force: Boolean(opts.force)
5015
+ force: Boolean(opts.force),
5016
+ eventEnvironmentKeys: config.eventEnvironmentKeys
4337
5017
  });
4338
5018
  const outcome = indexCommandOutcome(r);
4339
5019
  writeStdout(outcome.stdout);
@@ -4348,7 +5028,7 @@ function registerLinkCommand(program) {
4348
5028
  const upgradeWarnings = linkUpgradeWarnings(db, workspaceId);
4349
5029
  writeStdout(
4350
5030
  `${upgradeWarnings.length ? `Warnings: ${upgradeWarnings.map((item) => String(item.code)).join(", ")}. Run service-flow doctor --strict for remediation.
4351
- ` : ""}Linked ${r.edgeCount} edges: ${r.remoteResolvedCount} remote operation calls resolved, ${r.localResolvedCount} local operation calls resolved, ${r.unresolvedCount} unresolved operation calls, ${r.ambiguousCount} ambiguous operation calls, ${r.dynamicCount} dynamic operation calls, ${r.terminalCount} terminal call edges, ${r.dependencyResolvedCount} dependency resolved, ${r.dependencyAmbiguousCount} dependency ambiguous, ${r.implementationResolvedCount} implementation resolved, ${r.implementationAmbiguousCount} implementation ambiguous, ${r.implementationUnresolvedCount} implementation unresolved, ${r.subscriptionHandlerResolvedCount} subscription handlers resolved, ${r.subscriptionHandlerAmbiguousCount} subscription handlers ambiguous, ${r.subscriptionHandlerUnresolvedCount} subscription handlers unresolved, ${r.subscriptionHandlerMissingAssociationCount} subscription handler associations missing
5031
+ ` : ""}Linked ${r.edgeCount} edges: ${r.remoteResolvedCount} remote operation calls resolved, ${r.localResolvedCount} local operation calls resolved, ${r.unresolvedCount} unresolved operation calls, ${r.ambiguousCount} ambiguous operation calls, ${r.dynamicCount} dynamic operation calls, ${r.terminalCount} terminal call edges, ${r.dependencyResolvedCount} dependency resolved, ${r.dependencyAmbiguousCount} dependency ambiguous, ${r.implementationResolvedCount} implementation resolved, ${r.implementationAmbiguousCount} implementation ambiguous, ${r.implementationUnresolvedCount} implementation unresolved, ${r.subscriptionHandlerResolvedCount} subscription handlers resolved, ${r.subscriptionHandlerAmbiguousCount} subscription handlers ambiguous, ${r.subscriptionHandlerUnresolvedCount} subscription handlers unresolved, ${r.subscriptionHandlerMissingAssociationCount} subscription handler associations missing, ${r.eventShapeCandidateCount} event shape candidates, ${r.eventShapeCandidateOmittedCount} event shape candidates omitted by the link cap
4352
5032
  `
4353
5033
  );
4354
5034
  }).catch(fail)
@@ -4358,18 +5038,20 @@ function registerTraceCommand(program) {
4358
5038
  program.command("trace").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--handler <name>").option("--depth <n>", "trace depth", "25").addOption(traceFormatOption()).option("--include-external").option("--include-db").option("--include-async").option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).option("--dynamic-mode <mode>", "strict|candidates|infer", "strict").option("--max-dynamic-candidates <n>", "maximum dynamic candidates to show", "5").action((opts) => void runTraceCommand(opts).catch(fail));
4359
5039
  }
4360
5040
  function listRepositoriesCommand(opts) {
4361
- return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => writeStdout(
4362
- renderJson(
5041
+ return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
5042
+ if (writeLifecycleBlock(db, workspaceId)) return;
5043
+ writeStdout(renderJson(
4363
5044
  listRepositories(db, workspaceId).map((repo) => ({
4364
5045
  name: repo.name,
4365
5046
  kind: repo.kind,
4366
5047
  packageName: repo.package_name
4367
5048
  }))
4368
- )
4369
- ));
5049
+ ));
5050
+ });
4370
5051
  }
4371
5052
  function listServicesCommand(opts) {
4372
5053
  return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
5054
+ if (writeLifecycleBlock(db, workspaceId)) return;
4373
5055
  const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
4374
5056
  if (selection.diagnostic) {
4375
5057
  writeStdout(renderJson([selection.diagnostic]));
@@ -4384,6 +5066,7 @@ function listServicesCommand(opts) {
4384
5066
  }
4385
5067
  function listOperationsCommand(opts) {
4386
5068
  return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
5069
+ if (writeLifecycleBlock(db, workspaceId)) return;
4387
5070
  const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
4388
5071
  if (selection.diagnostic) {
4389
5072
  writeStdout(renderJson([selection.diagnostic]));
@@ -4404,6 +5087,7 @@ function listOperationsCommand(opts) {
4404
5087
  }
4405
5088
  function listCallsCommand(opts) {
4406
5089
  return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
5090
+ if (writeLifecycleBlock(db, workspaceId)) return;
4407
5091
  const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
4408
5092
  if (selection.diagnostic) {
4409
5093
  writeStdout(renderJson([selection.diagnostic]));
@@ -4444,20 +5128,29 @@ function registerGraphCommand(program) {
4444
5128
  }
4445
5129
  function inspectRepositoryCommand(name, opts) {
4446
5130
  return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
5131
+ if (writeLifecycleBlock(db, workspaceId)) return;
4447
5132
  const selection = selectRepository(db, name, workspaceId);
4448
5133
  writeStdout(renderJson(
4449
- selection.repo ?? selection.diagnostic ?? { error: "repo not found" }
5134
+ selection.repo ? projectRepositoryInspection(selection.repo) : selection.diagnostic ?? { error: "repo not found" }
4450
5135
  ));
4451
5136
  });
4452
5137
  }
4453
5138
  function inspectOperationCommand(selector, opts) {
4454
5139
  return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
5140
+ if (writeLifecycleBlock(db, workspaceId)) return;
4455
5141
  const rows = db.prepare(
4456
5142
  "SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)"
4457
5143
  ).all(workspaceId, selector, selector);
4458
5144
  writeStdout(renderJson(rows));
4459
5145
  });
4460
5146
  }
5147
+ function writeLifecycleBlock(db, workspaceId) {
5148
+ const diagnostic = factLifecycleDiagnostic(db, workspaceId);
5149
+ if (!diagnostic) return false;
5150
+ writeStdout(renderJson([diagnostic]));
5151
+ process.exitCode = 1;
5152
+ return true;
5153
+ }
4461
5154
  function registerInspectCommands(program) {
4462
5155
  const inspect = program.command("inspect");
4463
5156
  inspect.command("repo").argument("<name>").option("--workspace <path>").action(