@saptools/service-flow 0.1.66 → 0.1.68

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 (80) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +20 -8
  3. package/TECHNICAL-NOTE.md +27 -0
  4. package/dist/{chunk-TOVX4WYH.js → chunk-AEM4JY22.js} +12676 -6714
  5. package/dist/chunk-AEM4JY22.js.map +1 -0
  6. package/dist/cli.js +2325 -413
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +61 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/003-doctor-package-resolution.ts +68 -0
  12. package/src/cli/doctor.ts +25 -14
  13. package/src/db/000-call-fact-repository.ts +475 -342
  14. package/src/db/001-fact-lifecycle.ts +60 -30
  15. package/src/db/002-fact-json-inventory.ts +169 -0
  16. package/src/db/003-current-fact-semantics.ts +698 -0
  17. package/src/db/004-package-target-invalidation.ts +173 -0
  18. package/src/db/005-schema-structure.ts +201 -0
  19. package/src/db/006-relative-symbol-resolution.ts +443 -0
  20. package/src/db/007-package-fact-semantics.ts +573 -0
  21. package/src/db/008-relative-fact-semantics.ts +207 -0
  22. package/src/db/009-binding-fact-semantics.ts +347 -0
  23. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  24. package/src/db/011-symbol-call-semantics.ts +144 -0
  25. package/src/db/012-binding-reference-proof.ts +264 -0
  26. package/src/db/migrations.ts +16 -3
  27. package/src/db/repositories.ts +113 -6
  28. package/src/db/schema.ts +4 -2
  29. package/src/index.ts +12 -0
  30. package/src/indexer/repository-indexer.ts +72 -11
  31. package/src/indexer/workspace-indexer.ts +123 -32
  32. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  33. package/src/linker/004-event-subscription-handler-linker.ts +2 -0
  34. package/src/linker/005-odata-path-structure.ts +371 -0
  35. package/src/linker/cross-repo-linker.ts +2 -1
  36. package/src/linker/odata-path-normalizer.ts +273 -180
  37. package/src/linker/service-resolver.ts +197 -77
  38. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  39. package/src/parsers/003-package-public-surface.ts +661 -0
  40. package/src/parsers/004-fact-identity.ts +108 -0
  41. package/src/parsers/005-event-subscription-facts.ts +281 -0
  42. package/src/parsers/006-binding-identity.ts +343 -0
  43. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  44. package/src/parsers/008-package-surface-publication.ts +82 -0
  45. package/src/parsers/009-symbol-call-facts.ts +528 -0
  46. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  47. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  48. package/src/parsers/012-package-fact-contract.ts +306 -0
  49. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  50. package/src/parsers/014-service-binding-helper-flow.ts +240 -0
  51. package/src/parsers/015-service-binding-collector.ts +693 -0
  52. package/src/parsers/016-local-symbol-reference.ts +261 -0
  53. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  54. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  55. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  56. package/src/parsers/020-stable-local-value.ts +217 -0
  57. package/src/parsers/021-binding-visibility.ts +152 -0
  58. package/src/parsers/operation-path-analysis.ts +6 -1
  59. package/src/parsers/outbound-call-parser.ts +19 -6
  60. package/src/parsers/package-json-parser.ts +45 -3
  61. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  62. package/src/parsers/service-binding-parser.ts +147 -597
  63. package/src/parsers/symbol-parser.ts +482 -353
  64. package/src/trace/002-trace-diagnostics.ts +36 -1
  65. package/src/trace/016-compact-projector.ts +16 -17
  66. package/src/trace/020-compact-field-projection.ts +48 -37
  67. package/src/trace/021-compact-decision-normalization.ts +105 -0
  68. package/src/trace/022-trace-fact-preflight.ts +21 -0
  69. package/src/trace/023-nested-event-scopes.ts +23 -0
  70. package/src/trace/024-compact-observation-decision.ts +76 -0
  71. package/src/trace/025-trace-implementation-scope.ts +123 -0
  72. package/src/trace/026-trace-start-scope.ts +335 -0
  73. package/src/trace/027-trace-scope-execution.ts +566 -0
  74. package/src/trace/028-trace-operation-execution.ts +336 -0
  75. package/src/trace/029-trace-start-implementation.ts +172 -0
  76. package/src/trace/trace-engine.ts +122 -624
  77. package/src/types.ts +56 -0
  78. package/src/utils/001-placeholders.ts +188 -10
  79. package/src/version.ts +1 -1
  80. package/dist/chunk-TOVX4WYH.js.map +0 -1
package/dist/cli.js CHANGED
@@ -2,26 +2,26 @@
2
2
  import {
3
3
  ANALYZER_VERSION,
4
4
  VERSION,
5
+ analyzePackagePublicSurface,
6
+ bindingSite,
5
7
  classifyODataPathIntent,
6
8
  classifyOutboundCallsInSource,
7
- clearRepoFacts,
9
+ collectSymbolImportBindings,
8
10
  compactTrace,
9
11
  containsSupportedOutboundCall,
12
+ createBindingLexicalIndex,
13
+ derivedMemberImportReference,
10
14
  discoverRepositories,
15
+ executableSymbolCandidates,
11
16
  factLifecycleDiagnostic,
12
- getWorkspace,
13
- handlerMethodIsExecutable,
17
+ identifierMatchesDeclaration,
14
18
  implementationHintSuggestions,
15
- insertBindings,
16
19
  insertCalls,
17
- insertExecutableSymbols,
18
- insertHandler,
19
- insertRegistrations,
20
- insertRequires,
21
- insertService,
22
20
  insertSymbolCalls,
21
+ lexicalIdentifierDeclaration,
22
+ lexicalIdentifierDeclarations,
23
+ lexicalScopeChain,
23
24
  linkWorkspace,
24
- listRepositories,
25
25
  loadPackageJsonSnapshot,
26
26
  loadRepositorySourceContext,
27
27
  migrate,
@@ -32,17 +32,19 @@ import {
32
32
  parseHandlerRegistrations,
33
33
  parseImplementationHint,
34
34
  parseOutboundCalls,
35
+ parsePackageImportReference,
35
36
  parsePackageJson,
36
37
  parseServiceBindings,
37
38
  parseVars,
38
39
  projectBoundedInOrder,
39
40
  redactValue,
40
- reposByName,
41
+ selectCallOwner,
42
+ selectVisibleBinding,
41
43
  selectorRepoAmbiguousDiagnostic,
42
- trace,
43
- upsertRepository,
44
- upsertWorkspace
45
- } from "./chunk-TOVX4WYH.js";
44
+ stableLocalValueReference,
45
+ symbolImportReference,
46
+ trace
47
+ } from "./chunk-AEM4JY22.js";
46
48
 
47
49
  // src/cli.ts
48
50
  import { Command, Option } from "commander";
@@ -209,6 +211,345 @@ function openReadOnlyDatabase(dbPath) {
209
211
  return openDatabase(dbPath, { readonly: true, migrate: false });
210
212
  }
211
213
 
214
+ // src/db/repositories.ts
215
+ function initialPackagePublicSurface(packageName2) {
216
+ return JSON.stringify({
217
+ schema: "service-flow/package-public-surface@1",
218
+ status: packageName2 ? "incomplete" : "not_applicable",
219
+ reason: packageName2 ? "package_surface_pending_index" : null,
220
+ recordCap: 256,
221
+ total: 0,
222
+ shown: 0,
223
+ omitted: 0,
224
+ packageName: packageName2 ?? null,
225
+ exportsPresent: false,
226
+ exportsAuthoritative: false,
227
+ main: null,
228
+ module: null,
229
+ entries: [],
230
+ scopes: []
231
+ });
232
+ }
233
+ function upsertWorkspace(db, rootPath, dbPath) {
234
+ const now = (/* @__PURE__ */ new Date()).toISOString();
235
+ db.prepare(
236
+ "INSERT INTO workspaces(root_path,db_path,created_at,updated_at) VALUES(?,?,?,?) ON CONFLICT(root_path) DO UPDATE SET db_path=excluded.db_path,updated_at=excluded.updated_at"
237
+ ).run(rootPath, dbPath, now, now);
238
+ return Number(
239
+ db.prepare("SELECT id FROM workspaces WHERE root_path=?").get(rootPath)?.id
240
+ );
241
+ }
242
+ function getWorkspace(db, rootPath) {
243
+ return db.prepare("SELECT * FROM workspaces WHERE root_path=?").get(rootPath);
244
+ }
245
+ function upsertRepository(db, workspaceId, r) {
246
+ db.prepare(
247
+ `INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
248
+ package_name,package_version,dependencies_json,
249
+ package_public_surface_json,kind,is_git_repo)
250
+ VALUES(?,?,?,?,?,?,?,?,?,?)
251
+ ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET
252
+ name=excluded.name,relative_path=excluded.relative_path,
253
+ package_name=excluded.package_name,
254
+ package_version=excluded.package_version,
255
+ dependencies_json=excluded.dependencies_json,kind=excluded.kind`
256
+ ).run(
257
+ workspaceId,
258
+ r.name,
259
+ r.absolutePath,
260
+ r.relativePath,
261
+ r.packageName,
262
+ r.packageVersion,
263
+ JSON.stringify(r.dependencies ?? {}),
264
+ initialPackagePublicSurface(r.packageName),
265
+ r.kind ?? "unknown",
266
+ r.isGitRepo ? 1 : 0
267
+ );
268
+ return Number(
269
+ db.prepare(
270
+ "SELECT id FROM repositories WHERE workspace_id=? AND absolute_path=?"
271
+ ).get(workspaceId, r.absolutePath)?.id
272
+ );
273
+ }
274
+ function listRepositories(db, workspaceId) {
275
+ return db.prepare("SELECT * FROM repositories WHERE (? IS NULL OR workspace_id=?) ORDER BY name,absolute_path,id").all(workspaceId, workspaceId);
276
+ }
277
+ function reposByName(db, name, workspaceId) {
278
+ return db.prepare(`SELECT * FROM repositories
279
+ WHERE (? IS NULL OR workspace_id=?) AND (name=? OR package_name=?)
280
+ ORDER BY name,absolute_path,id`).all(workspaceId, workspaceId, name, name);
281
+ }
282
+ function clearRepoFacts(db, repoId) {
283
+ for (const t of [
284
+ "cds_requires",
285
+ "cds_services",
286
+ "handler_classes",
287
+ "outbound_calls",
288
+ "symbol_calls",
289
+ "handler_registrations",
290
+ "service_bindings",
291
+ "symbols",
292
+ "diagnostics",
293
+ "files"
294
+ ])
295
+ db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
296
+ db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
297
+ }
298
+ function insertRequires(db, repoId, rows) {
299
+ const stmt = db.prepare(
300
+ "INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
301
+ );
302
+ for (const r of rows)
303
+ stmt.run(
304
+ repoId,
305
+ r.alias,
306
+ r.kind,
307
+ r.model,
308
+ r.destination,
309
+ r.servicePath,
310
+ r.requestTimeout,
311
+ r.rawJson
312
+ );
313
+ }
314
+ function insertService(db, repoId, s) {
315
+ const id = Number(
316
+ db.prepare(
317
+ "INSERT INTO cds_services(repo_id,namespace,service_name,qualified_name,service_path,is_extend,source_file,source_line,extension_local_ref,extension_imported_symbol,extension_local_alias,extension_module_specifier,extension_import_kind) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id"
318
+ ).get(
319
+ repoId,
320
+ s.namespace,
321
+ s.serviceName,
322
+ s.qualifiedName,
323
+ s.servicePath,
324
+ s.isExtend ? 1 : 0,
325
+ s.sourceFile,
326
+ s.sourceLine,
327
+ s.extension?.localReference,
328
+ s.extension?.importedSymbol,
329
+ s.extension?.localAlias,
330
+ s.extension?.moduleSpecifier,
331
+ s.extension?.importKind
332
+ )?.id
333
+ );
334
+ const stmt = db.prepare(
335
+ "INSERT INTO cds_operations(service_id,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line,provenance,base_operation_id) VALUES(?,?,?,?,?,?,?,?,?,?)"
336
+ );
337
+ db.prepare(
338
+ "INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
339
+ ).run("service", s.qualifiedName, s.servicePath, String(repoId));
340
+ for (const o of s.operations)
341
+ stmt.run(
342
+ id,
343
+ o.operationType,
344
+ o.operationName,
345
+ o.operationPath,
346
+ o.paramsJson,
347
+ o.returnType,
348
+ o.sourceFile,
349
+ o.sourceLine,
350
+ o.provenance ?? "direct",
351
+ o.baseOperationId ?? null
352
+ );
353
+ const search = db.prepare(
354
+ "INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
355
+ );
356
+ for (const o of s.operations)
357
+ search.run("operation", o.operationName, o.operationPath, String(repoId));
358
+ return id;
359
+ }
360
+ function insertHandler(db, repoId, h) {
361
+ const sid = insertHandlerClassSymbol(db, repoId, h);
362
+ const hid = Number(
363
+ db.prepare(
364
+ "INSERT INTO handler_classes(repo_id,symbol_id,class_name,source_file,source_line) VALUES(?,?,?,?,?) RETURNING id"
365
+ ).get(repoId, sid, h.className, h.sourceFile, h.sourceLine)?.id
366
+ );
367
+ const stmt = db.prepare(
368
+ "INSERT INTO handler_methods(handler_class_id,method_name,decorator_kind,decorator_value,decorator_raw_expression,decorator_resolution_json,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
369
+ );
370
+ for (const m of h.methods)
371
+ stmt.run(
372
+ hid,
373
+ m.methodName,
374
+ m.decoratorKind,
375
+ m.decoratorValue,
376
+ m.decoratorRawExpression,
377
+ JSON.stringify(canonicalHandlerMethodResolution(m)),
378
+ m.sourceFile,
379
+ m.sourceLine
380
+ );
381
+ insertHandlerIndexDiagnostic(db, repoId, h);
382
+ return hid;
383
+ }
384
+ function insertHandlerClassSymbol(db, repoId, h) {
385
+ const classEvidence = {
386
+ hasHandlerDecorator: h.hasHandlerDecorator ?? false,
387
+ classDecoratorNames: h.classDecoratorNames ?? [],
388
+ observedDecoratorNames: h.observedDecoratorNames ?? [],
389
+ unsupportedDecoratorNames: h.unsupportedDecoratorNames ?? [],
390
+ unsupportedMethods: h.methods.filter((method) => !handlerMethodIsExecutable(method)).map((method) => ({
391
+ methodName: method.methodName,
392
+ decoratorKind: method.decoratorKind,
393
+ sourceFile: method.sourceFile,
394
+ sourceLine: method.sourceLine,
395
+ reason: method.decoratorResolution.unresolvedReason
396
+ }))
397
+ };
398
+ return Number(
399
+ db.prepare(
400
+ "INSERT INTO symbols(repo_id,kind,name,qualified_name,exported,start_line,end_line,source_file,evidence_json) VALUES(?,?,?,?,?,?,?,?,?) RETURNING id"
401
+ ).get(
402
+ repoId,
403
+ "class",
404
+ h.className,
405
+ h.className,
406
+ 1,
407
+ h.sourceLine,
408
+ h.sourceLine,
409
+ h.sourceFile,
410
+ JSON.stringify(classEvidence)
411
+ )?.id
412
+ );
413
+ }
414
+ function insertHandlerIndexDiagnostic(db, repoId, h) {
415
+ if (!h.hasHandlerDecorator) return;
416
+ const hasExecutable = h.methods.some(handlerMethodIsExecutable);
417
+ const unsupported = h.methods.filter((method) => !handlerMethodIsExecutable(method));
418
+ if (hasExecutable && unsupported.length === 0) return;
419
+ const code = hasExecutable ? "handler_decorators_not_indexed" : "handler_methods_not_indexed";
420
+ const names = unsupported.map((method) => method.decoratorKind).sort();
421
+ const detail = names.length > 0 ? ` Unsupported decorators: ${[...new Set(names)].join(", ")}.` : "";
422
+ db.prepare(
423
+ "INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line) VALUES(?,?,?,?,?,?)"
424
+ ).run(
425
+ repoId,
426
+ "warning",
427
+ code,
428
+ hasExecutable ? `Handler class ${h.className} contains methods that were not indexed.${detail}` : `Handler class ${h.className} has no indexed executable methods; use a supported CAP handler decorator and re-index.${detail}`,
429
+ h.sourceFile,
430
+ h.sourceLine
431
+ );
432
+ }
433
+ function canonicalHandlerMethodResolution(method) {
434
+ const handlerKind = method.handlerKind ?? method.decoratorResolution.handlerKind ?? legacyHandlerKind(method.decoratorKind);
435
+ const executable = method.executable ?? method.decoratorResolution.executable ?? (handlerKind === "operation" || handlerKind === "event" || handlerKind === "entity_lifecycle");
436
+ return {
437
+ ...method.decoratorResolution,
438
+ handlerKind,
439
+ executable,
440
+ lifecyclePhase: method.lifecyclePhase ?? method.decoratorResolution.lifecyclePhase,
441
+ lifecycleEvent: method.lifecycleEvent ?? method.decoratorResolution.lifecycleEvent
442
+ };
443
+ }
444
+ function handlerMethodIsExecutable(method) {
445
+ return canonicalHandlerMethodResolution(method).executable === true;
446
+ }
447
+ function legacyHandlerKind(kind) {
448
+ if (kind === "Event") return "event";
449
+ if (["Action", "Func", "On"].includes(kind)) return "operation";
450
+ return "unsupported_decorator";
451
+ }
452
+ function insertRegistrations(db, repoId, rows) {
453
+ const stmt = db.prepare(
454
+ "INSERT INTO handler_registrations(repo_id,handler_class_id,class_name,import_source,registration_file,registration_line,registration_kind,confidence) VALUES(?,?,?,?,?,?,?,?)"
455
+ );
456
+ for (const r of rows) {
457
+ const handlerClass = r.className ? db.prepare(
458
+ "SELECT id FROM handler_classes WHERE repo_id=? AND class_name=? ORDER BY id"
459
+ ).all(repoId, r.className) : [];
460
+ stmt.run(
461
+ repoId,
462
+ handlerClass.length === 1 ? handlerClass[0]?.id : null,
463
+ r.className,
464
+ r.importSource,
465
+ r.registrationFile,
466
+ r.registrationLine,
467
+ r.registrationKind,
468
+ r.confidence
469
+ );
470
+ }
471
+ }
472
+ function insertBindings(db, repoId, rows) {
473
+ assertUniquePreparedBindingSites(rows);
474
+ const stmt = db.prepare(
475
+ "INSERT INTO service_bindings(repo_id,symbol_id,variable_name,alias,alias_expr,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,binding_site_start_offset,binding_site_end_offset,owner_resolution,helper_chain_json) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
476
+ );
477
+ for (const r of rows)
478
+ stmt.run(
479
+ repoId,
480
+ bindingOwnerId(db, repoId, r),
481
+ r.variableName,
482
+ r.alias,
483
+ r.aliasExpr,
484
+ r.destinationExpr,
485
+ r.servicePathExpr,
486
+ r.isDynamic ? 1 : 0,
487
+ JSON.stringify(r.placeholders),
488
+ r.sourceFile,
489
+ r.sourceLine,
490
+ r.bindingSiteStartOffset,
491
+ r.bindingSiteEndOffset,
492
+ r.ownerResolution,
493
+ r.helperChain ? JSON.stringify(r.helperChain) : null
494
+ );
495
+ }
496
+ function persistedOwnerCandidates(db, repoId, fact) {
497
+ const rows = db.prepare(`SELECT id,kind,qualified_name qualifiedName,
498
+ start_offset startOffset,end_offset endOffset FROM symbols
499
+ WHERE repo_id=? AND source_file=? AND start_offset<=? AND end_offset>=?`).all(
500
+ repoId,
501
+ fact.sourceFile,
502
+ fact.bindingSiteStartOffset,
503
+ fact.bindingSiteEndOffset
504
+ );
505
+ return rows.flatMap((row) => typeof row.id === "number" && typeof row.kind === "string" && typeof row.qualifiedName === "string" && typeof row.startOffset === "number" && typeof row.endOffset === "number" ? [{
506
+ id: row.id,
507
+ kind: row.kind,
508
+ qualifiedName: row.qualifiedName,
509
+ startOffset: row.startOffset,
510
+ endOffset: row.endOffset
511
+ }] : []);
512
+ }
513
+ function bindingOwnerId(db, repoId, fact) {
514
+ const candidates = persistedOwnerCandidates(db, repoId, fact);
515
+ const selected = selectCallOwner(
516
+ candidates,
517
+ fact.bindingSiteStartOffset ?? -1,
518
+ fact.bindingSiteEndOffset ?? -1
519
+ );
520
+ if (fact.ownerResolution === "ownerless_file_scope") {
521
+ if (selected.status !== "none")
522
+ throw new Error("invalid_prepared_repository_snapshot:binding_owner_mismatch");
523
+ return null;
524
+ }
525
+ if (fact.ownerResolution !== "owned_exact" || selected.status !== "resolved" || selected.owner?.qualifiedName !== fact.sourceSymbolQualifiedName)
526
+ throw new Error("invalid_prepared_repository_snapshot:binding_owner_mismatch");
527
+ const owner = selected.owner;
528
+ if (!owner)
529
+ throw new Error("invalid_prepared_repository_snapshot:binding_owner_mismatch");
530
+ return owner.id;
531
+ }
532
+ function assertUniquePreparedBindingSites(rows) {
533
+ const seen = /* @__PURE__ */ new Set();
534
+ for (const row of rows) {
535
+ if (row.bindingSiteStartOffset === void 0 || row.bindingSiteEndOffset === void 0)
536
+ throw new Error("invalid_prepared_repository_snapshot:binding_site_missing");
537
+ const key = [
538
+ row.sourceFile,
539
+ row.variableName,
540
+ row.bindingSiteStartOffset,
541
+ row.bindingSiteEndOffset
542
+ ].join("\0");
543
+ if (seen.has(key))
544
+ throw new Error("invalid_prepared_repository_snapshot:duplicate_service_binding_site");
545
+ seen.add(key);
546
+ }
547
+ }
548
+ function insertExecutableSymbols(db, repoId, rows) {
549
+ const stmt = db.prepare("INSERT INTO symbols(repo_id,file_id,kind,name,qualified_name,exported,start_line,end_line,start_offset,end_offset,source_file,exported_name,evidence_json) VALUES(?,(SELECT id FROM files WHERE repo_id=? AND relative_path=?),?,?,?,?,?,?,?,?,?,?,?)");
550
+ for (const r of rows) stmt.run(repoId, repoId, r.sourceFile, r.kind, r.localName, r.qualifiedName, r.exported ? 1 : 0, r.startLine, r.endLine, r.startOffset, r.endOffset, r.sourceFile, r.exportedName, r.importExportEvidence ? JSON.stringify(r.importExportEvidence) : null);
551
+ }
552
+
212
553
  // src/discovery/classify-repository.ts
213
554
  import fs3 from "fs/promises";
214
555
  import path3 from "path";
@@ -258,91 +599,1039 @@ import path5 from "path";
258
599
  // src/parsers/symbol-parser.ts
259
600
  import fs4 from "fs/promises";
260
601
  import path4 from "path";
602
+ import ts8 from "typescript";
603
+
604
+ // src/parsers/005-event-subscription-facts.ts
605
+ import ts2 from "typescript";
606
+
607
+ // src/parsers/016-local-symbol-reference.ts
261
608
  import ts from "typescript";
262
- function lineOf(source, pos) {
263
- return source.getLineAndCharacterOfPosition(pos).line + 1;
609
+ function functionTargetNode(declaration) {
610
+ const parent = declaration.parent;
611
+ if (ts.isFunctionDeclaration(parent)) return parent;
612
+ if (!ts.isVariableDeclaration(parent)) return void 0;
613
+ const initializer = parent.initializer;
614
+ return initializer && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) ? initializer : void 0;
615
+ }
616
+ function declarationKey(identifier) {
617
+ return `${identifier.getStart(identifier.getSourceFile())}:${identifier.getEnd()}`;
618
+ }
619
+ function unwrapAssignmentTarget(expression) {
620
+ if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression) || ts.isTypeAssertionExpression(expression) || ts.isNonNullExpression(expression))
621
+ return unwrapAssignmentTarget(expression.expression);
622
+ return expression;
623
+ }
624
+ function objectAssignmentTargets(expression) {
625
+ return expression.properties.flatMap((property) => {
626
+ if (ts.isShorthandPropertyAssignment(property)) return [property.name];
627
+ if (ts.isPropertyAssignment(property))
628
+ return assignmentTargets(property.initializer);
629
+ return ts.isSpreadAssignment(property) ? assignmentTargets(property.expression) : [];
630
+ });
264
631
  }
265
- function nameOf(node) {
266
- if (!node) return void 0;
267
- if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
268
- return void 0;
632
+ function assignmentTargets(expression) {
633
+ const target = unwrapAssignmentTarget(expression);
634
+ if (ts.isIdentifier(target)) return [target];
635
+ if (ts.isSpreadElement(target))
636
+ return assignmentTargets(target.expression);
637
+ if (ts.isBinaryExpression(target) && target.operatorToken.kind === ts.SyntaxKind.EqualsToken)
638
+ return assignmentTargets(target.left);
639
+ if (ts.isArrayLiteralExpression(target))
640
+ return target.elements.flatMap((item) => ts.isExpression(item) ? assignmentTargets(item) : []);
641
+ return ts.isObjectLiteralExpression(target) ? objectAssignmentTargets(target) : [];
642
+ }
643
+ function assignmentOperator(kind) {
644
+ return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;
645
+ }
646
+ function mutationUnary(node) {
647
+ return node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken;
648
+ }
649
+ function binaryWritesDeclaration(node, matches) {
650
+ if (!ts.isBinaryExpression(node) || !assignmentOperator(node.operatorToken.kind)) return false;
651
+ return assignmentTargets(node.left).some(matches);
652
+ }
653
+ function unaryWritesDeclaration(node, matches) {
654
+ if (!ts.isPrefixUnaryExpression(node) && !ts.isPostfixUnaryExpression(node)) return false;
655
+ return mutationUnary(node) && ts.isIdentifier(node.operand) && matches(node.operand);
656
+ }
657
+ function loopWritesDeclaration(node, matches) {
658
+ if (!ts.isForInStatement(node) && !ts.isForOfStatement(node)) return false;
659
+ return !ts.isVariableDeclarationList(node.initializer) && assignmentTargets(node.initializer).some(matches);
660
+ }
661
+ function declarationWritten(source, declarations) {
662
+ const keys = new Set(declarations.map(declarationKey));
663
+ let written = false;
664
+ const matches = (identifier) => lexicalIdentifierDeclarations(identifier).some((item) => keys.has(declarationKey(item)));
665
+ const visit = (node) => {
666
+ if (written) return;
667
+ written = binaryWritesDeclaration(node, matches) || unaryWritesDeclaration(node, matches) || loopWritesDeclaration(node, matches);
668
+ if (!written) ts.forEachChild(node, visit);
669
+ };
670
+ visit(source);
671
+ return written;
269
672
  }
270
- function isFunctionLike(node) {
271
- return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node);
673
+ function singleImmutableDeclaration(declaration) {
674
+ const parent = declaration?.parent;
675
+ if (parent && ts.isClassDeclaration(parent)) return true;
676
+ return Boolean(parent && ts.isVariableDeclaration(parent) && ts.isVariableDeclarationList(parent.parent) && (parent.parent.flags & ts.NodeFlags.Const) !== 0);
272
677
  }
273
- function exported(node) {
274
- return Boolean(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export);
678
+ function immutableDeclarationSet(declarations, source) {
679
+ if (declarations.length === 0 || declarationWritten(source, declarations)) return false;
680
+ if (declarations.every((item) => ts.isFunctionDeclaration(item.parent)))
681
+ return true;
682
+ if (declarations.length !== 1) return false;
683
+ return singleImmutableDeclaration(declarations[0]);
275
684
  }
276
- function isPublicClassMethod(node) {
277
- const flags = ts.getCombinedModifierFlags(node);
278
- return (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
685
+ function propertyContainer(declaration) {
686
+ const parent = declaration.parent;
687
+ if (ts.isClassDeclaration(parent)) return parent;
688
+ if (!ts.isVariableDeclaration(parent)) return void 0;
689
+ const initializer = parent.initializer;
690
+ return initializer && (ts.isObjectLiteralExpression(initializer) || ts.isClassExpression(initializer)) ? initializer : void 0;
279
691
  }
280
- function exportDeclarations(source) {
281
- const exports = /* @__PURE__ */ new Map();
282
- const visit = (node) => {
283
- if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
284
- for (const el of node.exportClause.elements) exports.set((el.propertyName ?? el.name).text, el.name.text);
285
- }
286
- ts.forEachChild(node, visit);
692
+ function identity(symbol) {
693
+ return {
694
+ sourceFile: symbol.sourceFile,
695
+ qualifiedName: symbol.qualifiedName,
696
+ startOffset: symbol.startOffset,
697
+ endOffset: symbol.endOffset
287
698
  };
288
- visit(source);
289
- return exports;
290
699
  }
291
- function isRelativeImport(value) {
292
- return Boolean(value?.startsWith("."));
700
+ function executableBody(symbol) {
701
+ const value = symbol.importExportEvidence?.executableBodyEligibility;
702
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && "eligible" in value && value.eligible === true);
703
+ }
704
+ function exactFunctionTarget(declarations, symbols, source) {
705
+ if (!immutableDeclarationSet(declarations, source)) return void 0;
706
+ const spans = declarations.flatMap((declaration) => {
707
+ const target = functionTargetNode(declaration);
708
+ return target ? [{ start: target.getStart(source), end: target.getEnd() }] : [];
709
+ });
710
+ const matches = symbols.filter((symbol) => localFunctionMatches(symbol, spans, source.fileName));
711
+ return matches.length === 1 && matches[0] ? identity(matches[0]) : void 0;
293
712
  }
294
- function directHandlerReferenceTarget(expression, source, imports, namespaceImports) {
713
+ function localFunctionMatches(symbol, spans, sourceFile2) {
714
+ if (symbol.sourceFile !== sourceFile2 || !executableBody(symbol))
715
+ return false;
716
+ return spans.some((span) => symbol.startOffset === span.start && symbol.endOffset === span.end);
717
+ }
718
+ function exactPropertyTarget(expression, declaration, symbols, source) {
719
+ if (!immutableDeclarationSet([declaration], source) || !stableLocalValueReference(source, declaration))
720
+ return void 0;
721
+ const container = propertyContainer(declaration);
722
+ if (!container) return void 0;
723
+ const qualifiedName = `${declaration.text}.${expression.name.text}`;
724
+ const start = container.getStart(source);
725
+ const end = container.getEnd();
726
+ const matches = symbols.filter((symbol) => symbol.sourceFile === source.fileName && symbol.qualifiedName === qualifiedName && symbol.startOffset >= start && symbol.endOffset <= end && executableBody(symbol));
727
+ return matches.length === 1 && matches[0] ? identity(matches[0]) : void 0;
728
+ }
729
+ function localSymbolTarget(expression, source, symbols) {
295
730
  if (ts.isIdentifier(expression)) {
296
- const importSource2 = imports.get(expression.text);
731
+ return exactFunctionTarget(
732
+ lexicalIdentifierDeclarations(expression),
733
+ symbols,
734
+ source
735
+ );
736
+ }
737
+ if (!ts.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts.isIdentifier(expression.expression)) return void 0;
738
+ const declaration = lexicalIdentifierDeclaration(expression.expression);
739
+ return declaration ? exactPropertyTarget(expression, declaration, symbols, source) : void 0;
740
+ }
741
+
742
+ // src/parsers/005-event-subscription-facts.ts
743
+ function lineOf(source, node) {
744
+ return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
745
+ }
746
+ function lineAt(source, position) {
747
+ return source.getLineAndCharacterOfPosition(position).line + 1;
748
+ }
749
+ function importRelation(binding) {
750
+ if (binding.moduleKind === "package") return "package_import";
751
+ return binding.referenceShape === "namespace_member" ? "relative_import_namespace_member" : "relative_import";
752
+ }
753
+ function directTarget(expression, source, imports, symbols) {
754
+ const imported = symbolImportReference(expression, imports);
755
+ if (imported) return importedTarget(expression, source, imported);
756
+ if (ts2.isIdentifier(expression)) {
757
+ const exact = localSymbolTarget(expression, source, symbols);
297
758
  return {
298
759
  calleeExpression: expression.text,
299
760
  calleeLocalName: expression.text,
300
- importSource: importSource2,
301
- relation: importSource2 ? isRelativeImport(importSource2) ? "relative_import" : "package_import" : "indexed_local_symbol"
761
+ relation: exact ? "indexed_local_symbol" : "indexed_local_symbol_unproven",
762
+ referenceShape: "identifier",
763
+ localTargetIdentity: exact
302
764
  };
303
765
  }
304
- if (!ts.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts.isIdentifier(expression.expression) || !ts.isIdentifier(expression.name))
305
- return void 0;
306
- const objectName = expression.expression.text;
307
- const memberName = expression.name.text;
308
- const importSource = imports.get(objectName);
309
- if (namespaceImports.has(objectName)) return {
766
+ return propertyTarget(expression, source, symbols);
767
+ }
768
+ function importedTarget(expression, source, binding) {
769
+ return {
310
770
  calleeExpression: expression.getText(source),
311
- calleeLocalName: memberName,
312
- importSource,
313
- relation: "relative_import_namespace_member"
771
+ calleeLocalName: binding.requestedPublicName,
772
+ importSource: binding.rawModuleSpecifier,
773
+ relation: importRelation(binding),
774
+ referenceShape: binding.referenceShape,
775
+ importBinding: binding
314
776
  };
315
- const qualifiedName = `${objectName}.${memberName}`;
777
+ }
778
+ function propertyTarget(expression, source, symbols) {
779
+ if (!ts2.isPropertyAccessExpression(expression) || expression.questionDotToken || !ts2.isIdentifier(expression.expression)) return void 0;
780
+ const exact = localSymbolTarget(expression, source, symbols);
316
781
  return {
317
- calleeExpression: qualifiedName,
318
- calleeLocalName: qualifiedName,
319
- importSource,
320
- relation: importSource ? isRelativeImport(importSource) ? "relative_import" : "package_import" : "indexed_local_symbol"
782
+ calleeExpression: expression.getText(source),
783
+ calleeLocalName: `${expression.expression.text}.${expression.name.text}`,
784
+ relation: exact ? "indexed_local_symbol" : "indexed_local_symbol_unproven",
785
+ referenceShape: "static_member",
786
+ localTargetIdentity: exact
321
787
  };
322
788
  }
323
- function handlerReferenceTarget(expression, source, imports, namespaceImports) {
324
- const direct = directHandlerReferenceTarget(
325
- expression,
789
+ function unsupportedClassification(status, reason, referenceShape) {
790
+ return { status, reason, referenceShape };
791
+ }
792
+ function classifyHandlerReference(expression, source, imports = collectSymbolImportBindings(source), symbols = []) {
793
+ if (!expression)
794
+ return unsupportedClassification(
795
+ "missing_argument",
796
+ "handler_argument_missing",
797
+ "missing"
798
+ );
799
+ if (ts2.isArrowFunction(expression) || ts2.isFunctionExpression(expression))
800
+ return unsupportedClassification(
801
+ "unsupported_inline",
802
+ "inline_handler_body_not_indexed",
803
+ "inline_callback"
804
+ );
805
+ const direct = directTarget(expression, source, imports, symbols);
806
+ if (direct)
807
+ return { status: "role_required", referenceShape: direct.referenceShape, target: direct };
808
+ if (!ts2.isCallExpression(expression))
809
+ return unsupportedClassification(
810
+ "unsupported_reference_shape",
811
+ "handler_reference_shape_unsupported",
812
+ "unsupported_expression"
813
+ );
814
+ return classifyWrapperReference(expression, source, imports, symbols);
815
+ }
816
+ function classifyWrapperReference(expression, source, imports, symbols) {
817
+ if (expression.questionDotToken || expression.arguments.length !== 1)
818
+ return unsupportedClassification(
819
+ "unsupported_wrapper",
820
+ "wrapper_requires_one_reference",
821
+ "wrapper_call"
822
+ );
823
+ const inner = expression.arguments[0];
824
+ const target = inner ? directTarget(inner, source, imports, symbols) : void 0;
825
+ if (!target)
826
+ return unsupportedClassification(
827
+ "unsupported_wrapper",
828
+ "wrapper_reference_shape_unsupported",
829
+ "wrapper_call"
830
+ );
831
+ return {
832
+ status: "role_required",
833
+ referenceShape: `wrapped_${target.referenceShape}`,
834
+ target: { ...target, wrapperFunction: expression.expression.getText(source) }
835
+ };
836
+ }
837
+ function eventSymbol(source, classified) {
838
+ const node = classified.node;
839
+ const eventName = classified.fact.eventNameExpr ?? "";
840
+ const line = lineOf(source, node);
841
+ const safeName = eventName.replace(/[^A-Za-z0-9_$-]/g, "_");
842
+ const name = `event:${safeName}:${line}`;
843
+ const receiver = classified.fact.evidence?.receiver;
844
+ return {
845
+ kind: "event_registration",
846
+ localName: name,
847
+ qualifiedName: `module:${classified.fact.sourceFile}#${name}`,
848
+ sourceFile: classified.fact.sourceFile,
849
+ startLine: line,
850
+ endLine: lineAt(source, node.getEnd() - 1),
851
+ startOffset: node.getStart(source),
852
+ endOffset: node.getEnd(),
853
+ exported: false,
854
+ importExportEvidence: {
855
+ source: "synthetic_event_registration",
856
+ eventName,
857
+ registrationLine: line,
858
+ receiver: typeof receiver === "string" ? receiver : void 0
859
+ }
860
+ };
861
+ }
862
+ function eventCall(classified, owner, classification) {
863
+ const target = classification.target;
864
+ if (!target) return void 0;
865
+ return {
866
+ callerQualifiedName: owner.qualifiedName,
867
+ calleeExpression: target.calleeExpression,
868
+ calleeLocalName: target.calleeLocalName,
869
+ importSource: target.importSource,
870
+ sourceFile: classified.fact.sourceFile,
871
+ sourceLine: classified.fact.sourceLine,
872
+ callSiteStartOffset: classified.fact.callSiteStartOffset,
873
+ callSiteEndOffset: classified.fact.callSiteEndOffset,
874
+ callRole: "event_subscribe_handler",
875
+ evidence: {
876
+ relation: target.relation,
877
+ caller: owner.qualifiedName,
878
+ targetName: target.calleeLocalName,
879
+ referenceShape: classification.referenceShape,
880
+ ...target.importBinding ? { importBinding: target.importBinding } : {},
881
+ ...target.localTargetIdentity ? { localTargetIdentity: target.localTargetIdentity } : {},
882
+ ...target.wrapperFunction ? { wrapperFunction: target.wrapperFunction } : {},
883
+ factOrigin: "event_subscribe_handler_reference"
884
+ }
885
+ };
886
+ }
887
+ function enrichSubscription(source, classified, symbols) {
888
+ const classification = classifyHandlerReference(
889
+ classified.node.arguments[1],
326
890
  source,
327
- imports,
328
- namespaceImports
891
+ void 0,
892
+ symbols
329
893
  );
330
- if (direct) return direct;
331
- if (!ts.isCallExpression(expression) || expression.questionDotToken || expression.arguments.length !== 1) return void 0;
332
- const inner = directHandlerReferenceTarget(
333
- expression.arguments[0],
894
+ const evidence = {
895
+ ...classified.fact.evidence ?? {},
896
+ handlerReferenceStatus: classification.status,
897
+ ...classification.reason ? { handlerReferenceReason: classification.reason } : {},
898
+ handlerReferenceShape: classification.referenceShape
899
+ };
900
+ return {
901
+ classified: { ...classified, fact: { ...classified.fact, evidence } },
902
+ classification
903
+ };
904
+ }
905
+ function reconcileEventSubscriptions(source, classifications, symbols, calls) {
906
+ const subscriptions = classifications.filter(
907
+ (item) => item.fact.callType === "async_subscribe"
908
+ ).map((item) => enrichSubscription(source, item, symbols));
909
+ const eventSymbols = subscriptions.map((item) => eventSymbol(source, item.classified));
910
+ const eventCalls = subscriptions.flatMap((item, index) => {
911
+ const owner = eventSymbols[index];
912
+ const call = owner ? eventCall(item.classified, owner, item.classification) : void 0;
913
+ return call ? [call] : [];
914
+ });
915
+ return {
916
+ classifications: classifications.map((item) => subscriptions.find((candidate) => candidate.classified.node === item.node)?.classified ?? item),
917
+ symbols: [...symbols.filter((item) => item.kind !== "event_registration"), ...eventSymbols],
918
+ calls: [
919
+ ...calls.filter((item) => item.callRole !== "event_subscribe_handler"),
920
+ ...eventCalls
921
+ ]
922
+ };
923
+ }
924
+
925
+ // src/parsers/007-source-fact-reconciliation.ts
926
+ import "typescript";
927
+
928
+ // src/parsers/006-binding-identity.ts
929
+ import ts3 from "typescript";
930
+ var scopeChainCap = 16;
931
+ function matchFactsToSites(index, facts) {
932
+ return facts.map((fact) => {
933
+ if (fact.bindingSiteStartOffset === void 0 || fact.bindingSiteEndOffset === void 0)
934
+ throw new Error("invalid_prepared_repository_snapshot:binding_site_missing");
935
+ const site = bindingSite(
936
+ index,
937
+ fact.variableName,
938
+ fact.bindingSiteStartOffset,
939
+ fact.bindingSiteEndOffset
940
+ );
941
+ if (!site)
942
+ throw new Error("invalid_prepared_repository_snapshot:binding_site_invalid");
943
+ return { fact, site };
944
+ });
945
+ }
946
+ function exactOwner(fact, site, symbols) {
947
+ const candidates = executableSymbolCandidates(symbols, fact.sourceFile);
948
+ const owner = selectCallOwner(
949
+ candidates,
950
+ site.startOffset,
951
+ site.endOffset
952
+ );
953
+ if (owner.status === "ambiguous")
954
+ throw new Error("invalid_prepared_repository_snapshot:binding_owner_ambiguous");
955
+ return {
956
+ ...fact,
957
+ bindingSiteStartOffset: site.startOffset,
958
+ bindingSiteEndOffset: site.endOffset,
959
+ sourceSymbolQualifiedName: owner.owner?.qualifiedName,
960
+ ownerResolution: owner.status === "resolved" ? "owned_exact" : "ownerless_file_scope"
961
+ };
962
+ }
963
+ function prepareBindings(index, facts, symbols) {
964
+ return matchFactsToSites(index, facts).map(({ fact, site }) => ({
965
+ fact: exactOwner(fact, site, symbols),
966
+ site,
967
+ aliasProven: site.aliasSource === void 0
968
+ }));
969
+ }
970
+ function aliasStep(binding, source) {
971
+ const aliasKind = binding.site.aliasKind ?? (binding.site.flow === "assignment" ? "identity-assignment" : "identity");
972
+ return {
973
+ callerVariable: binding.fact.variableName,
974
+ aliasOf: source.fact.variableName,
975
+ aliasKind,
976
+ scopeRule: "exact_lexical_scope",
977
+ ...aliasKind === "transaction" ? { transactionAliasSource: source.fact.variableName } : {},
978
+ ...binding.site.aliasArrayIndex === void 0 ? {} : {
979
+ sourceVariable: source.fact.variableName,
980
+ arrayIndex: binding.site.aliasArrayIndex,
981
+ promiseAll: binding.site.aliasPromiseAll ?? false,
982
+ arrayContainer: binding.site.aliasPromiseAll ? "Promise.all" : "array_literal"
983
+ }
984
+ };
985
+ }
986
+ function copyAliasProvenance(binding, source) {
987
+ binding.fact = {
988
+ ...binding.fact,
989
+ alias: source.fact.alias,
990
+ aliasExpr: source.fact.aliasExpr,
991
+ destinationExpr: source.fact.destinationExpr,
992
+ servicePathExpr: source.fact.servicePathExpr,
993
+ isDynamic: source.fact.isDynamic,
994
+ placeholders: source.fact.placeholders,
995
+ helperChain: [
996
+ ...source.fact.helperChain ?? [],
997
+ aliasStep(binding, source)
998
+ ]
999
+ };
1000
+ binding.aliasProven = true;
1001
+ }
1002
+ function bindingCandidates(bindings) {
1003
+ return bindings.map((binding) => ({
1004
+ variableName: binding.fact.variableName,
1005
+ bindingSiteStartOffset: binding.site.startOffset,
1006
+ bindingSiteEndOffset: binding.site.endOffset,
1007
+ value: binding
1008
+ }));
1009
+ }
1010
+ function resolvedAliasSource(selected) {
1011
+ if (selected.status !== "resolved") return void 0;
1012
+ if (!selected.site?.deterministic) return void 0;
1013
+ if (selected.declarationSite?.declarationKind === "var") return void 0;
1014
+ const source = selected.candidate?.value;
1015
+ return source?.aliasProven ? source : void 0;
1016
+ }
1017
+ function reconcileAliases(index, bindings) {
1018
+ const candidates = bindingCandidates(bindings);
1019
+ for (const binding of bindings) {
1020
+ const sourceName = binding.site.aliasSource;
1021
+ if (!sourceName) continue;
1022
+ const selected = selectVisibleBinding(
1023
+ index,
1024
+ candidates,
1025
+ sourceName,
1026
+ binding.site.node
1027
+ );
1028
+ const source = resolvedAliasSource(selected);
1029
+ if (source) copyAliasProvenance(binding, source);
1030
+ }
1031
+ }
1032
+ function assertUniqueSites(bindings) {
1033
+ const seen = /* @__PURE__ */ new Set();
1034
+ for (const binding of bindings) {
1035
+ const key = [
1036
+ binding.fact.sourceFile,
1037
+ binding.fact.variableName,
1038
+ binding.site.startOffset,
1039
+ binding.site.endOffset
1040
+ ].join("\0");
1041
+ if (seen.has(key))
1042
+ throw new Error("invalid_prepared_repository_snapshot:duplicate_service_binding_site");
1043
+ seen.add(key);
1044
+ }
1045
+ }
1046
+ function callNodeMap(source) {
1047
+ const calls = /* @__PURE__ */ new Map();
1048
+ const visit = (node) => {
1049
+ if (ts3.isCallExpression(node))
1050
+ calls.set(`${node.getStart(source)}:${node.getEnd()}`, node);
1051
+ ts3.forEachChild(node, visit);
1052
+ };
1053
+ visit(source);
1054
+ return calls;
1055
+ }
1056
+ function emptyReference(call, reason) {
1057
+ return {
1058
+ status: call.serviceVariableName ? "unresolved" : "not_applicable",
1059
+ variableName: call.serviceVariableName,
1060
+ scopeChainTotal: 0,
1061
+ scopeChainShown: 0,
1062
+ scopeChainOmitted: 0,
1063
+ reason
1064
+ };
1065
+ }
1066
+ function rejectedReference(call, chain, reason, status = "unresolved") {
1067
+ return {
1068
+ status,
1069
+ variableName: call.serviceVariableName,
1070
+ scopeChainTotal: chain.length,
1071
+ scopeChainShown: Math.min(chain.length, scopeChainCap),
1072
+ scopeChainOmitted: Math.max(0, chain.length - scopeChainCap),
1073
+ reason
1074
+ };
1075
+ }
1076
+ function resolvedReference(call, selected, chain, scopeIndex) {
1077
+ return {
1078
+ status: "resolved_exact",
1079
+ variableName: call.serviceVariableName,
1080
+ bindingSourceFile: selected.fact.sourceFile,
1081
+ bindingSiteStartOffset: selected.site.startOffset,
1082
+ bindingSiteEndOffset: selected.site.endOffset,
1083
+ resolutionStrategy: selected.site.flow === "assignment" ? "deterministic_reaching_assignment" : selected.site.aliasSource ? "lexical_alias_declaration" : "lexical_declaration",
1084
+ lexicalScopeChain: chain,
1085
+ bindingScopeIndex: scopeIndex,
1086
+ scopeChainTotal: chain.length,
1087
+ scopeChainShown: chain.length,
1088
+ scopeChainOmitted: 0
1089
+ };
1090
+ }
1091
+ function selectedBindingReference(call, chain, chosen) {
1092
+ const selected = chosen.candidate?.value;
1093
+ if (!selected) {
1094
+ return rejectedReference(
1095
+ call,
1096
+ chain,
1097
+ chosen.reason ?? "binding_not_found"
1098
+ );
1099
+ }
1100
+ if (chosen.declarationSite?.declarationKind === "var")
1101
+ return rejectedReference(call, chain, "unsupported_var_binding");
1102
+ if (!selected.site.deterministic || !selected.aliasProven)
1103
+ return rejectedReference(call, chain, "unsupported_reaching_assignment");
1104
+ return resolvedReference(call, selected, chain, chosen.scopeIndex ?? 0);
1105
+ }
1106
+ function bindingReference(call, node, index, bindings) {
1107
+ if (!call.serviceVariableName) return emptyReference(call);
1108
+ if (!node) return emptyReference(call, "binding_flow_unsupported");
1109
+ const chain = lexicalScopeChain(node, index.source);
1110
+ if (chain.length > scopeChainCap)
1111
+ return rejectedReference(call, chain, "scope_chain_limit_exceeded");
1112
+ const chosen = selectVisibleBinding(
1113
+ index,
1114
+ bindingCandidates(bindings),
1115
+ call.serviceVariableName,
1116
+ node
1117
+ );
1118
+ if (chosen.status === "ambiguous")
1119
+ return rejectedReference(
1120
+ call,
1121
+ chain,
1122
+ "binding_scope_ambiguous",
1123
+ "ambiguous"
1124
+ );
1125
+ return selectedBindingReference(call, chain, chosen);
1126
+ }
1127
+ function exactCallOwner(call, symbols) {
1128
+ const start = call.callSiteStartOffset;
1129
+ const end = call.callSiteEndOffset;
1130
+ if (start === void 0 || end === void 0)
1131
+ return { resolution: "ownerless_file_scope" };
1132
+ const selected = selectCallOwner(
1133
+ executableSymbolCandidates(symbols, call.sourceFile),
1134
+ start,
1135
+ end,
1136
+ call.callType === "async_subscribe"
1137
+ );
1138
+ if (selected.status === "ambiguous")
1139
+ throw new Error("invalid_prepared_repository_snapshot:outbound_owner_ambiguous");
1140
+ if (call.callType === "async_subscribe" && selected.status !== "resolved")
1141
+ throw new Error("invalid_prepared_repository_snapshot:subscription_owner_missing");
1142
+ return selected.owner ? { qualifiedName: selected.owner.qualifiedName, resolution: "owned_exact" } : { resolution: "ownerless_file_scope" };
1143
+ }
1144
+ function reconcileBindingAndCallIdentity(source, bindingFacts, callFacts, symbols) {
1145
+ const lexicalIndex = createBindingLexicalIndex(source);
1146
+ const bindings = prepareBindings(lexicalIndex, bindingFacts, symbols);
1147
+ reconcileAliases(lexicalIndex, bindings);
1148
+ assertUniqueSites(bindings);
1149
+ const nodes = callNodeMap(source);
1150
+ const calls = callFacts.map((call) => {
1151
+ const key = `${call.callSiteStartOffset}:${call.callSiteEndOffset}`;
1152
+ const reference = bindingReference(
1153
+ call,
1154
+ nodes.get(key),
1155
+ lexicalIndex,
1156
+ bindings
1157
+ );
1158
+ const owner = exactCallOwner(call, symbols);
1159
+ return {
1160
+ ...call,
1161
+ sourceSymbolQualifiedName: owner.qualifiedName,
1162
+ serviceBindingReference: reference,
1163
+ evidence: {
1164
+ ...call.evidence ?? {},
1165
+ sourceOwnerResolution: owner.resolution,
1166
+ serviceBindingReference: reference
1167
+ }
1168
+ };
1169
+ });
1170
+ return { bindings: bindings.map((binding) => binding.fact), calls };
1171
+ }
1172
+
1173
+ // src/parsers/007-source-fact-reconciliation.ts
1174
+ function callKey(call) {
1175
+ return `${call.callType}:${call.callSiteStartOffset}:${call.callSiteEndOffset}`;
1176
+ }
1177
+ function mergedOutboundCalls(existing, classifications) {
1178
+ const classifiedKeys = new Set(
1179
+ classifications.map((item) => callKey(item.fact))
1180
+ );
1181
+ return [
1182
+ ...classifications.map((item) => item.fact),
1183
+ ...existing.filter((call) => !classifiedKeys.has(callKey(call)))
1184
+ ];
1185
+ }
1186
+ function exactSymbolCallOwner(call, symbols) {
1187
+ const start = call.callSiteStartOffset;
1188
+ const end = call.callSiteEndOffset;
1189
+ if (start === void 0 || end === void 0) return void 0;
1190
+ const selected = selectCallOwner(
1191
+ executableSymbolCandidates(symbols, call.sourceFile),
1192
+ start,
1193
+ end,
1194
+ call.callRole === "event_subscribe_handler"
1195
+ );
1196
+ if (selected.status === "ambiguous")
1197
+ throw new Error("invalid_prepared_repository_snapshot:symbol_call_owner_ambiguous");
1198
+ if (call.callRole === "event_subscribe_handler" && !selected.owner)
1199
+ throw new Error("invalid_prepared_repository_snapshot:handler_owner_missing");
1200
+ return selected.owner;
1201
+ }
1202
+ function reconcileSymbolCallOwners(calls, symbols) {
1203
+ return calls.flatMap((call) => {
1204
+ const owner = exactSymbolCallOwner(call, symbols);
1205
+ if (!owner) return [];
1206
+ return [{
1207
+ ...call,
1208
+ callerQualifiedName: owner.qualifiedName,
1209
+ evidence: {
1210
+ ...call.evidence,
1211
+ caller: owner.qualifiedName,
1212
+ callerResolution: "full_span_containment"
1213
+ }
1214
+ }];
1215
+ });
1216
+ }
1217
+ function reconcileSourceFacts(source, classifications, bindings, outboundCalls, symbols, symbolCalls) {
1218
+ const events = reconcileEventSubscriptions(
1219
+ source,
1220
+ classifications,
1221
+ symbols,
1222
+ symbolCalls
1223
+ );
1224
+ const allCalls = mergedOutboundCalls(outboundCalls, events.classifications);
1225
+ const identities = reconcileBindingAndCallIdentity(
334
1226
  source,
335
- imports,
336
- namespaceImports
1227
+ bindings,
1228
+ allCalls,
1229
+ events.symbols
337
1230
  );
338
- return inner ? { ...inner, wrapperFunction: expression.expression.getText(source) } : void 0;
1231
+ return {
1232
+ bindings: identities.bindings,
1233
+ outboundCalls: identities.calls,
1234
+ symbols: events.symbols,
1235
+ symbolCalls: reconcileSymbolCallOwners(events.calls, events.symbols),
1236
+ classifications: events.classifications
1237
+ };
339
1238
  }
340
- function isObjectFunction(node) {
341
- return ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node);
1239
+
1240
+ // src/parsers/009-symbol-call-facts.ts
1241
+ import ts5 from "typescript";
1242
+ var commonTerminalMembers = /* @__PURE__ */ new Set([
1243
+ "push",
1244
+ "includes",
1245
+ "find",
1246
+ "findIndex",
1247
+ "map",
1248
+ "filter",
1249
+ "reduce",
1250
+ "forEach",
1251
+ "some",
1252
+ "every",
1253
+ "toUpperCase",
1254
+ "toLowerCase",
1255
+ "trim",
1256
+ "split",
1257
+ "join",
1258
+ "get",
1259
+ "set",
1260
+ "has"
1261
+ ]);
1262
+ var loggerMembers = /* @__PURE__ */ new Set([
1263
+ "trace",
1264
+ "debug",
1265
+ "info",
1266
+ "warn",
1267
+ "error",
1268
+ "fatal",
1269
+ "log"
1270
+ ]);
1271
+ var globalObjects = /* @__PURE__ */ new Set([
1272
+ "JSON",
1273
+ "Object",
1274
+ "Array",
1275
+ "String",
1276
+ "Number",
1277
+ "Boolean",
1278
+ "Math",
1279
+ "Date",
1280
+ "Promise",
1281
+ "Reflect"
1282
+ ]);
1283
+ var capDslRoots = /* @__PURE__ */ new Set([
1284
+ "SELECT",
1285
+ "INSERT",
1286
+ "UPSERT",
1287
+ "UPDATE",
1288
+ "DELETE"
1289
+ ]);
1290
+ var requestHelpers = /* @__PURE__ */ new Set([
1291
+ "reject",
1292
+ "error",
1293
+ "info",
1294
+ "warn",
1295
+ "notify"
1296
+ ]);
1297
+ var transportMembers = /* @__PURE__ */ new Set([
1298
+ "emit",
1299
+ "publish",
1300
+ "send",
1301
+ "on"
1302
+ ]);
1303
+ var cdsFrameworkPrefixes = [
1304
+ "cds.connect.",
1305
+ "cds.services.",
1306
+ "cds.parse."
1307
+ ];
1308
+ function symbolCallName(expr) {
1309
+ if (ts5.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
1310
+ if (!ts5.isPropertyAccessExpression(expr))
1311
+ return { expression: expr.getText() };
1312
+ const left = expr.expression.getText();
1313
+ return {
1314
+ expression: expr.getText(),
1315
+ local: left === "this" ? void 0 : left.split(".")[0],
1316
+ member: expr.name.text,
1317
+ receiver: left
1318
+ };
1319
+ }
1320
+ function hasSetMember(value, values) {
1321
+ return value ? values.has(value) : false;
1322
+ }
1323
+ function cdsFrameworkCall(callee) {
1324
+ return callee.expression === "cds.run" || cdsFrameworkPrefixes.some((prefix) => callee.expression.startsWith(prefix));
1325
+ }
1326
+ function requestHelperCall(callee) {
1327
+ return callee.local === "req" && hasSetMember(callee.member, requestHelpers);
1328
+ }
1329
+ function ignoredFrameworkCall(callee) {
1330
+ const checks = [
1331
+ hasSetMember(callee.local, capDslRoots),
1332
+ cdsFrameworkCall(callee),
1333
+ requestHelperCall(callee),
1334
+ hasSetMember(callee.member, transportMembers),
1335
+ hasSetMember(callee.local, globalObjects),
1336
+ callee.expression.startsWith("new Date().")
1337
+ ];
1338
+ return checks.some(Boolean);
1339
+ }
1340
+ function argumentEvidence(args) {
1341
+ return args.map((arg) => {
1342
+ if (ts5.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
1343
+ if (ts5.isArrayLiteralExpression(arg)) return {
1344
+ kind: "array_literal",
1345
+ elements: arg.elements.flatMap((item, index) => ts5.isIdentifier(item) ? [{ index, kind: "identifier", name: item.text }] : [])
1346
+ };
1347
+ if (ts5.isObjectLiteralExpression(arg))
1348
+ return objectArgumentEvidence(arg);
1349
+ return { kind: "unsupported", expression: arg.getText() };
1350
+ });
1351
+ }
1352
+ function objectArgumentEvidence(argument) {
1353
+ const properties = argument.properties.flatMap((property) => {
1354
+ if (ts5.isShorthandPropertyAssignment(property))
1355
+ return [{
1356
+ kind: "shorthand",
1357
+ property: property.name.text,
1358
+ argument: property.name.text
1359
+ }];
1360
+ if (!ts5.isPropertyAssignment(property) || !ts5.isIdentifier(property.initializer)) return [];
1361
+ const name = propertyName(property.name);
1362
+ return name ? [{
1363
+ kind: "property_assignment",
1364
+ property: name,
1365
+ argument: property.initializer.text
1366
+ }] : [];
1367
+ });
1368
+ return { kind: "object_literal", properties };
1369
+ }
1370
+ function propertyName(name) {
1371
+ return ts5.isIdentifier(name) || ts5.isStringLiteralLike(name) || ts5.isNumericLiteral(name) ? name.text : void 0;
1372
+ }
1373
+ function exactCaller(collection, node) {
1374
+ const selected = selectCallOwner(
1375
+ executableSymbolCandidates(collection.symbols, collection.sourceFile),
1376
+ node.getStart(collection.source),
1377
+ node.getEnd()
1378
+ );
1379
+ if (selected.status === "ambiguous")
1380
+ throw new Error("invalid_prepared_repository_snapshot:symbol_call_owner_ambiguous");
1381
+ return selected.owner;
1382
+ }
1383
+ function relation(reference, instance, proxy, localTarget, provenThisMethod) {
1384
+ const derived = derivedRelation(instance, proxy);
1385
+ if (derived) return derived;
1386
+ if (reference?.moduleKind === "package") return "package_import";
1387
+ if (reference?.referenceShape === "namespace_member")
1388
+ return "relative_import_namespace_member";
1389
+ if (reference) return "relative_import";
1390
+ if (localTarget) return "indexed_local_symbol";
1391
+ return provenThisMethod ? "indexed_this_method" : "indexed_local_symbol";
1392
+ }
1393
+ function derivedRelation(instance, proxy) {
1394
+ if (instance?.importBinding?.moduleKind === "package")
1395
+ return "package_import_derived_member";
1396
+ if (instance) return "class_instance_method";
1397
+ if (proxy?.importBinding.moduleKind === "package")
1398
+ return "package_import_derived_member";
1399
+ return proxy ? "relative_import_proxy_member" : void 0;
1400
+ }
1401
+ function targetName(callee, reference, instance, proxy) {
1402
+ if (instance && callee.member)
1403
+ return `${instance.className}.${callee.member}`;
1404
+ if (proxy && callee.member) return callee.member;
1405
+ if (callee.receiver === "this") return callee.member;
1406
+ if (reference) return reference.requestedPublicName;
1407
+ return callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
1408
+ }
1409
+ function callFact(collection, node) {
1410
+ const caller = exactCaller(collection, node);
1411
+ if (!caller) return void 0;
1412
+ const callee = symbolCallName(node.expression);
1413
+ const proxy = callProxy(collection, node);
1414
+ const instance = callInstance(collection, node, callee);
1415
+ const reference = callImportReference(
1416
+ collection,
1417
+ node.expression,
1418
+ callee,
1419
+ instance,
1420
+ proxy
1421
+ );
1422
+ const localTarget = reference || instance || proxy ? void 0 : localSymbolTarget(node.expression, collection.source, collection.symbols);
1423
+ return retainedCallFact(collection, node, caller, callee, {
1424
+ proxy,
1425
+ instance,
1426
+ reference,
1427
+ localTarget
1428
+ });
1429
+ }
1430
+ function expressionRoot(expression) {
1431
+ let current = expression;
1432
+ while (ts5.isPropertyAccessExpression(current))
1433
+ current = current.expression;
1434
+ return ts5.isIdentifier(current) ? current : void 0;
1435
+ }
1436
+ function exactDeclaredContext(identifier, candidates) {
1437
+ const matches = candidates.filter((candidate) => identifierMatchesDeclaration(
1438
+ identifier,
1439
+ candidate.declarationStartOffset,
1440
+ candidate.declarationEndOffset
1441
+ ));
1442
+ return matches.length === 1 ? matches[0] : void 0;
1443
+ }
1444
+ function callProxy(collection, node) {
1445
+ const root = expressionRoot(node.expression);
1446
+ if (!root) return void 0;
1447
+ return exactDeclaredContext(
1448
+ root,
1449
+ collection.proxies.get(root.text) ?? []
1450
+ );
1451
+ }
1452
+ function callInstance(collection, node, callee) {
1453
+ const root = expressionRoot(node.expression);
1454
+ if (root && root.text !== "this") {
1455
+ const exact = exactDeclaredContext(
1456
+ root,
1457
+ collection.instances.get(root.text) ?? []
1458
+ );
1459
+ if (exact) return exact;
1460
+ }
1461
+ return callee.receiver ? thisPropertyInstance(collection, node, callee.receiver) : void 0;
1462
+ }
1463
+ function enclosingClass(node) {
1464
+ let current = node.parent;
1465
+ while (current && !ts5.isSourceFile(current)) {
1466
+ if (ts5.isClassLike(current)) return current;
1467
+ current = current.parent;
1468
+ }
1469
+ return void 0;
1470
+ }
1471
+ function thisPropertyInstance(collection, node, receiver) {
1472
+ if (!receiver.startsWith("this.")) return void 0;
1473
+ const container = enclosingClass(node);
1474
+ if (!container) return void 0;
1475
+ const start = container.getStart(collection.source);
1476
+ const end = container.getEnd();
1477
+ const matches = (collection.instances.get(receiver) ?? []).filter(
1478
+ (candidate) => candidate.containerStartOffset === start && candidate.containerEndOffset === end
1479
+ );
1480
+ return matches.length === 1 ? matches[0] : void 0;
1481
+ }
1482
+ function callImportReference(collection, expression, callee, instance, proxy) {
1483
+ const direct = symbolImportReference(expression, collection.importBindings);
1484
+ if (direct || !callee.member) return direct;
1485
+ const inherited = instance?.importBinding ?? proxy?.importBinding;
1486
+ return inherited ? derivedMemberImportReference(inherited, callee.member) : void 0;
1487
+ }
1488
+ function retainedCallFact(collection, node, caller, callee, context) {
1489
+ const target = targetName(
1490
+ callee,
1491
+ context.reference,
1492
+ context.instance,
1493
+ context.proxy
1494
+ );
1495
+ const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
1496
+ const thisTarget = callee.receiver === "this" && className && callee.member ? `${className}.${callee.member}` : void 0;
1497
+ const resolvedTarget = thisTarget ?? target;
1498
+ if (!resolvedTarget || !shouldRetainCall(
1499
+ collection,
1500
+ callee,
1501
+ resolvedTarget,
1502
+ context,
1503
+ Boolean(thisTarget)
1504
+ ))
1505
+ return void 0;
1506
+ return createCallFact(
1507
+ collection,
1508
+ node,
1509
+ caller,
1510
+ callee,
1511
+ resolvedTarget,
1512
+ thisTarget,
1513
+ context
1514
+ );
1515
+ }
1516
+ function shouldRetainCall(collection, callee, target, context, provenThisMethod) {
1517
+ if (!target || ignoredCall(callee)) return false;
1518
+ const callables = new Set(collection.symbols.flatMap((symbol) => [symbol.localName, symbol.qualifiedName]));
1519
+ return provenThisMethod || Boolean(context.localTarget) || callables.has(target) && Boolean(context.instance) || hasImportedContext(context);
1520
+ }
1521
+ function ignoredCall(callee) {
1522
+ return loggerCall(callee) || terminalCall(callee) || ignoredFrameworkCall(callee);
1523
+ }
1524
+ function loggerCall(callee) {
1525
+ if (callee.local === "logger") return true;
1526
+ if (callee.receiver?.endsWith(".logger")) return true;
1527
+ return callee.expression.startsWith("this.logger.") && hasSetMember(callee.member, loggerMembers);
1528
+ }
1529
+ function terminalCall(callee) {
1530
+ return hasSetMember(callee.member, commonTerminalMembers) || hasSetMember(callee.member, loggerMembers);
1531
+ }
1532
+ function hasImportedContext(context) {
1533
+ return Boolean(context.reference ?? context.proxy ?? context.instance);
1534
+ }
1535
+ function createCallFact(collection, node, caller, callee, target, thisTarget, context) {
1536
+ const importSource = context.instance?.importSource ?? context.proxy?.importSource ?? context.reference?.rawModuleSpecifier;
1537
+ return {
1538
+ callerQualifiedName: caller.qualifiedName,
1539
+ calleeExpression: callee.expression,
1540
+ calleeLocalName: target,
1541
+ receiverLocalName: callee.member ? callee.local ?? callee.receiver : void 0,
1542
+ importSource,
1543
+ sourceFile: collection.sourceFile,
1544
+ sourceLine: collection.source.getLineAndCharacterOfPosition(
1545
+ node.getStart(collection.source)
1546
+ ).line + 1,
1547
+ callSiteStartOffset: node.getStart(collection.source),
1548
+ callSiteEndOffset: node.getEnd(),
1549
+ callRole: "ordinary_call",
1550
+ evidence: callEvidence(caller, callee, target, thisTarget, context, node)
1551
+ };
1552
+ }
1553
+ function callEvidence(caller, callee, target, thisTarget, context, node) {
1554
+ return {
1555
+ relation: relation(
1556
+ context.reference,
1557
+ context.instance,
1558
+ context.proxy,
1559
+ context.localTarget,
1560
+ Boolean(thisTarget)
1561
+ ),
1562
+ caller: caller.qualifiedName,
1563
+ targetName: target,
1564
+ ...importReferenceEvidence(context),
1565
+ ...instanceEvidence(context.instance, callee),
1566
+ callArguments: argumentEvidence(node.arguments),
1567
+ ...proxyEvidence(context.proxy),
1568
+ ...context.localTarget ? { localTargetIdentity: context.localTarget } : {},
1569
+ candidateStrategy: parserCandidateStrategy(context)
1570
+ };
1571
+ }
1572
+ function importReferenceEvidence(context) {
1573
+ if (!context.reference) return {};
1574
+ const derived = context.instance ?? context.proxy;
1575
+ return derived && context.reference.moduleKind === "package" ? { derivedImportBinding: context.reference } : { importBinding: context.reference };
1576
+ }
1577
+ function instanceEvidence(instance, callee) {
1578
+ if (!instance) return {};
1579
+ return {
1580
+ instanceVariable: instance.propertyName ?? callee.local,
1581
+ className: instance.className,
1582
+ methodName: callee.member,
1583
+ classImportSource: instance.importSource,
1584
+ classImportBinding: instance.importBinding
1585
+ };
1586
+ }
1587
+ function proxyEvidence(proxy) {
1588
+ if (!proxy) return {};
1589
+ return {
1590
+ proxyVariableName: proxy.variableName,
1591
+ factory: proxy.factory,
1592
+ factoryExpression: proxy.factory,
1593
+ factoryImportSource: proxy.importSource,
1594
+ factoryImportBinding: proxy.importBinding
1595
+ };
342
1596
  }
343
- var commonTerminalMembers = /* @__PURE__ */ new Set(["push", "includes", "find", "findIndex", "map", "filter", "reduce", "forEach", "some", "every", "toUpperCase", "toLowerCase", "trim", "split", "join", "get", "set", "has"]);
344
- var loggerMembers = /* @__PURE__ */ new Set(["trace", "debug", "info", "warn", "error", "fatal", "log"]);
345
- var globalObjects = /* @__PURE__ */ new Set(["JSON", "Object", "Array", "String", "Number", "Boolean", "Math", "Date", "Promise", "Reflect"]);
1597
+ function parserCandidateStrategy(context) {
1598
+ if (context.instance?.importSource)
1599
+ return "relative_import_class_instance_method";
1600
+ if (context.instance) return "same_file_class_instance_method";
1601
+ return context.proxy ? "proxy_member_exact_export_or_unique_member" : void 0;
1602
+ }
1603
+ function collectSymbolCallFacts(collection) {
1604
+ const calls = [];
1605
+ const visit = (node) => {
1606
+ if (ts5.isCallExpression(node)) {
1607
+ const call = callFact(collection, node);
1608
+ if (call) calls.push(call);
1609
+ }
1610
+ ts5.forEachChild(node, visit);
1611
+ };
1612
+ visit(collection.source);
1613
+ return calls;
1614
+ }
1615
+
1616
+ // src/parsers/013-executable-body-eligibility.ts
1617
+ import ts6 from "typescript";
1618
+ function hasModifier(node, kind) {
1619
+ return ts6.canHaveModifiers(node) && Boolean(ts6.getModifiers(node)?.some((item) => item.kind === kind));
1620
+ }
1621
+ function executableBodyEligibility(node, source) {
1622
+ if (node.body) return { eligible: true, reason: "body_present" };
1623
+ if (hasModifier(node, ts6.SyntaxKind.AbstractKeyword))
1624
+ return { eligible: false, reason: "abstract_bodyless" };
1625
+ if (hasModifier(node, ts6.SyntaxKind.DeclareKeyword))
1626
+ return { eligible: false, reason: "ambient_declaration" };
1627
+ return {
1628
+ eligible: false,
1629
+ reason: source.isDeclarationFile ? "declaration_only" : "overload_signature"
1630
+ };
1631
+ }
1632
+
1633
+ // src/parsers/017-symbol-derived-contexts.ts
1634
+ import ts7 from "typescript";
346
1635
  var builtInConstructors = /* @__PURE__ */ new Set([
347
1636
  "Set",
348
1637
  "Map",
@@ -377,86 +1666,222 @@ var builtInConstructors = /* @__PURE__ */ new Set([
377
1666
  "Promise",
378
1667
  "AbortController"
379
1668
  ]);
380
- var capDslRoots = /* @__PURE__ */ new Set(["SELECT", "INSERT", "UPSERT", "UPDATE", "DELETE"]);
381
- var requestHelpers = /* @__PURE__ */ new Set(["reject", "error", "info", "warn", "notify"]);
382
- var transportMembers = /* @__PURE__ */ new Set(["emit", "publish", "send", "on"]);
383
- function callName(expr) {
384
- if (ts.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
385
- if (ts.isPropertyAccessExpression(expr)) {
386
- const left = expr.expression.getText();
387
- const root = left.split(".")[0];
388
- return { expression: expr.getText(), local: left === "this" ? void 0 : root, member: expr.name.text, receiver: left };
389
- }
390
- return { expression: expr.getText() };
1669
+ function appendNamed(values, name, value) {
1670
+ const matches = values.get(name) ?? [];
1671
+ matches.push(value);
1672
+ values.set(name, matches);
1673
+ }
1674
+ function assignmentOperator2(kind) {
1675
+ return kind >= ts7.SyntaxKind.FirstAssignment && kind <= ts7.SyntaxKind.LastAssignment;
1676
+ }
1677
+ function mutationUnary2(node) {
1678
+ return node.operator === ts7.SyntaxKind.PlusPlusToken || node.operator === ts7.SyntaxKind.MinusMinusToken;
1679
+ }
1680
+ function receiverIdentifier(expression) {
1681
+ let current = expression;
1682
+ while (ts7.isPropertyAccessExpression(current) || ts7.isElementAccessExpression(current))
1683
+ current = current.expression;
1684
+ return ts7.isIdentifier(current) ? current : void 0;
1685
+ }
1686
+ function nodeWritesMember(node, matches) {
1687
+ if (ts7.isBinaryExpression(node))
1688
+ return assignmentOperator2(node.operatorToken.kind) && matches(node.left);
1689
+ if (ts7.isPrefixUnaryExpression(node) || ts7.isPostfixUnaryExpression(node))
1690
+ return mutationUnary2(node) && matches(node.operand);
1691
+ return ts7.isDeleteExpression(node) && matches(node.expression);
1692
+ }
1693
+ function memberWrite(source, declaration) {
1694
+ const start = declaration.getStart(source);
1695
+ const end = declaration.getEnd();
1696
+ const matches = (expression) => {
1697
+ const receiver = receiverIdentifier(expression);
1698
+ return Boolean(receiver && receiver !== expression && identifierMatchesDeclaration(receiver, start, end));
1699
+ };
1700
+ let written = false;
1701
+ const visit = (node) => {
1702
+ if (written) return;
1703
+ written = nodeWritesMember(node, matches);
1704
+ if (!written) ts7.forEachChild(node, visit);
1705
+ };
1706
+ visit(source);
1707
+ return written;
391
1708
  }
392
- function requireSource(expr) {
393
- if (!ts.isCallExpression(expr) || !ts.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
394
- const first = expr.arguments[0];
395
- return first && ts.isStringLiteral(first) ? first.text : void 0;
1709
+ function stableVariable(collection, node) {
1710
+ return ts7.isIdentifier(node.name) && ts7.isVariableDeclarationList(node.parent) && (node.parent.flags & ts7.NodeFlags.Const) !== 0 && !memberWrite(collection.source, node.name);
396
1711
  }
397
- function ignoredFrameworkCall(callee) {
398
- if (callee.local && capDslRoots.has(callee.local)) return true;
399
- if (callee.expression === "cds.run" || callee.expression.startsWith("cds.connect.") || callee.expression.startsWith("cds.services.") || callee.expression.startsWith("cds.parse.")) return true;
400
- if (callee.local === "req" && callee.member && requestHelpers.has(callee.member)) return true;
401
- if (callee.member && transportMembers.has(callee.member)) return true;
402
- if (callee.local && globalObjects.has(callee.local)) return true;
403
- if (callee.expression.startsWith("new Date().")) return true;
404
- return false;
405
- }
406
- function nearest(symbols, line) {
407
- return symbols.filter((s) => s.startLine <= line && s.endLine >= line).sort((a, b) => a.endLine - a.startLine - (b.endLine - b.startLine))[0];
408
- }
409
- function argumentEvidence(args, source) {
410
- return args.map((arg) => {
411
- if (ts.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
412
- if (ts.isObjectLiteralExpression(arg)) {
413
- const properties = [];
414
- for (const prop of arg.properties) {
415
- if (ts.isShorthandPropertyAssignment(prop)) properties.push({ kind: "shorthand", property: prop.name.text, argument: prop.name.text });
416
- if (ts.isPropertyAssignment(prop)) {
417
- const propName = nameOf(prop.name);
418
- if (propName && ts.isIdentifier(prop.initializer)) properties.push({ kind: "property_assignment", property: propName, argument: prop.initializer.text });
419
- }
420
- }
421
- return { kind: "object_literal", properties };
422
- }
423
- if (ts.isArrayLiteralExpression(arg)) {
424
- const elements = arg.elements.flatMap((element, index) => ts.isIdentifier(element) ? [{ index, kind: "identifier", name: element.text }] : []);
425
- return { kind: "array_literal", elements };
426
- }
427
- return { kind: "unsupported", expression: arg.getText(source) };
1712
+ function collectProxy(collection, node) {
1713
+ if (!ts7.isVariableDeclaration(node) || !stableVariable(collection, node) || !node.initializer || !ts7.isCallExpression(node.initializer) || !ts7.isPropertyAccessExpression(node.initializer.expression)) return;
1714
+ const callee = symbolCallName(node.initializer.expression);
1715
+ const binding = symbolImportReference(
1716
+ node.initializer.expression,
1717
+ collection.importBindings
1718
+ );
1719
+ if (!callee.member || !binding) return;
1720
+ appendNamed(collection.proxies, node.name.text, {
1721
+ importSource: binding.rawModuleSpecifier,
1722
+ importBinding: binding,
1723
+ factory: callee.expression,
1724
+ variableName: node.name.text,
1725
+ declarationStartOffset: node.name.getStart(collection.source),
1726
+ declarationEndOffset: node.name.getEnd()
428
1727
  });
429
1728
  }
1729
+ function propertyContainer2(declaration, propertyName3) {
1730
+ const property = propertyName3 && ts7.isPropertyDeclaration(declaration.parent) ? declaration.parent : void 0;
1731
+ return property && ts7.isClassLike(property.parent) ? property.parent : void 0;
1732
+ }
1733
+ function eligibleClassName(collection, className, imported) {
1734
+ if (builtInConstructors.has(className)) return false;
1735
+ return Boolean(imported || collection.declaredClasses.has(className));
1736
+ }
1737
+ function resolvedClassName(className, imported) {
1738
+ const importedName = imported?.importedName;
1739
+ return importedName && importedName !== "default" ? importedName : className;
1740
+ }
1741
+ function rememberInstance(collection, declaration, classExpression, propertyName3) {
1742
+ const className = classExpression.text;
1743
+ const imported = symbolImportReference(
1744
+ classExpression,
1745
+ collection.importBindings
1746
+ );
1747
+ if (!eligibleClassName(collection, className, imported)) return;
1748
+ const container = propertyContainer2(declaration, propertyName3);
1749
+ const variableName = propertyName3 ? `this.${propertyName3}` : declaration.getText();
1750
+ appendNamed(collection.instances, variableName, {
1751
+ className: resolvedClassName(className, imported),
1752
+ importSource: imported?.rawModuleSpecifier,
1753
+ importBinding: imported,
1754
+ propertyName: propertyName3,
1755
+ declarationStartOffset: declaration.getStart(collection.source),
1756
+ declarationEndOffset: declaration.getEnd(),
1757
+ containerStartOffset: container?.getStart(collection.source),
1758
+ containerEndOffset: container?.getEnd()
1759
+ });
1760
+ }
1761
+ function collectVariableInstance(collection, node) {
1762
+ if (!ts7.isVariableDeclaration(node) || !stableVariable(collection, node))
1763
+ return;
1764
+ const initializer = node.initializer;
1765
+ if (initializer && ts7.isNewExpression(initializer) && ts7.isIdentifier(initializer.expression))
1766
+ rememberInstance(collection, node.name, initializer.expression);
1767
+ }
1768
+ function propertyName2(name) {
1769
+ return ts7.isIdentifier(name) || ts7.isStringLiteralLike(name) || ts7.isNumericLiteral(name) ? name.text : void 0;
1770
+ }
1771
+ function thisPropertyWrite(expression, name) {
1772
+ if (ts7.isPropertyAccessExpression(expression))
1773
+ return expression.expression.kind === ts7.SyntaxKind.ThisKeyword && expression.name.text === name;
1774
+ return ts7.isElementAccessExpression(expression) && expression.expression.kind === ts7.SyntaxKind.ThisKeyword && Boolean(expression.argumentExpression && ts7.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === name);
1775
+ }
1776
+ function nodeWritesThisProperty(node, name) {
1777
+ if (ts7.isBinaryExpression(node))
1778
+ return assignmentOperator2(node.operatorToken.kind) && thisPropertyWrite(node.left, name);
1779
+ if (ts7.isPrefixUnaryExpression(node) || ts7.isPostfixUnaryExpression(node))
1780
+ return mutationUnary2(node) && thisPropertyWrite(node.operand, name);
1781
+ return ts7.isDeleteExpression(node) && thisPropertyWrite(node.expression, name);
1782
+ }
1783
+ function stableProperty(node, name) {
1784
+ const flags = ts7.getCombinedModifierFlags(node);
1785
+ if ((flags & (ts7.ModifierFlags.Private | ts7.ModifierFlags.Readonly)) === 0 || !ts7.isClassLike(node.parent)) return false;
1786
+ let written = false;
1787
+ const visit = (child) => {
1788
+ if (written || child === node) return;
1789
+ written = nodeWritesThisProperty(child, name);
1790
+ if (!written) ts7.forEachChild(child, visit);
1791
+ };
1792
+ visit(node.parent);
1793
+ return !written;
1794
+ }
1795
+ function collectPropertyInstance(collection, node) {
1796
+ if (!ts7.isPropertyDeclaration(node)) return;
1797
+ const initializer = node.initializer;
1798
+ if (!initializer || !ts7.isNewExpression(initializer) || !ts7.isIdentifier(initializer.expression)) return;
1799
+ const name = propertyName2(node.name);
1800
+ if (name && stableProperty(node, name)) rememberInstance(
1801
+ collection,
1802
+ node.name,
1803
+ initializer.expression,
1804
+ name
1805
+ );
1806
+ }
1807
+ function collectDerivedSymbolContexts(collection) {
1808
+ const visit = (node) => {
1809
+ collectProxy(collection, node);
1810
+ collectVariableInstance(collection, node);
1811
+ collectPropertyInstance(collection, node);
1812
+ ts7.forEachChild(node, visit);
1813
+ };
1814
+ visit(collection.source);
1815
+ }
1816
+
1817
+ // src/parsers/symbol-parser.ts
1818
+ function lineOf2(source, pos) {
1819
+ return source.getLineAndCharacterOfPosition(pos).line + 1;
1820
+ }
1821
+ function nameOf(node) {
1822
+ if (!node) return void 0;
1823
+ if (ts8.isIdentifier(node) || ts8.isStringLiteral(node) || ts8.isNumericLiteral(node)) return node.text;
1824
+ return void 0;
1825
+ }
1826
+ function isFunctionLike(node) {
1827
+ return ts8.isFunctionDeclaration(node) || ts8.isMethodDeclaration(node) || ts8.isFunctionExpression(node) || ts8.isArrowFunction(node);
1828
+ }
1829
+ function exported(node) {
1830
+ return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Export);
1831
+ }
1832
+ function isPublicClassMethod(node) {
1833
+ const flags = ts8.getCombinedModifierFlags(node);
1834
+ return (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
1835
+ }
1836
+ function exportDeclarations(source) {
1837
+ const exports = /* @__PURE__ */ new Map();
1838
+ const visit = (node) => {
1839
+ if (ts8.isExportDeclaration(node) && node.exportClause && ts8.isNamedExports(node.exportClause)) {
1840
+ for (const el of node.exportClause.elements) exports.set((el.propertyName ?? el.name).text, el.name.text);
1841
+ }
1842
+ ts8.forEachChild(node, visit);
1843
+ };
1844
+ visit(source);
1845
+ return exports;
1846
+ }
1847
+ function isObjectFunction(node) {
1848
+ return ts8.isFunctionExpression(node) || ts8.isArrowFunction(node) || ts8.isMethodDeclaration(node);
1849
+ }
1850
+ function requireSource(expr) {
1851
+ if (!ts8.isCallExpression(expr) || !ts8.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
1852
+ const first = expr.arguments[0];
1853
+ return first && ts8.isStringLiteral(first) ? first.text : void 0;
1854
+ }
430
1855
  function bindingLocalName(name, initializer) {
431
- if (ts.isIdentifier(name)) return name.text;
432
- if (initializer && ts.isIdentifier(initializer)) return initializer.text;
1856
+ if (ts8.isIdentifier(name)) return name.text;
1857
+ if (initializer && ts8.isIdentifier(initializer)) return initializer.text;
433
1858
  return void 0;
434
1859
  }
435
1860
  function objectPatternAliases(pattern, parameter, source, lineNode) {
436
1861
  return pattern.elements.flatMap((element) => {
437
- if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
1862
+ if (element.dotDotDotToken || ts8.isObjectBindingPattern(element.name) || ts8.isArrayBindingPattern(element.name)) return [];
438
1863
  const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
439
1864
  if (!property) return [];
440
1865
  const local = bindingLocalName(element.name, element.initializer);
441
- return local ? [{ parameter, property, local, kind: "object_parameter_destructure", line: lineOf(source, lineNode.getStart(source)) }] : [];
1866
+ return local ? [{ parameter, property, local, kind: "object_parameter_destructure", line: lineOf2(source, lineNode.getStart(source)) }] : [];
442
1867
  });
443
1868
  }
444
1869
  function parameterPropertyAliases(fn, source) {
445
- const parameterNames = new Set(fn.parameters.flatMap((param) => ts.isIdentifier(param.name) ? [param.name.text] : []));
1870
+ const parameterNames = new Set(fn.parameters.flatMap((param) => ts8.isIdentifier(param.name) ? [param.name.text] : []));
446
1871
  if (!fn.body || parameterNames.size === 0) return [];
447
1872
  const aliases = [];
448
1873
  const addFromAssignment = (left, right, node) => {
449
- if (!ts.isObjectLiteralExpression(left) || !ts.isIdentifier(right) || !parameterNames.has(right.text)) return;
1874
+ if (!ts8.isObjectLiteralExpression(left) || !ts8.isIdentifier(right) || !parameterNames.has(right.text)) return;
450
1875
  for (const prop of left.properties) {
451
- if (!ts.isPropertyAssignment(prop)) continue;
1876
+ if (!ts8.isPropertyAssignment(prop)) continue;
452
1877
  const property = nameOf(prop.name);
453
- if (property && ts.isIdentifier(prop.initializer)) aliases.push({ parameter: right.text, property, local: prop.initializer.text, kind: "object_parameter_destructure", line: lineOf(source, node.getStart(source)) });
1878
+ 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)) });
454
1879
  }
455
1880
  };
456
1881
  const visit = (node) => {
457
- if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name) && node.initializer && ts.isIdentifier(node.initializer) && parameterNames.has(node.initializer.text)) aliases.push(...objectPatternAliases(node.name, node.initializer.text, source, node));
458
- if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) addFromAssignment(ts.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
459
- ts.forEachChild(node, visit);
1882
+ 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));
1883
+ if (ts8.isBinaryExpression(node) && node.operatorToken.kind === ts8.SyntaxKind.EqualsToken) addFromAssignment(ts8.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
1884
+ ts8.forEachChild(node, visit);
460
1885
  };
461
1886
  visit(fn.body);
462
1887
  const seen = /* @__PURE__ */ new Set();
@@ -469,10 +1894,10 @@ function parameterPropertyAliases(fn, source) {
469
1894
  }
470
1895
  function parameterBindings(params) {
471
1896
  return params.flatMap((param, index) => {
472
- if (ts.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
473
- if (ts.isObjectBindingPattern(param.name)) {
1897
+ if (ts8.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
1898
+ if (ts8.isObjectBindingPattern(param.name)) {
474
1899
  const properties = param.name.elements.flatMap((element) => {
475
- if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
1900
+ if (element.dotDotDotToken || ts8.isObjectBindingPattern(element.name) || ts8.isArrayBindingPattern(element.name)) return [];
476
1901
  const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
477
1902
  if (!property) return [];
478
1903
  const local = bindingLocalName(element.name, element.initializer);
@@ -480,237 +1905,431 @@ function parameterBindings(params) {
480
1905
  });
481
1906
  return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
482
1907
  }
483
- if (ts.isArrayBindingPattern(param.name)) {
484
- const elements = param.name.elements.flatMap((element, elementIndex) => ts.isBindingElement(element) && !element.dotDotDotToken && ts.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
1908
+ if (ts8.isArrayBindingPattern(param.name)) {
1909
+ const elements = param.name.elements.flatMap((element, elementIndex) => ts8.isBindingElement(element) && !element.dotDotDotToken && ts8.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
485
1910
  return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
486
1911
  }
487
1912
  return [];
488
1913
  });
489
1914
  }
490
- async function parseExecutableSymbols(repoPath, filePath, context) {
491
- const snapshot = context?.get(filePath);
492
- const text = snapshot?.text ?? await fs4.readFile(path4.join(repoPath, filePath), "utf8");
493
- const source = snapshot?.sourceFile() ?? ts.createSourceFile(
494
- filePath,
495
- text,
496
- ts.ScriptTarget.Latest,
497
- true,
498
- filePath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS
1915
+ function symbolSourceEvidence(collection, node, options) {
1916
+ if (options.evidence) return options.evidence;
1917
+ if (options.classMemberExported) return {
1918
+ source: "exported_class_member",
1919
+ exportedClass: options.parentRoot,
1920
+ memberKind: ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static ? "static_method" : "class_method"
1921
+ };
1922
+ if (options.classContainerExported && ts8.isMethodDeclaration(node) && isPublicClassMethod(node)) return {
1923
+ source: "exported_class_instance_member",
1924
+ exportedClass: options.parentRoot,
1925
+ memberKind: "class_method"
1926
+ };
1927
+ if (options.declaredExportName) return {
1928
+ exportedName: options.declaredExportName,
1929
+ source: "export_declaration"
1930
+ };
1931
+ return options.objectExported ? { exportedName: options.qualifiedName, source: "exported_object_literal" } : {};
1932
+ }
1933
+ function executableEvidence(node, source) {
1934
+ if (!isFunctionLike(node)) return {};
1935
+ const bindings = parameterBindings(node.parameters);
1936
+ const parameters = bindings.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
1937
+ const aliases = parameterPropertyAliases(node, source);
1938
+ return {
1939
+ executableBodyEligibility: executableBodyEligibility(node, source),
1940
+ ...bindings.length > 0 ? { parameters, parameterBindings: bindings } : {},
1941
+ ...aliases.length > 0 ? { parameterPropertyAliases: aliases } : {}
1942
+ };
1943
+ }
1944
+ function exportedClassMember(collection, kind, parentName, parentRoot, node) {
1945
+ if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !ts8.isMethodDeclaration(node)) return false;
1946
+ return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static) && isPublicClassMethod(node);
1947
+ }
1948
+ function symbolNames(collection, kind, localName, node, parentName, exportedName) {
1949
+ const parentRoot = parentName?.split(".")[0] ?? "";
1950
+ const declaredExportName = exportedName ?? collection.exportNames.get(
1951
+ parentName ? parentRoot : localName
499
1952
  );
500
- const sourceFile = normalizePath(filePath);
501
- const symbols = [];
502
- const calls = [];
503
- const imports = /* @__PURE__ */ new Map();
504
- const namespaceImports = /* @__PURE__ */ new Set();
505
- const eventSubscriptionOffsets = new Set(
506
- classifyOutboundCallsInSource(source, sourceFile).filter((call) => call.fact.callType === "async_subscribe").map((call) => `${call.node.getStart(source)}:${call.node.getEnd()}`)
507
- );
508
- const exportNames = exportDeclarations(source);
509
- const objectExports = /* @__PURE__ */ new Set();
510
- const exportedClasses = /* @__PURE__ */ new Set();
511
- const declaredClasses = /* @__PURE__ */ new Set();
512
- const proxyVariables = /* @__PURE__ */ new Map();
513
- const classInstances = /* @__PURE__ */ new Map();
514
- const addSymbol = (kind, localName, node, parentName, exportedName, evidence) => {
515
- const parentRoot = parentName?.split(".")[0] ?? "";
516
- const declaredExportName = exportedName ?? exportNames.get(parentName ? parentRoot : localName);
517
- const qualifiedName = parentName ? `${parentName}.${localName}` : localName;
518
- const objectExported = parentName ? objectExports.has(parentRoot) : false;
519
- const classMemberExported = kind === "method" && parentName ? exportedClasses.has(parentRoot) && ts.isMethodDeclaration(node) && (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 && isPublicClassMethod(node) : false;
520
- const effectiveExportedName = classMemberExported || objectExported ? qualifiedName : declaredExportName;
521
- const bindings = isFunctionLike(node) ? parameterBindings(node.parameters) : void 0;
522
- const params = bindings?.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
523
- const sourceEvidence = evidence ?? (classMemberExported ? { source: "exported_class_member", exportedClass: parentRoot, memberKind: (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 ? "static_method" : "class_method", parameters: params } : declaredExportName ? { exportedName: declaredExportName, source: "export_declaration" } : objectExported ? { exportedName: qualifiedName, source: "exported_object_literal" } : void 0);
524
- const aliases = isFunctionLike(node) ? parameterPropertyAliases(node, source) : [];
525
- const parameterEvidence = { ...bindings && bindings.length > 0 ? { parameters: params, parameterBindings: bindings } : {}, ...aliases.length > 0 ? { parameterPropertyAliases: aliases } : {} };
526
- symbols.push({ kind, localName: kind === "object_method" ? qualifiedName : localName, exportedName: effectiveExportedName, qualifiedName, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: exported(node) || Boolean(effectiveExportedName), importExportEvidence: sourceEvidence ? { ...sourceEvidence, ...parameterEvidence } : bindings && bindings.length > 0 ? parameterEvidence : void 0 });
527
- };
528
- const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
529
- symbols.push({ kind: "object_alias", localName: propertyName, exportedName: propertyName, qualifiedName: `${objectName}.${propertyName}`, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: true, importExportEvidence: { source: "exported_object_shorthand", objectName, propertyName, targetImportSource } });
530
- };
531
- const visitImports = (node) => {
532
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
533
- const sourceText = node.moduleSpecifier.text;
534
- const clause = node.importClause;
535
- if (clause?.name) imports.set(clause.name.text, sourceText);
536
- const named = clause?.namedBindings;
537
- if (named && ts.isNamedImports(named)) for (const el of named.elements) imports.set(el.name.text, sourceText);
538
- if (named && ts.isNamespaceImport(named)) {
539
- imports.set(named.name.text, sourceText);
540
- namespaceImports.add(named.name.text);
541
- }
1953
+ const qualifiedName = parentName ? `${parentName}.${localName}` : localName;
1954
+ const objectExported = Boolean(
1955
+ parentName && collection.objectExports.has(parentRoot)
1956
+ );
1957
+ const classMemberExported = exportedClassMember(
1958
+ collection,
1959
+ kind,
1960
+ parentName,
1961
+ parentRoot,
1962
+ node
1963
+ );
1964
+ const classContainerExported = Boolean(
1965
+ parentName && collection.exportedClasses.has(parentRoot)
1966
+ );
1967
+ return {
1968
+ parentRoot,
1969
+ declaredExportName,
1970
+ qualifiedName,
1971
+ objectExported,
1972
+ classContainerExported,
1973
+ classMemberExported,
1974
+ effectiveName: classMemberExported || objectExported ? qualifiedName : declaredExportName
1975
+ };
1976
+ }
1977
+ function addExecutableSymbol(collection, kind, localName, node, parentName, exportedName, evidence) {
1978
+ const names = symbolNames(
1979
+ collection,
1980
+ kind,
1981
+ localName,
1982
+ node,
1983
+ parentName,
1984
+ exportedName
1985
+ );
1986
+ const sourceEvidence = symbolSourceEvidence(collection, node, {
1987
+ parentRoot: names.parentRoot,
1988
+ qualifiedName: names.qualifiedName,
1989
+ declaredExportName: names.declaredExportName,
1990
+ classContainerExported: names.classContainerExported,
1991
+ classMemberExported: names.classMemberExported,
1992
+ objectExported: names.objectExported,
1993
+ evidence
1994
+ });
1995
+ collection.symbols.push({
1996
+ kind,
1997
+ localName: kind === "object_method" ? names.qualifiedName : localName,
1998
+ exportedName: names.effectiveName,
1999
+ qualifiedName: names.qualifiedName,
2000
+ sourceFile: collection.sourceFile,
2001
+ startLine: lineOf2(collection.source, node.getStart(collection.source)),
2002
+ endLine: lineOf2(collection.source, node.getEnd()),
2003
+ startOffset: node.getStart(collection.source),
2004
+ endOffset: node.getEnd(),
2005
+ exported: exported(node) || Boolean(names.effectiveName),
2006
+ importExportEvidence: {
2007
+ ...sourceEvidence,
2008
+ ...executableEvidence(node, collection.source)
542
2009
  }
543
- if (ts.isVariableStatement(node)) {
544
- for (const declaration of node.declarationList.declarations) {
545
- const requiredSource = declaration.initializer ? requireSource(declaration.initializer) : void 0;
546
- if (ts.isIdentifier(declaration.name) && requiredSource) imports.set(declaration.name.text, requiredSource);
547
- if (ts.isObjectBindingPattern(declaration.name) && requiredSource) {
548
- for (const element of declaration.name.elements) if (ts.isIdentifier(element.name)) imports.set(element.name.text, requiredSource);
549
- }
550
- }
2010
+ });
2011
+ }
2012
+ function addAliasSymbol(collection, objectName, propertyName3, node) {
2013
+ collection.symbols.push({
2014
+ kind: "object_alias",
2015
+ localName: propertyName3,
2016
+ exportedName: propertyName3,
2017
+ qualifiedName: `${objectName}.${propertyName3}`,
2018
+ sourceFile: collection.sourceFile,
2019
+ startLine: lineOf2(collection.source, node.getStart(collection.source)),
2020
+ endLine: lineOf2(collection.source, node.getEnd()),
2021
+ startOffset: node.getStart(collection.source),
2022
+ endOffset: node.getEnd(),
2023
+ exported: true,
2024
+ importExportEvidence: {
2025
+ source: "exported_object_shorthand",
2026
+ objectName,
2027
+ propertyName: propertyName3,
2028
+ targetImportSource: collection.imports.get(propertyName3)
551
2029
  }
552
- ts.forEachChild(node, visitImports);
553
- };
554
- visitImports(source);
555
- const visitSymbols = (node, parentClass) => {
556
- if (ts.isClassDeclaration(node) && node.name) {
557
- declaredClasses.add(node.name.text);
558
- if (exported(node) || exportNames.has(node.name.text)) exportedClasses.add(node.name.text);
559
- for (const member of node.members) visitSymbols(member, node.name.text);
560
- return;
2030
+ });
2031
+ }
2032
+ function collectImportSources(collection) {
2033
+ const visit = (node) => {
2034
+ if (ts8.isImportDeclaration(node) && ts8.isStringLiteral(node.moduleSpecifier))
2035
+ collectEsmImportSources(collection.imports, node);
2036
+ if (ts8.isVariableStatement(node))
2037
+ collectCjsImportSources(collection.imports, node);
2038
+ ts8.forEachChild(node, visit);
2039
+ };
2040
+ visit(collection.source);
2041
+ }
2042
+ function collectEsmImportSources(imports, node) {
2043
+ if (!ts8.isStringLiteral(node.moduleSpecifier)) return;
2044
+ const source = node.moduleSpecifier.text;
2045
+ const clause = node.importClause;
2046
+ if (clause?.name) imports.set(clause.name.text, source);
2047
+ const named = clause?.namedBindings;
2048
+ if (named && ts8.isNamedImports(named))
2049
+ for (const item of named.elements) imports.set(item.name.text, source);
2050
+ if (named && ts8.isNamespaceImport(named))
2051
+ imports.set(named.name.text, source);
2052
+ }
2053
+ function collectCjsImportSources(imports, node) {
2054
+ for (const declaration of node.declarationList.declarations) {
2055
+ const source = declaration.initializer ? requireSource(declaration.initializer) : void 0;
2056
+ if (!source) continue;
2057
+ if (ts8.isIdentifier(declaration.name))
2058
+ imports.set(declaration.name.text, source);
2059
+ if (ts8.isObjectBindingPattern(declaration.name)) {
2060
+ for (const item of declaration.name.elements)
2061
+ if (ts8.isIdentifier(item.name)) imports.set(item.name.text, source);
561
2062
  }
562
- if (ts.isMethodDeclaration(node)) {
563
- const localName = nameOf(node.name);
564
- if (localName) addSymbol("method", localName, node, parentClass);
565
- } else if (ts.isPropertyDeclaration(node) && parentClass && node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
566
- const localName = nameOf(node.name);
567
- if (localName) {
568
- const flags = ts.getCombinedModifierFlags(node);
569
- const staticPublicExported = exportedClasses.has(parentClass) && (flags & ts.ModifierFlags.Static) !== 0 && (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
570
- const isArrow = ts.isArrowFunction(node.initializer);
571
- const memberKind = isArrow ? staticPublicExported ? "static_arrow_function" : "arrow_function_property" : staticPublicExported ? "static_function_expression" : "function_expression_property";
572
- addSymbol("method", localName, node.initializer, parentClass, staticPublicExported ? `${parentClass}.${localName}` : void 0, staticPublicExported ? { source: "exported_class_member", exportedClass: parentClass, memberKind } : { source: "class_property_function", memberKind });
573
- }
574
- } else if (ts.isFunctionDeclaration(node) && node.name) addSymbol("function", node.name.text, node, void 0, exported(node) ? node.name.text : void 0);
575
- else if (ts.isVariableStatement(node)) {
576
- for (const d of node.declarationList.declarations) {
577
- const localName = nameOf(d.name);
578
- if (!localName || !d.initializer) continue;
579
- if (isFunctionLike(d.initializer)) addSymbol("function", localName, d.initializer, void 0, exported(node) ? localName : exportNames.get(localName));
580
- if (ts.isObjectLiteralExpression(d.initializer)) {
581
- const objectIsExported = exported(node) || exportNames.has(localName);
582
- if (objectIsExported) objectExports.add(localName);
583
- for (const prop of d.initializer.properties) {
584
- if (objectIsExported && ts.isShorthandPropertyAssignment(prop)) addAliasSymbol(localName, prop.name.text, prop.name, imports.get(prop.name.text));
585
- if (ts.isPropertyAssignment(prop) && isObjectFunction(prop.initializer)) {
586
- const propName = nameOf(prop.name);
587
- if (propName) addSymbol("object_method", propName, prop.initializer, localName);
588
- } else if (ts.isMethodDeclaration(prop)) {
589
- const propName = nameOf(prop.name);
590
- if (propName) addSymbol("object_method", propName, prop, localName);
591
- }
592
- }
593
- }
594
- }
595
- } else ts.forEachChild(node, (child) => visitSymbols(child, parentClass));
596
- };
597
- visitSymbols(source);
598
- const isTopLevelCallback = (node) => {
599
- if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return false;
600
- if (!ts.isCallExpression(node.parent)) return false;
601
- const callee = callName(node.parent.expression);
602
- const member = callee.member ?? callee.local;
603
- return Boolean(member && ["bootstrap", "served", "connect", "on", "once", "use", "get", "post", "put", "patch", "delete", "subscribe"].includes(member));
604
- };
605
- const visitCallbackSymbols = (node) => {
606
- if (isTopLevelCallback(node) && containsSupportedOutboundCall(node)) {
607
- const startLine = lineOf(source, node.getStart(source));
2063
+ }
2064
+ }
2065
+ function classPropertySymbol(collection, node, parentClass) {
2066
+ const initializer = node.initializer;
2067
+ const localName = nameOf(node.name);
2068
+ if (!localName || !initializer || !ts8.isArrowFunction(initializer) && !ts8.isFunctionExpression(initializer)) return;
2069
+ const staticPublic = publicStaticProperty(collection, node, parentClass);
2070
+ const memberKind = propertyMemberKind(initializer, staticPublic);
2071
+ addExecutableSymbol(
2072
+ collection,
2073
+ "method",
2074
+ localName,
2075
+ initializer,
2076
+ parentClass,
2077
+ staticPublic ? `${parentClass}.${localName}` : void 0,
2078
+ staticPublic ? { source: "exported_class_member", exportedClass: parentClass, memberKind } : { source: "class_property_function", memberKind }
2079
+ );
2080
+ }
2081
+ function publicStaticProperty(collection, node, parentClass) {
2082
+ const flags = ts8.getCombinedModifierFlags(node);
2083
+ return collection.exportedClasses.has(parentClass) && Boolean(flags & ts8.ModifierFlags.Static) && (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
2084
+ }
2085
+ function propertyMemberKind(initializer, staticPublic) {
2086
+ if (ts8.isArrowFunction(initializer))
2087
+ return staticPublic ? "static_arrow_function" : "arrow_function_property";
2088
+ return staticPublic ? "static_function_expression" : "function_expression_property";
2089
+ }
2090
+ function objectCallable(property) {
2091
+ if (ts8.isMethodDeclaration(property)) return property;
2092
+ return ts8.isPropertyAssignment(property) && isObjectFunction(property.initializer) ? property.initializer : void 0;
2093
+ }
2094
+ function objectLiteralSymbols(collection, objectName, object, objectIsExported) {
2095
+ if (objectIsExported) collection.objectExports.add(objectName);
2096
+ for (const property of object.properties) {
2097
+ if (objectIsExported && ts8.isShorthandPropertyAssignment(property))
2098
+ addAliasSymbol(collection, objectName, property.name.text, property.name);
2099
+ const callable = objectCallable(property);
2100
+ const propertyName3 = callable ? nameOf(property.name) : void 0;
2101
+ if (callable && propertyName3)
2102
+ addExecutableSymbol(
2103
+ collection,
2104
+ "object_method",
2105
+ propertyName3,
2106
+ callable,
2107
+ objectName
2108
+ );
2109
+ }
2110
+ }
2111
+ function variableSymbols(collection, node) {
2112
+ for (const declaration of node.declarationList.declarations) {
2113
+ const localName = nameOf(declaration.name);
2114
+ const initializer = declaration.initializer;
2115
+ if (!localName || !initializer) continue;
2116
+ if (isFunctionLike(initializer)) addExecutableSymbol(
2117
+ collection,
2118
+ "function",
2119
+ localName,
2120
+ initializer,
2121
+ void 0,
2122
+ exported(node) ? localName : collection.exportNames.get(localName)
2123
+ );
2124
+ if (ts8.isObjectLiteralExpression(initializer))
2125
+ objectLiteralSymbols(
2126
+ collection,
2127
+ localName,
2128
+ initializer,
2129
+ exported(node) || collection.exportNames.has(localName)
2130
+ );
2131
+ }
2132
+ }
2133
+ function collectClassDeclaration(collection, node) {
2134
+ if (!ts8.isClassDeclaration(node) || !node.name) return false;
2135
+ collection.declaredClasses.add(node.name.text);
2136
+ if (exported(node) || collection.exportNames.has(node.name.text))
2137
+ collection.exportedClasses.add(node.name.text);
2138
+ for (const member of node.members)
2139
+ visitDeclaredSymbol(collection, member, node.name.text);
2140
+ return true;
2141
+ }
2142
+ function collectMethodDeclaration(collection, node, parentClass) {
2143
+ if (!ts8.isMethodDeclaration(node)) return false;
2144
+ const localName = nameOf(node.name);
2145
+ if (localName)
2146
+ addExecutableSymbol(collection, "method", localName, node, parentClass);
2147
+ return true;
2148
+ }
2149
+ function visitDeclaredSymbol(collection, node, parentClass) {
2150
+ if (collectClassDeclaration(collection, node)) return;
2151
+ if (collectMethodDeclaration(collection, node, parentClass)) return;
2152
+ if (ts8.isPropertyDeclaration(node)) {
2153
+ if (parentClass) classPropertySymbol(collection, node, parentClass);
2154
+ return;
2155
+ }
2156
+ if (ts8.isFunctionDeclaration(node) && node.name) {
2157
+ addExecutableSymbol(
2158
+ collection,
2159
+ "function",
2160
+ node.name.text,
2161
+ node,
2162
+ void 0,
2163
+ exported(node) ? node.name.text : void 0
2164
+ );
2165
+ return;
2166
+ }
2167
+ if (ts8.isVariableStatement(node)) {
2168
+ variableSymbols(collection, node);
2169
+ return;
2170
+ }
2171
+ ts8.forEachChild(node, (child) => visitDeclaredSymbol(collection, child, parentClass));
2172
+ }
2173
+ function collectDeclaredSymbols(collection) {
2174
+ visitDeclaredSymbol(collection, collection.source);
2175
+ }
2176
+ function isTopLevelCallback(node) {
2177
+ if (!ts8.isArrowFunction(node) && !ts8.isFunctionExpression(node) || !ts8.isCallExpression(node.parent)) return false;
2178
+ const callee = symbolCallName(node.parent.expression);
2179
+ const member = callee.member ?? callee.local;
2180
+ return Boolean(member && [
2181
+ "bootstrap",
2182
+ "served",
2183
+ "connect",
2184
+ "on",
2185
+ "once",
2186
+ "use",
2187
+ "get",
2188
+ "post",
2189
+ "put",
2190
+ "patch",
2191
+ "delete",
2192
+ "subscribe"
2193
+ ].includes(member));
2194
+ }
2195
+ function collectCallbackSymbols(collection) {
2196
+ const visit = (node) => {
2197
+ if (isTopLevelCallback(node) && containsSupportedOutboundCall(node, collection.classifiedCalls)) {
2198
+ const startLine = lineOf2(
2199
+ collection.source,
2200
+ node.getStart(collection.source)
2201
+ );
608
2202
  const name = `callback:${startLine}`;
609
- symbols.push({ kind: "callback", localName: name, qualifiedName: `module:${sourceFile}#${name}`, sourceFile, startLine, endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: false, importExportEvidence: { source: "synthetic_outbound_callback", callbackLine: startLine } });
610
- }
611
- ts.forEachChild(node, visitCallbackSymbols);
612
- };
613
- visitCallbackSymbols(source);
614
- const visitEventRegistrationSymbols = (node) => {
615
- if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "on") {
616
- const receiver = node.expression.expression.getText(source);
617
- const eventArg = node.arguments[0];
618
- if ((receiver === "cds" || /^(srv|service|serviceClient|messaging|messageClient|eventClient|.*Client)$/.test(receiver)) && eventArg && (ts.isStringLiteral(eventArg) || ts.isNoSubstitutionTemplateLiteral(eventArg))) {
619
- const startLine = lineOf(source, node.getStart(source));
620
- const eventName = eventArg.text.replace(/[^A-Za-z0-9_$-]/g, "_");
621
- const name = `event:${eventName}:${startLine}`;
622
- symbols.push({ kind: "event_registration", localName: name, qualifiedName: `module:${sourceFile}#${name}`, sourceFile, startLine, endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: false, importExportEvidence: { source: "synthetic_event_registration", eventName: eventArg.text, registrationLine: startLine, receiver } });
623
- }
624
- if (eventSubscriptionOffsets.has(`${node.getStart(source)}:${node.getEnd()}`)) {
625
- const startLine = lineOf(source, node.getStart(source));
626
- const handlerArgument = node.arguments[1];
627
- const target = handlerArgument ? handlerReferenceTarget(
628
- handlerArgument,
629
- source,
630
- imports,
631
- namespaceImports
632
- ) : void 0;
633
- const anchor = nearest(symbols, startLine);
634
- if (target && anchor) calls.push({
635
- callerQualifiedName: anchor.qualifiedName,
636
- calleeExpression: target.calleeExpression,
637
- calleeLocalName: target.calleeLocalName,
638
- importSource: target.importSource,
639
- sourceFile,
640
- sourceLine: startLine,
641
- callSiteStartOffset: node.getStart(source),
642
- callSiteEndOffset: node.getEnd(),
643
- callRole: "event_subscribe_handler",
644
- evidence: {
645
- relation: target.relation,
646
- caller: anchor.qualifiedName,
647
- targetName: target.calleeLocalName,
648
- ...target.wrapperFunction ? { wrapperFunction: target.wrapperFunction } : {},
649
- factOrigin: "event_subscribe_handler_reference"
650
- }
651
- });
652
- }
653
- }
654
- ts.forEachChild(node, visitEventRegistrationSymbols);
655
- };
656
- visitEventRegistrationSymbols(source);
657
- const visitProxyVariables = (node) => {
658
- if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isPropertyAccessExpression(node.initializer.expression)) {
659
- const callee = callName(node.initializer.expression);
660
- const importSource = callee.local ? imports.get(callee.local) : void 0;
661
- if (callee.member && importSource && isRelativeImport(importSource)) proxyVariables.set(node.name.text, { importSource, factory: callee.expression, variableName: node.name.text });
2203
+ collection.symbols.push({
2204
+ kind: "callback",
2205
+ localName: name,
2206
+ qualifiedName: `module:${collection.sourceFile}#${name}`,
2207
+ sourceFile: collection.sourceFile,
2208
+ startLine,
2209
+ endLine: lineOf2(collection.source, node.getEnd()),
2210
+ startOffset: node.getStart(collection.source),
2211
+ endOffset: node.getEnd(),
2212
+ exported: false,
2213
+ importExportEvidence: {
2214
+ source: "synthetic_outbound_callback",
2215
+ callbackLine: startLine
2216
+ }
2217
+ });
662
2218
  }
663
- ts.forEachChild(node, visitProxyVariables);
2219
+ ts8.forEachChild(node, visit);
664
2220
  };
665
- visitProxyVariables(source);
666
- const rememberClassInstance = (variableName, className, propertyName) => {
667
- const importSource = imports.get(className);
668
- if (!builtInConstructors.has(className) && (importSource && isRelativeImport(importSource) || declaredClasses.has(className))) classInstances.set(variableName, { className, importSource, propertyName });
2221
+ visit(collection.source);
2222
+ }
2223
+ function createCollection(source, sourceFile2, classifiedCalls) {
2224
+ return {
2225
+ source,
2226
+ sourceFile: sourceFile2,
2227
+ classifiedCalls,
2228
+ symbols: [],
2229
+ imports: /* @__PURE__ */ new Map(),
2230
+ importBindings: collectSymbolImportBindings(source),
2231
+ exportNames: exportDeclarations(source),
2232
+ objectExports: /* @__PURE__ */ new Set(),
2233
+ exportedClasses: /* @__PURE__ */ new Set(),
2234
+ declaredClasses: /* @__PURE__ */ new Set(),
2235
+ proxies: /* @__PURE__ */ new Map(),
2236
+ instances: /* @__PURE__ */ new Map()
669
2237
  };
670
- const visitClassInstances = (node) => {
671
- if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isNewExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
672
- rememberClassInstance(node.name.text, node.initializer.expression.text);
673
- }
674
- if (ts.isPropertyDeclaration(node) && node.initializer && ts.isNewExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
675
- const propertyName = nameOf(node.name);
676
- if (propertyName) rememberClassInstance(`this.${propertyName}`, node.initializer.expression.text, propertyName);
677
- }
678
- ts.forEachChild(node, visitClassInstances);
679
- };
680
- visitClassInstances(source);
681
- const localCallables = new Set(symbols.flatMap((sym) => [sym.localName, sym.qualifiedName]));
682
- const visitCalls = (node) => {
683
- if (ts.isCallExpression(node)) {
684
- const line = lineOf(source, node.getStart(source));
685
- const caller = nearest(symbols, line);
686
- if (caller) {
687
- const callee = callName(node.expression);
688
- const proxy = callee.local ? proxyVariables.get(callee.local) : void 0;
689
- const instance = (callee.local ? classInstances.get(callee.local) : void 0) ?? (callee.receiver ? classInstances.get(callee.receiver) : void 0);
690
- const importSource = instance?.importSource ?? proxy?.importSource ?? (callee.local ? imports.get(callee.local) : void 0) ?? (callee.member && callee.local ? imports.get(callee.local) : void 0);
691
- const directThisMethod = callee.receiver === "this";
692
- const namespaceMember = Boolean(callee.member && callee.local && namespaceImports.has(callee.local));
693
- const packageImport = Boolean(importSource && !isRelativeImport(importSource));
694
- const targetName = instance && callee.member ? `${instance.className}.${callee.member}` : proxy && callee.member ? callee.member : directThisMethod ? callee.member : namespaceMember ? callee.member : packageImport ? callee.member ?? callee.local : callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
695
- const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
696
- const thisTarget = directThisMethod && className && callee.member ? `${className}.${callee.member}` : void 0;
697
- const loggerLike = callee.receiver?.endsWith(".logger") || callee.local === "logger" || (callee.expression.startsWith("this.logger.") && callee.member ? loggerMembers.has(callee.member) : false);
698
- const terminalMember = callee.member ? commonTerminalMembers.has(callee.member) || loggerMembers.has(callee.member) : false;
699
- const provenLocal = Boolean(targetName) && localCallables.has(String(targetName));
700
- const provenThisMethod = Boolean(thisTarget && localCallables.has(thisTarget));
701
- const provenRelativeImport = Boolean(isRelativeImport(importSource) && targetName);
702
- const provenClassInstance = Boolean(instance && callee.member && targetName);
703
- const provenPackageImport = Boolean(packageImport && targetName);
704
- const ignored = loggerLike || terminalMember || ignoredFrameworkCall(callee);
705
- const resolvedTarget = provenThisMethod ? thisTarget : targetName;
706
- const keep = Boolean(resolvedTarget) && !ignored && (provenLocal || provenThisMethod || provenRelativeImport || provenClassInstance || provenPackageImport);
707
- if (keep) calls.push({ callerQualifiedName: caller.qualifiedName, calleeExpression: callee.expression, calleeLocalName: resolvedTarget, receiverLocalName: callee.member ? callee.local ?? callee.receiver : void 0, importSource, sourceFile, sourceLine: line, callSiteStartOffset: node.getStart(source), callSiteEndOffset: node.getEnd(), callRole: "ordinary_call", evidence: { relation: instance ? "class_instance_method" : proxy ? "relative_import_proxy_member" : packageImport ? "package_import" : namespaceMember ? "relative_import_namespace_member" : importSource ? "relative_import" : provenThisMethod ? "indexed_this_method" : "indexed_local_symbol", caller: caller.qualifiedName, targetName: resolvedTarget, instanceVariable: instance ? instance.propertyName ?? callee.local : void 0, className: instance?.className, methodName: instance ? callee.member : void 0, classImportSource: instance?.importSource, callArguments: argumentEvidence(node.arguments, source), proxyVariableName: proxy?.variableName, factory: proxy?.factory, factoryExpression: proxy?.factory, factoryImportSource: proxy?.importSource, candidateStrategy: instance ? instance.importSource ? "relative_import_class_instance_method" : "same_file_class_instance_method" : proxy ? "proxy_member_exact_export_or_unique_member" : void 0 } });
708
- }
709
- }
710
- ts.forEachChild(node, visitCalls);
2238
+ }
2239
+ function populateCollection(collection) {
2240
+ collectImportSources(collection);
2241
+ collectDeclaredSymbols(collection);
2242
+ collectCallbackSymbols(collection);
2243
+ collectDerivedSymbolContexts(collection);
2244
+ }
2245
+ async function sourceFile(repoPath, filePath, context) {
2246
+ const snapshot = context?.get(filePath);
2247
+ const text = snapshot?.text ?? await fs4.readFile(path4.join(repoPath, filePath), "utf8");
2248
+ return snapshot?.sourceFile() ?? ts8.createSourceFile(
2249
+ filePath,
2250
+ text,
2251
+ ts8.ScriptTarget.Latest,
2252
+ true,
2253
+ filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
2254
+ );
2255
+ }
2256
+ async function parseExecutableSymbols(repoPath, filePath, context, preparedOutboundCalls) {
2257
+ const source = await sourceFile(repoPath, filePath, context);
2258
+ const normalizedFile = normalizePath(filePath);
2259
+ const classified = preparedOutboundCalls ?? classifyOutboundCallsInSource(source, normalizedFile);
2260
+ const collection = createCollection(source, normalizedFile, classified);
2261
+ populateCollection(collection);
2262
+ const calls = collectSymbolCallFacts({
2263
+ source,
2264
+ sourceFile: normalizedFile,
2265
+ symbols: collection.symbols,
2266
+ imports: collection.imports,
2267
+ importBindings: collection.importBindings,
2268
+ proxies: collection.proxies,
2269
+ instances: collection.instances
2270
+ });
2271
+ const events = reconcileEventSubscriptions(
2272
+ source,
2273
+ classified,
2274
+ collection.symbols,
2275
+ calls
2276
+ );
2277
+ return {
2278
+ symbols: events.symbols,
2279
+ calls: reconcileSymbolCallOwners(events.calls, events.symbols)
711
2280
  };
712
- visitCalls(source);
713
- return { symbols, calls };
2281
+ }
2282
+
2283
+ // src/parsers/008-package-surface-publication.ts
2284
+ function symbolIdentity(symbol) {
2285
+ return [
2286
+ symbol.sourceFile,
2287
+ symbol.kind,
2288
+ symbol.qualifiedName,
2289
+ symbol.startOffset,
2290
+ symbol.endOffset
2291
+ ].join("\0");
2292
+ }
2293
+ function exposureBySymbol(analysis) {
2294
+ return new Map(analysis.symbols.map((item) => [
2295
+ symbolIdentity({
2296
+ sourceFile: item.target.sourceFile,
2297
+ kind: item.target.kind,
2298
+ qualifiedName: item.target.qualifiedName,
2299
+ startOffset: item.target.startOffset,
2300
+ endOffset: item.target.endOffset
2301
+ }),
2302
+ item
2303
+ ]));
2304
+ }
2305
+ function analyzeRepositoryPackageSurface(facts, manifest, sources) {
2306
+ const modules = sources.entries().filter((snapshot) => /\.[jt]s$/.test(snapshot.filePath)).map((snapshot) => ({
2307
+ sourceFile: normalizePath(snapshot.filePath),
2308
+ source: snapshot.sourceFile()
2309
+ }));
2310
+ return analyzePackagePublicSurface(facts.packageName, manifest, modules);
2311
+ }
2312
+ function mergePackageSymbolEvidence(symbols, analysis) {
2313
+ const exposures = exposureBySymbol(analysis);
2314
+ return symbols.map((symbol) => {
2315
+ const exposure = exposures.get(symbolIdentity(symbol));
2316
+ if (!exposure) return symbol;
2317
+ return {
2318
+ ...symbol,
2319
+ importExportEvidence: {
2320
+ ...symbol.importExportEvidence ?? {},
2321
+ packagePublicSurface: {
2322
+ schema: analysis.surface.schema,
2323
+ recordCap: analysis.surface.recordCap,
2324
+ bodyEligibility: exposure.target.bodyEligibility,
2325
+ exposures: exposure.exposures,
2326
+ exposureTotal: exposure.exposureTotal,
2327
+ shownExposureCount: exposure.shownExposureCount,
2328
+ omittedExposureCount: exposure.omittedExposureCount
2329
+ }
2330
+ }
2331
+ };
2332
+ });
714
2333
  }
715
2334
 
716
2335
  // src/utils/hashing.ts
@@ -720,6 +2339,130 @@ function sha256Text(text) {
720
2339
  return createHash("sha256").update(text).digest("hex");
721
2340
  }
722
2341
 
2342
+ // src/db/004-package-target-invalidation.ts
2343
+ var resolverKeys = /* @__PURE__ */ new Set([
2344
+ "candidateStrategy",
2345
+ "candidateCount",
2346
+ "eligibleCandidateCount",
2347
+ "selectedCandidateCount",
2348
+ "candidateSetComplete",
2349
+ "resolvedModulePath",
2350
+ "resolvedTargetRepositoryId",
2351
+ "unresolvedReason",
2352
+ "targetRepositoryCandidateCount",
2353
+ "targetRepositoryCandidates",
2354
+ "shownTargetRepositoryCandidateCount",
2355
+ "omittedTargetRepositoryCandidateCount",
2356
+ "publicSurface"
2357
+ ]);
2358
+ function record(value) {
2359
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2360
+ }
2361
+ function parsedEvidence(value) {
2362
+ if (typeof value !== "string") return void 0;
2363
+ try {
2364
+ return record(JSON.parse(value));
2365
+ } catch {
2366
+ return void 0;
2367
+ }
2368
+ }
2369
+ function packageName(evidence) {
2370
+ return parsePackageImportReference(evidence.importBinding)?.requestedPackageName ?? void 0;
2371
+ }
2372
+ function packageCallEvidenceValid(evidence) {
2373
+ const binding = record(evidence.importBinding);
2374
+ const classified = evidence.relation === "package_import" || binding?.moduleKind === "package";
2375
+ return !classified || packageName(evidence) !== void 0;
2376
+ }
2377
+ function currentCalls(db, workspaceId, targetRepoId) {
2378
+ const rows = db.prepare(`SELECT sc.id,sc.repo_id repoId,
2379
+ sc.evidence_json evidenceJson FROM symbol_calls sc
2380
+ JOIN repositories r ON r.id=sc.repo_id
2381
+ WHERE r.workspace_id=? AND r.id<>? AND r.fact_analyzer_version=?
2382
+ ORDER BY sc.id`).all(workspaceId, targetRepoId, ANALYZER_VERSION);
2383
+ return rows.flatMap((row) => {
2384
+ const evidence = parsedEvidence(row.evidenceJson);
2385
+ if (!evidence || typeof row.id !== "number" || typeof row.repoId !== "number")
2386
+ throw new Error("invalid_current_package_import_evidence");
2387
+ if (!packageCallEvidenceValid(evidence))
2388
+ throw new Error("invalid_current_package_import_evidence");
2389
+ return evidence.relation === "package_import" || record(evidence.importBinding)?.moduleKind === "package" ? [{ id: row.id, repoId: row.repoId, evidence }] : [];
2390
+ });
2391
+ }
2392
+ function pendingEvidence(evidence) {
2393
+ const parser = Object.fromEntries(Object.entries(evidence).filter(
2394
+ ([key]) => !resolverKeys.has(key)
2395
+ ));
2396
+ if (!packageName(parser))
2397
+ throw new Error("invalid_current_package_import_evidence");
2398
+ return JSON.stringify({
2399
+ ...parser,
2400
+ candidateStrategy: "package_import_pending",
2401
+ candidateCount: 0,
2402
+ eligibleCandidateCount: 0,
2403
+ selectedCandidateCount: 0,
2404
+ candidateSetComplete: false,
2405
+ unresolvedReason: "package_resolution_pending"
2406
+ });
2407
+ }
2408
+ function targetWorkspace(db, repoId) {
2409
+ const row = db.prepare(`SELECT workspace_id workspaceId,
2410
+ package_name packageName FROM repositories WHERE id=?`).get(repoId);
2411
+ if (typeof row?.workspaceId !== "number")
2412
+ throw new Error("Repository target is missing its workspace");
2413
+ return {
2414
+ workspaceId: row.workspaceId,
2415
+ packageName: typeof row.packageName === "string" || row.packageName === null ? row.packageName : void 0
2416
+ };
2417
+ }
2418
+ function packageIdentityChanged(previous, next) {
2419
+ const previousName = typeof previous === "string" ? previous : null;
2420
+ const nextName = typeof next === "string" ? next : null;
2421
+ return previousName !== nextName;
2422
+ }
2423
+ function invalidatePackageTargetFacts(db, targetRepoId, newPackageName, batch) {
2424
+ const target = targetWorkspace(db, targetRepoId);
2425
+ const names = new Set(
2426
+ [target.packageName, newPackageName].filter(
2427
+ (value) => typeof value === "string" && value.length > 0
2428
+ )
2429
+ );
2430
+ if (names.size === 0) return;
2431
+ const update = db.prepare(`UPDATE symbol_calls SET callee_symbol_id=NULL,
2432
+ status='unresolved',unresolved_reason='package_resolution_pending',
2433
+ evidence_json=? WHERE id=?`);
2434
+ let matched = false;
2435
+ for (const call of currentCalls(db, target.workspaceId, targetRepoId)) {
2436
+ const requested = packageName(call.evidence);
2437
+ if (!requested || !names.has(requested)) continue;
2438
+ update.run(pendingEvidence(call.evidence), call.id);
2439
+ batch.affectedCallerRepoIds.add(call.repoId);
2440
+ matched = true;
2441
+ }
2442
+ if (matched || packageIdentityChanged(
2443
+ target.packageName,
2444
+ newPackageName
2445
+ )) batch.affectedWorkspaceIds.add(target.workspaceId);
2446
+ }
2447
+ function createPackageInvalidationBatch(publishingRepoIds) {
2448
+ return {
2449
+ publishingRepoIds: new Set(publishingRepoIds),
2450
+ affectedCallerRepoIds: /* @__PURE__ */ new Set(),
2451
+ affectedWorkspaceIds: /* @__PURE__ */ new Set()
2452
+ };
2453
+ }
2454
+ function finalizePackageTargetInvalidations(db, batch) {
2455
+ const increment = db.prepare(`UPDATE repositories
2456
+ SET fact_generation=fact_generation+1 WHERE id=?`);
2457
+ for (const repoId of batch.affectedCallerRepoIds)
2458
+ if (!batch.publishingRepoIds.has(repoId)) increment.run(repoId);
2459
+ const stale = db.prepare(`UPDATE repositories
2460
+ SET graph_stale_reason='package_target_facts_changed',
2461
+ graph_stale_at=datetime('now') WHERE workspace_id=?`);
2462
+ for (const workspaceId of batch.affectedWorkspaceIds)
2463
+ stale.run(workspaceId);
2464
+ }
2465
+
723
2466
  // src/indexer/repository-indexer.ts
724
2467
  async function prepareRepositoryIndex(repo, force, instrumentation) {
725
2468
  const sourceFiles = await findSourceFiles(repo.absolute_path);
@@ -739,24 +2482,51 @@ async function prepareRepositoryIndex(repo, force, instrumentation) {
739
2482
  packageSnapshot.rawText
740
2483
  );
741
2484
  if (!force && repo.fingerprint === fingerprint) return { repo, fileCount: 0, diagnosticCount: 0, skipped: true };
742
- const parsed = await parseAllSourceFacts(repo.absolute_path, sources);
2485
+ const parsedFacts = await parseAllSourceFacts(repo.absolute_path, sources);
2486
+ const packageSurface = analyzeRepositoryPackageSurface(
2487
+ packageFacts,
2488
+ packageSnapshot.manifest,
2489
+ sources
2490
+ );
2491
+ const parsed = {
2492
+ ...parsedFacts,
2493
+ symbols: mergePackageSymbolEvidence(parsedFacts.symbols, packageSurface)
2494
+ };
743
2495
  return {
744
2496
  repo,
745
2497
  packageFacts,
746
2498
  fingerprint,
747
2499
  kind: await classifyRepository(repo.absolute_path, packageFacts),
748
2500
  parsed,
2501
+ packagePublicSurface: packageSurface.surface,
749
2502
  fileCount: sourceFiles.length,
750
2503
  diagnosticCount: parsed.handlers.filter((handler) => handler.hasHandlerDecorator && (handler.methods.length === 0 || handler.methods.some((method) => !handlerMethodIsExecutable(method)))).length,
751
2504
  skipped: false
752
2505
  };
753
2506
  }
754
- function publishPreparedRepositoryIndex(db, prepared) {
2507
+ function publishPreparedRepositoryIndex(db, prepared, invalidations) {
755
2508
  if (prepared.skipped) return;
756
- if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind) throw new Error("Prepared repository index is missing publication facts");
2509
+ if (!prepared.packageFacts || !prepared.parsed || !prepared.fingerprint || !prepared.kind || !prepared.packagePublicSurface)
2510
+ throw new Error("Prepared repository index is missing publication facts");
757
2511
  const now = (/* @__PURE__ */ new Date()).toISOString();
758
2512
  const repoId = prepared.repo.id;
759
- db.prepare("UPDATE repositories SET package_name=?, package_version=?, dependencies_json=?, kind=?, index_status=? WHERE id=?").run(prepared.packageFacts.packageName, prepared.packageFacts.packageVersion, JSON.stringify(prepared.packageFacts.dependencies), prepared.kind, "indexing", repoId);
2513
+ invalidatePackageTargetFacts(
2514
+ db,
2515
+ repoId,
2516
+ prepared.packageFacts.packageName,
2517
+ invalidations
2518
+ );
2519
+ db.prepare(`UPDATE repositories SET package_name=?, package_version=?,
2520
+ dependencies_json=?,package_public_surface_json=?,kind=?,index_status=?
2521
+ WHERE id=?`).run(
2522
+ prepared.packageFacts.packageName,
2523
+ prepared.packageFacts.packageVersion,
2524
+ JSON.stringify(prepared.packageFacts.dependencies),
2525
+ JSON.stringify(prepared.packagePublicSurface),
2526
+ prepared.kind,
2527
+ "indexing",
2528
+ repoId
2529
+ );
760
2530
  clearRepoFacts(db, repoId);
761
2531
  insertRequires(db, repoId, prepared.packageFacts.cdsRequires);
762
2532
  const fileStmt = db.prepare("INSERT INTO files(repo_id,relative_path,extension,sha256,size_bytes,last_indexed_at) VALUES(?,?,?,?,?,?) ON CONFLICT(repo_id,relative_path) DO UPDATE SET sha256=excluded.sha256,size_bytes=excluded.size_bytes,last_indexed_at=excluded.last_indexed_at");
@@ -783,13 +2553,36 @@ async function parseAllSourceFacts(root, sources) {
783
2553
  facts.fileRecords.push({ relativePath: normalizePath(file), extension: path5.extname(file), sha256: sha256Text(snapshot.text), sizeBytes: snapshot.sizeBytes });
784
2554
  if (file.endsWith(".cds")) facts.services.push(...await parseCdsFile(root, file, sources));
785
2555
  if (/\.[jt]s$/.test(file)) {
2556
+ const source = snapshot.sourceFile();
2557
+ const classified = classifyOutboundCallsInSource(source, file);
786
2558
  facts.handlers.push(...await parseDecorators(root, file, sources));
787
2559
  facts.registrations.push(...await parseHandlerRegistrations(root, file, sources));
788
- facts.bindings.push(...await parseServiceBindings(root, file, sources));
789
- const symbolFacts = await parseExecutableSymbols(root, file, sources);
790
- facts.symbols.push(...symbolFacts.symbols);
791
- facts.symbolCalls.push(...symbolFacts.calls);
792
- facts.calls.push(...await parseOutboundCalls(root, file, sources));
2560
+ const bindings = await parseServiceBindings(root, file, sources);
2561
+ const symbolFacts = await parseExecutableSymbols(
2562
+ root,
2563
+ file,
2564
+ sources,
2565
+ classified
2566
+ );
2567
+ const outboundCalls = await parseOutboundCalls(
2568
+ root,
2569
+ file,
2570
+ sources,
2571
+ classified,
2572
+ bindings
2573
+ );
2574
+ const reconciled = reconcileSourceFacts(
2575
+ source,
2576
+ classified,
2577
+ bindings,
2578
+ outboundCalls,
2579
+ symbolFacts.symbols,
2580
+ symbolFacts.calls
2581
+ );
2582
+ facts.bindings.push(...reconciled.bindings);
2583
+ facts.symbols.push(...reconciled.symbols);
2584
+ facts.symbolCalls.push(...reconciled.symbolCalls);
2585
+ facts.calls.push(...reconciled.outboundCalls);
793
2586
  }
794
2587
  }
795
2588
  return facts;
@@ -906,15 +2699,15 @@ function relativeBase(db, extension, symbol) {
906
2699
  return db.prepare(`SELECT s.id,s.repo_id repoId FROM cds_services s WHERE s.repo_id=? AND s.is_extend=0 AND (s.qualified_name=? OR s.service_name=?) AND (s.source_file=? OR s.source_file=?) ORDER BY s.id`).all(extension.repoId, symbol, symbol, modulePath, `${modulePath}.cds`);
907
2700
  }
908
2701
  function packageBase(db, workspaceId, specifier, symbol) {
909
- const packageName = packageNameFromSpecifier(specifier);
910
- const moduleSuffix = specifier.slice(packageName.length).replace(/^\//, "");
911
- return db.prepare(`SELECT s.id,s.repo_id repoId FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND s.is_extend=0 AND r.package_name=? AND (s.qualified_name=? OR s.service_name=?) AND (?='' OR s.source_file=? OR s.source_file=?) ORDER BY s.id`).all(workspaceId, packageName, symbol, symbol, moduleSuffix, moduleSuffix, `${moduleSuffix}.cds`);
2702
+ const packageName2 = packageNameFromSpecifier(specifier);
2703
+ const moduleSuffix = specifier.slice(packageName2.length).replace(/^\//, "");
2704
+ return db.prepare(`SELECT s.id,s.repo_id repoId FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND s.is_extend=0 AND r.package_name=? AND (s.qualified_name=? OR s.service_name=?) AND (?='' OR s.source_file=? OR s.source_file=?) ORDER BY s.id`).all(workspaceId, packageName2, symbol, symbol, moduleSuffix, moduleSuffix, `${moduleSuffix}.cds`);
912
2705
  }
913
2706
  function sameRepoBase(db, extension, symbol) {
914
2707
  return db.prepare("SELECT id,repo_id repoId FROM cds_services WHERE repo_id=? AND is_extend=0 AND (qualified_name=? OR service_name=?) ORDER BY id").all(extension.repoId, symbol, symbol);
915
2708
  }
916
- function normalizeModulePath(sourceFile, specifier) {
917
- const base = sourceFile.split("/").slice(0, -1).join("/");
2709
+ function normalizeModulePath(sourceFile2, specifier) {
2710
+ const base = sourceFile2.split("/").slice(0, -1).join("/");
918
2711
  const parts = `${base}/${specifier}`.split("/");
919
2712
  const out = [];
920
2713
  for (const part of parts) {
@@ -985,42 +2778,91 @@ function claimIndexRun(db, workspaceId, repoCount) {
985
2778
  }
986
2779
  }
987
2780
  async function indexWorkspace(db, workspaceId, options) {
988
- const repos = options.repo ? reposByName(db, options.repo, workspaceId) : listRepositories(db, workspaceId);
989
- if (options.repo && repos.length === 0)
990
- throw new Error(`selector_repo_not_found: no indexed repository matched ${options.repo}.`);
991
- if (options.repo && repos.length > 1)
992
- throw new Error(`selector_repo_ambiguous: repository selector ${options.repo} matched ${repos.length} repositories; use a unique repository name.`);
2781
+ const repos = selectedRepositories(db, workspaceId, options.repo);
993
2782
  const runId = claimIndexRun(db, workspaceId, repos.length);
994
- let fileCount = 0;
995
- let diagnosticCount = 0;
996
- let skippedCount = 0;
997
- const preparedRows = [];
998
- let activeRepoId;
2783
+ const state = {
2784
+ fileCount: 0,
2785
+ diagnosticCount: 0,
2786
+ skippedCount: 0,
2787
+ rows: []
2788
+ };
999
2789
  try {
1000
- for (const repo of repos) {
1001
- activeRepoId = repo.id;
1002
- const result = await prepareRepositoryIndex(repo, options.force);
1003
- preparedRows.push(result);
1004
- fileCount += result.fileCount;
1005
- diagnosticCount += result.diagnosticCount;
1006
- skippedCount += result.skipped ? 1 : 0;
1007
- }
1008
- db.transaction(() => {
1009
- for (const row of preparedRows) {
1010
- activeRepoId = row.repo.id;
1011
- publishPreparedRepositoryIndex(db, row);
1012
- }
1013
- if (options.injectDerivedMaterializationFailure) throw new Error("Injected derived materialization failure");
1014
- materializeCdsExtensionOperations(db, workspaceId);
1015
- db.prepare("UPDATE index_runs SET finished_at=?, status='success', file_count=?, diagnostic_count=? WHERE id=?").run((/* @__PURE__ */ new Date()).toISOString(), fileCount, diagnosticCount, runId);
1016
- });
1017
- return { repoCount: repos.length, indexedCount: repos.length - skippedCount, skippedCount, fileCount, diagnosticCount };
2790
+ await prepareRepositories(repos, options.force, state);
2791
+ publishPreparedRows(db, workspaceId, options, runId, state);
2792
+ return indexSummary(repos.length, state);
1018
2793
  } catch (error) {
1019
- db.prepare("UPDATE index_runs SET finished_at=?, status='failed', file_count=?, diagnostic_count=?, error_message=? WHERE id=?").run((/* @__PURE__ */ new Date()).toISOString(), fileCount, diagnosticCount + 1, errorMessage(error), runId);
1020
- if (activeRepoId && preparedRows.length < repos.length) recordIndexFailure(db, activeRepoId, error);
2794
+ finishFailedRun(db, runId, state, error);
2795
+ if (state.activeRepoId && state.rows.length < repos.length)
2796
+ recordIndexFailure(db, state.activeRepoId, error);
1021
2797
  throw error;
1022
2798
  }
1023
2799
  }
2800
+ function selectedRepositories(db, workspaceId, repoName) {
2801
+ const repos = repoName ? reposByName(db, repoName, workspaceId) : listRepositories(db, workspaceId);
2802
+ if (repoName && repos.length === 0)
2803
+ throw new Error(
2804
+ `selector_repo_not_found: no indexed repository matched ${repoName}.`
2805
+ );
2806
+ if (repoName && repos.length > 1)
2807
+ throw new Error(
2808
+ `selector_repo_ambiguous: repository selector ${repoName} matched ${repos.length} repositories; use a unique repository name.`
2809
+ );
2810
+ return repos;
2811
+ }
2812
+ async function prepareRepositories(repos, force, state) {
2813
+ for (const repo of repos) {
2814
+ state.activeRepoId = repo.id;
2815
+ const result = await prepareRepositoryIndex(repo, force);
2816
+ state.rows.push(result);
2817
+ state.fileCount += result.fileCount;
2818
+ state.diagnosticCount += result.diagnosticCount;
2819
+ state.skippedCount += result.skipped ? 1 : 0;
2820
+ }
2821
+ }
2822
+ function publishPreparedRows(db, workspaceId, options, runId, state) {
2823
+ db.transaction(() => {
2824
+ const invalidations = createPackageInvalidationBatch(
2825
+ state.rows.filter((row) => !row.skipped).map((row) => row.repo.id)
2826
+ );
2827
+ for (const row of state.rows) {
2828
+ state.activeRepoId = row.repo.id;
2829
+ publishPreparedRepositoryIndex(db, row, invalidations);
2830
+ }
2831
+ if (options.injectDerivedMaterializationFailure)
2832
+ throw new Error("Injected derived materialization failure");
2833
+ materializeCdsExtensionOperations(db, workspaceId);
2834
+ finalizePackageTargetInvalidations(db, invalidations);
2835
+ finishSuccessfulRun(db, runId, state);
2836
+ });
2837
+ }
2838
+ function finishSuccessfulRun(db, runId, state) {
2839
+ db.prepare(`UPDATE index_runs SET finished_at=?,status='success',
2840
+ file_count=?,diagnostic_count=? WHERE id=?`).run(
2841
+ (/* @__PURE__ */ new Date()).toISOString(),
2842
+ state.fileCount,
2843
+ state.diagnosticCount,
2844
+ runId
2845
+ );
2846
+ }
2847
+ function finishFailedRun(db, runId, state, error) {
2848
+ db.prepare(`UPDATE index_runs SET finished_at=?,status='failed',
2849
+ file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
2850
+ (/* @__PURE__ */ new Date()).toISOString(),
2851
+ state.fileCount,
2852
+ state.diagnosticCount + 1,
2853
+ errorMessage(error),
2854
+ runId
2855
+ );
2856
+ }
2857
+ function indexSummary(repoCount, state) {
2858
+ return {
2859
+ repoCount,
2860
+ indexedCount: repoCount - state.skippedCount,
2861
+ skippedCount: state.skippedCount,
2862
+ fileCount: state.fileCount,
2863
+ diagnosticCount: state.diagnosticCount
2864
+ };
2865
+ }
1024
2866
 
1025
2867
  // src/cli/001-doctor-projection.ts
1026
2868
  var boundedDoctorArrayKeys = /* @__PURE__ */ new Set([
@@ -1191,15 +3033,88 @@ function analyzerVersionDiagnostics(db, strict, workspaceId) {
1191
3033
  return [{ severity: "warning", code: "reindex_required_after_analyzer_upgrade", message: "Repository facts were produced by an older or unknown analyzer; run service-flow index --force before relink to apply current parser semantics.", scope: "workspace", affectedRepositoryCount: rows.length, currentAnalyzerVersion: ANALYZER_VERSION, repositories: rows, remediation: "service-flow index --force && service-flow link" }];
1192
3034
  }
1193
3035
 
3036
+ // src/cli/003-doctor-package-resolution.ts
3037
+ function pendingPredicate(alias) {
3038
+ return `${alias}.status='unresolved'
3039
+ AND ${alias}.callee_symbol_id IS NULL
3040
+ AND ${alias}.unresolved_reason='package_resolution_pending'
3041
+ AND json_extract(${alias}.evidence_json,'$.relation')='package_import'
3042
+ AND json_extract(${alias}.evidence_json,
3043
+ '$.importBinding.moduleKind')='package'
3044
+ AND json_extract(${alias}.evidence_json,
3045
+ '$.candidateStrategy')='package_import_pending'
3046
+ AND json_extract(${alias}.evidence_json,'$.candidateCount')=0
3047
+ AND json_extract(${alias}.evidence_json,'$.eligibleCandidateCount')=0
3048
+ AND json_extract(${alias}.evidence_json,'$.selectedCandidateCount')=0
3049
+ AND json_extract(${alias}.evidence_json,'$.candidateSetComplete')=0`;
3050
+ }
3051
+ function count(db, sql) {
3052
+ return Number(db.prepare(sql).get()?.count ?? 0);
3053
+ }
3054
+ function packagePendingDiagnostics(db) {
3055
+ const pending = count(db, `SELECT COUNT(*) count FROM symbol_calls sc
3056
+ WHERE ${pendingPredicate("sc")}`);
3057
+ if (pending === 0) return [];
3058
+ const stale = count(db, `SELECT COUNT(*) count FROM repositories
3059
+ WHERE graph_stale_reason IS NOT NULL`);
3060
+ return [{
3061
+ severity: "warning",
3062
+ code: "package_import_resolution_pending",
3063
+ message: "Package-import facts await workspace linking; terminal package-resolution quality is deferred.",
3064
+ packageResolutionState: "pre_link_pending",
3065
+ pendingPackageImportCount: pending,
3066
+ graphState: "stale",
3067
+ staleRepositoryCount: stale,
3068
+ requiredAction: "relink",
3069
+ remediation: "service-flow link --workspace /workspace --force"
3070
+ }];
3071
+ }
3072
+ function symbolCallQuality(db) {
3073
+ const terminal = `NOT (${pendingPredicate("sc")})`;
3074
+ const row = db.prepare(`SELECT COUNT(*) total,
3075
+ SUM(CASE WHEN sc.status='resolved' THEN 1 ELSE 0 END) resolved,
3076
+ SUM(CASE WHEN sc.status='unresolved' THEN 1 ELSE 0 END) unresolved
3077
+ FROM symbol_calls sc WHERE ${terminal}`).get();
3078
+ const top = db.prepare(`SELECT sc.callee_expression calleeExpression,
3079
+ COUNT(*) count FROM symbol_calls sc
3080
+ WHERE sc.status='unresolved' AND ${terminal}
3081
+ GROUP BY sc.callee_expression
3082
+ ORDER BY count DESC,sc.callee_expression COLLATE BINARY LIMIT 5`).all();
3083
+ const total = Number(row?.total ?? 0);
3084
+ const unresolved = Number(row?.unresolved ?? 0);
3085
+ const ratio = total === 0 ? 0 : Number((unresolved / total).toFixed(4));
3086
+ return {
3087
+ severity: ratio > 0.05 ? "warning" : "info",
3088
+ code: "strict_symbol_call_quality",
3089
+ message: "Terminal symbol-call quality aggregate",
3090
+ total,
3091
+ resolved: Number(row?.resolved ?? 0),
3092
+ unresolved,
3093
+ unresolvedRatio: ratio,
3094
+ unresolvedRatioThreshold: 0.05,
3095
+ topUnresolvedCallees: top
3096
+ };
3097
+ }
3098
+
1194
3099
  // src/cli/doctor.ts
1195
3100
  function doctorDiagnostics(db, strict, options = {}) {
1196
3101
  const lifecycle = factLifecycleDiagnostic(db, options.workspaceId);
1197
- if (lifecycle?.code === "schema_upgrade_required" || lifecycle?.code === "unsupported_future_schema")
1198
- return boundDoctorDiagnostics([lifecycle]);
3102
+ if (lifecycle) return boundDoctorDiagnostics([lifecycle]);
3103
+ const globalLifecycle = options.workspaceId === void 0 ? void 0 : factLifecycleDiagnostic(db);
3104
+ if (globalLifecycle) return boundDoctorDiagnostics([
3105
+ ...schemaDriftDiagnostics(db, strict, options.workspaceId),
3106
+ ...analyzerVersionDiagnostics(db, strict, options.workspaceId),
3107
+ {
3108
+ severity: "warning",
3109
+ code: "workspace_detail_checks_deferred",
3110
+ message: "Selected-workspace lifecycle is valid, but JSON-dependent global detail checks were deferred because another workspace requires reindexing.",
3111
+ remediation: "Run doctor for the affected workspace after force index and link."
3112
+ }
3113
+ ]);
1199
3114
  const diagnostics = db.prepare("SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id").all();
1200
3115
  return boundDoctorDiagnostics([
1201
- ...lifecycle ? [lifecycle] : [],
1202
3116
  ...diagnostics,
3117
+ ...packagePendingDiagnostics(db),
1203
3118
  ...healthDiagnostics(db, strict),
1204
3119
  ...remoteTargetWithoutImplementationDiagnostics(db, strict, Boolean(options.detail)),
1205
3120
  ...localServiceDiagnostics(db, strict),
@@ -1338,14 +3253,6 @@ function jsonEvidenceQuality(db) {
1338
3253
  { severity: Number(graph.nonObject ?? 0) > 0 || Number(graph.withOutboundEvidence ?? 0) < Number(graph.total ?? 0) ? "warning" : "info", code: "strict_graph_evidence_quality", message: "Call-derived graph evidence and parser-evidence propagation aggregate", total: Number(graph.total ?? 0), nonObject: Number(graph.nonObject ?? 0), withOutboundEvidence: Number(graph.withOutboundEvidence ?? 0), examples: graphExamples }
1339
3254
  ];
1340
3255
  }
1341
- function symbolCallQuality(db) {
1342
- const row = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN status='resolved' THEN 1 ELSE 0 END) resolved, SUM(CASE WHEN status='unresolved' THEN 1 ELSE 0 END) unresolved FROM symbol_calls").get();
1343
- const top = db.prepare("SELECT callee_expression calleeExpression,COUNT(*) count FROM symbol_calls WHERE status='unresolved' GROUP BY callee_expression ORDER BY count DESC,callee_expression LIMIT 5").all();
1344
- const total = Number(row.total ?? 0);
1345
- const unresolved = Number(row.unresolved ?? 0);
1346
- const ratio = total === 0 ? 0 : Number((unresolved / total).toFixed(4));
1347
- return { severity: ratio > 0.05 ? "warning" : "info", code: "strict_symbol_call_quality", message: "Symbol-call quality aggregate", total, resolved: Number(row.resolved ?? 0), unresolved, unresolvedRatio: ratio, unresolvedRatioThreshold: 0.05, topUnresolvedCallees: top };
1348
- }
1349
3256
  function dbQueryQuality(db) {
1350
3257
  const row = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN query_entity IS NOT NULL THEN 1 ELSE 0 END) known, SUM(CASE WHEN query_entity IS NULL THEN 1 ELSE 0 END) unknown FROM outbound_calls WHERE call_type='local_db_query'").get();
1351
3258
  const total = Number(row.total ?? 0);
@@ -1561,8 +3468,12 @@ function serviceBindingQuality(db, detail) {
1561
3468
  function bindingCategory(row) {
1562
3469
  const evidence = parseObject(row.evidenceJson);
1563
3470
  const resolution = parseObject(evidence.serviceBindingResolution);
3471
+ const reference = parseObject(evidence.serviceBindingReference);
1564
3472
  if (resolution.status === "rejected_future_binding") return "direct_binding_missing";
1565
- if (resolution.status === "ambiguous") return "ambiguous_binding_candidates";
3473
+ if (reference.reason === "binding_declared_after_call")
3474
+ return "direct_binding_missing";
3475
+ if (resolution.status === "ambiguous" || reference.status === "ambiguous" || reference.reason === "unsupported_reaching_assignment")
3476
+ return "ambiguous_binding_candidates";
1566
3477
  const receiver = evidence.receiver;
1567
3478
  const symbolEvidence = parseObject(row.symbolEvidenceJson);
1568
3479
  if (symbolHasReceiverParameter(symbolEvidence, receiver))
@@ -1588,7 +3499,8 @@ function bindingExample(row) {
1588
3499
  sourceLine: row.sourceLine,
1589
3500
  receiver: evidence.receiver,
1590
3501
  unresolvedReason: row.unresolvedReason,
1591
- bindingResolution: evidence.serviceBindingResolution
3502
+ bindingResolution: evidence.serviceBindingResolution,
3503
+ bindingReference: evidence.serviceBindingReference
1592
3504
  };
1593
3505
  }
1594
3506
  function bindingCategoryAction(category) {
@@ -1841,8 +3753,8 @@ function hintLines(evidence) {
1841
3753
  }
1842
3754
  function dynamicHintLines(evidence) {
1843
3755
  const exploration = isRecord(evidence.dynamicTargetExploration) ? evidence.dynamicTargetExploration : evidence;
1844
- const count = numberValue(exploration.candidateCount);
1845
- if (count === 0) return [];
3756
+ const count2 = numberValue(exploration.candidateCount);
3757
+ if (count2 === 0) return [];
1846
3758
  const shown = numberValue(exploration.shownCandidateCount);
1847
3759
  const omitted = numberValue(exploration.omittedCandidateCount);
1848
3760
  const rejected = numberValue(exploration.rejectedCandidateCount);
@@ -1850,7 +3762,7 @@ function dynamicHintLines(evidence) {
1850
3762
  `viable candidates: ${shown} shown, ${omitted} omitted; rejected: ${rejected}`
1851
3763
  ];
1852
3764
  lines.push(...varSetHints(exploration.suggestedVarSets));
1853
- if (omitted > 0 || rejected > 0 || shown < count)
3765
+ if (omitted > 0 || rejected > 0 || shown < count2)
1854
3766
  lines.push("use --dynamic-mode candidates --max-dynamic-candidates 20 to explore candidate branches");
1855
3767
  return lines;
1856
3768
  }
@@ -1920,9 +3832,9 @@ function diagnosticLocation(diagnostic) {
1920
3832
  }
1921
3833
  function compactMessage(diagnostic) {
1922
3834
  const message = String(diagnostic.message ?? "");
1923
- const count = typeof diagnostic.count === "number" ? ` count=${diagnostic.count}` : "";
3835
+ const count2 = typeof diagnostic.count === "number" ? ` count=${diagnostic.count}` : "";
1924
3836
  const total = typeof diagnostic.total === "number" ? ` total=${diagnostic.total}` : "";
1925
- return `${message}${count}${total}`.trim();
3837
+ return `${message}${count2}${total}`.trim();
1926
3838
  }
1927
3839
  function suggestedHintLines(diagnostic) {
1928
3840
  const direct = cliHints(diagnostic.suggestedHints);