@saptools/service-flow 0.1.50 → 0.1.52

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 (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +16 -3
  3. package/TECHNICAL-NOTE.md +10 -1
  4. package/dist/{chunk-52OUS3MO.js → chunk-PTLDSHRC.js} +2663 -363
  5. package/dist/chunk-PTLDSHRC.js.map +1 -0
  6. package/dist/cli.js +318 -510
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +59 -17
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/000-clean.ts +82 -0
  12. package/src/cli.ts +97 -57
  13. package/src/db/connection.ts +1 -1
  14. package/src/db/migrations.ts +5 -3
  15. package/src/db/repositories.ts +120 -22
  16. package/src/db/schema.ts +7 -2
  17. package/src/index.ts +1 -1
  18. package/src/indexer/repository-indexer.ts +57 -29
  19. package/src/indexer/workspace-indexer.ts +84 -6
  20. package/src/linker/cross-repo-linker.ts +22 -2
  21. package/src/linker/service-resolver.ts +15 -4
  22. package/src/output/table-output.ts +56 -3
  23. package/src/parsers/cds-parser.ts +8 -2
  24. package/src/parsers/decorator-parser.ts +382 -48
  25. package/src/parsers/handler-registration-parser.ts +38 -21
  26. package/src/parsers/imported-wrapper-parser.ts +18 -5
  27. package/src/parsers/outbound-call-parser.ts +25 -8
  28. package/src/parsers/package-json-parser.ts +36 -11
  29. package/src/parsers/service-binding-parser-helpers.ts +8 -1
  30. package/src/parsers/service-binding-parser.ts +6 -3
  31. package/src/parsers/symbol-parser.ts +13 -3
  32. package/src/parsers/ts-project.ts +54 -0
  33. package/src/trace/000-dynamic-target-types.ts +84 -0
  34. package/src/trace/001-dynamic-identity.ts +280 -0
  35. package/src/trace/002-trace-diagnostics.ts +54 -0
  36. package/src/trace/003-dynamic-references.ts +82 -0
  37. package/src/trace/dynamic-branches.ts +45 -0
  38. package/src/trace/dynamic-targets.ts +654 -0
  39. package/src/trace/evidence.ts +391 -22
  40. package/src/trace/selectors.ts +483 -0
  41. package/src/trace/trace-engine.ts +101 -94
  42. package/src/types.ts +39 -1
  43. package/dist/chunk-52OUS3MO.js.map +0 -1
@@ -1,5 +1,468 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/db/repositories.ts
4
+ function upsertWorkspace(db, rootPath, dbPath) {
5
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6
+ db.prepare(
7
+ "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"
8
+ ).run(rootPath, dbPath, now, now);
9
+ return Number(
10
+ db.prepare("SELECT id FROM workspaces WHERE root_path=?").get(rootPath)?.id
11
+ );
12
+ }
13
+ function getWorkspace(db, rootPath) {
14
+ return db.prepare("SELECT * FROM workspaces WHERE root_path=?").get(rootPath);
15
+ }
16
+ function upsertRepository(db, workspaceId, r) {
17
+ db.prepare(
18
+ `INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,package_name,package_version,dependencies_json,kind,is_git_repo) VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET name=excluded.name,relative_path=excluded.relative_path,package_name=excluded.package_name,package_version=excluded.package_version,dependencies_json=excluded.dependencies_json,kind=excluded.kind`
19
+ ).run(
20
+ workspaceId,
21
+ r.name,
22
+ r.absolutePath,
23
+ r.relativePath,
24
+ r.packageName,
25
+ r.packageVersion,
26
+ JSON.stringify(r.dependencies ?? {}),
27
+ r.kind ?? "unknown",
28
+ r.isGitRepo ? 1 : 0
29
+ );
30
+ return Number(
31
+ db.prepare(
32
+ "SELECT id FROM repositories WHERE workspace_id=? AND absolute_path=?"
33
+ ).get(workspaceId, r.absolutePath)?.id
34
+ );
35
+ }
36
+ function listRepositories(db, workspaceId) {
37
+ return db.prepare("SELECT * FROM repositories WHERE (? IS NULL OR workspace_id=?) ORDER BY name,absolute_path,id").all(workspaceId, workspaceId);
38
+ }
39
+ function reposByName(db, name, workspaceId) {
40
+ return db.prepare(`SELECT * FROM repositories
41
+ WHERE (? IS NULL OR workspace_id=?) AND (name=? OR package_name=?)
42
+ ORDER BY name,absolute_path,id`).all(workspaceId, workspaceId, name, name);
43
+ }
44
+ function clearRepoFacts(db, repoId) {
45
+ for (const t of [
46
+ "cds_requires",
47
+ "cds_services",
48
+ "handler_classes",
49
+ "outbound_calls",
50
+ "symbol_calls",
51
+ "handler_registrations",
52
+ "service_bindings",
53
+ "symbols",
54
+ "diagnostics",
55
+ "files"
56
+ ])
57
+ db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
58
+ db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
59
+ }
60
+ function insertRequires(db, repoId, rows2) {
61
+ const stmt = db.prepare(
62
+ "INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
63
+ );
64
+ for (const r of rows2)
65
+ stmt.run(
66
+ repoId,
67
+ r.alias,
68
+ r.kind,
69
+ r.model,
70
+ r.destination,
71
+ r.servicePath,
72
+ r.requestTimeout,
73
+ r.rawJson
74
+ );
75
+ }
76
+ function insertService(db, repoId, s) {
77
+ const id = Number(
78
+ db.prepare(
79
+ "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"
80
+ ).get(
81
+ repoId,
82
+ s.namespace,
83
+ s.serviceName,
84
+ s.qualifiedName,
85
+ s.servicePath,
86
+ s.isExtend ? 1 : 0,
87
+ s.sourceFile,
88
+ s.sourceLine,
89
+ s.extension?.localReference,
90
+ s.extension?.importedSymbol,
91
+ s.extension?.localAlias,
92
+ s.extension?.moduleSpecifier,
93
+ s.extension?.importKind
94
+ )?.id
95
+ );
96
+ const stmt = db.prepare(
97
+ "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(?,?,?,?,?,?,?,?,?,?)"
98
+ );
99
+ db.prepare(
100
+ "INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
101
+ ).run("service", s.qualifiedName, s.servicePath, String(repoId));
102
+ for (const o of s.operations)
103
+ stmt.run(
104
+ id,
105
+ o.operationType,
106
+ o.operationName,
107
+ o.operationPath,
108
+ o.paramsJson,
109
+ o.returnType,
110
+ o.sourceFile,
111
+ o.sourceLine,
112
+ o.provenance ?? "direct",
113
+ o.baseOperationId ?? null
114
+ );
115
+ const search = db.prepare(
116
+ "INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
117
+ );
118
+ for (const o of s.operations)
119
+ search.run("operation", o.operationName, o.operationPath, String(repoId));
120
+ return id;
121
+ }
122
+ function insertHandler(db, repoId, h) {
123
+ const sid = insertHandlerClassSymbol(db, repoId, h);
124
+ const hid = Number(
125
+ db.prepare(
126
+ "INSERT INTO handler_classes(repo_id,symbol_id,class_name,source_file,source_line) VALUES(?,?,?,?,?) RETURNING id"
127
+ ).get(repoId, sid, h.className, h.sourceFile, h.sourceLine)?.id
128
+ );
129
+ const stmt = db.prepare(
130
+ "INSERT INTO handler_methods(handler_class_id,method_name,decorator_kind,decorator_value,decorator_raw_expression,decorator_resolution_json,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
131
+ );
132
+ for (const m of h.methods)
133
+ stmt.run(
134
+ hid,
135
+ m.methodName,
136
+ m.decoratorKind,
137
+ m.decoratorValue,
138
+ m.decoratorRawExpression,
139
+ JSON.stringify(canonicalHandlerMethodResolution(m)),
140
+ m.sourceFile,
141
+ m.sourceLine
142
+ );
143
+ insertHandlerIndexDiagnostic(db, repoId, h);
144
+ return hid;
145
+ }
146
+ function insertHandlerClassSymbol(db, repoId, h) {
147
+ const classEvidence = {
148
+ hasHandlerDecorator: h.hasHandlerDecorator ?? false,
149
+ classDecoratorNames: h.classDecoratorNames ?? [],
150
+ observedDecoratorNames: h.observedDecoratorNames ?? [],
151
+ unsupportedDecoratorNames: h.unsupportedDecoratorNames ?? [],
152
+ unsupportedMethods: h.methods.filter((method) => !handlerMethodIsExecutable(method)).map((method) => ({
153
+ methodName: method.methodName,
154
+ decoratorKind: method.decoratorKind,
155
+ sourceFile: method.sourceFile,
156
+ sourceLine: method.sourceLine,
157
+ reason: method.decoratorResolution.unresolvedReason
158
+ }))
159
+ };
160
+ return Number(
161
+ db.prepare(
162
+ "INSERT INTO symbols(repo_id,kind,name,qualified_name,exported,start_line,end_line,source_file,evidence_json) VALUES(?,?,?,?,?,?,?,?,?) RETURNING id"
163
+ ).get(
164
+ repoId,
165
+ "class",
166
+ h.className,
167
+ h.className,
168
+ 1,
169
+ h.sourceLine,
170
+ h.sourceLine,
171
+ h.sourceFile,
172
+ JSON.stringify(classEvidence)
173
+ )?.id
174
+ );
175
+ }
176
+ function insertHandlerIndexDiagnostic(db, repoId, h) {
177
+ if (!h.hasHandlerDecorator) return;
178
+ const hasExecutable = h.methods.some(handlerMethodIsExecutable);
179
+ const unsupported = h.methods.filter((method) => !handlerMethodIsExecutable(method));
180
+ if (hasExecutable && unsupported.length === 0) return;
181
+ const code = hasExecutable ? "handler_decorators_not_indexed" : "handler_methods_not_indexed";
182
+ const names = unsupported.map((method) => method.decoratorKind).sort();
183
+ const detail = names.length > 0 ? ` Unsupported decorators: ${[...new Set(names)].join(", ")}.` : "";
184
+ db.prepare(
185
+ "INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line) VALUES(?,?,?,?,?,?)"
186
+ ).run(
187
+ repoId,
188
+ "warning",
189
+ code,
190
+ 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}`,
191
+ h.sourceFile,
192
+ h.sourceLine
193
+ );
194
+ }
195
+ function canonicalHandlerMethodResolution(method) {
196
+ const handlerKind = method.handlerKind ?? method.decoratorResolution.handlerKind ?? legacyHandlerKind(method.decoratorKind);
197
+ const executable = method.executable ?? method.decoratorResolution.executable ?? (handlerKind === "operation" || handlerKind === "event" || handlerKind === "entity_lifecycle");
198
+ return {
199
+ ...method.decoratorResolution,
200
+ handlerKind,
201
+ executable,
202
+ lifecyclePhase: method.lifecyclePhase ?? method.decoratorResolution.lifecyclePhase,
203
+ lifecycleEvent: method.lifecycleEvent ?? method.decoratorResolution.lifecycleEvent
204
+ };
205
+ }
206
+ function handlerMethodIsExecutable(method) {
207
+ return canonicalHandlerMethodResolution(method).executable === true;
208
+ }
209
+ function legacyHandlerKind(kind) {
210
+ if (kind === "Event") return "event";
211
+ if (["Action", "Func", "On"].includes(kind)) return "operation";
212
+ return "unsupported_decorator";
213
+ }
214
+ function insertRegistrations(db, repoId, rows2) {
215
+ const stmt = db.prepare(
216
+ "INSERT INTO handler_registrations(repo_id,handler_class_id,class_name,import_source,registration_file,registration_line,registration_kind,confidence) VALUES(?,?,?,?,?,?,?,?)"
217
+ );
218
+ for (const r of rows2) {
219
+ const handlerClass = r.className ? db.prepare(
220
+ "SELECT id FROM handler_classes WHERE repo_id=? AND class_name=? ORDER BY id"
221
+ ).all(repoId, r.className) : [];
222
+ stmt.run(
223
+ repoId,
224
+ handlerClass.length === 1 ? handlerClass[0]?.id : null,
225
+ r.className,
226
+ r.importSource,
227
+ r.registrationFile,
228
+ r.registrationLine,
229
+ r.registrationKind,
230
+ r.confidence
231
+ );
232
+ }
233
+ }
234
+ function insertBindings(db, repoId, rows2) {
235
+ const stmt = db.prepare(
236
+ "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,helper_chain_json) VALUES(?,(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1),?,?,?,?,?,?,?,?,?,?)"
237
+ );
238
+ for (const r of rows2)
239
+ stmt.run(
240
+ repoId,
241
+ repoId,
242
+ r.sourceFile,
243
+ r.sourceLine,
244
+ r.sourceLine,
245
+ r.variableName,
246
+ r.alias,
247
+ r.aliasExpr,
248
+ r.destinationExpr,
249
+ r.servicePathExpr,
250
+ r.isDynamic ? 1 : 0,
251
+ JSON.stringify(r.placeholders),
252
+ r.sourceFile,
253
+ r.sourceLine,
254
+ r.helperChain ? JSON.stringify(r.helperChain) : null
255
+ );
256
+ }
257
+ function insertExecutableSymbols(db, repoId, rows2) {
258
+ 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=?),?,?,?,?,?,?,?,?,?,?,?)");
259
+ for (const r of rows2) 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);
260
+ }
261
+ function insertSymbolCalls(db, repoId, rows2) {
262
+ const callerStmt = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1");
263
+ const insertStmt = db.prepare("INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?)");
264
+ for (const r of rows2) {
265
+ const caller = callerStmt.get(repoId, r.sourceFile, r.callerQualifiedName);
266
+ const target = resolveSymbolCallTarget(db, repoId, r);
267
+ insertStmt.run(repoId, caller?.id, target.id, r.calleeExpression, r.importSource, r.sourceFile, r.sourceLine, target.status, 0.8, JSON.stringify({ ...r.evidence, candidateStrategy: target.strategy, candidateCount: target.candidateCount }), target.reason);
268
+ }
269
+ }
270
+ function isRelativeImportedSymbolCall(r) {
271
+ return Boolean(r.importSource?.startsWith("."));
272
+ }
273
+ function resolveSymbolCallTarget(db, repoId, r) {
274
+ const evidence = r.evidence;
275
+ const localRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName);
276
+ if (localRows.length === 1) return { id: localRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "same_file_exact", candidateCount: 1 };
277
+ if (localRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple same-file symbol targets matched exactly", strategy: "same_file_exact", candidateCount: localRows.length };
278
+ if (evidence.relation === "class_instance_method" && isRelativeImportedSymbolCall(r)) {
279
+ const classRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName);
280
+ if (classRows.length === 1) return { id: classRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "relative_import_class_instance_method", candidateCount: 1 };
281
+ if (classRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple relative class instance method targets matched exactly", strategy: "relative_import_class_instance_method", candidateCount: classRows.length };
282
+ }
283
+ const rows2 = db.prepare("SELECT id,kind,evidence_json evidenceJson FROM symbols WHERE repo_id=? AND source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName);
284
+ if (evidence.relation === "relative_import_proxy_member" && rows2.length > 1) {
285
+ const objectMapRows = rows2.filter((row) => String(row.evidenceJson ?? "").includes("exported_object_shorthand") || String(row.evidenceJson ?? "").includes("exported_object_literal"));
286
+ if (objectMapRows.length > 0) {
287
+ const concrete = rows2.find((row) => row.kind !== "object_alias") ?? objectMapRows[0];
288
+ return { id: concrete?.id ?? null, status: "resolved", reason: null, strategy: "proxy_member_exported_object_map", candidateCount: rows2.length };
289
+ }
290
+ return { id: null, status: "ambiguous", reason: "Proxy member target requires explicit factory/module/type evidence; global member name is ambiguous", strategy: "proxy_member_no_global_name_fallback", candidateCount: rows2.length };
291
+ }
292
+ if (rows2.length === 1) return { id: rows2[0]?.id ?? null, status: "resolved", reason: null, strategy: evidence.relation === "relative_import_proxy_member" ? "proxy_member_unique_exported_candidate" : "relative_import_exported_exact", candidateCount: 1 };
293
+ if (rows2.length > 1) return { id: null, status: "ambiguous", reason: "Multiple exported symbol targets matched exactly", strategy: "exported_exact", candidateCount: rows2.length };
294
+ return { id: null, status: "unresolved", reason: "No local symbol target matched exactly", strategy: evidence.relation === "relative_import_proxy_member" ? "proxy_member_no_global_name_fallback" : "exact_symbol_match", candidateCount: 0 };
295
+ }
296
+ function insertCalls(db, repoId, rows2) {
297
+ const stmt = db.prepare(
298
+ "INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,evidence_json,external_target_kind,external_target_id,external_target_label,external_target_dynamic,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
299
+ );
300
+ for (const r of rows2) {
301
+ const binding = resolvePersistedBinding(db, repoId, r);
302
+ const evidence = {
303
+ ...r.evidence ?? {},
304
+ serviceBindingResolution: binding.evidence
305
+ };
306
+ stmt.run(
307
+ repoId,
308
+ repoId,
309
+ r.sourceFile,
310
+ r.sourceSymbolQualifiedName,
311
+ repoId,
312
+ r.sourceFile,
313
+ r.sourceLine,
314
+ r.sourceLine,
315
+ r.callType,
316
+ r.method,
317
+ r.operationPathExpr,
318
+ r.queryEntity,
319
+ r.eventNameExpr,
320
+ r.payloadSummary,
321
+ r.sourceFile,
322
+ r.sourceLine,
323
+ r.confidence,
324
+ r.unresolvedReason ?? binding.unresolvedReason,
325
+ r.localServiceName,
326
+ r.localServiceLookup,
327
+ r.aliasChain ? JSON.stringify(r.aliasChain) : null,
328
+ JSON.stringify(evidence),
329
+ r.externalTarget?.kind ?? null,
330
+ r.externalTarget?.stableId ?? null,
331
+ r.externalTarget?.label ?? null,
332
+ r.externalTarget?.dynamic ? 1 : 0,
333
+ binding.bindingId
334
+ );
335
+ }
336
+ }
337
+ function resolvePersistedBinding(db, repoId, call) {
338
+ if (!call.serviceVariableName)
339
+ return { bindingId: null, evidence: { status: "not_applicable", candidateCount: 0 } };
340
+ const candidates = bindingCandidates(db, repoId, call);
341
+ const prior = candidates.filter((candidate) => candidate.sourceLine <= call.sourceLine);
342
+ const families = new Set(prior.map(bindingSignature));
343
+ if (prior.length > 0 && families.size === 1) {
344
+ const selected = prior.at(-1);
345
+ return {
346
+ bindingId: selected?.id ?? null,
347
+ evidence: bindingEvidence("selected", prior, selected)
348
+ };
349
+ }
350
+ if (prior.length > 1) {
351
+ return {
352
+ bindingId: null,
353
+ unresolvedReason: "ambiguous_service_binding_candidates",
354
+ evidence: bindingEvidence("ambiguous", prior)
355
+ };
356
+ }
357
+ if (candidates.length > 0) {
358
+ return {
359
+ bindingId: null,
360
+ unresolvedReason: "service_binding_declared_after_call",
361
+ evidence: bindingEvidence("rejected_future_binding", candidates)
362
+ };
363
+ }
364
+ return {
365
+ bindingId: null,
366
+ evidence: bindingEvidence("unrecoverable", [])
367
+ };
368
+ }
369
+ function bindingCandidates(db, repoId, call) {
370
+ const ownerId = callSymbolId(db, repoId, call);
371
+ const rows2 = db.prepare(`
372
+ SELECT id,symbol_id symbolId,variable_name variableName,alias,alias_expr aliasExpr,
373
+ destination_expr destinationExpr,service_path_expr servicePathExpr,
374
+ source_file sourceFile,source_line sourceLine,helper_chain_json helperChainJson
375
+ FROM service_bindings
376
+ WHERE repo_id=? AND variable_name=? AND source_file=?
377
+ AND (? IS NULL OR symbol_id IS NULL OR symbol_id=?)
378
+ ORDER BY source_line,id
379
+ `).all(
380
+ repoId,
381
+ call.serviceVariableName,
382
+ call.sourceFile,
383
+ ownerId,
384
+ ownerId
385
+ );
386
+ return rows2.flatMap((row) => {
387
+ if (typeof row.id !== "number" || typeof row.variableName !== "string" || typeof row.sourceFile !== "string" || typeof row.sourceLine !== "number")
388
+ return [];
389
+ return [{
390
+ id: row.id,
391
+ symbolId: nullableNumber(row.symbolId),
392
+ variableName: row.variableName,
393
+ alias: nullableString(row.alias),
394
+ aliasExpr: nullableString(row.aliasExpr),
395
+ destinationExpr: nullableString(row.destinationExpr),
396
+ servicePathExpr: nullableString(row.servicePathExpr),
397
+ sourceFile: row.sourceFile,
398
+ sourceLine: row.sourceLine,
399
+ helperChainJson: nullableString(row.helperChainJson)
400
+ }];
401
+ });
402
+ }
403
+ function nullableString(value) {
404
+ return value === null || typeof value === "string" ? value : void 0;
405
+ }
406
+ function nullableNumber(value) {
407
+ return value === null || typeof value === "number" ? value : void 0;
408
+ }
409
+ function callSymbolId(db, repoId, call) {
410
+ const row = db.prepare(`
411
+ SELECT id FROM symbols
412
+ WHERE repo_id=? AND source_file=?
413
+ AND ((? IS NOT NULL AND qualified_name=?)
414
+ OR (start_line<=? AND end_line>=?))
415
+ ORDER BY CASE WHEN qualified_name=? THEN 0 ELSE 1 END,
416
+ (end_line-start_line),id
417
+ LIMIT 1
418
+ `).get(
419
+ repoId,
420
+ call.sourceFile,
421
+ call.sourceSymbolQualifiedName,
422
+ call.sourceSymbolQualifiedName,
423
+ call.sourceLine,
424
+ call.sourceLine,
425
+ call.sourceSymbolQualifiedName
426
+ );
427
+ return typeof row?.id === "number" ? row.id : void 0;
428
+ }
429
+ function bindingEvidence(status, candidates, selected) {
430
+ return {
431
+ status,
432
+ candidateCount: candidates.length,
433
+ selectedBindingId: selected?.id,
434
+ sourceOrderRule: "binding_source_line_must_not_follow_call",
435
+ candidates: candidates.map((candidate) => ({
436
+ bindingId: candidate.id,
437
+ symbolId: candidate.symbolId,
438
+ variableName: candidate.variableName,
439
+ alias: candidate.alias,
440
+ aliasExpr: candidate.aliasExpr,
441
+ destinationExpr: candidate.destinationExpr,
442
+ servicePathExpr: candidate.servicePathExpr,
443
+ sourceFile: candidate.sourceFile,
444
+ sourceLine: candidate.sourceLine,
445
+ helperChain: parseBindingChain(candidate.helperChainJson)
446
+ }))
447
+ };
448
+ }
449
+ function bindingSignature(candidate) {
450
+ return JSON.stringify([
451
+ candidate.alias,
452
+ candidate.aliasExpr,
453
+ candidate.destinationExpr,
454
+ candidate.servicePathExpr
455
+ ]);
456
+ }
457
+ function parseBindingChain(value) {
458
+ if (!value) return void 0;
459
+ try {
460
+ return JSON.parse(value);
461
+ } catch {
462
+ return void 0;
463
+ }
464
+ }
465
+
3
466
  // src/discovery/discover-repositories.ts
4
467
  import fs from "fs/promises";
5
468
  import path2 from "path";
@@ -74,6 +537,9 @@ async function discoverRepositories(rootPath, ignore) {
74
537
  // src/parsers/package-json-parser.ts
75
538
  import fs2 from "fs/promises";
76
539
  import path3 from "path";
540
+ function emptyPackageFacts() {
541
+ return { dependencies: {}, cdsRequires: [], scripts: {} };
542
+ }
77
543
  function recordOfString(value) {
78
544
  if (!value || typeof value !== "object") return {};
79
545
  return Object.fromEntries(
@@ -100,22 +566,31 @@ function readRequires(cds) {
100
566
  ];
101
567
  });
102
568
  }
103
- async function parsePackageJson(repoPath) {
569
+ async function parsePackageJson(repoPath, options = {}) {
570
+ return (await loadPackageJsonSnapshot(repoPath, options)).facts;
571
+ }
572
+ async function loadPackageJsonSnapshot(repoPath, options = {}) {
104
573
  try {
105
574
  const raw = await fs2.readFile(path3.join(repoPath, "package.json"), "utf8");
106
575
  const json = JSON.parse(raw);
107
576
  return {
108
- packageName: typeof json.name === "string" ? json.name : void 0,
109
- packageVersion: typeof json.version === "string" ? json.version : void 0,
110
- dependencies: {
111
- ...recordOfString(json.dependencies),
112
- ...recordOfString(json.devDependencies)
113
- },
114
- cdsRequires: readRequires(json.cds),
115
- scripts: recordOfString(json.scripts)
577
+ rawText: raw,
578
+ facts: {
579
+ packageName: typeof json.name === "string" ? json.name : void 0,
580
+ packageVersion: typeof json.version === "string" ? json.version : void 0,
581
+ dependencies: {
582
+ ...recordOfString(json.dependencies),
583
+ ...recordOfString(json.devDependencies)
584
+ },
585
+ cdsRequires: readRequires(json.cds),
586
+ scripts: recordOfString(json.scripts)
587
+ }
116
588
  };
117
- } catch {
118
- return { dependencies: {}, cdsRequires: [], scripts: {} };
589
+ } catch (error) {
590
+ const missing = typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
591
+ if (!options.strict || options.allowMissing && missing)
592
+ return { facts: emptyPackageFacts(), rawText: "" };
593
+ throw error;
119
594
  }
120
595
  }
121
596
 
@@ -259,9 +734,9 @@ function operationsFromBody(text, maskedBody, bodyOffset, filePath) {
259
734
  sourceLine: lineOf(text, bodyOffset + (m.index ?? 0))
260
735
  }));
261
736
  }
262
- async function parseCdsFile(repoPath, filePath) {
737
+ async function parseCdsFile(repoPath, filePath, context) {
263
738
  const absolute = path4.join(repoPath, filePath);
264
- const text = await fs3.readFile(absolute, "utf8");
739
+ const text = context?.get(filePath)?.text ?? await fs3.readFile(absolute, "utf8");
265
740
  const masked = maskCommentsAndStrings(text);
266
741
  const namespace = /namespace\s+([\w.]+)\s*;/.exec(masked)?.[1];
267
742
  const services = [];
@@ -311,8 +786,8 @@ async function parseCdsFile(repoPath, filePath) {
311
786
  }
312
787
 
313
788
  // src/parsers/decorator-parser.ts
314
- import fs4 from "fs/promises";
315
- import path5 from "path";
789
+ import fs5 from "fs/promises";
790
+ import path6 from "path";
316
791
  import ts2 from "typescript";
317
792
 
318
793
  // src/linker/operation-decorator-normalizer.ts
@@ -356,7 +831,34 @@ function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
356
831
  }
357
832
 
358
833
  // src/parsers/ts-project.ts
834
+ import fs4 from "fs/promises";
835
+ import path5 from "path";
359
836
  import ts from "typescript";
837
+ async function loadRepositorySourceContext(repoPath, filePaths, instrumentation) {
838
+ const snapshots = /* @__PURE__ */ new Map();
839
+ for (const inputPath of filePaths) {
840
+ const filePath = normalizePath(inputPath);
841
+ await instrumentation?.onSourceRead?.(repoPath, filePath);
842
+ const text = await fs4.readFile(path5.join(repoPath, filePath), "utf8");
843
+ let ast;
844
+ snapshots.set(filePath, {
845
+ repoPath,
846
+ filePath,
847
+ text,
848
+ sizeBytes: Buffer.byteLength(text),
849
+ sourceFile: () => {
850
+ if (ast) return ast;
851
+ instrumentation?.onAstCreated?.(repoPath, filePath);
852
+ ast = createSourceFile(filePath, text);
853
+ return ast;
854
+ }
855
+ });
856
+ }
857
+ return {
858
+ get: (filePath) => snapshots.get(normalizePath(filePath)),
859
+ entries: () => [...snapshots.values()]
860
+ };
861
+ }
360
862
  function createSourceFile(filePath, text) {
361
863
  return ts.createSourceFile(
362
864
  filePath,
@@ -368,6 +870,22 @@ function createSourceFile(filePath, text) {
368
870
  }
369
871
 
370
872
  // src/parsers/decorator-parser.ts
873
+ var OPERATION_DECORATORS = /* @__PURE__ */ new Set(["Action", "Func", "On"]);
874
+ var EVENT_DECORATORS = /* @__PURE__ */ new Set(["Event"]);
875
+ var LIFECYCLE_DECORATORS = /* @__PURE__ */ new Map([
876
+ ["BeforeCreate", { phase: "before", event: "CREATE" }],
877
+ ["OnCreate", { phase: "on", event: "CREATE" }],
878
+ ["AfterCreate", { phase: "after", event: "CREATE" }],
879
+ ["BeforeRead", { phase: "before", event: "READ" }],
880
+ ["OnRead", { phase: "on", event: "READ" }],
881
+ ["AfterRead", { phase: "after", event: "READ" }],
882
+ ["BeforeUpdate", { phase: "before", event: "UPDATE" }],
883
+ ["OnUpdate", { phase: "on", event: "UPDATE" }],
884
+ ["AfterUpdate", { phase: "after", event: "UPDATE" }],
885
+ ["BeforeDelete", { phase: "before", event: "DELETE" }],
886
+ ["OnDelete", { phase: "on", event: "DELETE" }],
887
+ ["AfterDelete", { phase: "after", event: "DELETE" }]
888
+ ]);
371
889
  function line(sf, pos) {
372
890
  return sf.getLineAndCharacterOfPosition(pos).line + 1;
373
891
  }
@@ -382,6 +900,12 @@ function firstArg(d) {
382
900
  const e = d.expression;
383
901
  return ts2.isCallExpression(e) ? e.arguments[0] : void 0;
384
902
  }
903
+ function decoratorArguments(d) {
904
+ return ts2.isCallExpression(d.expression) ? d.expression.arguments : void 0;
905
+ }
906
+ function methodName(name) {
907
+ return ts2.isIdentifier(name) || ts2.isStringLiteralLike(name) || ts2.isNumericLiteral(name) ? name.text : name.getText();
908
+ }
385
909
  function unwrapExpression(expression) {
386
910
  let current = expression;
387
911
  while (ts2.isParenthesizedExpression(current) || ts2.isAsExpression(current) || ts2.isTypeAssertionExpression(current) || ts2.isSatisfiesExpression(current)) current = current.expression;
@@ -419,9 +943,12 @@ function collectStringLookups(source) {
419
943
  const lookups = {
420
944
  identifiers: /* @__PURE__ */ new Map(),
421
945
  enumMembers: /* @__PURE__ */ new Map(),
422
- objectProperties: /* @__PURE__ */ new Map()
946
+ objectProperties: /* @__PURE__ */ new Map(),
947
+ capDecoratorNames: /* @__PURE__ */ new Map(),
948
+ capDecoratorNamespaces: /* @__PURE__ */ new Set()
423
949
  };
424
950
  for (const statement of source.statements) {
951
+ collectCapDecoratorImports(statement, lookups);
425
952
  if (ts2.isEnumDeclaration(statement)) collectEnumMembers(statement, lookups);
426
953
  if (!ts2.isVariableStatement(statement) || !(statement.declarationList.flags & ts2.NodeFlags.Const)) continue;
427
954
  for (const declaration of statement.declarationList.declarations) {
@@ -433,75 +960,277 @@ function collectStringLookups(source) {
433
960
  }
434
961
  return lookups;
435
962
  }
436
- function unresolved(rawExpression, reason) {
437
- return { rawExpression, resolutionKind: "unresolved", unresolvedReason: reason };
963
+ function collectCapDecoratorImports(statement, lookups) {
964
+ if (!ts2.isImportDeclaration(statement) || !ts2.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "cds-routing-handlers") return;
965
+ const clause = statement.importClause;
966
+ if (!clause || clause.isTypeOnly) return;
967
+ const bindings = clause.namedBindings;
968
+ if (bindings && ts2.isNamedImports(bindings)) {
969
+ for (const element of bindings.elements) {
970
+ if (element.isTypeOnly) continue;
971
+ lookups.capDecoratorNames.set(
972
+ element.name.text,
973
+ element.propertyName?.text ?? element.name.text
974
+ );
975
+ }
976
+ }
977
+ if (bindings && ts2.isNamespaceImport(bindings))
978
+ lookups.capDecoratorNamespaces.add(bindings.name.text);
979
+ }
980
+ function capDecoratorName(decorator, lookups) {
981
+ const expression = ts2.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
982
+ if (ts2.isIdentifier(expression))
983
+ return lookups.capDecoratorNames.get(expression.text);
984
+ if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression) && lookups.capDecoratorNamespaces.has(expression.expression.text))
985
+ return expression.name.text;
986
+ return void 0;
987
+ }
988
+ function unresolved(rawExpression, reason, argumentExpression) {
989
+ return {
990
+ rawExpression,
991
+ argumentExpression,
992
+ resolutionKind: "unresolved",
993
+ unresolvedReason: reason
994
+ };
995
+ }
996
+ function resolved2(rawExpression, argumentExpression, resolvedValue, resolutionKind) {
997
+ return { rawExpression, argumentExpression, resolvedValue, resolutionKind };
438
998
  }
439
999
  function generatedConstant(rawExpression) {
440
1000
  const match = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(rawExpression.trim());
441
1001
  return match?.[1] ? generatedOperationNameFromConstant(match[1]) : void 0;
442
1002
  }
443
- function resolveDecoratorArgument(argument, lookups) {
444
- if (!argument) return unresolved("", "decorator_argument_missing");
445
- const rawExpression = argument.getText();
1003
+ function resolveDecoratorArgument(argument, lookups, rawExpression) {
1004
+ if (!argument) return unresolved(rawExpression, "decorator_argument_missing");
1005
+ const argumentExpression = argument.getText();
446
1006
  const expression = unwrapExpression(argument);
447
1007
  if (ts2.isStringLiteralLike(expression))
448
- return { rawExpression, resolvedValue: expression.text, resolutionKind: "literal" };
1008
+ return resolved2(rawExpression, argumentExpression, expression.text, "literal");
449
1009
  if (ts2.isIdentifier(expression)) {
450
1010
  const value = lookups.identifiers.get(expression.text);
451
- return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string") : { rawExpression, resolvedValue: value, resolutionKind: "const_identifier" };
1011
+ return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string", argumentExpression) : resolved2(rawExpression, argumentExpression, value, "const_identifier");
452
1012
  }
453
1013
  if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression)) {
454
1014
  const key = `${expression.expression.text}.${expression.name.text}`;
455
1015
  const enumValue = lookups.enumMembers.get(key);
456
1016
  if (enumValue !== void 0)
457
- return { rawExpression, resolvedValue: enumValue, resolutionKind: "enum_member" };
1017
+ return resolved2(rawExpression, argumentExpression, enumValue, "enum_member");
458
1018
  const objectValue2 = lookups.objectProperties.get(key);
459
1019
  if (objectValue2 !== void 0)
460
- return { rawExpression, resolvedValue: objectValue2, resolutionKind: "const_object_property" };
1020
+ return resolved2(rawExpression, argumentExpression, objectValue2, "const_object_property");
461
1021
  }
462
- const generatedValue = generatedConstant(rawExpression);
1022
+ const generatedValue = generatedConstant(argumentExpression);
463
1023
  if (generatedValue !== void 0)
464
- return {
1024
+ return resolved2(
465
1025
  rawExpression,
466
- resolvedValue: generatedValue,
467
- resolutionKind: "generated_constant_name"
468
- };
1026
+ argumentExpression,
1027
+ generatedValue,
1028
+ "generated_constant_name"
1029
+ );
469
1030
  if (ts2.isPropertyAccessExpression(expression))
470
- return unresolved(rawExpression, "property_access_not_resolved_to_local_string");
471
- return unresolved(rawExpression, "unsupported_decorator_expression");
1031
+ return unresolved(
1032
+ rawExpression,
1033
+ "property_access_not_resolved_to_local_string",
1034
+ argumentExpression
1035
+ );
1036
+ return unresolved(rawExpression, "unsupported_decorator_expression", argumentExpression);
1037
+ }
1038
+ function classificationFor(name) {
1039
+ if (OPERATION_DECORATORS.has(name))
1040
+ return { handlerKind: "operation", executable: true };
1041
+ if (EVENT_DECORATORS.has(name))
1042
+ return { handlerKind: "event", executable: true };
1043
+ const lifecycle = LIFECYCLE_DECORATORS.get(name);
1044
+ if (!lifecycle) return void 0;
1045
+ return {
1046
+ handlerKind: "entity_lifecycle",
1047
+ executable: true,
1048
+ lifecyclePhase: lifecycle.phase,
1049
+ lifecycleEvent: lifecycle.event
1050
+ };
1051
+ }
1052
+ function lifecycleLikePhase(name) {
1053
+ if (/^On[A-Z]/.test(name)) return "on";
1054
+ if (/^Before[A-Z]/.test(name)) return "before";
1055
+ if (/^After[A-Z]/.test(name)) return "after";
1056
+ return void 0;
1057
+ }
1058
+ function withClassification(resolution, classification) {
1059
+ return {
1060
+ ...resolution,
1061
+ handlerKind: classification.handlerKind,
1062
+ executable: classification.executable,
1063
+ lifecyclePhase: classification.lifecyclePhase,
1064
+ lifecycleEvent: classification.lifecycleEvent
1065
+ };
1066
+ }
1067
+ function withDecoratorEvidence(resolution, decorator, resolvedDecoratorKind) {
1068
+ return {
1069
+ ...resolution,
1070
+ decoratorExpression: decorator.expression.getText(),
1071
+ resolvedDecoratorKind,
1072
+ decoratorImportSource: resolvedDecoratorKind ? "cds-routing-handlers" : void 0
1073
+ };
1074
+ }
1075
+ function lifecycleDecoratorResolution(decorator, lookups, classification) {
1076
+ const rawExpression = decorator.expression.getText();
1077
+ const args = decoratorArguments(decorator);
1078
+ if (args?.length === 0)
1079
+ return {
1080
+ classification,
1081
+ resolution: withClassification({
1082
+ rawExpression,
1083
+ resolutionKind: "lifecycle_implicit"
1084
+ }, classification)
1085
+ };
1086
+ const resolution = args?.length === 1 ? resolveDecoratorArgument(args[0], lookups, rawExpression) : unresolved(rawExpression, args ? "unsupported_lifecycle_argument_count" : "lifecycle_decorator_call_required");
1087
+ const unsupported = { ...classification, handlerKind: "unsupported_lifecycle", executable: false };
1088
+ const unsupportedResolution = args?.length === 1 ? { ...resolution, unresolvedReason: "lifecycle_decorator_arguments_not_supported" } : resolution;
1089
+ return {
1090
+ classification: unsupported,
1091
+ resolution: withClassification(unsupportedResolution, unsupported)
1092
+ };
472
1093
  }
473
- async function parseDecorators(repoPath, filePath) {
474
- const text = await fs4.readFile(path5.join(repoPath, filePath), "utf8");
475
- const sf = createSourceFile(filePath, text);
1094
+ function unsupportedLifecycleResolution(decorator, phase) {
1095
+ const classification = {
1096
+ handlerKind: "unsupported_lifecycle",
1097
+ executable: false,
1098
+ lifecyclePhase: phase
1099
+ };
1100
+ const resolution = unresolved(
1101
+ decorator.expression.getText(),
1102
+ "lifecycle_decorator_not_allowlisted"
1103
+ );
1104
+ return { classification, resolution: withClassification(resolution, classification) };
1105
+ }
1106
+ function unsupportedDecoratorResolution(decorator, reason, phase) {
1107
+ const classification = {
1108
+ handlerKind: phase ? "unsupported_lifecycle" : "unsupported_decorator",
1109
+ executable: false,
1110
+ lifecyclePhase: phase
1111
+ };
1112
+ return {
1113
+ classification,
1114
+ resolution: withClassification(
1115
+ unresolved(decorator.expression.getText(), reason),
1116
+ classification
1117
+ )
1118
+ };
1119
+ }
1120
+ function parseMethodDecorator(decorator, lookups, handlerClass, allowLifecycle) {
1121
+ const decoratorKind = callName(decorator);
1122
+ const importedKind = capDecoratorName(decorator, lookups);
1123
+ const resolvedKind = importedKind ?? decoratorKind;
1124
+ const base = classificationFor(resolvedKind);
1125
+ const phase = lifecycleLikePhase(resolvedKind);
1126
+ const isLifecycle = base?.handlerKind === "entity_lifecycle" || Boolean(phase && !base);
1127
+ if (!handlerClass && isLifecycle) return void 0;
1128
+ const parsed = isLifecycle && (!allowLifecycle || !importedKind) ? unsupportedDecoratorResolution(
1129
+ decorator,
1130
+ "lifecycle_decorator_import_not_supported",
1131
+ phase
1132
+ ) : base?.handlerKind === "entity_lifecycle" ? lifecycleDecoratorResolution(decorator, lookups, base) : phase && !base ? unsupportedLifecycleResolution(decorator, phase) : !base && handlerClass ? unsupportedDecoratorResolution(decorator, "decorator_not_allowlisted") : {
1133
+ classification: base,
1134
+ resolution: resolveDecoratorArgument(
1135
+ firstArg(decorator),
1136
+ lookups,
1137
+ firstArg(decorator)?.getText() ?? decorator.expression.getText()
1138
+ )
1139
+ };
1140
+ if (!parsed.classification) return void 0;
1141
+ return {
1142
+ classification: parsed.classification,
1143
+ resolution: parsed.resolution,
1144
+ decoratorKind,
1145
+ importedKind
1146
+ };
1147
+ }
1148
+ function methodDecoratorFact(method, decorator, lookups, source, filePath, handlerClass, allowLifecycle) {
1149
+ const parsed = parseMethodDecorator(decorator, lookups, handlerClass, allowLifecycle);
1150
+ if (!parsed) return void 0;
1151
+ const classification = method.body ? parsed.classification : { ...parsed.classification, executable: false };
1152
+ const resolution = method.body ? parsed.resolution : { ...parsed.resolution, unresolvedReason: "handler_method_body_missing" };
1153
+ const argumentExpression = firstArg(decorator)?.getText();
1154
+ return {
1155
+ methodName: methodName(method.name),
1156
+ decoratorKind: parsed.decoratorKind,
1157
+ decoratorValue: parsed.resolution.resolvedValue,
1158
+ decoratorRawExpression: argumentExpression ?? decorator.expression.getText(),
1159
+ handlerKind: classification.handlerKind,
1160
+ executable: classification.executable,
1161
+ lifecyclePhase: classification.lifecyclePhase,
1162
+ lifecycleEvent: classification.lifecycleEvent,
1163
+ decoratorResolution: withDecoratorEvidence(
1164
+ withClassification(resolution, classification),
1165
+ decorator,
1166
+ parsed.importedKind
1167
+ ),
1168
+ sourceFile: normalizePath(filePath),
1169
+ sourceLine: line(source, method.getStart())
1170
+ };
1171
+ }
1172
+ function unique(values) {
1173
+ return [...new Set(values)];
1174
+ }
1175
+ function parseClassMethods(node, lookups, source, filePath, handlerClass, allowLifecycle) {
1176
+ const methods = [];
1177
+ const observed = [];
1178
+ const unsupported = [];
1179
+ for (const method of node.members.filter(ts2.isMethodDeclaration)) {
1180
+ for (const decorator of decs(method)) {
1181
+ observed.push(callName(decorator));
1182
+ const fact = methodDecoratorFact(
1183
+ method,
1184
+ decorator,
1185
+ lookups,
1186
+ source,
1187
+ filePath,
1188
+ handlerClass,
1189
+ allowLifecycle
1190
+ );
1191
+ if (fact) methods.push(fact);
1192
+ if (!fact?.executable) unsupported.push(callName(decorator));
1193
+ }
1194
+ }
1195
+ return {
1196
+ methods,
1197
+ observedDecoratorNames: unique(observed),
1198
+ unsupportedDecoratorNames: unique(unsupported)
1199
+ };
1200
+ }
1201
+ function parseHandlerClass(node, lookups, source, filePath) {
1202
+ const classDecoratorNames = unique(decs(node).map(callName));
1203
+ const classDecorators = decs(node);
1204
+ const hasImportedHandler = classDecorators.some((decorator) => capDecoratorName(decorator, lookups) === "Handler");
1205
+ const hasHandlerDecorator = hasImportedHandler || classDecoratorNames.includes("Handler");
1206
+ const parsed = parseClassMethods(
1207
+ node,
1208
+ lookups,
1209
+ source,
1210
+ filePath,
1211
+ hasHandlerDecorator,
1212
+ hasImportedHandler
1213
+ );
1214
+ if (!hasHandlerDecorator && parsed.methods.length === 0) return void 0;
1215
+ return {
1216
+ className: node.name?.text ?? "AnonymousHandler",
1217
+ sourceFile: normalizePath(filePath),
1218
+ sourceLine: line(source, node.getStart()),
1219
+ ...parsed,
1220
+ hasHandlerDecorator,
1221
+ classDecoratorNames
1222
+ };
1223
+ }
1224
+ async function parseDecorators(repoPath, filePath, context) {
1225
+ const snapshot = context?.get(filePath);
1226
+ const text = snapshot?.text ?? await fs5.readFile(path6.join(repoPath, filePath), "utf8");
1227
+ const sf = snapshot?.sourceFile() ?? createSourceFile(filePath, text);
476
1228
  const lookups = collectStringLookups(sf);
477
1229
  const handlers = [];
478
1230
  function visit(node) {
479
1231
  if (ts2.isClassDeclaration(node)) {
480
- const className = node.name?.text ?? "AnonymousHandler";
481
- const hasHandler = decs(node).some((d) => callName(d) === "Handler");
482
- const methods = node.members.filter(ts2.isMethodDeclaration).flatMap(
483
- (m) => decs(m).filter(
484
- (d) => ["Func", "Action", "On", "Event"].includes(callName(d))
485
- ).map((d) => {
486
- const decoratorResolution = resolveDecoratorArgument(firstArg(d), lookups);
487
- return {
488
- methodName: m.name.getText(),
489
- decoratorKind: callName(d),
490
- decoratorValue: decoratorResolution.resolvedValue,
491
- decoratorRawExpression: decoratorResolution.rawExpression,
492
- decoratorResolution,
493
- sourceFile: normalizePath(filePath),
494
- sourceLine: line(sf, m.getStart())
495
- };
496
- })
497
- );
498
- if (hasHandler || methods.length > 0)
499
- handlers.push({
500
- className,
501
- sourceFile: normalizePath(filePath),
502
- sourceLine: line(sf, node.getStart()),
503
- methods
504
- });
1232
+ const handler = parseHandlerClass(node, lookups, sf, filePath);
1233
+ if (handler) handlers.push(handler);
505
1234
  }
506
1235
  ts2.forEachChild(node, visit);
507
1236
  }
@@ -511,8 +1240,8 @@ async function parseDecorators(repoPath, filePath) {
511
1240
 
512
1241
  // src/parsers/handler-registration-parser.ts
513
1242
  import fsSync from "fs";
514
- import fs5 from "fs/promises";
515
- import path6 from "path";
1243
+ import fs6 from "fs/promises";
1244
+ import path7 from "path";
516
1245
  import ts3 from "typescript";
517
1246
  var MAX_EXPORT_DEPTH = 5;
518
1247
  function lineOf2(sourceFile, node) {
@@ -529,15 +1258,37 @@ function importSourceFor(identifier, imports) {
529
1258
  const evidence = imports.get(identifier);
530
1259
  return evidence ? `${evidence.source}#${evidence.importedName}` : void 0;
531
1260
  }
532
- async function parseHandlerRegistrations(repoPath, filePath) {
533
- const absolutePath = path6.join(repoPath, filePath);
534
- const text = await fs5.readFile(absolutePath, "utf8");
535
- const sourceFile = ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
1261
+ async function parseHandlerRegistrations(repoPath, filePath, context) {
1262
+ const absolutePath = path7.join(repoPath, filePath);
1263
+ const snapshot = context?.get(filePath);
1264
+ const text = snapshot?.text ?? await fs6.readFile(absolutePath, "utf8");
1265
+ const sourceFile = snapshot?.sourceFile() ?? ts3.createSourceFile(
1266
+ filePath,
1267
+ text,
1268
+ ts3.ScriptTarget.Latest,
1269
+ true,
1270
+ ts3.ScriptKind.TS
1271
+ );
536
1272
  const imports = collectImports(sourceFile);
537
- const localArrays = collectLocalArrays(sourceFile, imports, /* @__PURE__ */ new Map(), repoPath, filePath);
1273
+ const localArrays = collectLocalArrays(
1274
+ sourceFile,
1275
+ imports,
1276
+ /* @__PURE__ */ new Map(),
1277
+ repoPath,
1278
+ filePath,
1279
+ context
1280
+ );
538
1281
  const out = [];
539
1282
  function emitFromExpression(expression, call) {
540
- const classes = resolveArrayExpression(expression, localArrays, imports, repoPath, filePath, /* @__PURE__ */ new Set());
1283
+ const classes = resolveArrayExpression(
1284
+ expression,
1285
+ localArrays,
1286
+ imports,
1287
+ repoPath,
1288
+ filePath,
1289
+ /* @__PURE__ */ new Set(),
1290
+ context
1291
+ );
541
1292
  for (const cls of classes) {
542
1293
  out.push({
543
1294
  className: cls.className,
@@ -598,70 +1349,86 @@ function collectImports(sourceFile) {
598
1349
  }
599
1350
  return imports;
600
1351
  }
601
- function collectLocalArrays(sourceFile, imports, seed, repoPath = "", fromFile = "") {
1352
+ function collectLocalArrays(sourceFile, imports, seed, repoPath = "", fromFile = "", context) {
602
1353
  const arrays = new Map(seed);
603
1354
  for (const statement of sourceFile.statements) {
604
1355
  if (ts3.isVariableStatement(statement)) {
605
1356
  for (const decl of statement.declarationList.declarations) {
606
1357
  if (ts3.isIdentifier(decl.name) && decl.initializer && ts3.isArrayLiteralExpression(decl.initializer)) {
607
- arrays.set(decl.name.text, resolveArrayLiteral(decl.initializer, arrays, imports, repoPath, fromFile, /* @__PURE__ */ new Set()));
1358
+ arrays.set(decl.name.text, resolveArrayLiteral(
1359
+ decl.initializer,
1360
+ arrays,
1361
+ imports,
1362
+ repoPath,
1363
+ fromFile,
1364
+ /* @__PURE__ */ new Set(),
1365
+ context
1366
+ ));
608
1367
  }
609
1368
  }
610
1369
  }
611
1370
  }
612
1371
  return arrays;
613
1372
  }
614
- function resolveArrayExpression(expr, arrays, imports, repoPath, fromFile, seen) {
615
- if (ts3.isArrayLiteralExpression(expr)) return resolveArrayLiteral(expr, arrays, imports, repoPath, fromFile, seen);
1373
+ function resolveArrayExpression(expr, arrays, imports, repoPath, fromFile, seen, context) {
1374
+ if (ts3.isArrayLiteralExpression(expr)) return resolveArrayLiteral(expr, arrays, imports, repoPath, fromFile, seen, context);
616
1375
  if (ts3.isIdentifier(expr)) {
617
1376
  const local = arrays.get(expr.text);
618
1377
  if (local) return local;
619
1378
  const evidence = imports.get(expr.text);
620
- if (evidence && isRelative(evidence.source)) return resolveImportedArray(repoPath, fromFile, evidence, seen);
1379
+ if (evidence && isRelative(evidence.source)) return resolveImportedArray(repoPath, fromFile, evidence, seen, context);
621
1380
  if (evidence) return [{ className: evidence.importedName === "default" ? expr.text : evidence.importedName, importSource: `${evidence.source}#${evidence.importedName}` }];
622
1381
  }
623
1382
  return [];
624
1383
  }
625
- function resolveArrayLiteral(array, arrays, imports, repoPath, fromFile, seen) {
1384
+ function resolveArrayLiteral(array, arrays, imports, repoPath, fromFile, seen, context) {
626
1385
  const out = [];
627
1386
  for (const element of array.elements) {
628
- if (ts3.isSpreadElement(element)) out.push(...resolveArrayExpression(element.expression, arrays, imports, repoPath, fromFile, seen));
1387
+ if (ts3.isSpreadElement(element)) out.push(...resolveArrayExpression(element.expression, arrays, imports, repoPath, fromFile, seen, context));
629
1388
  else if (ts3.isIdentifier(element)) out.push({ className: element.text, importSource: importSourceFor(element.text, imports) });
630
1389
  }
631
1390
  return out;
632
1391
  }
633
- function resolveImportedArray(repoPath, fromFile, evidence, seen) {
1392
+ function resolveImportedArray(repoPath, fromFile, evidence, seen, context) {
634
1393
  const moduleFile = resolveRelativeModule(repoPath, fromFile, evidence.source);
635
1394
  if (!moduleFile) return [];
636
1395
  const key = `${moduleFile}:${evidence.importedName}`;
637
1396
  if (seen.has(key) || seen.size > MAX_EXPORT_DEPTH) return [];
638
1397
  seen.add(key);
639
- const exports = readExports(repoPath, moduleFile, seen);
1398
+ const exports = readExports(repoPath, moduleFile, seen, context);
640
1399
  if (evidence.importedName === "default") return exports.defaultArray ?? [];
641
1400
  return exports.arrays.get(evidence.importedName) ?? exports.arrays.get(exports.aliases.get(evidence.importedName) ?? evidence.importedName) ?? [];
642
1401
  }
643
1402
  function resolveRelativeModule(repoPath, fromFile, specifier) {
644
- const base = path6.resolve(repoPath, path6.dirname(fromFile), specifier);
645
- for (const candidate of [base, `${base}.ts`, `${base}.js`, path6.join(base, "index.ts"), path6.join(base, "index.js")]) {
1403
+ const base = path7.resolve(repoPath, path7.dirname(fromFile), specifier);
1404
+ for (const candidate of [base, `${base}.ts`, `${base}.js`, path7.join(base, "index.ts"), path7.join(base, "index.js")]) {
646
1405
  try {
647
1406
  const stat = fsSync.statSync(candidate);
648
- if (stat.isFile()) return normalizePath(path6.relative(repoPath, candidate));
1407
+ if (stat.isFile()) return normalizePath(path7.relative(repoPath, candidate));
649
1408
  } catch {
650
1409
  }
651
1410
  }
652
1411
  return void 0;
653
1412
  }
654
- function readExports(repoPath, filePath, seen) {
655
- const absolute = path6.join(repoPath, filePath);
1413
+ function readExports(repoPath, filePath, seen, context) {
1414
+ const absolute = path7.join(repoPath, filePath);
656
1415
  let text;
1416
+ const snapshot = context?.get(filePath);
657
1417
  try {
658
- text = fsSync.readFileSync(absolute, "utf8");
1418
+ text = snapshot?.text ?? fsSync.readFileSync(absolute, "utf8");
659
1419
  } catch {
660
1420
  return { arrays: /* @__PURE__ */ new Map(), aliases: /* @__PURE__ */ new Map() };
661
1421
  }
662
- const sourceFile = ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
1422
+ const sourceFile = snapshot?.sourceFile() ?? ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
663
1423
  const imports = collectImports(sourceFile);
664
- const arrays = collectLocalArrays(sourceFile, imports, /* @__PURE__ */ new Map(), repoPath, filePath);
1424
+ const arrays = collectLocalArrays(
1425
+ sourceFile,
1426
+ imports,
1427
+ /* @__PURE__ */ new Map(),
1428
+ repoPath,
1429
+ filePath,
1430
+ context
1431
+ );
665
1432
  const aliases = /* @__PURE__ */ new Map();
666
1433
  let defaultArray;
667
1434
  for (const statement of sourceFile.statements) {
@@ -672,7 +1439,13 @@ function readExports(repoPath, filePath, seen) {
672
1439
  const local = element.propertyName?.text ?? element.name.text;
673
1440
  aliases.set(element.name.text, local);
674
1441
  if (module && isRelative(module)) {
675
- const imported = resolveImportedArray(repoPath, filePath, { source: module, importedName: local }, seen);
1442
+ const imported = resolveImportedArray(
1443
+ repoPath,
1444
+ filePath,
1445
+ { source: module, importedName: local },
1446
+ seen,
1447
+ context
1448
+ );
676
1449
  if (imported.length > 0) arrays.set(element.name.text, imported);
677
1450
  }
678
1451
  }
@@ -704,12 +1477,12 @@ function summarizeExpression(text) {
704
1477
  }
705
1478
 
706
1479
  // src/parsers/service-binding-parser.ts
707
- import path8 from "path";
1480
+ import path9 from "path";
708
1481
  import ts5 from "typescript";
709
1482
 
710
1483
  // src/parsers/service-binding-parser-helpers.ts
711
- import fs6 from "fs/promises";
712
- import path7 from "path";
1484
+ import fs7 from "fs/promises";
1485
+ import path8 from "path";
713
1486
  import ts4 from "typescript";
714
1487
  function lineOf3(sf, node) {
715
1488
  return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
@@ -795,9 +1568,11 @@ function findConnectInExpression(expr) {
795
1568
  visit(expr);
796
1569
  return found;
797
1570
  }
798
- async function readSource(abs) {
1571
+ async function readSource(abs, context, filePath) {
1572
+ const snapshot = filePath ? context?.get(filePath) : void 0;
1573
+ if (snapshot) return snapshot.sourceFile();
799
1574
  try {
800
- const text = await fs6.readFile(abs, "utf8");
1575
+ const text = await fs7.readFile(abs, "utf8");
801
1576
  return ts4.createSourceFile(abs, text, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
802
1577
  } catch {
803
1578
  return void 0;
@@ -805,12 +1580,12 @@ async function readSource(abs) {
805
1580
  }
806
1581
  async function resolveImport(repoPath, fromFile, spec) {
807
1582
  if (!spec.startsWith(".")) return void 0;
808
- const rawBase = path7.resolve(repoPath, path7.dirname(fromFile), spec);
809
- const parsed = path7.parse(rawBase);
810
- const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ? path7.join(parsed.dir, parsed.name) : rawBase;
811
- for (const candidate of [base, `${base}.ts`, `${base}.js`, path7.join(base, "index.ts"), path7.join(base, "index.js")]) {
812
- const stat = await fs6.stat(candidate).catch(() => void 0);
813
- if (stat?.isFile()) return normalizePath(path7.relative(repoPath, candidate));
1583
+ const rawBase = path8.resolve(repoPath, path8.dirname(fromFile), spec);
1584
+ const parsed = path8.parse(rawBase);
1585
+ const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ? path8.join(parsed.dir, parsed.name) : rawBase;
1586
+ for (const candidate of [base, `${base}.ts`, `${base}.js`, path8.join(base, "index.ts"), path8.join(base, "index.js")]) {
1587
+ const stat = await fs7.stat(candidate).catch(() => void 0);
1588
+ if (stat?.isFile()) return normalizePath(path8.relative(repoPath, candidate));
814
1589
  }
815
1590
  return void 0;
816
1591
  }
@@ -918,8 +1693,8 @@ function exportedLocalNames(sf) {
918
1693
  }
919
1694
  return exports;
920
1695
  }
921
- async function helperBindings(repoPath, filePath) {
922
- const sf = await readSource(path8.join(repoPath, filePath));
1696
+ async function helperBindings(repoPath, filePath, context) {
1697
+ const sf = await readSource(path9.join(repoPath, filePath), context, filePath);
923
1698
  if (!sf) return [];
924
1699
  const sourceFileAst = sf;
925
1700
  const out = [];
@@ -979,8 +1754,8 @@ async function helperBindings(repoPath, filePath) {
979
1754
  }
980
1755
  return out;
981
1756
  }
982
- async function parseServiceBindings(repoPath, filePath) {
983
- const sf = await readSource(path8.join(repoPath, filePath));
1757
+ async function parseServiceBindings(repoPath, filePath, context) {
1758
+ const sf = await readSource(path9.join(repoPath, filePath), context, filePath);
984
1759
  if (!sf) return [];
985
1760
  const sourceFileAst = sf;
986
1761
  const out = [];
@@ -1019,7 +1794,7 @@ async function parseServiceBindings(repoPath, filePath) {
1019
1794
  if (!helperCache.has(imp.sourceFile))
1020
1795
  helperCache.set(
1021
1796
  imp.sourceFile,
1022
- await helperBindings(repoPath, imp.sourceFile)
1797
+ await helperBindings(repoPath, imp.sourceFile, context)
1023
1798
  );
1024
1799
  return (helperCache.get(imp.sourceFile) ?? []).filter((h) => h.exportedName === imp.exportedName).map((helper) => ({ imp, helper }));
1025
1800
  }
@@ -1069,28 +1844,28 @@ async function parseServiceBindings(repoPath, filePath) {
1069
1844
  });
1070
1845
  else if (ts5.isIdentifier(call.expression)) {
1071
1846
  const localDirect = localDirectHelpers.get(call.expression.text);
1072
- const resolved2 = localDirect ? { helper: localDirect, imp: void 0 } : await importedHelper(call.expression.text);
1073
- if (resolved2)
1847
+ const resolved3 = localDirect ? { helper: localDirect, imp: void 0 } : await importedHelper(call.expression.text);
1848
+ if (resolved3)
1074
1849
  out.push({
1075
1850
  variableName: targetName,
1076
- alias: resolved2.helper.alias,
1077
- aliasExpr: resolved2.helper.aliasExpr,
1078
- destinationExpr: resolved2.helper.destinationExpr,
1079
- servicePathExpr: resolved2.helper.servicePathExpr,
1080
- isDynamic: resolved2.helper.isDynamic,
1081
- placeholders: resolved2.helper.placeholders,
1851
+ alias: resolved3.helper.alias,
1852
+ aliasExpr: resolved3.helper.aliasExpr,
1853
+ destinationExpr: resolved3.helper.destinationExpr,
1854
+ servicePathExpr: resolved3.helper.servicePathExpr,
1855
+ isDynamic: resolved3.helper.isDynamic,
1856
+ placeholders: resolved3.helper.placeholders,
1082
1857
  sourceFile: normalizePath(filePath),
1083
1858
  sourceLine: lineOf3(sourceFileAst, node),
1084
1859
  helperChain: [
1085
- ...resolved2.helper.helperChain ?? [],
1860
+ ...resolved3.helper.helperChain ?? [],
1086
1861
  {
1087
1862
  callerVariable: targetName,
1088
1863
  ...aliasKind === "assignment" ? { assignedFrom: call.expression.text, aliasKind, scopeRule: "same-file-source-order" } : {},
1089
1864
  importedHelper: call.expression.text,
1090
- importSource: resolved2.imp?.sourceFile,
1091
- exportedSymbol: resolved2.imp?.exportedName ?? resolved2.helper.exportedName,
1092
- helperSourceFile: resolved2.helper.sourceFile,
1093
- helperSourceLine: resolved2.helper.sourceLine
1865
+ importSource: resolved3.imp?.sourceFile,
1866
+ exportedSymbol: resolved3.imp?.exportedName ?? resolved3.helper.exportedName,
1867
+ helperSourceFile: resolved3.helper.sourceFile,
1868
+ helperSourceLine: resolved3.helper.sourceLine
1094
1869
  }
1095
1870
  ]
1096
1871
  });
@@ -1119,28 +1894,28 @@ async function parseServiceBindings(repoPath, filePath) {
1119
1894
  const helpers = objectHelperVariables.get(unwrapped.expression.text) ?? [];
1120
1895
  const matches2 = helpers.filter((row) => row.helper.returnedProperty === unwrapped.name.text);
1121
1896
  if (matches2.length !== 1) return false;
1122
- const resolved2 = matches2[0];
1897
+ const resolved3 = matches2[0];
1123
1898
  out.push({
1124
1899
  variableName: targetName,
1125
- alias: resolved2.helper.alias,
1126
- aliasExpr: resolved2.helper.aliasExpr,
1127
- destinationExpr: resolved2.helper.destinationExpr,
1128
- servicePathExpr: resolved2.helper.servicePathExpr,
1129
- isDynamic: resolved2.helper.isDynamic,
1130
- placeholders: resolved2.helper.placeholders,
1900
+ alias: resolved3.helper.alias,
1901
+ aliasExpr: resolved3.helper.aliasExpr,
1902
+ destinationExpr: resolved3.helper.destinationExpr,
1903
+ servicePathExpr: resolved3.helper.servicePathExpr,
1904
+ isDynamic: resolved3.helper.isDynamic,
1905
+ placeholders: resolved3.helper.placeholders,
1131
1906
  sourceFile: normalizePath(filePath),
1132
1907
  sourceLine: lineOf3(sourceFileAst, node),
1133
1908
  helperChain: [
1134
- ...resolved2.helper.helperChain ?? [],
1909
+ ...resolved3.helper.helperChain ?? [],
1135
1910
  {
1136
1911
  callerVariable: targetName,
1137
1912
  sourceVariable: unwrapped.expression.text,
1138
1913
  returnedProperty: unwrapped.name.text,
1139
1914
  assignedFromProperty: unwrapped.getText(sourceFileAst),
1140
- importSource: resolved2.imp?.sourceFile,
1141
- exportedSymbol: resolved2.imp?.exportedName,
1142
- helperSourceFile: resolved2.helper.sourceFile,
1143
- helperSourceLine: resolved2.helper.sourceLine
1915
+ importSource: resolved3.imp?.sourceFile,
1916
+ exportedSymbol: resolved3.imp?.exportedName,
1917
+ helperSourceFile: resolved3.helper.sourceFile,
1918
+ helperSourceLine: resolved3.helper.sourceLine
1144
1919
  }
1145
1920
  ]
1146
1921
  });
@@ -1157,18 +1932,18 @@ async function parseServiceBindings(repoPath, filePath) {
1157
1932
  const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1158
1933
  const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
1159
1934
  if (matches2.length !== 1) continue;
1160
- const resolved2 = matches2[0];
1935
+ const resolved3 = matches2[0];
1161
1936
  out.push({
1162
1937
  variableName: el.name.text,
1163
- alias: resolved2.helper.alias,
1164
- aliasExpr: resolved2.helper.aliasExpr,
1165
- destinationExpr: resolved2.helper.destinationExpr,
1166
- servicePathExpr: resolved2.helper.servicePathExpr,
1167
- isDynamic: resolved2.helper.isDynamic,
1168
- placeholders: resolved2.helper.placeholders,
1938
+ alias: resolved3.helper.alias,
1939
+ aliasExpr: resolved3.helper.aliasExpr,
1940
+ destinationExpr: resolved3.helper.destinationExpr,
1941
+ servicePathExpr: resolved3.helper.servicePathExpr,
1942
+ isDynamic: resolved3.helper.isDynamic,
1943
+ placeholders: resolved3.helper.placeholders,
1169
1944
  sourceFile: normalizePath(filePath),
1170
1945
  sourceLine: lineOf3(sourceFileAst, decl),
1171
- helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName3, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1946
+ helperChain: [...resolved3.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName3, importSource: resolved3.imp?.sourceFile, exportedSymbol: resolved3.imp?.exportedName, helperSourceFile: resolved3.helper.sourceFile, helperSourceLine: resolved3.helper.sourceLine }]
1172
1947
  });
1173
1948
  }
1174
1949
  }
@@ -1190,18 +1965,18 @@ async function parseServiceBindings(repoPath, filePath) {
1190
1965
  if (!propertyName3 || !targetName) continue;
1191
1966
  const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
1192
1967
  if (matches2.length !== 1) continue;
1193
- const resolved2 = matches2[0];
1968
+ const resolved3 = matches2[0];
1194
1969
  out.push({
1195
1970
  variableName: targetName,
1196
- alias: resolved2.helper.alias,
1197
- aliasExpr: resolved2.helper.aliasExpr,
1198
- destinationExpr: resolved2.helper.destinationExpr,
1199
- servicePathExpr: resolved2.helper.servicePathExpr,
1200
- isDynamic: resolved2.helper.isDynamic,
1201
- placeholders: resolved2.helper.placeholders,
1971
+ alias: resolved3.helper.alias,
1972
+ aliasExpr: resolved3.helper.aliasExpr,
1973
+ destinationExpr: resolved3.helper.destinationExpr,
1974
+ servicePathExpr: resolved3.helper.servicePathExpr,
1975
+ isDynamic: resolved3.helper.isDynamic,
1976
+ placeholders: resolved3.helper.placeholders,
1202
1977
  sourceFile: normalizePath(filePath),
1203
1978
  sourceLine: lineOf3(sourceFileAst, node),
1204
- helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName3, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1979
+ helperChain: [...resolved3.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName3, importSource: resolved3.imp?.sourceFile, exportedSymbol: resolved3.imp?.exportedName, helperSourceFile: resolved3.helper.sourceFile, helperSourceLine: resolved3.helper.sourceLine }]
1205
1980
  });
1206
1981
  }
1207
1982
  }
@@ -1400,8 +2175,8 @@ function collectClassHelpers(sf) {
1400
2175
  }
1401
2176
 
1402
2177
  // src/parsers/outbound-call-parser.ts
1403
- import fs7 from "fs/promises";
1404
- import path10 from "path";
2178
+ import fs8 from "fs/promises";
2179
+ import path11 from "path";
1405
2180
  import ts8 from "typescript";
1406
2181
 
1407
2182
  // src/linker/external-http-target.ts
@@ -1419,8 +2194,8 @@ function redactUrl(value) {
1419
2194
  url.username = "";
1420
2195
  url.password = "";
1421
2196
  for (const key of [...url.searchParams.keys()]) url.searchParams.set(key, sensitiveKeys.has(key.toLowerCase()) ? "<redacted>" : "<redacted>");
1422
- const path11 = `${url.pathname}${url.search ? url.search : ""}`;
1423
- return value.startsWith("/") ? path11 : `${url.origin}${path11}`;
2197
+ const path12 = `${url.pathname}${url.search ? url.search : ""}`;
2198
+ return value.startsWith("/") ? path12 : `${url.origin}${path12}`;
1424
2199
  } catch {
1425
2200
  return value.replace(/([?&][^=;&]*(?:token|key|password|secret|cookie|authorization)[^=;&]*=)[^&]*/gi, "$1<redacted>");
1426
2201
  }
@@ -1454,9 +2229,9 @@ function safeParse(value) {
1454
2229
  }
1455
2230
 
1456
2231
  // src/linker/odata-path-normalizer.ts
1457
- function normalizeODataOperationInvocationPath(path11) {
1458
- if (path11 === void 0) return void 0;
1459
- const raw = path11.trim();
2232
+ function normalizeODataOperationInvocationPath(path12) {
2233
+ if (path12 === void 0) return void 0;
2234
+ const raw = path12.trim();
1460
2235
  if (!raw) return void 0;
1461
2236
  const rejected = (reason) => ({ rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false, invocationArgumentPlaceholderKeys: [], normalizationRejectedReason: reason });
1462
2237
  const open = raw.indexOf("(");
@@ -1479,8 +2254,8 @@ function normalizeODataOperationInvocationPath(path11) {
1479
2254
  normalizationReason: "balanced_top_level_operation_invocation"
1480
2255
  };
1481
2256
  }
1482
- function classifyODataPathIntent(path11, method) {
1483
- const rawPath = (path11 ?? "").trim();
2257
+ function classifyODataPathIntent(path12, method) {
2258
+ const rawPath = (path12 ?? "").trim();
1484
2259
  const normalizedMethod = (method ?? "GET").trim().toUpperCase() || "GET";
1485
2260
  const queryIndex = rawPath.indexOf("?");
1486
2261
  const pathWithoutQuery = queryIndex >= 0 ? rawPath.slice(0, queryIndex) : rawPath;
@@ -1528,8 +2303,8 @@ function classifyODataPathIntent(path11, method) {
1528
2303
  if (/^[A-Z][A-Za-z0-9_]*$/.test(firstSegment)) return { ...base, kind: "entity_candidate", reason: "uppercase_collection_segment_without_indexed_entity_evidence" };
1529
2304
  return { ...base, kind: "unknown", reason: "get_path_has_no_query_key_or_navigation_signal" };
1530
2305
  }
1531
- function entitySegmentFromPath(path11) {
1532
- const first = path11.replace(/^\//, "").split("/")[0]?.trim();
2306
+ function entitySegmentFromPath(path12) {
2307
+ const first = path12.replace(/^\//, "").split("/")[0]?.trim();
1533
2308
  if (!first) return void 0;
1534
2309
  const open = first.indexOf("(");
1535
2310
  const entity = (open >= 0 ? first.slice(0, open) : first).trim();
@@ -1677,7 +2452,7 @@ function topLevelQueryIndex(text) {
1677
2452
  }
1678
2453
 
1679
2454
  // src/parsers/imported-wrapper-parser.ts
1680
- import path9 from "path";
2455
+ import path10 from "path";
1681
2456
  import ts7 from "typescript";
1682
2457
 
1683
2458
  // src/parsers/operation-path-analysis.ts
@@ -1686,8 +2461,8 @@ var maxAliasDepth = 6;
1686
2461
  function analyzeOperationPath(expression, use, method = "POST") {
1687
2462
  if (!expression) return emptyAnalysis();
1688
2463
  const state = collectExpressionState(expression, use, 0, /* @__PURE__ */ new Set());
1689
- const paths = unique(state.paths.map(normalizeRawPath));
1690
- const normalized = unique(paths.flatMap((value) => normalizedCandidate(value, method)));
2464
+ const paths = unique2(state.paths.map(normalizeRawPath));
2465
+ const normalized = unique2(paths.flatMap((value) => normalizedCandidate(value, method)));
1691
2466
  const status = pathStatus(paths, state.placeholders, state.dynamic);
1692
2467
  const runtimeIdentifier = state.dynamic.at(-1)?.expression;
1693
2468
  return {
@@ -1696,8 +2471,8 @@ function analyzeOperationPath(expression, use, method = "POST") {
1696
2471
  normalizedOperationPath: status === "static" && normalized.length === 1 ? normalized[0] : void 0,
1697
2472
  candidateRawPaths: paths,
1698
2473
  candidateNormalizedOperationPaths: normalized,
1699
- placeholderKeys: unique([...state.placeholders, ...runtimeIdentifier ? [runtimeIdentifier] : []]),
1700
- sourceKind: unique(state.sourceKinds).join("+") || "unknown",
2474
+ placeholderKeys: unique2([...state.placeholders, ...runtimeIdentifier ? [runtimeIdentifier] : []]),
2475
+ sourceKind: unique2(state.sourceKinds).join("+") || "unknown",
1701
2476
  candidateIdentifier: ts6.isIdentifier(expression) ? expression.text : void 0,
1702
2477
  runtimeIdentifier,
1703
2478
  dynamicReassignments: state.dynamic,
@@ -1828,8 +2603,8 @@ function templateState(expression) {
1828
2603
  sourceKinds: ["template_with_placeholders"]
1829
2604
  };
1830
2605
  }
1831
- function staticState(path11, sourceKind) {
1832
- return { paths: [path11], placeholders: [], dynamic: [], sourceKinds: [sourceKind] };
2606
+ function staticState(path12, sourceKind) {
2607
+ return { paths: [path12], placeholders: [], dynamic: [], sourceKinds: [sourceKind] };
1833
2608
  }
1834
2609
  function dynamicState(expression, sourceKind = "dynamic_expression") {
1835
2610
  return {
@@ -1898,7 +2673,7 @@ function sourceLine(node) {
1898
2673
  const source = node.getSourceFile();
1899
2674
  return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
1900
2675
  }
1901
- function unique(values) {
2676
+ function unique2(values) {
1902
2677
  return [...new Set(values)].sort();
1903
2678
  }
1904
2679
  function emptyAnalysis() {
@@ -1914,7 +2689,7 @@ function emptyAnalysis() {
1914
2689
  }
1915
2690
 
1916
2691
  // src/parsers/imported-wrapper-parser.ts
1917
- async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings) {
2692
+ async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings, context) {
1918
2693
  const imports = await importsFor(repoPath, filePath, source);
1919
2694
  const importedByLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
1920
2695
  const calls = collectImportedCalls(source, importedByLocal);
@@ -1924,7 +2699,7 @@ async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBind
1924
2699
  if (!ts7.isIdentifier(call.expression)) continue;
1925
2700
  const imported = importedByLocal.get(call.expression.text);
1926
2701
  if (!imported?.sourceFile) continue;
1927
- const spec = await loadWrapperSpec(repoPath, imported, cache, 0);
2702
+ const spec = await loadWrapperSpec(repoPath, imported, cache, 0, context);
1928
2703
  const fact = spec ? wrapperCallFact(source, filePath, call, spec, serviceBindings) : void 0;
1929
2704
  if (fact) out.push(fact);
1930
2705
  }
@@ -1939,23 +2714,43 @@ function collectImportedCalls(source, imports) {
1939
2714
  visit(source);
1940
2715
  return calls;
1941
2716
  }
1942
- async function loadWrapperSpec(repoPath, imported, cache, depth) {
2717
+ async function loadWrapperSpec(repoPath, imported, cache, depth, context) {
1943
2718
  if (!imported.sourceFile || depth > 5) return void 0;
1944
2719
  const key = `${imported.sourceFile}#${imported.exportedName}`;
1945
2720
  const existing = cache.get(key);
1946
2721
  if (existing) return existing;
1947
- const pending = inspectWrapper(repoPath, imported.sourceFile, imported.exportedName, cache, depth);
2722
+ const pending = inspectWrapper(
2723
+ repoPath,
2724
+ imported.sourceFile,
2725
+ imported.exportedName,
2726
+ cache,
2727
+ depth,
2728
+ context
2729
+ );
1948
2730
  cache.set(key, pending);
1949
2731
  return pending;
1950
2732
  }
1951
- async function inspectWrapper(repoPath, sourceFile, exportedName, cache, depth) {
1952
- const source = await readSource(path9.join(repoPath, sourceFile));
2733
+ async function inspectWrapper(repoPath, sourceFile, exportedName, cache, depth, context) {
2734
+ const source = await readSource(
2735
+ path10.join(repoPath, sourceFile),
2736
+ context,
2737
+ sourceFile
2738
+ );
1953
2739
  if (!source) return void 0;
1954
2740
  const named = findFunction(source, exportedName);
1955
2741
  if (!named) return void 0;
1956
2742
  const direct = directSendSpec(source, sourceFile, named.name, named.fn);
1957
2743
  if (direct) return direct;
1958
- return nestedSendSpec(repoPath, sourceFile, source, named.name, named.fn, cache, depth);
2744
+ return nestedSendSpec(
2745
+ repoPath,
2746
+ sourceFile,
2747
+ source,
2748
+ named.name,
2749
+ named.fn,
2750
+ cache,
2751
+ depth,
2752
+ context
2753
+ );
1959
2754
  }
1960
2755
  function directSendSpec(source, sourceFile, name, fn) {
1961
2756
  const params = parameterNames(fn);
@@ -1974,11 +2769,11 @@ function directSendSpec(source, sourceFile, name, fn) {
1974
2769
  const clientIndex = params.indexOf(receiver);
1975
2770
  const pathIndex = pathName ? params.indexOf(pathName) : -1;
1976
2771
  if (clientIndex < 0 || pathIndex < 0) return void 0;
1977
- const methodName = methodExpr && ts7.isIdentifier(methodExpr) ? methodExpr.text : void 0;
1978
- const methodIndex = methodName ? params.indexOf(methodName) : -1;
2772
+ const methodName2 = methodExpr && ts7.isIdentifier(methodExpr) ? methodExpr.text : void 0;
2773
+ const methodIndex = methodName2 ? params.indexOf(methodName2) : -1;
1979
2774
  return { clientIndex, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodLiteral: literal(methodExpr), sourceFile, sourceLine: lineOf3(source, fn), chain: [name] };
1980
2775
  }
1981
- async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, depth) {
2776
+ async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, depth, context) {
1982
2777
  const imports = await importsFor(repoPath, sourceFile, source);
1983
2778
  const byLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
1984
2779
  const calls = [];
@@ -1988,7 +2783,7 @@ async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, dep
1988
2783
  if (calls.length !== 1) return void 0;
1989
2784
  const call = calls[0];
1990
2785
  const imported = call && ts7.isIdentifier(call.expression) ? byLocal.get(call.expression.text) : void 0;
1991
- const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1) : void 0;
2786
+ const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1, context) : void 0;
1992
2787
  if (!call || !nested) return void 0;
1993
2788
  const params = parameterNames(fn);
1994
2789
  const clientIndex = mappedParameterIndex(call.arguments[nested.clientIndex], params);
@@ -2235,8 +3030,8 @@ function resolveExpression(expr, use, policy, depth = 0, seen = /* @__PURE__ */
2235
3030
  if (!binding.declaration || !binding.initializer || !binding.immutable) return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: binding.evidence, constName: expr.text };
2236
3031
  if (seen.has(binding.declaration)) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_cycle_detected"], constName: expr.text };
2237
3032
  seen.add(binding.declaration);
2238
- const resolved2 = resolveExpression(binding.initializer, binding.declaration, policy, depth + 1, seen);
2239
- return { ...resolved2, sourceKind: "const_alias", rawExpression: safeRaw(expr), constName: expr.text, evidence: [...binding.evidence, ...resolved2.evidence] };
3033
+ const resolved3 = resolveExpression(binding.initializer, binding.declaration, policy, depth + 1, seen);
3034
+ return { ...resolved3, sourceKind: "const_alias", rawExpression: safeRaw(expr), constName: expr.text, evidence: [...binding.evidence, ...resolved3.evidence] };
2240
3035
  }
2241
3036
  return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: [`unsupported_${ts8.SyntaxKind[expr.kind] ?? "expression"}`] };
2242
3037
  }
@@ -2260,10 +3055,10 @@ function destinationExpressionShape(expr) {
2260
3055
  return ts8.SyntaxKind[expr.kind] ?? "expression";
2261
3056
  }
2262
3057
  function staticConditionalCandidates(expr, initializers) {
2263
- const resolved2 = expr && ts8.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
2264
- if (!resolved2 || !ts8.isConditionalExpression(resolved2)) return void 0;
2265
- const left = staticExpressionText(resolved2.whenTrue, initializers);
2266
- const right = staticExpressionText(resolved2.whenFalse, initializers);
3058
+ const resolved3 = expr && ts8.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
3059
+ if (!resolved3 || !ts8.isConditionalExpression(resolved3)) return void 0;
3060
+ const left = staticExpressionText(resolved3.whenTrue, initializers);
3061
+ const right = staticExpressionText(resolved3.whenFalse, initializers);
2267
3062
  if (!left || !right) return void 0;
2268
3063
  return [.../* @__PURE__ */ new Set([left, right])];
2269
3064
  }
@@ -2311,15 +3106,15 @@ function hasTemplatePlaceholder(value) {
2311
3106
  return /\$\{|%7B|%7D/i.test(value);
2312
3107
  }
2313
3108
  function urlTargetFromExpression(expr, use) {
2314
- const resolved2 = resolveExpression(expr, use, "external");
2315
- if (resolved2.status === "static" && resolved2.value && !hasTemplatePlaceholder(resolved2.value)) return { kind: "static_url", expression: resolved2.value, dynamic: false, sourceKind: resolved2.sourceKind };
2316
- if (expr) return { kind: "url_expression", dynamic: true, expression: `${resolved2.sourceKind}:${resolved2.placeholderKeys.join("|")}`, expressionShape: resolved2.sourceKind, placeholderKeys: resolved2.placeholderKeys };
3109
+ const resolved3 = resolveExpression(expr, use, "external");
3110
+ if (resolved3.status === "static" && resolved3.value && !hasTemplatePlaceholder(resolved3.value)) return { kind: "static_url", expression: resolved3.value, dynamic: false, sourceKind: resolved3.sourceKind };
3111
+ if (expr) return { kind: "url_expression", dynamic: true, expression: `${resolved3.sourceKind}:${resolved3.placeholderKeys.join("|")}`, expressionShape: resolved3.sourceKind, placeholderKeys: resolved3.placeholderKeys };
2317
3112
  return { kind: "unknown", dynamic: false };
2318
3113
  }
2319
3114
  function destinationTargetFromExpression(expr, use) {
2320
- const resolved2 = resolveExpression(expr, use, "external");
2321
- const text = resolved2.value;
2322
- if (resolved2.status === "static" && text && !hasTemplatePlaceholder(text)) return { kind: "destination", expression: text, dynamic: false, sourceKind: resolved2.sourceKind };
3115
+ const resolved3 = resolveExpression(expr, use, "external");
3116
+ const text = resolved3.value;
3117
+ if (resolved3.status === "static" && text && !hasTemplatePlaceholder(text)) return { kind: "destination", expression: text, dynamic: false, sourceKind: resolved3.sourceKind };
2323
3118
  const candidates = staticConditionalCandidates(expr, /* @__PURE__ */ new Map());
2324
3119
  if (candidates) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates };
2325
3120
  const shape = destinationExpressionShape(expr);
@@ -2419,9 +3214,9 @@ function collectWrapperSpecs(source) {
2419
3214
  const pathProp = propertyInitializer(objectArg, "path");
2420
3215
  const methodProp = propertyInitializer(objectArg, "method");
2421
3216
  const pathName = pathProp && ts8.isIdentifier(pathProp) ? pathProp.text : void 0;
2422
- const methodName = methodProp && ts8.isIdentifier(methodProp) ? methodProp.text : void 0;
3217
+ const methodName2 = methodProp && ts8.isIdentifier(methodProp) ? methodProp.text : void 0;
2423
3218
  const methodLiteral = resolveExpression(methodProp, node, "literal").value;
2424
- if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName, methodLiteral, start: node.getStart(source), end: node.getEnd() });
3219
+ if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName2, methodLiteral, start: node.getStart(source), end: node.getEnd() });
2425
3220
  }
2426
3221
  }
2427
3222
  if (ts8.isCallExpression(node) && ts8.isIdentifier(node.expression) && specs.has(node.expression.text)) {
@@ -2558,15 +3353,37 @@ function containsSupportedOutboundCall(node) {
2558
3353
  const end = node.getEnd();
2559
3354
  return classifyOutboundCallsInSource(source, source.fileName).some((call) => call.node.getStart(source) >= start && call.node.getEnd() <= end);
2560
3355
  }
2561
- async function parseOutboundCalls(repoPath, filePath) {
2562
- const text = await fs7.readFile(path10.join(repoPath, filePath), "utf8");
2563
- const source = ts8.createSourceFile(filePath, text, ts8.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS);
2564
- const bindingNames = new Set((await parseServiceBindings(repoPath, filePath)).map((binding) => binding.variableName));
2565
- const importedWrappers = await parseImportedWrapperCalls(repoPath, filePath, source, bindingNames);
2566
- return [...classifyOutboundCallsInSource(source, filePath).map((call) => call.fact), ...importedWrappers, ...parseLocalServiceCalls(text, filePath)];
2567
- }
2568
- function parseLocalServiceCalls(text, filePath) {
2569
- const source = ts8.createSourceFile(filePath, text, ts8.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS);
3356
+ async function parseOutboundCalls(repoPath, filePath, context) {
3357
+ const snapshot = context?.get(filePath);
3358
+ const text = snapshot?.text ?? await fs8.readFile(path11.join(repoPath, filePath), "utf8");
3359
+ const source = snapshot?.sourceFile() ?? ts8.createSourceFile(
3360
+ filePath,
3361
+ text,
3362
+ ts8.ScriptTarget.Latest,
3363
+ true,
3364
+ filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
3365
+ );
3366
+ const bindingNames = new Set((await parseServiceBindings(
3367
+ repoPath,
3368
+ filePath,
3369
+ context
3370
+ )).map((binding) => binding.variableName));
3371
+ const importedWrappers = await parseImportedWrapperCalls(
3372
+ repoPath,
3373
+ filePath,
3374
+ source,
3375
+ bindingNames,
3376
+ context
3377
+ );
3378
+ return [...classifyOutboundCallsInSource(source, filePath).map((call) => call.fact), ...importedWrappers, ...parseLocalServiceCalls(text, filePath, source)];
3379
+ }
3380
+ function parseLocalServiceCalls(text, filePath, source = ts8.createSourceFile(
3381
+ filePath,
3382
+ text,
3383
+ ts8.ScriptTarget.Latest,
3384
+ true,
3385
+ filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
3386
+ )) {
2570
3387
  const aliases = /* @__PURE__ */ new Map();
2571
3388
  const calls = [];
2572
3389
  const visit = (node) => {
@@ -2842,8 +3659,8 @@ function buildRemoteQueryTarget(input) {
2842
3659
  // src/linker/service-resolver.ts
2843
3660
  function rows(db, operationPath, workspaceId) {
2844
3661
  const names = operationLookupNames(operationPath);
2845
- return db.prepare(
2846
- `SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score,'' reasons
3662
+ const result = db.prepare(
3663
+ `SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score
2847
3664
  FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
2848
3665
  WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path IN (?,?) OR o.operation_name IN (?,?)) ORDER BY r.name,s.service_path,o.operation_name`
2849
3666
  ).all(
@@ -2854,6 +3671,11 @@ function rows(db, operationPath, workspaceId) {
2854
3671
  names.name,
2855
3672
  names.simpleName
2856
3673
  );
3674
+ return result.map((row) => ({
3675
+ ...row,
3676
+ score: Number(row.score ?? 0),
3677
+ reasons: []
3678
+ }));
2857
3679
  }
2858
3680
  function operationLookupNames(operationPath) {
2859
3681
  const name = operationPath.replace(/^\//, "");
@@ -2870,7 +3692,11 @@ function resolveOperation(db, signals, workspaceId) {
2870
3692
  if (missing.length > 0)
2871
3693
  return {
2872
3694
  status: "dynamic",
2873
- candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId) : [],
3695
+ candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId).map((candidate) => ({
3696
+ ...candidate,
3697
+ score: 0.2,
3698
+ reasons: ["operation_path_match"]
3699
+ })) : [],
2874
3700
  reasons: [...new Set(missing)].map((name) => `missing_variable:${name}`)
2875
3701
  };
2876
3702
  if (!signals.operationPath)
@@ -3233,7 +4059,13 @@ function compactCandidateScores(candidates) {
3233
4059
  return candidates.flatMap((candidate) => {
3234
4060
  const row = objectValue(candidate);
3235
4061
  if (!row) return [];
3236
- return [{ repo: row.repoName, servicePath: row.servicePath, operationPath: row.operationPath, score: row.score, reasons: row.reasons }];
4062
+ return [{
4063
+ repo: row.repoName,
4064
+ servicePath: row.servicePath,
4065
+ operationPath: row.operationPath,
4066
+ score: row.score,
4067
+ reasons: Array.isArray(row.reasons) ? row.reasons.filter((reason) => typeof reason === "string") : ["operation_path_match"]
4068
+ }];
3237
4069
  });
3238
4070
  }
3239
4071
  function placeholderKeys(values) {
@@ -3262,7 +4094,7 @@ function linkImplementations(db, workspaceId, generation) {
3262
4094
  const duplicateFamilies = duplicatePackageFamilies(accepted);
3263
4095
  const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
3264
4096
  const selected = duplicatePackageAmbiguous ? accepted : winners;
3265
- const unique2 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
4097
+ const unique3 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
3266
4098
  const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
3267
4099
  const evidence = {
3268
4100
  servicePath: operation.servicePath,
@@ -3276,16 +4108,16 @@ function linkImplementations(db, workspaceId, generation) {
3276
4108
  candidateFamilies: duplicateFamilies,
3277
4109
  candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
3278
4110
  };
3279
- const evidenceWithHints = unique2 ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
4111
+ const evidenceWithHints = unique3 ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
3280
4112
  if (accepted.length === 0) {
3281
4113
  db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", "unresolved", "operation", graphId(operation.operationId), "handler_method_candidates", candidates.map((row) => graphId(row.methodId)).join(","), 0, JSON.stringify(evidenceWithHints), 0, "No implementation candidate passed policy", generation);
3282
4114
  edgeCount += 1;
3283
4115
  unresolvedCount += 1;
3284
4116
  continue;
3285
4117
  }
3286
- db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique2 ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique2 ? "handler_method" : "handler_method_candidates", unique2 ? graphId(unique2.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique2 ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique2 ? null : "Ambiguous registered handler implementation candidates", generation);
4118
+ db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique3 ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique3 ? "handler_method" : "handler_method_candidates", unique3 ? graphId(unique3.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique3 ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique3 ? null : "Ambiguous registered handler implementation candidates", generation);
3287
4119
  edgeCount += 1;
3288
- if (unique2) resolvedCount += 1;
4120
+ if (unique3) resolvedCount += 1;
3289
4121
  else ambiguousCount += 1;
3290
4122
  }
3291
4123
  return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
@@ -3428,7 +4260,7 @@ function implementationCandidates(db, workspaceId, operation) {
3428
4260
  CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
3429
4261
  CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id AND localService.service_path=?) THEN 1 ELSE 0 END localServicePathMatch,
3430
4262
  CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
3431
- CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
4263
+ CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.handlerKind'),CASE WHEN localMethod.decorator_kind='Event' THEN 'event' WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 'operation' ELSE 'unsupported' END)='operation' AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.executable'),CASE WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 1 ELSE 0 END)=1 AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
3432
4264
  CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
3433
4265
  CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
3434
4266
  CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
@@ -3460,6 +4292,13 @@ function implementationCandidates(db, workspaceId, operation) {
3460
4292
  )
3461
4293
  JOIN repositories appRepo ON appRepo.id=hr.repo_id
3462
4294
  WHERE appRepo.workspace_id=?
4295
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
4296
+ CASE WHEN hm.decorator_kind='Event' THEN 'event'
4297
+ WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
4298
+ ELSE 'unsupported' END)='operation'
4299
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
4300
+ CASE WHEN hm.decorator_kind IN ('Action','Func','On')
4301
+ THEN 1 ELSE 0 END)=1
3463
4302
  AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
3464
4303
  operation.modelRepoId,
3465
4304
  operation.modelRepo,
@@ -3588,6 +4427,11 @@ function candidateEvidence(candidate, rank) {
3588
4427
  };
3589
4428
  }
3590
4429
  function implementationMethodSignal(row, operation) {
4430
+ const resolution = objectJson(row.decoratorResolutionJson) ?? {};
4431
+ if (resolution.handlerKind && resolution.handlerKind !== "operation")
4432
+ return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["non_operation_handler_kind"] };
4433
+ if (resolution.executable === false)
4434
+ return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["handler_method_not_executable"] };
3591
4435
  const op = normalizedOperationName(String(operation.operationPath ?? operation.operationName ?? ""));
3592
4436
  const decorator = normalizeDecoratorOperationSignal(typeof row.decoratorValue === "string" ? row.decoratorValue : void 0, typeof row.decoratorRawExpression === "string" ? row.decoratorRawExpression : void 0, op);
3593
4437
  if (decorator.status === "resolved" && decorator.operationName === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
@@ -3617,101 +4461,1337 @@ function parseVars(values) {
3617
4461
  }
3618
4462
  return out;
3619
4463
  }
3620
- function ambiguousStartDiagnostic(requested, candidates, message) {
3621
- const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
4464
+ function selectorRepoNotFoundDiagnostic(requested) {
3622
4465
  return {
3623
4466
  severity: "warning",
3624
- code: "trace_start_ambiguous",
3625
- message,
3626
- normalizedSelectorValue: requested,
3627
- resolutionStage: "operation",
3628
- resolutionStatus: "ambiguous_operation",
3629
- candidates,
3630
- serviceSuggestions,
3631
- selectorSuggestions: fullSelectorSuggestions(candidates)
4467
+ code: "selector_repo_not_found",
4468
+ message: `No indexed repository matched selector: ${requested}`,
4469
+ selectorKind: "repo",
4470
+ requestedRepository: requested
3632
4471
  };
3633
4472
  }
3634
- function fullSelectorSuggestions(candidates) {
3635
- const includeRepo = new Set(candidates.map((row) => row.repoName)).size > 1;
3636
- return [...new Set(candidates.flatMap((row) => {
3637
- if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
3638
- const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
3639
- return [
3640
- `${repoSelector}--service ${row.servicePath} --path ${row.operationPath}`
3641
- ];
3642
- }))].sort();
4473
+ function selectorRepoAmbiguousDiagnostic(requested, candidates) {
4474
+ const uniqueName = (value) => candidates.filter((candidate) => candidate.name === value).length === 1;
4475
+ const uniquePackage = (value) => candidates.filter((candidate) => candidate.packageName === value).length === 1;
4476
+ const suggestions = candidates.flatMap((candidate) => {
4477
+ if (uniqueName(candidate.name)) return [`--repo ${candidate.name}`];
4478
+ if (candidate.packageName && uniquePackage(candidate.packageName))
4479
+ return [`--repo ${candidate.packageName}`];
4480
+ return [];
4481
+ });
4482
+ return {
4483
+ severity: "warning",
4484
+ code: "selector_repo_ambiguous",
4485
+ message: `Repository selector matched multiple indexed repositories: ${requested}`,
4486
+ selectorKind: "repo",
4487
+ requestedRepository: requested,
4488
+ candidates,
4489
+ selectorSuggestions: [...new Set(suggestions)].sort(),
4490
+ remediation: suggestions.length > 0 ? "Use one of the unique --repo selectors shown." : "Repository names and package names must be unique before this selector can be traced safely."
4491
+ };
3643
4492
  }
3644
-
3645
- // src/trace/evidence.ts
3646
- function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
3647
- const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
4493
+ function selectorNotFoundDiagnostic(start) {
4494
+ const serviceOnly = start.servicePath && !start.operation && !start.operationPath && !start.handler;
3648
4495
  return {
3649
- ...evidence,
3650
- graphEdgeId: row.id,
3651
- persistedGraphEdgeId: row.id > 0 ? row.id : void 0,
3652
- outboundCallId: call.id,
3653
- callSite: { sourceFile: call.source_file, sourceLine: call.source_line },
3654
- sourceFile: call.source_file,
3655
- sourceLine: call.source_line,
3656
- file: call.source_file,
3657
- line: call.source_line,
3658
- persistedTarget: { kind: row.to_kind, id: row.to_id },
3659
- contextualResolutionParticipated: Boolean(contextualEvidence?.contextualServiceBindingAttempted),
3660
- persistedResolution: persistedResolution(row)
4496
+ severity: "warning",
4497
+ code: "trace_start_not_found",
4498
+ message: serviceOnly ? "Service-only trace requires --operation or --path and will not broaden to the whole workspace" : "No handler source matched the requested trace start selector",
4499
+ selectorKind: start.handler ? "handler" : start.operation || start.operationPath ? "operation" : start.servicePath ? "service" : void 0
3661
4500
  };
3662
4501
  }
3663
- function runtimeResolution(db, row, evidence, vars, workspaceId, contextualUnresolvedReason) {
3664
- const substituted = evidenceWithRuntimeVariables(evidence, vars);
3665
- if (!isRemoteRuntimeCandidate(row, evidence, vars)) {
3666
- const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason;
3667
- const withSections = withEffectiveResolution(substituted, row, unresolvedReason2);
3668
- return { row, evidence: withSections, unresolvedReason: unresolvedReason2 };
3669
- }
3670
- const resolution = resolveRuntimeOperation(db, substituted, workspaceId);
3671
- if (resolution.target) {
3672
- const resolvedRow = { ...row, status: "resolved", to_kind: "operation", to_id: String(resolution.target.operationId), unresolved_reason: void 0, confidence: Math.max(0, Math.min(1, resolution.target.score)) };
3673
- return { row: resolvedRow, evidence: withEffectiveResolution(substituted, resolvedRow, void 0, resolution), target: resolution.target };
4502
+ function sourceScopeForSelector(db, repoId, start, workspaceId) {
4503
+ if (start.handler) {
4504
+ const classRows = handlerClassRows(db, repoId, start.handler, workspaceId);
4505
+ if (classRows.length > 0) return handlerClassScope(classRows, start.handler);
4506
+ const methodRows = handlerMethodRows(db, repoId, start.handler, workspaceId);
4507
+ if (methodRows.length > 0)
4508
+ return handlerMethodScope(methodRows, repoId, start.handler);
3674
4509
  }
3675
- const unresolvedReason = runtimeUnresolvedReason(resolution);
3676
- return { row, evidence: withEffectiveResolution(substituted, row, unresolvedReason, resolution), unresolvedReason };
3677
- }
3678
- function runtimeVariableDiagnostic(edges) {
3679
- const missing = /* @__PURE__ */ new Set();
3680
- for (const edge of edges) {
3681
- const effective = parseObject(edge.evidence.effectiveResolution);
3682
- if (!["dynamic", "unresolved", "ambiguous"].includes(String(effective.status ?? "")))
3683
- continue;
3684
- const substitutions = edge.evidence.runtimeSubstitutions;
3685
- if (!substitutions || typeof substitutions !== "object" || Array.isArray(substitutions)) continue;
3686
- for (const value of Object.values(substitutions))
3687
- for (const key of value.missing ?? []) missing.add(key);
4510
+ const operation = normalizeOperation(start.operation ?? start.operationPath);
4511
+ if (!operation) return void 0;
4512
+ const operationRows = operationHandlerRows(
4513
+ db,
4514
+ repoId,
4515
+ operation,
4516
+ start.servicePath,
4517
+ workspaceId
4518
+ );
4519
+ if (operationRows.length > 0)
4520
+ return operationHandlerScope(operationRows, repoId, operation);
4521
+ if (!start.servicePath) return void 0;
4522
+ const implementationRows = implementationHandlerRows(
4523
+ db,
4524
+ repoId,
4525
+ start.servicePath,
4526
+ operation,
4527
+ workspaceId
4528
+ );
4529
+ return implementationRows.length > 0 ? executableScope(implementationRows, repoId) : void 0;
4530
+ }
4531
+ function handlerClassRows(db, repoId, handler, workspaceId) {
4532
+ return db.prepare(`SELECT hc.id handlerClassId,hc.repo_id repoId,
4533
+ r.name repoName,hc.class_name className,
4534
+ hc.source_file sourceFile,hc.source_line sourceLine,hm.id methodId,
4535
+ sym.id symbolId,COALESCE(classSym.evidence_json,
4536
+ (SELECT fallback.evidence_json FROM symbols fallback
4537
+ WHERE fallback.repo_id=hc.repo_id AND fallback.kind='class'
4538
+ AND fallback.name=hc.class_name AND fallback.source_file=hc.source_file
4539
+ ORDER BY fallback.id LIMIT 1)) classEvidence
4540
+ FROM handler_classes hc
4541
+ JOIN repositories r ON r.id=hc.repo_id
4542
+ LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
4543
+ AND sym.source_file=hc.source_file
4544
+ AND sym.kind='method'
4545
+ AND substr(sym.qualified_name,1,length(hc.class_name)+1)=hc.class_name || '.'
4546
+ AND (NOT EXISTS (SELECT 1 FROM handler_methods declared
4547
+ WHERE declared.handler_class_id=hc.id
4548
+ AND declared.method_name=sym.name)
4549
+ OR EXISTS (SELECT 1 FROM handler_methods executable
4550
+ WHERE executable.handler_class_id=hc.id
4551
+ AND executable.method_name=sym.name
4552
+ AND COALESCE(json_extract(executable.decorator_resolution_json,
4553
+ '$.executable'),CASE WHEN executable.decorator_kind IN
4554
+ ('Action','Func','On','Event') THEN 1 ELSE 0 END)=1))
4555
+ LEFT JOIN handler_methods hm ON hm.handler_class_id=hc.id
4556
+ AND hm.method_name=sym.name
4557
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
4558
+ CASE WHEN hm.decorator_kind IN ('Action','Func','On','Event')
4559
+ THEN 1 ELSE 0 END)=1
4560
+ LEFT JOIN symbols classSym ON classSym.id=hc.symbol_id
4561
+ WHERE (? IS NULL OR r.workspace_id=?)
4562
+ AND (? IS NULL OR hc.repo_id=?) AND hc.class_name=?
4563
+ ORDER BY hc.repo_id,hc.id,hm.id`).all(
4564
+ workspaceId,
4565
+ workspaceId,
4566
+ repoId,
4567
+ repoId,
4568
+ handler
4569
+ );
4570
+ }
4571
+ function handlerMethodRows(db, repoId, handler, workspaceId) {
4572
+ return db.prepare(`SELECT hc.id handlerClassId,hc.repo_id repoId,
4573
+ r.name repoName,hc.class_name className,hc.source_file sourceFile,
4574
+ hm.source_line sourceLine,hm.id methodId,sym.id symbolId
4575
+ FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
4576
+ JOIN repositories r ON r.id=hc.repo_id
4577
+ LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
4578
+ AND sym.source_file=hc.source_file
4579
+ AND sym.qualified_name=hc.class_name || '.' || hm.method_name
4580
+ AND sym.start_line=hm.source_line
4581
+ WHERE (? IS NULL OR r.workspace_id=?)
4582
+ AND (? IS NULL OR hc.repo_id=?) AND hm.method_name=?
4583
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
4584
+ CASE WHEN hm.decorator_kind IN ('Action','Func','On','Event')
4585
+ THEN 1 ELSE 0 END)=1
4586
+ ORDER BY hc.repo_id,hc.id,hm.id`).all(
4587
+ workspaceId,
4588
+ workspaceId,
4589
+ repoId,
4590
+ repoId,
4591
+ handler
4592
+ );
4593
+ }
4594
+ function operationHandlerRows(db, repoId, operation, servicePath, workspaceId) {
4595
+ return db.prepare(`SELECT DISTINCT hc.id handlerClassId,hc.repo_id repoId,
4596
+ r.name repoName,hc.class_name className,hc.source_file sourceFile,
4597
+ hm.source_line sourceLine,hm.id methodId,sym.id symbolId
4598
+ FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
4599
+ JOIN repositories r ON r.id=hc.repo_id
4600
+ LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
4601
+ AND sym.source_file=hc.source_file
4602
+ AND sym.qualified_name=hc.class_name || '.' || hm.method_name
4603
+ AND sym.start_line=hm.source_line
4604
+ WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR hc.repo_id=?)
4605
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
4606
+ CASE WHEN hm.decorator_kind='Event' THEN 'event'
4607
+ WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
4608
+ ELSE 'unsupported' END)='operation'
4609
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
4610
+ CASE WHEN hm.decorator_kind IN ('Action','Func','On')
4611
+ THEN 1 ELSE 0 END)=1
4612
+ AND (hm.decorator_value=? OR hm.method_name=?)
4613
+ AND (? IS NULL OR EXISTS (
4614
+ SELECT 1 FROM cds_services svc JOIN cds_operations op ON op.service_id=svc.id
4615
+ WHERE svc.repo_id=hc.repo_id AND svc.service_path=?
4616
+ AND (op.operation_path=? OR op.operation_name=?)))
4617
+ ORDER BY hc.repo_id,hc.id,hm.id`).all(
4618
+ workspaceId,
4619
+ workspaceId,
4620
+ repoId,
4621
+ repoId,
4622
+ operation,
4623
+ operation,
4624
+ servicePath,
4625
+ servicePath,
4626
+ operation,
4627
+ operation
4628
+ );
4629
+ }
4630
+ function operationHandlerScope(rows2, fallbackRepoId, requested) {
4631
+ const candidates = handlerSelectorCandidates(rows2, "method");
4632
+ if (candidates.length < 2) return executableScope(rows2, fallbackRepoId);
4633
+ const classes = /* @__PURE__ */ new Map();
4634
+ for (const candidate of candidates) {
4635
+ const key = `${String(candidate.repoName)}:${String(candidate.className)}`;
4636
+ classes.set(key, /* @__PURE__ */ new Set([
4637
+ ...classes.get(key) ?? [],
4638
+ String(candidate.handlerClassId)
4639
+ ]));
4640
+ }
4641
+ const suggestions = candidates.flatMap((candidate) => {
4642
+ if (typeof candidate.repoName !== "string" || typeof candidate.className !== "string") return [];
4643
+ const key = `${candidate.repoName}:${candidate.className}`;
4644
+ return classes.get(key)?.size === 1 ? [`--repo ${candidate.repoName} --handler ${candidate.className}`] : [];
4645
+ });
4646
+ return { diagnostics: [{
4647
+ severity: "warning",
4648
+ code: "trace_start_ambiguous",
4649
+ message: "Operation selector matched multiple handler-only executable scopes",
4650
+ selectorKind: "operation",
4651
+ normalizedSelectorValue: requested,
4652
+ resolutionStage: "handler",
4653
+ resolutionStatus: "ambiguous_handler_operation",
4654
+ candidates,
4655
+ selectorSuggestions: [...new Set(suggestions)].sort(),
4656
+ remediation: "Select one handler class explicitly; no operation was chosen automatically."
4657
+ }] };
4658
+ }
4659
+ function implementationHandlerRows(db, repoId, servicePath, operation, workspaceId) {
4660
+ return db.prepare(`SELECT DISTINCT hc.repo_id repoId,
4661
+ hc.source_file sourceFile,hm.id methodId,sym.id symbolId
4662
+ FROM cds_services svc JOIN cds_operations op ON op.service_id=svc.id
4663
+ JOIN repositories r ON r.id=svc.repo_id
4664
+ JOIN graph_edges edge ON edge.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER'
4665
+ AND edge.status='resolved' AND edge.from_kind='operation'
4666
+ AND edge.from_id=CAST(op.id AS TEXT)
4667
+ JOIN handler_methods hm ON hm.id=CAST(edge.to_id AS INTEGER)
4668
+ JOIN handler_classes hc ON hc.id=hm.handler_class_id
4669
+ LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
4670
+ AND sym.source_file=hc.source_file
4671
+ AND sym.qualified_name=hc.class_name || '.' || hm.method_name
4672
+ AND sym.start_line=hm.source_line
4673
+ WHERE (? IS NULL OR r.workspace_id=?)
4674
+ AND (? IS NULL OR svc.repo_id=?) AND svc.service_path=?
4675
+ AND (op.operation_path=? OR op.operation_name=?)
4676
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
4677
+ CASE WHEN hm.decorator_kind='Event' THEN 'event'
4678
+ WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
4679
+ ELSE 'unsupported' END)='operation'
4680
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
4681
+ CASE WHEN hm.decorator_kind IN ('Action','Func','On')
4682
+ THEN 1 ELSE 0 END)=1
4683
+ ORDER BY hc.repo_id,hc.id,hm.id`).all(
4684
+ workspaceId,
4685
+ workspaceId,
4686
+ repoId,
4687
+ repoId,
4688
+ servicePath,
4689
+ operation,
4690
+ operation
4691
+ );
4692
+ }
4693
+ function handlerClassScope(rows2, requested) {
4694
+ const ambiguity = handlerSelectorAmbiguity(rows2, requested, "class");
4695
+ if (ambiguity) return { diagnostics: [ambiguity] };
4696
+ const executable = rows2.filter((row) => typeof row.symbolId === "number");
4697
+ const repoId = numericValue(rows2[0]?.repoId);
4698
+ if (executable.length > 0) {
4699
+ const scope = executableScope(executable, repoId);
4700
+ const warning = executable.some((row) => typeof row.methodId === "number") ? handlerDecoratorsNotIndexedDiagnostic(rows2[0]) : handlerMethodsNotIndexedDiagnostic(rows2[0]);
4701
+ return warning ? { ...scope, diagnostics: [warning] } : scope;
4702
+ }
4703
+ const first = rows2[0];
4704
+ return {
4705
+ repoId,
4706
+ diagnostics: [handlerMethodsNotIndexedDiagnostic(first)]
4707
+ };
4708
+ }
4709
+ function handlerMethodScope(rows2, fallbackRepoId, requested) {
4710
+ const ambiguity = handlerSelectorAmbiguity(rows2, requested, "method");
4711
+ return ambiguity ? { diagnostics: [ambiguity] } : executableScope(rows2, fallbackRepoId);
4712
+ }
4713
+ function handlerSelectorAmbiguity(rows2, requested, matchKind) {
4714
+ const candidates = handlerSelectorCandidates(rows2, matchKind);
4715
+ if (candidates.length < 2) return void 0;
4716
+ const repoCounts = /* @__PURE__ */ new Map();
4717
+ for (const candidate of candidates) {
4718
+ if (typeof candidate.repoName !== "string") continue;
4719
+ repoCounts.set(
4720
+ candidate.repoName,
4721
+ (repoCounts.get(candidate.repoName) ?? 0) + 1
4722
+ );
4723
+ }
4724
+ const suggestions = candidates.flatMap((candidate) => {
4725
+ const repoName = typeof candidate.repoName === "string" ? candidate.repoName : void 0;
4726
+ if (repoName && repoCounts.get(repoName) === 1)
4727
+ return [`--repo ${repoName} --handler ${requested}`];
4728
+ if (matchKind === "method" && typeof candidate.className === "string")
4729
+ return [`${repoName ? `--repo ${repoName} ` : ""}--handler ${candidate.className}`];
4730
+ return [];
4731
+ });
4732
+ return {
4733
+ severity: "warning",
4734
+ code: "trace_start_ambiguous",
4735
+ message: "Handler selector matched multiple executable scopes and was not selected automatically",
4736
+ selectorKind: "handler",
4737
+ requestedHandler: requested,
4738
+ resolutionStage: "handler",
4739
+ resolutionStatus: "ambiguous_handler",
4740
+ candidates,
4741
+ selectorSuggestions: [...new Set(suggestions)].sort(),
4742
+ remediation: suggestions.length > 0 ? "Use one of the scoped handler selectors shown." : "No current CLI selector uniquely identifies these duplicate handler classes."
4743
+ };
4744
+ }
4745
+ function handlerSelectorCandidates(rows2, matchKind) {
4746
+ const candidates = /* @__PURE__ */ new Map();
4747
+ for (const row of rows2) {
4748
+ const identity = matchKind === "class" ? `class:${String(row.handlerClassId)}` : `method:${String(row.repoId)}:${String(row.symbolId ?? row.methodId)}`;
4749
+ candidates.set(identity, {
4750
+ handlerClassId: row.handlerClassId,
4751
+ repoId: row.repoId,
4752
+ repoName: row.repoName,
4753
+ className: row.className,
4754
+ sourceFile: row.sourceFile,
4755
+ sourceLine: row.sourceLine,
4756
+ matchKind
4757
+ });
4758
+ }
4759
+ return [...candidates.values()].sort((left, right) => String(left.repoName ?? "").localeCompare(String(right.repoName ?? "")) || String(left.className ?? "").localeCompare(String(right.className ?? "")) || String(left.sourceFile ?? "").localeCompare(String(right.sourceFile ?? "")));
4760
+ }
4761
+ function executableScope(rows2, fallbackRepoId) {
4762
+ const files = rows2.flatMap((row) => row.sourceFile ? [row.sourceFile] : []);
4763
+ const symbols = rows2.flatMap((row) => typeof row.symbolId === "number" ? [row.symbolId] : []);
4764
+ if (files.length === 0 || symbols.length === 0) return { repoId: fallbackRepoId };
4765
+ return {
4766
+ files: new Set(files),
4767
+ symbols: new Set(symbols),
4768
+ repoId: numericValue(rows2[0]?.repoId) ?? fallbackRepoId
4769
+ };
4770
+ }
4771
+ function handlerMethodsNotIndexedDiagnostic(row) {
4772
+ return {
4773
+ severity: "warning",
4774
+ code: "handler_methods_not_indexed",
4775
+ message: `Handler class ${row?.className ?? "unknown"} has no indexed executable methods`,
4776
+ selectorKind: "handler",
4777
+ className: row?.className,
4778
+ sourceFile: row?.sourceFile,
4779
+ sourceLine: row?.sourceLine,
4780
+ observedDecoratorNames: stringEvidenceArray(
4781
+ row?.classEvidence,
4782
+ "observedDecoratorNames"
4783
+ ),
4784
+ unsupportedDecoratorNames: stringEvidenceArray(
4785
+ row?.classEvidence,
4786
+ "unsupportedDecoratorNames"
4787
+ ),
4788
+ remediation: "Use a supported CAP handler decorator on at least one class method and re-index the workspace."
4789
+ };
4790
+ }
4791
+ function handlerDecoratorsNotIndexedDiagnostic(row) {
4792
+ const names = stringEvidenceArray(
4793
+ row?.classEvidence,
4794
+ "unsupportedDecoratorNames"
4795
+ );
4796
+ const methods = arrayEvidence(row?.classEvidence, "unsupportedMethods");
4797
+ if (names.length === 0 && methods.length === 0) return void 0;
4798
+ return {
4799
+ severity: "warning",
4800
+ code: "handler_decorators_not_indexed",
4801
+ message: `Handler class ${row?.className ?? "unknown"} contains methods that were not indexed`,
4802
+ selectorKind: "handler",
4803
+ className: row?.className,
4804
+ sourceFile: row?.sourceFile,
4805
+ sourceLine: row?.sourceLine,
4806
+ unsupportedDecoratorNames: names,
4807
+ unsupportedMethods: methods,
4808
+ remediation: "Use a supported CAP handler decorator shape and re-index the workspace."
4809
+ };
4810
+ }
4811
+ function evidenceRecord(evidenceJson) {
4812
+ if (!evidenceJson) return {};
4813
+ try {
4814
+ const parsed = JSON.parse(evidenceJson);
4815
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
4816
+ } catch {
4817
+ return {};
4818
+ }
4819
+ }
4820
+ function stringEvidenceArray(evidenceJson, key) {
4821
+ const value = evidenceRecord(evidenceJson)[key];
4822
+ return Array.isArray(value) ? [...new Set(value.filter((item) => typeof item === "string"))].sort() : [];
4823
+ }
4824
+ function arrayEvidence(evidenceJson, key) {
4825
+ const value = evidenceRecord(evidenceJson)[key];
4826
+ return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
4827
+ }
4828
+ function numericValue(value) {
4829
+ return typeof value === "number" ? value : void 0;
4830
+ }
4831
+ function normalizeOperation(value) {
4832
+ if (!value) return void 0;
4833
+ return value.startsWith("/") ? value.slice(1) : value;
4834
+ }
4835
+ function ambiguousStartDiagnostic(requested, candidates, message) {
4836
+ const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
4837
+ return {
4838
+ severity: "warning",
4839
+ code: "trace_start_ambiguous",
4840
+ message,
4841
+ normalizedSelectorValue: requested,
4842
+ resolutionStage: "operation",
4843
+ resolutionStatus: "ambiguous_operation",
4844
+ candidates,
4845
+ serviceSuggestions,
4846
+ selectorSuggestions: fullSelectorSuggestions(candidates)
4847
+ };
4848
+ }
4849
+ function fullSelectorSuggestions(candidates) {
4850
+ const includeRepo = new Set(candidates.map((row) => row.repoName)).size > 1;
4851
+ return [...new Set(candidates.flatMap((row) => {
4852
+ if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
4853
+ const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
4854
+ return [
4855
+ `${repoSelector}--service ${row.servicePath} --path ${row.operationPath}`
4856
+ ];
4857
+ }))].sort();
4858
+ }
4859
+
4860
+ // src/trace/001-dynamic-identity.ts
4861
+ function uniqueIdentityDerivations(db, candidates, templates) {
4862
+ const identities = workspaceIdentities(db, candidates);
4863
+ const proposals = candidates.flatMap((candidate) => {
4864
+ const owner = implementationOwner(db, candidate.candidateOperationId);
4865
+ const identity = identities.find((item) => item.repoId === candidate.repoId);
4866
+ return ownerAgrees(candidate, owner) && identity ? identityProposals(candidate, identity, templates) : [];
4867
+ });
4868
+ const matches2 = workspaceIdentityMatches(identities, templates);
4869
+ const competing = competingIdentityKeys(matches2);
4870
+ const duplicates = duplicateNormalizedIdentities(identities);
4871
+ return proposals.filter((proposal) => !competing.has(`${proposal.key}:${proposal.value}`)).filter((proposal) => !duplicates.has(proposal.normalizedIdentity)).map((proposal) => ({
4872
+ operationId: proposal.operationId,
4873
+ key: proposal.key,
4874
+ value: proposal.value,
4875
+ provenance: proposal.provenance
4876
+ }));
4877
+ }
4878
+ function identityProposals(candidate, identity, templates) {
4879
+ const routeTemplates = [templates.alias, templates.destination].filter((value) => Boolean(value));
4880
+ const identities = [
4881
+ { name: identity.packageName, sourceKind: "package_identity", npmPackage: true },
4882
+ { name: identity.repoName, sourceKind: "repository_identity", npmPackage: false }
4883
+ ].filter((item) => Boolean(item.name));
4884
+ const proposals = routeTemplates.flatMap((template) => identities.flatMap((identity2) => proposalForIdentity(
4885
+ candidate,
4886
+ template,
4887
+ identity2.name,
4888
+ identity2.sourceKind,
4889
+ identity2.npmPackage
4890
+ )));
4891
+ return deduplicateProposals(proposals);
4892
+ }
4893
+ function proposalForIdentity(candidate, template, identity, sourceKind, npmPackage) {
4894
+ const match = matchIdentityTemplate(template, identity, npmPackage);
4895
+ if (!match) return [];
4896
+ return [{
4897
+ operationId: candidate.candidateOperationId,
4898
+ key: match.key,
4899
+ value: match.value,
4900
+ normalizedIdentity: match.normalizedIdentity,
4901
+ provenance: {
4902
+ sourceKind,
4903
+ value: match.value,
4904
+ rule: "exact_normalized_identity_template_match",
4905
+ template,
4906
+ matchedName: identity,
4907
+ normalizedForm: match.normalizedIdentity,
4908
+ sourceRepo: candidate.repoName
4909
+ }
4910
+ }];
4911
+ }
4912
+ function matchIdentityTemplate(template, identity, npmPackage) {
4913
+ const matches2 = [...template.matchAll(/\$\{([^}]*)\}/g)];
4914
+ if (matches2.length !== 1 || !matches2[0]?.[1]) return void 0;
4915
+ const placeholder = matches2[0][0];
4916
+ const sentinel = "dynamicplaceholdertoken";
4917
+ const normalizedTemplate = normalizeIdentity(template.replace(placeholder, sentinel));
4918
+ const [prefix, suffix, extra] = normalizedTemplate.split(sentinel);
4919
+ if (!prefix || !suffix || extra !== void 0) return void 0;
4920
+ const normalizedIdentity = normalizeIdentity(identity, npmPackage);
4921
+ const match = new RegExp(`^${escapeRegex(prefix)}([a-z0-9]+)${escapeRegex(suffix)}$`).exec(normalizedIdentity);
4922
+ if (!match?.[1]) return void 0;
4923
+ return { key: matches2[0][1].trim(), value: match[1], normalizedIdentity };
4924
+ }
4925
+ function competingIdentityKeys(proposals) {
4926
+ const owners = /* @__PURE__ */ new Map();
4927
+ for (const proposal of proposals) {
4928
+ const key = `${proposal.key}:${proposal.value}`;
4929
+ owners.set(key, /* @__PURE__ */ new Set([...owners.get(key) ?? [], proposal.ownerKey]));
4930
+ }
4931
+ return new Set([...owners.entries()].filter(([, repos]) => repos.size > 1).map(([key]) => key));
4932
+ }
4933
+ function duplicateNormalizedIdentities(identities) {
4934
+ const owners = /* @__PURE__ */ new Map();
4935
+ for (const identity of identities) {
4936
+ for (const [name, npmPackage] of [
4937
+ [identity.packageName, true],
4938
+ [identity.repoName, false]
4939
+ ]) {
4940
+ if (!name) continue;
4941
+ const normalized = normalizeIdentity(name, npmPackage);
4942
+ owners.set(normalized, /* @__PURE__ */ new Set([
4943
+ ...owners.get(normalized) ?? [],
4944
+ `repository:${identity.repoId}`
4945
+ ]));
4946
+ }
4947
+ }
4948
+ return new Set([...owners.entries()].filter(([, repos]) => repos.size > 1).map(([identity]) => identity));
4949
+ }
4950
+ function workspaceIdentityMatches(identities, templates) {
4951
+ const routeTemplates = [templates.alias, templates.destination].filter((value) => Boolean(value));
4952
+ return identities.flatMap((identity) => {
4953
+ const names = [
4954
+ { name: identity.packageName, npmPackage: true },
4955
+ { name: identity.repoName, npmPackage: false }
4956
+ ];
4957
+ return routeTemplates.flatMap((template) => names.flatMap(({ name, npmPackage }) => {
4958
+ if (!name) return [];
4959
+ const match = matchIdentityTemplate(template, name, npmPackage);
4960
+ return match ? [{
4961
+ ownerKey: `repository:${identity.repoId}`,
4962
+ key: match.key,
4963
+ value: match.value,
4964
+ normalizedIdentity: match.normalizedIdentity
4965
+ }] : [];
4966
+ }));
4967
+ });
4968
+ }
4969
+ function workspaceIdentities(db, candidates) {
4970
+ const repoIds = [...new Set(candidates.flatMap((candidate) => candidate.repoId === void 0 ? [] : [candidate.repoId]))].sort((a, b) => a - b);
4971
+ if (repoIds.length === 0) return [];
4972
+ const placeholders3 = repoIds.map(() => "?").join(",");
4973
+ const rows2 = db.prepare(`SELECT id repoId,name repoName,package_name packageName
4974
+ FROM repositories WHERE workspace_id IN (
4975
+ SELECT DISTINCT workspace_id FROM repositories WHERE id IN (${placeholders3})
4976
+ ) ORDER BY workspace_id,name,absolute_path,id`).all(...repoIds);
4977
+ return rows2.flatMap((row) => {
4978
+ const repoId = numberValue(row.repoId);
4979
+ const repoName = stringValue4(row.repoName);
4980
+ return repoId === void 0 || !repoName ? [] : [{
4981
+ repoId,
4982
+ repoName,
4983
+ packageName: stringValue4(row.packageName)
4984
+ }];
4985
+ });
4986
+ }
4987
+ function deduplicateProposals(rows2) {
4988
+ const sorted = [...rows2].sort((left, right) => left.operationId - right.operationId || left.key.localeCompare(right.key) || left.value.localeCompare(right.value) || left.provenance.sourceKind.localeCompare(right.provenance.sourceKind));
4989
+ const seen = /* @__PURE__ */ new Set();
4990
+ return sorted.filter((row) => {
4991
+ const key = [row.operationId, row.key, row.value, row.normalizedIdentity].join(":");
4992
+ if (seen.has(key)) return false;
4993
+ seen.add(key);
4994
+ return true;
4995
+ });
4996
+ }
4997
+ function ownerAgrees(candidate, owner) {
4998
+ return candidate.repoId !== void 0 && owner?.repoId !== void 0 && owner.repoId === candidate.repoId;
4999
+ }
5000
+ function implementationOwner(db, operationId) {
5001
+ const rows2 = db.prepare(
5002
+ `SELECT r.id repoId
5003
+ FROM graph_edges e JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
5004
+ JOIN handler_classes hc ON hc.id=hm.handler_class_id
5005
+ JOIN repositories r ON r.id=hc.repo_id
5006
+ WHERE e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved'
5007
+ AND e.from_kind='operation' AND e.from_id=?
5008
+ ORDER BY r.id,hm.id,e.id`
5009
+ ).all(String(operationId));
5010
+ if (rows2.length !== 1) return void 0;
5011
+ const row = rows2[0];
5012
+ if (!row) return void 0;
5013
+ return {
5014
+ repoId: numberValue(row.repoId)
5015
+ };
5016
+ }
5017
+ function normalizeIdentity(value, npmPackage = false) {
5018
+ const unscoped = npmPackage && /^@[^/]+\/[^/]+$/.test(value) ? value.slice(value.indexOf("/") + 1) : value;
5019
+ return unscoped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
5020
+ }
5021
+ function stringValue4(value) {
5022
+ return typeof value === "string" ? value : void 0;
5023
+ }
5024
+ function numberValue(value) {
5025
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5026
+ }
5027
+ function escapeRegex(value) {
5028
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5029
+ }
5030
+
5031
+ // src/trace/003-dynamic-references.ts
5032
+ function dynamicReferenceRows(db, workspaceId, callerRepoId, callerRepo) {
5033
+ if (callerRepoId === void 0 && callerRepo === void 0) return [];
5034
+ const rows2 = db.prepare(
5035
+ `SELECT COALESCE(b.alias,b.alias_expr) alias,b.destination_expr destination,
5036
+ b.service_path_expr servicePath,'service_binding' sourceKind,r.name repoName,
5037
+ b.source_file sourceFile,b.source_line sourceLine,0 sourcePriority
5038
+ FROM service_bindings b JOIN repositories r ON r.id=b.repo_id
5039
+ WHERE (? IS NULL OR r.workspace_id=?)
5040
+ AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
5041
+ UNION ALL
5042
+ SELECT req.alias,req.destination,req.service_path,'cds_require',r.name,
5043
+ 'package.json',1,1 FROM cds_requires req JOIN repositories r ON r.id=req.repo_id
5044
+ WHERE (? IS NULL OR r.workspace_id=?)
5045
+ AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
5046
+ ORDER BY sourcePriority,repoName,sourceFile,sourceLine`
5047
+ ).all(
5048
+ workspaceId,
5049
+ workspaceId,
5050
+ callerRepoId,
5051
+ callerRepoId,
5052
+ callerRepoId,
5053
+ callerRepo,
5054
+ workspaceId,
5055
+ workspaceId,
5056
+ callerRepoId,
5057
+ callerRepoId,
5058
+ callerRepoId,
5059
+ callerRepo
5060
+ );
5061
+ return rows2.flatMap(referenceFromRow);
5062
+ }
5063
+ function dynamicReferenceProvenance(reference, kind, template, value) {
5064
+ return {
5065
+ sourceKind: `${reference.sourceKind}.${kind}`,
5066
+ value,
5067
+ rule: "exact_indexed_reference_template_match",
5068
+ template,
5069
+ sourceRepo: reference.repoName,
5070
+ sourceFile: reference.sourceFile,
5071
+ sourceLine: reference.sourceLine
5072
+ };
5073
+ }
5074
+ function referenceFromRow(row) {
5075
+ const sourceKind = row.sourceKind;
5076
+ const repoName = stringValue5(row.repoName);
5077
+ if (sourceKind !== "service_binding" && sourceKind !== "cds_require" || !repoName) return [];
5078
+ return [{
5079
+ alias: stringValue5(row.alias),
5080
+ destination: stringValue5(row.destination),
5081
+ servicePath: stringValue5(row.servicePath),
5082
+ sourceKind,
5083
+ repoName,
5084
+ sourceFile: stringValue5(row.sourceFile),
5085
+ sourceLine: numberValue2(row.sourceLine)
5086
+ }];
5087
+ }
5088
+ function stringValue5(value) {
5089
+ return typeof value === "string" ? value : void 0;
5090
+ }
5091
+ function numberValue2(value) {
5092
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5093
+ }
5094
+
5095
+ // src/trace/dynamic-targets.ts
5096
+ function analyzeDynamicTargetCandidates(db, evidence, workspaceId, mode, maxCandidates) {
5097
+ const inputs = analysisInputs(evidence);
5098
+ if (inputs.required.length === 0) return void 0;
5099
+ const targets = candidateTargets(db, evidence, workspaceId);
5100
+ const references = dynamicReferenceRows(
5101
+ db,
5102
+ workspaceId,
5103
+ inputs.callerRepoId,
5104
+ inputs.callerRepo
5105
+ );
5106
+ const candidates = buildCandidates(db, targets, references, inputs);
5107
+ applyUniqueIdentityEvidence(db, candidates, inputs);
5108
+ finalizeCandidates(candidates, inputs.order);
5109
+ const ranked = stableRank(candidates);
5110
+ const inference = inferenceDecision(ranked);
5111
+ applyModeState(ranked, mode, inference);
5112
+ const viable = ranked.filter((candidate) => candidate.viable);
5113
+ const rejected = ranked.filter((candidate) => candidate.rejected);
5114
+ const shown = viable.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
5115
+ const shownRejected = rejected.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
5116
+ return {
5117
+ mode,
5118
+ maxCandidates,
5119
+ candidateCount: ranked.length,
5120
+ viableCandidateCount: viable.length,
5121
+ rejectedCandidateCount: rejected.length,
5122
+ shownCandidateCount: shown.length,
5123
+ omittedCandidateCount: Math.max(0, viable.length - shown.length),
5124
+ shownRejectedCandidateCount: shownRejected.length,
5125
+ omittedRejectedCandidateCount: Math.max(0, rejected.length - shownRejected.length),
5126
+ missingVariables: inputs.required.filter((key) => inputs.supplied[key] === void 0),
5127
+ requiredVariables: inputs.required,
5128
+ suppliedVariables: inputs.supplied,
5129
+ appliedSuppliedVariables: requiredSuppliedVariables(inputs),
5130
+ substitutedSignals: inputs.effective,
5131
+ candidates: shown,
5132
+ shownCandidates: shown,
5133
+ rejectedCandidates: shownRejected,
5134
+ suggestedVarSets: suggestedVarSets(viable, inputs.order, maxCandidates),
5135
+ inference
5136
+ };
5137
+ }
5138
+ function analysisInputs(evidence) {
5139
+ const original = templatesFromEvidence(evidence, "original");
5140
+ const effective = templatesFromEvidence(evidence, "effective");
5141
+ const requiredSources = placeholderSources(original);
5142
+ const required = Object.keys(requiredSources);
5143
+ const supplied = stringRecord(evidence.suppliedRuntimeVariables);
5144
+ return {
5145
+ original,
5146
+ effective,
5147
+ required,
5148
+ requiredSources,
5149
+ supplied,
5150
+ order: variableOrder(original, required),
5151
+ callerRepo: stringValue6(evidence.repo),
5152
+ callerRepoId: numberValue3(evidence.repoId)
5153
+ };
5154
+ }
5155
+ function candidateTargets(db, evidence, workspaceId) {
5156
+ const embedded = rowsFromEvidence(evidence.candidates);
5157
+ if (embedded.length > 0) return embedded;
5158
+ const operationPath = effectiveSignal(evidence, "operationPath");
5159
+ if (!operationPath || extractPlaceholders(operationPath).length > 0) return [];
5160
+ return queryOperationTargets(db, operationPath, workspaceId);
5161
+ }
5162
+ function rowsFromEvidence(value) {
5163
+ if (!Array.isArray(value)) return [];
5164
+ return value.flatMap((item) => {
5165
+ const row = record(item);
5166
+ const operationId = numberValue3(row.operationId);
5167
+ const repoName = stringValue6(row.repoName);
5168
+ const servicePath = stringValue6(row.servicePath);
5169
+ const operationPath = stringValue6(row.operationPath);
5170
+ const operationName = stringValue6(row.operationName) ?? operationPath?.replace(/^\//, "");
5171
+ if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
5172
+ return [{
5173
+ operationId,
5174
+ repoId: numberValue3(row.repoId),
5175
+ repoName,
5176
+ packageName: stringValue6(row.packageName),
5177
+ serviceName: stringValue6(row.serviceName) ?? "",
5178
+ qualifiedName: stringValue6(row.qualifiedName) ?? "",
5179
+ servicePath,
5180
+ operationPath,
5181
+ operationName,
5182
+ sourceFile: stringValue6(row.sourceFile) ?? "",
5183
+ sourceLine: numberValue3(row.sourceLine) ?? 0,
5184
+ score: numberValue3(row.score) ?? 0,
5185
+ reasons: stringArray(row.reasons)
5186
+ }];
5187
+ });
5188
+ }
5189
+ function queryOperationTargets(db, operationPath, workspaceId) {
5190
+ const simple = operationPath.replace(/^\//, "").split(".").at(-1) ?? operationPath;
5191
+ const rows2 = db.prepare(
5192
+ `SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
5193
+ s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
5194
+ o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
5195
+ o.source_line sourceLine FROM cds_operations o
5196
+ JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
5197
+ WHERE (? IS NULL OR r.workspace_id=?)
5198
+ AND (o.operation_path IN (?,?) OR o.operation_name=?)
5199
+ ORDER BY r.name,s.service_path,o.operation_name,o.id`
5200
+ ).all(workspaceId, workspaceId, operationPath, `/${simple}`, simple);
5201
+ return rows2.flatMap(operationTargetFromRow);
5202
+ }
5203
+ function operationTargetFromRow(row) {
5204
+ const operationId = numberValue3(row.operationId);
5205
+ const repoName = stringValue6(row.repoName);
5206
+ const servicePath = stringValue6(row.servicePath);
5207
+ const operationPath = stringValue6(row.operationPath);
5208
+ const operationName = stringValue6(row.operationName);
5209
+ if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
5210
+ return [{
5211
+ operationId,
5212
+ repoId: numberValue3(row.repoId),
5213
+ repoName,
5214
+ packageName: stringValue6(row.packageName),
5215
+ serviceName: stringValue6(row.serviceName) ?? "",
5216
+ qualifiedName: stringValue6(row.qualifiedName) ?? "",
5217
+ servicePath,
5218
+ operationPath,
5219
+ operationName,
5220
+ sourceFile: stringValue6(row.sourceFile) ?? "",
5221
+ sourceLine: numberValue3(row.sourceLine) ?? 0,
5222
+ score: 0.2,
5223
+ reasons: ["operation_path_match"]
5224
+ }];
5225
+ }
5226
+ function buildCandidates(db, targets, references, inputs) {
5227
+ return targets.map((target) => {
5228
+ const state = emptyCandidate(target, inputs);
5229
+ applyDirectSignal(state, inputs, "operationPath", target.operationPath, 0.25);
5230
+ applyDirectSignal(state, inputs, "servicePath", target.servicePath, 0.35);
5231
+ const matchingReferences = references.filter((reference) => reference.servicePath === target.servicePath);
5232
+ applyReferenceSignal(state, inputs, matchingReferences, "alias");
5233
+ applyReferenceSignal(state, inputs, matchingReferences, "destination");
5234
+ if (hasResolvedImplementation(db, target.operationId))
5235
+ addScore(state, 0.1, "implementation_edge_resolved");
5236
+ return state;
5237
+ });
5238
+ }
5239
+ function emptyCandidate(target, inputs) {
5240
+ return {
5241
+ candidateOperationId: target.operationId,
5242
+ repoId: target.repoId,
5243
+ repoName: target.repoName,
5244
+ packageName: target.packageName ?? void 0,
5245
+ serviceName: target.serviceName,
5246
+ qualifiedName: target.qualifiedName,
5247
+ servicePath: target.servicePath,
5248
+ operationPath: target.operationPath,
5249
+ operationName: target.operationName,
5250
+ sourceFile: target.sourceFile,
5251
+ sourceLine: target.sourceLine,
5252
+ originalTemplates: inputs.original,
5253
+ effectiveValues: inputs.effective,
5254
+ requiredVariables: inputs.required,
5255
+ requiredVariableSources: inputs.requiredSources,
5256
+ suppliedVariables: inputs.supplied,
5257
+ completeVariables: { ...inputs.supplied },
5258
+ derivedVariables: {},
5259
+ derivedVariableSources: {},
5260
+ derivationProvenance: {},
5261
+ missingVariables: [],
5262
+ conflicts: [],
5263
+ score: Math.max(0.2, Number(target.score ?? 0)),
5264
+ explicitSignalStrength: 0,
5265
+ reasons: nonEmptyStrings(target.reasons, ["operation_path_match"]),
5266
+ rejectedReasons: [],
5267
+ inferenceBlockReasons: [],
5268
+ viable: true,
5269
+ rejected: false,
5270
+ selected: false,
5271
+ exploratory: false
5272
+ };
5273
+ }
5274
+ function applyDirectSignal(state, inputs, kind, concrete, score) {
5275
+ const effective = inputs.effective[kind];
5276
+ const original = inputs.original[kind];
5277
+ if (effective && !matchTemplate(effective, concrete)) {
5278
+ reject(state, `${signalCode(kind)}_contradicts_runtime_substitution`);
5279
+ return;
5280
+ }
5281
+ if (!effective) return;
5282
+ const suppliedKeys = extractPlaceholders(original).filter((key) => inputs.supplied[key] !== void 0);
5283
+ state.explicitSignalStrength += suppliedKeys.length;
5284
+ const matched = matchTemplate(original, concrete) ?? {};
5285
+ for (const [key, value] of Object.entries(matched)) {
5286
+ addDerivation(state, key, value, {
5287
+ sourceKind: `${signalCode(kind)}_template`,
5288
+ value,
5289
+ rule: "exact_template_match",
5290
+ template: original
5291
+ });
5292
+ }
5293
+ addScore(state, score, `${signalCode(kind)}_template_match`);
5294
+ }
5295
+ function applyReferenceSignal(state, inputs, references, kind) {
5296
+ const original = inputs.original[kind];
5297
+ const effective = inputs.effective[kind];
5298
+ if (!original || extractPlaceholders(original).length === 0) return;
5299
+ const values = references.flatMap((reference) => {
5300
+ const concrete = kind === "alias" ? reference.alias : reference.destination;
5301
+ return isConcrete(concrete) ? [{ reference, concrete }] : [];
5302
+ });
5303
+ if (effective && extractPlaceholders(effective).length === 0 && values.length > 0 && !values.some(({ concrete }) => concrete === effective)) {
5304
+ reject(state, `${kind}_contradicts_runtime_substitution`);
5305
+ }
5306
+ let matchedSignal = false;
5307
+ for (const { reference, concrete } of values) {
5308
+ const matched = matchTemplate(original, concrete);
5309
+ if (!matched) continue;
5310
+ matchedSignal = true;
5311
+ for (const [key, value] of Object.entries(matched)) {
5312
+ addDerivation(
5313
+ state,
5314
+ key,
5315
+ value,
5316
+ dynamicReferenceProvenance(reference, kind, original, value)
5317
+ );
5318
+ }
3688
5319
  }
3689
- const missingVariables = [...missing].sort();
5320
+ if (matchedSignal) {
5321
+ state.explicitSignalStrength += extractPlaceholders(original).filter((key) => inputs.supplied[key] !== void 0).length;
5322
+ addScore(state, 0.2, `${kind}_template_match`);
5323
+ }
5324
+ }
5325
+ function applyUniqueIdentityEvidence(db, candidates, inputs) {
5326
+ for (const derivation of uniqueIdentityDerivations(db, candidates, inputs.original)) {
5327
+ const candidate = candidates.find((item) => item.candidateOperationId === derivation.operationId);
5328
+ if (!candidate) continue;
5329
+ addDerivation(candidate, derivation.key, derivation.value, derivation.provenance);
5330
+ addScore(candidate, 0.2, "exact_identity_template_match");
5331
+ }
5332
+ }
5333
+ function addDerivation(state, key, value, provenance) {
5334
+ const priorProvenance = state.derivationProvenance[key] ?? [];
5335
+ state.derivationProvenance[key] = uniqueProvenance([...priorProvenance, provenance]);
5336
+ const supplied = state.suppliedVariables[key];
5337
+ if (supplied !== void 0 && supplied !== value) {
5338
+ addConflict(state, key, [supplied, value], "explicit_value_conflicts_with_derived_value");
5339
+ return;
5340
+ }
5341
+ const prior = state.derivedVariables[key];
5342
+ if (prior !== void 0 && prior !== value) {
5343
+ addConflict(state, key, [prior, value], "conflicting_strong_derivations");
5344
+ return;
5345
+ }
5346
+ if (supplied === void 0) state.derivedVariables[key] = value;
5347
+ state.completeVariables[key] = supplied ?? value;
5348
+ state.derivedVariableSources[key] ??= provenance;
5349
+ }
5350
+ function addConflict(state, key, values, reason) {
5351
+ const sources = (state.derivationProvenance[key] ?? []).map((item) => item.sourceKind).sort();
5352
+ state.conflicts.push({ key, values: [...new Set(values)].sort(), reason, sources });
5353
+ reject(state, reason);
5354
+ }
5355
+ function uniqueProvenance(rows2) {
5356
+ const sorted = [...rows2].sort((left, right) => left.sourceKind.localeCompare(right.sourceKind) || String(left.matchedName ?? "").localeCompare(String(right.matchedName ?? "")) || left.value.localeCompare(right.value));
5357
+ const seen = /* @__PURE__ */ new Set();
5358
+ return sorted.filter((row) => {
5359
+ const key = JSON.stringify(row);
5360
+ if (seen.has(key)) return false;
5361
+ seen.add(key);
5362
+ return true;
5363
+ });
5364
+ }
5365
+ function finalizeCandidates(candidates, order) {
5366
+ for (const candidate of candidates) {
5367
+ candidate.missingVariables = order.filter((key) => candidate.completeVariables[key] === void 0);
5368
+ candidate.viable = candidate.rejectedReasons.length === 0;
5369
+ candidate.rejected = !candidate.viable;
5370
+ if (candidate.missingVariables.length === 0 && candidate.viable)
5371
+ addScore(candidate, 0.15, "all_runtime_variables_derived");
5372
+ else if (candidate.missingVariables.length > 0)
5373
+ addReason(candidate, "missing_required_runtime_variable");
5374
+ candidate.score = Math.max(0, Math.min(1, candidate.score));
5375
+ candidate.cli = candidate.missingVariables.length === 0 && candidate.viable ? cliFor(candidate.completeVariables, order) : void 0;
5376
+ }
5377
+ }
5378
+ function stableRank(candidates) {
5379
+ return [...candidates].sort((left, right) => Number(right.viable) - Number(left.viable) || right.score - left.score || right.explicitSignalStrength - left.explicitSignalStrength || left.repoName.localeCompare(right.repoName) || String(left.packageName ?? "").localeCompare(String(right.packageName ?? "")) || left.servicePath.localeCompare(right.servicePath) || left.operationPath.localeCompare(right.operationPath) || left.operationName.localeCompare(right.operationName) || left.candidateOperationId - right.candidateOperationId);
5380
+ }
5381
+ function inferenceDecision(candidates) {
5382
+ const viable = candidates.filter((candidate) => candidate.viable);
5383
+ const first = viable[0];
5384
+ const second = viable[1];
5385
+ if (!first || first.missingVariables.length > 0)
5386
+ return { status: "unresolved", reason: "missing_required_runtime_variable" };
5387
+ if (first.score < 0.85)
5388
+ return { status: "unresolved", reason: "candidate_score_below_inference_threshold" };
5389
+ const scoreGap = second ? Number((first.score - second.score).toFixed(12)) : void 0;
5390
+ if (second && scoreGap !== void 0 && scoreGap <= 0.05) {
5391
+ const reason = scoreGap === 0 ? "candidate_tied_with_equal_score" : "candidate_within_inference_margin";
5392
+ for (const candidate of viable.filter((item) => first.score - item.score <= 0.05))
5393
+ addInferenceBlock(candidate, reason);
5394
+ return { status: "ambiguous", reason, scoreGap, requiredMargin: 0.05 };
5395
+ }
5396
+ return {
5397
+ status: "resolved",
5398
+ candidateOperationId: first.candidateOperationId,
5399
+ inferredVariables: first.completeVariables,
5400
+ score: first.score,
5401
+ reasons: first.reasons
5402
+ };
5403
+ }
5404
+ function boundedCandidate(candidate, limit) {
5405
+ const derivationProvenance = Object.fromEntries(
5406
+ Object.entries(candidate.derivationProvenance).sort(([left], [right]) => left.localeCompare(right)).map(([key, rows2]) => [key, rows2.slice(0, limit)])
5407
+ );
5408
+ const conflicts = candidate.conflicts.slice(0, limit).map((conflict) => ({
5409
+ ...conflict,
5410
+ sources: conflict.sources.slice(0, limit)
5411
+ }));
5412
+ return { ...candidate, derivationProvenance, conflicts };
5413
+ }
5414
+ function applyModeState(candidates, mode, inference) {
5415
+ const selectedId = mode === "infer" && inference.status === "resolved" ? numberValue3(inference.candidateOperationId) : void 0;
5416
+ for (const candidate of candidates) {
5417
+ candidate.selected = selectedId === candidate.candidateOperationId;
5418
+ candidate.exploratory = mode === "candidates" && candidate.viable;
5419
+ }
5420
+ }
5421
+ function suggestedVarSets(candidates, order, limit) {
5422
+ const seen = /* @__PURE__ */ new Set();
5423
+ const rows2 = [];
5424
+ for (const candidate of candidates) {
5425
+ if (!candidate.cli || candidate.missingVariables.length > 0) continue;
5426
+ if (seen.has(candidate.cli)) continue;
5427
+ seen.add(candidate.cli);
5428
+ rows2.push({ variables: orderedVariables(candidate.completeVariables, order), cli: candidate.cli });
5429
+ if (rows2.length >= limit) break;
5430
+ }
5431
+ return rows2;
5432
+ }
5433
+ function hasResolvedImplementation(db, operationId) {
5434
+ return Boolean(db.prepare(
5435
+ "SELECT 1 FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status='resolved' AND from_kind='operation' AND from_id=? LIMIT 1"
5436
+ ).get(String(operationId)));
5437
+ }
5438
+ function templatesFromEvidence(evidence, phase) {
5439
+ return {
5440
+ servicePath: substitutionSignal(evidence, "servicePath", phase),
5441
+ operationPath: substitutionSignal(evidence, "operationPath", phase),
5442
+ alias: substitutionSignal(
5443
+ evidence,
5444
+ evidence.serviceAliasExpr !== void 0 ? "serviceAliasExpr" : "serviceAlias",
5445
+ phase
5446
+ ),
5447
+ destination: substitutionSignal(evidence, "destination", phase)
5448
+ };
5449
+ }
5450
+ function substitutionSignal(evidence, key, phase) {
5451
+ const substitution = record(record(evidence.runtimeSubstitutions)[key]);
5452
+ return stringValue6(substitution[phase]) ?? stringValue6(evidence[key]);
5453
+ }
5454
+ function effectiveSignal(evidence, key) {
5455
+ return substitutionSignal(evidence, key, "effective");
5456
+ }
5457
+ function placeholderSources(templates) {
5458
+ const sources = {};
5459
+ for (const [kind, template] of Object.entries(templates)) {
5460
+ if (typeof template !== "string") continue;
5461
+ for (const key of extractPlaceholders(template))
5462
+ sources[key] = [.../* @__PURE__ */ new Set([...sources[key] ?? [], `${kind}:${template}`])].sort();
5463
+ }
5464
+ return Object.fromEntries(Object.entries(sources).sort(([left], [right]) => left.localeCompare(right)));
5465
+ }
5466
+ function variableOrder(templates, required) {
5467
+ const ordered = [
5468
+ templates.servicePath,
5469
+ templates.operationPath,
5470
+ templates.alias,
5471
+ templates.destination
5472
+ ].flatMap((value) => extractPlaceholders(value));
5473
+ return [.../* @__PURE__ */ new Set([...ordered, ...required])];
5474
+ }
5475
+ function matchTemplate(template, concrete) {
5476
+ if (!template || !concrete) return void 0;
5477
+ const keys = extractPlaceholders(template);
5478
+ if (keys.length === 0) return template === concrete ? {} : void 0;
5479
+ const match = new RegExp(`^${templateToPattern(template)}$`).exec(concrete);
5480
+ if (!match) return void 0;
5481
+ const values = {};
5482
+ for (let index = 0; index < keys.length; index += 1) {
5483
+ const key = keys[index];
5484
+ const value = match[index + 1];
5485
+ if (!key || value === void 0) return void 0;
5486
+ if (values[key] !== void 0 && values[key] !== value) return void 0;
5487
+ values[key] = value;
5488
+ }
5489
+ return values;
5490
+ }
5491
+ function templateToPattern(template) {
5492
+ let pattern = "";
5493
+ let lastIndex = 0;
5494
+ for (const match of template.matchAll(/\$\{([^}]*)\}/g)) {
5495
+ pattern += escapeRegex2(template.slice(lastIndex, match.index));
5496
+ pattern += "([^/]+?)";
5497
+ lastIndex = (match.index ?? 0) + match[0].length;
5498
+ }
5499
+ return `${pattern}${escapeRegex2(template.slice(lastIndex))}`;
5500
+ }
5501
+ function cliFor(variables, order) {
5502
+ return order.filter((key) => variables[key] !== void 0).map((key) => `--var ${shellArgument(`${key}=${variables[key]}`)}`).join(" ");
5503
+ }
5504
+ function shellArgument(value) {
5505
+ return /^[A-Za-z0-9_./:=+-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
5506
+ }
5507
+ function orderedVariables(variables, order) {
5508
+ return Object.fromEntries(order.flatMap((key) => variables[key] === void 0 ? [] : [[key, variables[key]]]));
5509
+ }
5510
+ function addScore(state, amount, reason) {
5511
+ state.score += amount;
5512
+ addReason(state, reason);
5513
+ }
5514
+ function addReason(state, reason) {
5515
+ if (!state.reasons.includes(reason)) state.reasons.push(reason);
5516
+ }
5517
+ function reject(state, reason) {
5518
+ rejectReasonOnly(state, reason);
5519
+ state.viable = false;
5520
+ state.rejected = true;
5521
+ }
5522
+ function rejectReasonOnly(state, reason) {
5523
+ if (!state.rejectedReasons.includes(reason)) state.rejectedReasons.push(reason);
5524
+ }
5525
+ function addInferenceBlock(state, reason) {
5526
+ addReason(state, reason);
5527
+ if (!state.inferenceBlockReasons.includes(reason))
5528
+ state.inferenceBlockReasons.push(reason);
5529
+ }
5530
+ function requiredSuppliedVariables(inputs) {
5531
+ return Object.fromEntries(inputs.required.flatMap((key) => inputs.supplied[key] === void 0 ? [] : [[key, inputs.supplied[key]]]));
5532
+ }
5533
+ function signalCode(kind) {
5534
+ return kind === "servicePath" ? "service_path" : "operation_path";
5535
+ }
5536
+ function record(value) {
5537
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
5538
+ }
5539
+ function stringRecord(value) {
5540
+ const entries = Object.entries(record(value)).filter((entry) => typeof entry[1] === "string").sort(([left], [right]) => left.localeCompare(right));
5541
+ return Object.fromEntries(entries);
5542
+ }
5543
+ function stringValue6(value) {
5544
+ return typeof value === "string" ? value : void 0;
5545
+ }
5546
+ function numberValue3(value) {
5547
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5548
+ }
5549
+ function stringArray(value) {
5550
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
5551
+ }
5552
+ function nonEmptyStrings(value, fallback) {
5553
+ const values = stringArray(value).filter((item) => item.length > 0);
5554
+ return values.length > 0 ? values : fallback;
5555
+ }
5556
+ function isConcrete(value) {
5557
+ return typeof value === "string" && value.length > 0 && extractPlaceholders(value).length === 0;
5558
+ }
5559
+ function escapeRegex2(value) {
5560
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5561
+ }
5562
+
5563
+ // src/trace/evidence.ts
5564
+ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
5565
+ const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
5566
+ return {
5567
+ ...evidence,
5568
+ graphEdgeId: row.id,
5569
+ persistedGraphEdgeId: row.id > 0 ? row.id : void 0,
5570
+ outboundCallId: call.id,
5571
+ callSite: { sourceFile: call.source_file, sourceLine: call.source_line },
5572
+ callType: call.call_type,
5573
+ repoId: call.repo_id,
5574
+ sourceFile: call.source_file,
5575
+ sourceLine: call.source_line,
5576
+ file: call.source_file,
5577
+ line: call.source_line,
5578
+ persistedTarget: { kind: row.to_kind, id: row.to_id },
5579
+ contextualResolutionParticipated: Boolean(contextualEvidence?.contextualServiceBindingAttempted),
5580
+ persistedResolution: persistedResolution(row)
5581
+ };
5582
+ }
5583
+ function runtimeResolution(db, row, evidence, options, workspaceId, contextualUnresolvedReason) {
5584
+ const dynamicMode = options.dynamicMode ?? "strict";
5585
+ const candidateCap = positiveCandidateCap(options.maxDynamicCandidates);
5586
+ if (!isDynamicRemoteOperationEdge(row, evidence))
5587
+ return unchangedRuntimeResolution(
5588
+ row,
5589
+ boundDynamicEvidence(evidence, candidateCap),
5590
+ contextualUnresolvedReason
5591
+ );
5592
+ const substituted = evidenceWithRuntimeVariables(evidence, options.vars);
5593
+ const analysis = analyzeDynamicTargetCandidates(
5594
+ db,
5595
+ substituted,
5596
+ workspaceId,
5597
+ dynamicMode,
5598
+ candidateCap
5599
+ );
5600
+ const enriched = boundDynamicEvidence(
5601
+ analysis ? evidenceWithDynamicAnalysis(substituted, analysis) : substituted,
5602
+ candidateCap
5603
+ );
5604
+ if (analysis && analysis.candidateCount > 0 && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
5605
+ return noCandidateRuntimeResolution(row, enriched);
5606
+ if (dynamicMode === "infer") {
5607
+ const inferred = inferredTarget(analysis);
5608
+ if (inferred)
5609
+ return resolvedRuntimeResolution(row, enriched, inferred, inferred.reasons);
5610
+ }
5611
+ if (!hasApplicableRuntimeVariables(evidence, options.vars)) {
5612
+ const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason;
5613
+ const withSections = withEffectiveResolution(enriched, row, unresolvedReason2);
5614
+ return { row, evidence: withSections, unresolvedReason: unresolvedReason2 };
5615
+ }
5616
+ const resolution = resolveRuntimeOperation(db, enriched, workspaceId);
5617
+ if (resolution.target)
5618
+ return resolvedRuntimeResolution(
5619
+ row,
5620
+ enriched,
5621
+ resolution.target,
5622
+ resolution.reasons
5623
+ );
5624
+ if (analysis && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
5625
+ return noCandidateRuntimeResolution(row, enriched);
5626
+ const unresolvedReason = runtimeUnresolvedReason(resolution);
5627
+ return { row, evidence: withEffectiveResolution(enriched, row, unresolvedReason, resolution), unresolvedReason };
5628
+ }
5629
+ function unchangedRuntimeResolution(row, evidence, contextualUnresolvedReason) {
5630
+ const unresolvedReason = contextualUnresolvedReason ?? row.unresolved_reason;
5631
+ return {
5632
+ row,
5633
+ evidence: withEffectiveResolution(evidence, row, unresolvedReason),
5634
+ unresolvedReason
5635
+ };
5636
+ }
5637
+ function noCandidateRuntimeResolution(row, evidence) {
5638
+ const unresolvedReason = "No candidate remained after runtime substitution";
5639
+ return {
5640
+ row,
5641
+ evidence: withEffectiveResolution(evidence, row, unresolvedReason),
5642
+ unresolvedReason
5643
+ };
5644
+ }
5645
+ function resolvedRuntimeResolution(row, evidence, target, reasons) {
5646
+ const resolvedRow = {
5647
+ ...row,
5648
+ status: "resolved",
5649
+ to_kind: "operation",
5650
+ to_id: String(target.operationId),
5651
+ unresolved_reason: void 0,
5652
+ confidence: Math.max(0, Math.min(1, target.score))
5653
+ };
5654
+ const resolution = {
5655
+ status: "resolved",
5656
+ target,
5657
+ candidates: [],
5658
+ reasons
5659
+ };
5660
+ return {
5661
+ row: resolvedRow,
5662
+ evidence: withEffectiveResolution(evidence, resolvedRow, void 0, resolution),
5663
+ target
5664
+ };
5665
+ }
5666
+ function runtimeVariableDiagnostic(edges) {
5667
+ const totals = runtimeDiagnosticTotals(edges);
5668
+ const missingVariables = [...totals.missing].sort();
3690
5669
  if (missingVariables.length === 0) return void 0;
5670
+ const shownSuggestions = totals.candidateSuggestions.slice(0, totals.maxCandidates);
5671
+ const shownRejected = totals.rejectedCandidates.slice(0, totals.maxCandidates);
5672
+ const shownCandidateCount = shownSuggestions.length;
3691
5673
  return {
3692
5674
  severity: "warning",
3693
5675
  code: "trace_runtime_variables_missing",
3694
5676
  message: `Runtime variables are required to resolve dynamic trace targets: ${missingVariables.join(", ")}`,
3695
5677
  missingVariables,
3696
- suggestions: missingVariables.map((key) => `--var ${key}=<value>`)
5678
+ suggestions: missingVariables.map((key) => `--var ${key}=<value>`),
5679
+ candidateCount: totals.candidateCount,
5680
+ viableCandidateCount: totals.viableCandidateCount,
5681
+ rejectedCandidateCount: totals.rejectedCandidateCount,
5682
+ shownCandidateCount,
5683
+ omittedCandidateCount: Math.max(0, totals.viableCandidateCount - shownCandidateCount),
5684
+ maxDynamicCandidates: totals.maxCandidates,
5685
+ shownRejectedCandidateCount: shownRejected.length,
5686
+ omittedRejectedCandidateCount: Math.max(0, totals.rejectedCandidateCount - shownRejected.length),
5687
+ candidateSuggestions: shownSuggestions,
5688
+ rejectedCandidates: shownRejected,
5689
+ suggestedVarSets: uniqueCliRows(totals.suggestedVarSets).slice(0, totals.maxCandidates),
5690
+ copyableExamples: copyableExamples(totals.suggestedVarSets, totals.candidateCount, totals.maxCandidates)
5691
+ };
5692
+ }
5693
+ function runtimeDiagnosticTotals(edges) {
5694
+ const missing = /* @__PURE__ */ new Set();
5695
+ let candidateCount = 0;
5696
+ let viableCandidateCount = 0;
5697
+ let rejectedCandidateCount = 0;
5698
+ let maxCandidates = 5;
5699
+ const candidateSuggestions = [];
5700
+ const rejectedCandidates = [];
5701
+ const suggestedVarSets2 = [];
5702
+ for (const edge of edges) {
5703
+ const effective = parseObject(edge.evidence.effectiveResolution);
5704
+ if (!["dynamic", "unresolved", "ambiguous"].includes(String(effective.status ?? "")))
5705
+ continue;
5706
+ const substitutions = edge.evidence.runtimeSubstitutions;
5707
+ if (!substitutions || typeof substitutions !== "object" || Array.isArray(substitutions)) continue;
5708
+ for (const value of Object.values(substitutions))
5709
+ for (const key of value.missing ?? []) missing.add(key);
5710
+ const exploration = parseObject(edge.evidence.dynamicTargetExploration);
5711
+ maxCandidates = positiveCandidateCap(numeric(exploration.maxCandidates) || maxCandidates);
5712
+ candidateCount += numeric(exploration.candidateCount);
5713
+ viableCandidateCount += numeric(exploration.viableCandidateCount);
5714
+ rejectedCandidateCount += numeric(exploration.rejectedCandidateCount);
5715
+ appendBounded(
5716
+ candidateSuggestions,
5717
+ recordArray(edge.evidence.dynamicTargetCandidateSuggestions),
5718
+ maxCandidates
5719
+ );
5720
+ appendBounded(
5721
+ rejectedCandidates,
5722
+ recordArray(exploration.rejectedCandidates),
5723
+ maxCandidates
5724
+ );
5725
+ appendBounded(
5726
+ suggestedVarSets2,
5727
+ recordArray(exploration.suggestedVarSets),
5728
+ maxCandidates
5729
+ );
5730
+ }
5731
+ return {
5732
+ missing,
5733
+ candidateCount,
5734
+ viableCandidateCount,
5735
+ rejectedCandidateCount,
5736
+ maxCandidates,
5737
+ candidateSuggestions,
5738
+ rejectedCandidates,
5739
+ suggestedVarSets: suggestedVarSets2
3697
5740
  };
3698
5741
  }
5742
+ function runtimeNoCandidateDiagnostics(edges) {
5743
+ const seen = /* @__PURE__ */ new Set();
5744
+ return edges.flatMap((edge) => {
5745
+ const exploration = parseObject(edge.evidence.dynamicTargetExploration);
5746
+ const suppliedVariables = parseObject(exploration.suppliedVariables);
5747
+ const appliedSuppliedVariables = parseObject(
5748
+ exploration.appliedSuppliedVariables
5749
+ );
5750
+ if (numeric(exploration.viableCandidateCount) !== 0 || Object.keys(appliedSuppliedVariables).length === 0) return [];
5751
+ const callSite = parseObject(edge.evidence.callSite);
5752
+ const key = JSON.stringify([callSite, suppliedVariables]);
5753
+ if (seen.has(key)) return [];
5754
+ seen.add(key);
5755
+ const maxCandidates = positiveCandidateCap(numeric(exploration.maxCandidates));
5756
+ return [{
5757
+ severity: "warning",
5758
+ code: "no_candidate_after_runtime_substitution",
5759
+ message: "No dynamic target candidate remained after applying runtime variables",
5760
+ suppliedVariables,
5761
+ appliedSuppliedVariables,
5762
+ substitutedSignals: parseObject(exploration.substitutedSignals),
5763
+ candidateCount: numeric(exploration.candidateCount),
5764
+ viableCandidateCount: 0,
5765
+ rejectedCandidateCount: numeric(exploration.rejectedCandidateCount),
5766
+ shownCandidateCount: numeric(exploration.shownCandidateCount),
5767
+ omittedCandidateCount: numeric(exploration.omittedCandidateCount),
5768
+ shownRejectedCandidateCount: numeric(
5769
+ exploration.shownRejectedCandidateCount
5770
+ ),
5771
+ omittedRejectedCandidateCount: numeric(
5772
+ exploration.omittedRejectedCandidateCount
5773
+ ),
5774
+ rejectedCandidates: recordArray(exploration.rejectedCandidates).slice(0, maxCandidates),
5775
+ callSite
5776
+ }];
5777
+ });
5778
+ }
3699
5779
  function edgeTarget(row, evidence) {
3700
5780
  const effective = parseObject(evidence.effectiveResolution);
3701
- const targetServicePath = stringValue4(effective.targetServicePath ?? evidence.targetServicePath);
3702
- const targetOperationPath = stringValue4(effective.targetOperationPath ?? evidence.targetOperationPath);
5781
+ const targetServicePath = stringValue7(effective.targetServicePath ?? evidence.targetServicePath);
5782
+ const targetOperationPath = stringValue7(effective.targetOperationPath ?? evidence.targetOperationPath);
3703
5783
  if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
3704
5784
  const runtimeCandidate = evidence.runtimeResolvedCandidate;
3705
5785
  if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
3706
- const servicePath = stringValue4(evidence.servicePath);
3707
- const operationPath = stringValue4(evidence.operationPath);
3708
- const targetOperation = stringValue4(evidence.targetOperation);
3709
- const targetRepo = stringValue4(evidence.targetRepo) ?? "";
5786
+ const servicePath = stringValue7(evidence.servicePath);
5787
+ const operationPath = stringValue7(evidence.operationPath);
5788
+ const targetOperation = stringValue7(evidence.targetOperation);
5789
+ const targetRepo = stringValue7(evidence.targetRepo) ?? "";
3710
5790
  if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
3711
- if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue4(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
5791
+ if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue7(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
3712
5792
  if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
3713
5793
  const target = parseObject(evidence.externalTarget);
3714
- return stringValue4(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
5794
+ return stringValue7(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
3715
5795
  }
3716
5796
  if (servicePath && operationPath) return `${servicePath}${operationPath}`;
3717
5797
  return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
@@ -3750,23 +5830,102 @@ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
3750
5830
  return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
3751
5831
  }
3752
5832
  function resolveRuntimeOperation(db, evidence, workspaceId) {
3753
- const servicePath = stringValue4(evidence.servicePath);
3754
- const rawOperationPath = stringValue4(evidence.operationPath);
5833
+ const servicePath = stringValue7(evidence.servicePath);
5834
+ const rawOperationPath = stringValue7(evidence.operationPath);
3755
5835
  const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
3756
- const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue4(evidence.normalizedOperationPath) ?? rawOperationPath;
3757
- const alias = stringValue4(evidence.serviceAliasExpr ?? evidence.serviceAlias);
3758
- const destination = stringValue4(evidence.destination);
5836
+ const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue7(evidence.normalizedOperationPath) ?? rawOperationPath;
5837
+ const alias = stringValue7(evidence.serviceAliasExpr ?? evidence.serviceAlias);
5838
+ const destination = stringValue7(evidence.destination);
3759
5839
  return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
3760
5840
  }
3761
5841
  function evidenceWithRuntimeVariables(evidence, vars) {
3762
5842
  const substitutions = runtimeSubstitutions(evidence, vars ?? {});
3763
- const next = { ...evidence, runtimeSubstitutions: substitutions };
5843
+ const suppliedRuntimeVariables = Object.fromEntries(
5844
+ Object.entries(vars ?? {}).sort(([left], [right]) => left.localeCompare(right))
5845
+ );
5846
+ const next = {
5847
+ ...evidence,
5848
+ runtimeSubstitutions: substitutions,
5849
+ suppliedRuntimeVariables
5850
+ };
3764
5851
  for (const [key, value] of Object.entries(substitutions)) if (value.effective) next[key] = value.effective;
3765
5852
  const missing = Object.values(substitutions).flatMap((value) => value.missing);
3766
5853
  if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)].sort();
3767
- if (Object.keys(vars ?? {}).length > 0) next.runtimeVariablesApplied = true;
5854
+ if (Object.keys(suppliedRuntimeVariables).length > 0) next.runtimeVariablesApplied = true;
3768
5855
  return next;
3769
5856
  }
5857
+ function evidenceWithDynamicAnalysis(evidence, analysis) {
5858
+ const persistedCandidates = recordArray(evidence.candidates);
5859
+ const persistedScores = recordArray(evidence.candidateScores);
5860
+ return {
5861
+ ...evidence,
5862
+ candidates: persistedCandidates.slice(0, analysis.maxCandidates),
5863
+ candidateScores: persistedScores.slice(0, analysis.maxCandidates),
5864
+ persistedCandidateCount: persistedCandidates.length,
5865
+ persistedCandidateOmittedCount: Math.max(
5866
+ 0,
5867
+ persistedCandidates.length - analysis.maxCandidates
5868
+ ),
5869
+ dynamicTargetExploration: {
5870
+ mode: analysis.mode,
5871
+ maxCandidates: analysis.maxCandidates,
5872
+ missingVariables: analysis.missingVariables,
5873
+ requiredVariables: analysis.requiredVariables,
5874
+ suppliedVariables: analysis.suppliedVariables,
5875
+ appliedSuppliedVariables: analysis.appliedSuppliedVariables,
5876
+ substitutedSignals: analysis.substitutedSignals,
5877
+ candidateCount: analysis.candidateCount,
5878
+ viableCandidateCount: analysis.viableCandidateCount,
5879
+ rejectedCandidateCount: analysis.rejectedCandidateCount,
5880
+ shownCandidateCount: analysis.shownCandidateCount,
5881
+ omittedCandidateCount: analysis.omittedCandidateCount,
5882
+ shownRejectedCandidateCount: analysis.shownRejectedCandidateCount,
5883
+ omittedRejectedCandidateCount: analysis.omittedRejectedCandidateCount,
5884
+ rejectedCandidates: analysis.rejectedCandidates,
5885
+ suggestedVarSets: analysis.suggestedVarSets
5886
+ },
5887
+ dynamicTargetCandidates: analysis.candidates,
5888
+ dynamicTargetCandidateSuggestions: analysis.shownCandidates,
5889
+ rejectedCandidates: analysis.rejectedCandidates,
5890
+ dynamicTargetInference: analysis.inference
5891
+ };
5892
+ }
5893
+ var boundedDynamicListKeys = /* @__PURE__ */ new Set([
5894
+ "candidates",
5895
+ "candidateScores",
5896
+ "dynamicTargetCandidates",
5897
+ "dynamicTargetCandidateSuggestions",
5898
+ "candidateSuggestions",
5899
+ "suggestedVarSets",
5900
+ "rejectedCandidates",
5901
+ "rejectedCandidateSuggestions",
5902
+ "copyableExamples",
5903
+ "conflicts"
5904
+ ]);
5905
+ function boundDynamicEvidence(evidence, limit) {
5906
+ const candidateCount = numeric(evidence.persistedCandidateCount) || (Array.isArray(evidence.candidates) ? evidence.candidates.length : 0);
5907
+ const projected = boundDynamicValue(evidence, limit);
5908
+ const next = parseObject(projected);
5909
+ if (candidateCount === 0) return next;
5910
+ return {
5911
+ ...next,
5912
+ persistedCandidateCount: candidateCount,
5913
+ persistedCandidateOmittedCount: Math.max(0, candidateCount - limit)
5914
+ };
5915
+ }
5916
+ function boundDynamicValue(value, limit, key, parentKey) {
5917
+ if (Array.isArray(value)) {
5918
+ const bounded = Boolean(key && (boundedDynamicListKeys.has(key) || parentKey === "derivationProvenance" || parentKey === "conflicts" && key === "sources"));
5919
+ const input = bounded ? value.slice(0, limit) : value;
5920
+ const projected = input.map((item) => boundDynamicValue(item, limit, void 0, key ?? parentKey));
5921
+ return projected;
5922
+ }
5923
+ if (!value || typeof value !== "object") return value;
5924
+ return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
5925
+ childKey,
5926
+ boundDynamicValue(child, limit, childKey, key ?? parentKey)
5927
+ ]));
5928
+ }
3770
5929
  function runtimeSubstitutions(evidence, vars) {
3771
5930
  const substitutions = {};
3772
5931
  for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
@@ -3776,17 +5935,44 @@ function runtimeSubstitutions(evidence, vars) {
3776
5935
  return substitutions;
3777
5936
  }
3778
5937
  function substitutionValue(evidence, key) {
3779
- const value = stringValue4(evidence[key]);
5938
+ const value = stringValue7(evidence[key]);
3780
5939
  if (key !== "operationPath") return value;
3781
5940
  const normalized = normalizeODataOperationInvocationPath(value);
3782
5941
  return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
3783
5942
  }
3784
- function isRemoteRuntimeCandidate(row, evidence, vars) {
3785
- if (!vars || Object.keys(vars).length === 0) return false;
5943
+ function isDynamicRemoteOperationEdge(row, evidence) {
5944
+ if (evidence.callType !== "remote_action") return false;
3786
5945
  if (!["dynamic", "ambiguous", "unresolved"].includes(String(row.status ?? ""))) return false;
3787
- if (!["DYNAMIC_EDGE_CANDIDATE", "UNRESOLVED_EDGE", "REMOTE_CALL_RESOLVES_TO_OPERATION"].includes(row.edge_type)) return false;
5946
+ return ["DYNAMIC_EDGE_CANDIDATE", "UNRESOLVED_EDGE", "REMOTE_CALL_RESOLVES_TO_OPERATION"].includes(row.edge_type);
5947
+ }
5948
+ function hasApplicableRuntimeVariables(evidence, vars) {
5949
+ if (!vars || Object.keys(vars).length === 0) return false;
3788
5950
  return ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"].some((key) => hasRuntimeVariable(evidence[key], vars));
3789
5951
  }
5952
+ function inferredTarget(analysis) {
5953
+ if (analysis?.inference.status !== "resolved") return void 0;
5954
+ const id = Number(analysis.inference.candidateOperationId);
5955
+ const candidate = analysis.candidates.find((item) => item.candidateOperationId === id);
5956
+ if (!candidate) return void 0;
5957
+ return targetFromCandidate(candidate);
5958
+ }
5959
+ function targetFromCandidate(candidate) {
5960
+ return {
5961
+ operationId: candidate.candidateOperationId,
5962
+ repoName: candidate.repoName,
5963
+ serviceName: "",
5964
+ qualifiedName: "",
5965
+ servicePath: candidate.servicePath,
5966
+ operationPath: candidate.operationPath,
5967
+ operationName: candidate.operationName,
5968
+ repoId: candidate.repoId,
5969
+ packageName: candidate.packageName,
5970
+ score: candidate.score,
5971
+ reasons: candidate.reasons,
5972
+ sourceFile: candidate.sourceFile,
5973
+ sourceLine: candidate.sourceLine
5974
+ };
5975
+ }
3790
5976
  function hasRuntimeVariable(value, vars) {
3791
5977
  return typeof value === "string" && extractPlaceholders(value).some((key) => Object.hasOwn(vars, key));
3792
5978
  }
@@ -3798,25 +5984,118 @@ function runtimeUnresolvedReason(resolution) {
3798
5984
  function parseObject(value) {
3799
5985
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
3800
5986
  }
3801
- function stringValue4(value) {
5987
+ function stringValue7(value) {
3802
5988
  return typeof value === "string" ? value : void 0;
3803
5989
  }
5990
+ function numeric(value) {
5991
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
5992
+ }
5993
+ function recordArray(value) {
5994
+ return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
5995
+ }
5996
+ function appendBounded(target, values, limit) {
5997
+ const remaining = Math.max(0, limit - target.length);
5998
+ if (remaining > 0) target.push(...values.slice(0, remaining));
5999
+ }
6000
+ function uniqueCliRows(rows2) {
6001
+ const seen = /* @__PURE__ */ new Set();
6002
+ return rows2.filter((row) => {
6003
+ const cli = typeof row.cli === "string" ? row.cli : JSON.stringify(row);
6004
+ if (seen.has(cli)) return false;
6005
+ seen.add(cli);
6006
+ return true;
6007
+ });
6008
+ }
6009
+ function copyableExamples(suggestedVarSets2, candidateCount, limit) {
6010
+ const variableExamples = uniqueCliRows(suggestedVarSets2).flatMap((row) => typeof row.cli === "string" ? [row.cli] : []);
6011
+ const exploration = candidateCount > 0 ? ["--dynamic-mode candidates --max-dynamic-candidates 20"] : [];
6012
+ return [...variableExamples, ...exploration].slice(0, limit);
6013
+ }
6014
+ function positiveCandidateCap(value) {
6015
+ return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : 5;
6016
+ }
6017
+
6018
+ // src/trace/dynamic-branches.ts
6019
+ function dynamicCandidateBranches(depth, call, evidence) {
6020
+ const exploration = objectRecord(evidence.dynamicTargetExploration);
6021
+ return recordArray2(evidence.dynamicTargetCandidateSuggestions).map((candidate) => ({
6022
+ step: depth,
6023
+ type: "dynamic_candidate_branch",
6024
+ from: `${call.repoName}:${call.source_file}:${call.source_line}`,
6025
+ to: `${String(candidate.servicePath ?? "")}${String(candidate.operationPath ?? "")}`,
6026
+ evidence: {
6027
+ ...candidate,
6028
+ exploratory: true,
6029
+ dynamicMode: String(exploration.mode ?? "candidates"),
6030
+ selected: false,
6031
+ omittedCandidateCount: numericValue2(exploration.omittedCandidateCount)
6032
+ },
6033
+ confidence: numericValue2(candidate.score),
6034
+ unresolvedReason: "Exploratory dynamic target candidate; provide runtime variables to select it"
6035
+ }));
6036
+ }
6037
+ function objectRecord(value) {
6038
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6039
+ }
6040
+ function recordArray2(value) {
6041
+ return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
6042
+ }
6043
+ function numericValue2(value) {
6044
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
6045
+ }
6046
+
6047
+ // src/trace/002-trace-diagnostics.ts
6048
+ function loadTraceDiagnostics(db, repoId, includeWorkspaceDiagnostics, workspaceId) {
6049
+ if (repoId === void 0 && !includeWorkspaceDiagnostics) return [];
6050
+ return db.prepare(`SELECT d.repo_id repoId,d.severity,d.code,d.message,
6051
+ d.source_file sourceFile,d.source_line sourceLine
6052
+ FROM diagnostics d LEFT JOIN repositories r ON r.id=d.repo_id
6053
+ WHERE (? IS NULL OR d.repo_id=?)
6054
+ AND (? IS NULL OR d.repo_id IS NULL OR r.workspace_id=?)
6055
+ ORDER BY severity,code,COALESCE(source_file,''),
6056
+ COALESCE(source_line,0),d.id`).all(
6057
+ repoId,
6058
+ repoId,
6059
+ workspaceId,
6060
+ workspaceId
6061
+ );
6062
+ }
6063
+ function prependTraceDiagnostic(diagnostics, diagnostic) {
6064
+ const duplicate = diagnostics.findIndex((item) => sameDiagnosticLocation(item, diagnostic));
6065
+ if (duplicate >= 0) diagnostics.splice(duplicate, 1);
6066
+ diagnostics.unshift(diagnostic);
6067
+ }
6068
+ function sameDiagnosticLocation(left, right) {
6069
+ if (left.code !== right.code) return false;
6070
+ const leftFile = stringValue8(left.sourceFile);
6071
+ const rightFile = stringValue8(right.sourceFile);
6072
+ if (leftFile || rightFile)
6073
+ return leftFile === rightFile && numericValue3(left.sourceLine) === numericValue3(right.sourceLine);
6074
+ return String(left.message ?? "") === String(right.message ?? "");
6075
+ }
6076
+ function stringValue8(value) {
6077
+ return typeof value === "string" ? value : void 0;
6078
+ }
6079
+ function numericValue3(value) {
6080
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
6081
+ }
3804
6082
 
3805
6083
  // src/trace/trace-engine.ts
3806
- function normalizeOperation(value) {
6084
+ function normalizeOperation2(value) {
3807
6085
  if (!value) return void 0;
3808
6086
  return value.startsWith("/") ? value.slice(1) : value;
3809
6087
  }
3810
6088
  function positiveDepth(value) {
3811
6089
  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
3812
6090
  }
3813
- function operationStartScope(db, repoId, start, hintOptions) {
3814
- const requested = normalizeOperation(start.operationPath ?? start.operation);
6091
+ function operationStartScope(db, repoId, start, hintOptions, workspaceId) {
6092
+ const requested = normalizeOperation2(start.operationPath ?? start.operation);
3815
6093
  if (!requested) return void 0;
3816
6094
  const rows2 = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.service_path servicePath,r.id repoId,r.name repoName
3817
6095
  FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
3818
- WHERE (? IS NULL OR r.id=?) AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
3819
- ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith("/") ? requested : `/${requested}`);
6096
+ WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR r.id=?)
6097
+ AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
6098
+ ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(workspaceId, workspaceId, repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith("/") ? requested : `/${requested}`);
3820
6099
  if (rows2.length === 0) return void 0;
3821
6100
  const repoCount = new Set(rows2.map((row) => String(row.repoName))).size;
3822
6101
  const serviceCount = new Set(rows2.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
@@ -3847,54 +6126,34 @@ function implementationRejectionReasons(evidence) {
3847
6126
  const reasons = candidates.flatMap((candidate) => Array.isArray(candidate.rejectedReasons) ? candidate.rejectedReasons.filter((reason) => typeof reason === "string") : []);
3848
6127
  return [...new Set(reasons)].sort();
3849
6128
  }
3850
- function sourceFilesForStart(db, repoId, start) {
3851
- const handler = start.handler;
3852
- const operation = normalizeOperation(start.operation ?? start.operationPath);
3853
- if (!handler && !operation) return void 0;
3854
- const rows2 = db.prepare(
3855
- `SELECT DISTINCT hc.source_file sourceFile,s.id symbolId
3856
- FROM handler_classes hc LEFT JOIN handler_methods hm ON hm.handler_class_id=hc.id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name
3857
- WHERE (? IS NULL OR hc.repo_id=?) AND (? IS NULL OR hc.class_name=? OR hm.method_name=?)
3858
- AND (? IS NULL OR hm.decorator_value=? OR hm.method_name=?)
3859
- AND (? IS NULL OR EXISTS (SELECT 1 FROM cds_services s JOIN cds_operations o ON o.service_id=s.id WHERE s.repo_id=hc.repo_id AND s.service_path=? AND (? IS NULL OR o.operation_path=? OR o.operation_name=? OR hm.decorator_value=? OR hm.method_name=?)))`
3860
- ).all(
3861
- repoId,
3862
- repoId,
3863
- handler,
3864
- handler,
3865
- handler,
3866
- operation,
3867
- operation,
3868
- operation,
3869
- start.servicePath,
3870
- start.servicePath,
3871
- operation,
3872
- operation,
3873
- operation,
3874
- operation,
3875
- operation
6129
+ function sourceFilesForStart(db, repoId, start, workspaceId) {
6130
+ return sourceScopeForSelector(db, repoId, start, workspaceId);
6131
+ }
6132
+ function startScope(db, start, hintOptions, workspaceId) {
6133
+ const repos = start.repo ? reposByName(db, start.repo, workspaceId).map((row) => ({
6134
+ id: row.id,
6135
+ name: row.name,
6136
+ packageName: row.package_name ?? void 0
6137
+ })) : [];
6138
+ if (start.repo && repos.length === 0) return {
6139
+ selectorMatched: false,
6140
+ startDiagnostics: [selectorRepoNotFoundDiagnostic(start.repo)]
6141
+ };
6142
+ if (start.repo && repos.length > 1) return {
6143
+ selectorMatched: false,
6144
+ startDiagnostics: [selectorRepoAmbiguousDiagnostic(start.repo, repos)]
6145
+ };
6146
+ const repo = repos[0];
6147
+ const operationScope = operationStartScope(
6148
+ db,
6149
+ repo?.id,
6150
+ start,
6151
+ hintOptions,
6152
+ workspaceId
3876
6153
  );
3877
- if (rows2.length > 0) return { files: new Set(rows2.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(rows2.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
3878
- if (start.servicePath && operation) {
3879
- const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId
3880
- FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
3881
- JOIN graph_edges e ON e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved' AND e.from_kind='operation' AND e.from_id=CAST(o.id AS TEXT)
3882
- JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
3883
- JOIN handler_classes hc ON hc.id=hm.handler_class_id
3884
- LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
3885
- WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?)`).all(repoId, repoId, start.servicePath, operation, operation);
3886
- if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
3887
- }
3888
- return void 0;
3889
- }
3890
- function startScope(db, start, hintOptions) {
3891
- const repo = start.repo ? db.prepare(
3892
- "SELECT id,name FROM repositories WHERE name=? OR package_name=?"
3893
- ).get(start.repo, start.repo) : void 0;
3894
- if (start.repo && !repo) return { repo, selectorMatched: false };
3895
- const operationScope = operationStartScope(db, repo?.id, start, hintOptions);
3896
6154
  const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === "operation" || d.resolutionStage === "implementation");
3897
- const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
6155
+ const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start, workspaceId);
6156
+ const terminalSelectorScope = Boolean(sourceScope?.diagnostics?.length && !sourceScope.files);
3898
6157
  const sourceFiles = sourceScope?.files;
3899
6158
  const hasSelector = Boolean(
3900
6159
  start.handler ?? start.operation ?? start.operationPath ?? start.servicePath
@@ -3906,9 +6165,9 @@ function startScope(db, start, hintOptions) {
3906
6165
  executionRepoId: sourceScope?.repoId ?? repo?.id,
3907
6166
  sourceFiles,
3908
6167
  symbolIds: sourceScope?.symbols,
3909
- selectorMatched: !terminalOperationScope && (!hasSelector || sourceFiles !== void 0),
6168
+ selectorMatched: !terminalOperationScope && !terminalSelectorScope && (!hasSelector || sourceFiles !== void 0),
3910
6169
  startOperationId: operationScope?.operationId,
3911
- startDiagnostics: operationScope?.diagnostics
6170
+ startDiagnostics: operationScope?.diagnostics?.length ? operationScope.diagnostics : sourceScope?.diagnostics
3912
6171
  };
3913
6172
  }
3914
6173
  function handlerFilesForOperation(db, operationId) {
@@ -3917,12 +6176,23 @@ function handlerFilesForOperation(db, operationId) {
3917
6176
  FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?`
3918
6177
  ).get(operationId);
3919
6178
  if (!op) return /* @__PURE__ */ new Set();
3920
- const operation = normalizeOperation(op.operationPath ?? op.operationName);
6179
+ const operation = normalizeOperation2(op.operationPath ?? op.operationName);
3921
6180
  const rows2 = db.prepare(
3922
6181
  `SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId FROM handler_classes hc
3923
6182
  JOIN handler_methods hm ON hm.handler_class_id=hc.id
3924
- LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
3925
- WHERE hc.repo_id=? AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`
6183
+ LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
6184
+ AND sym.source_file=hc.source_file
6185
+ AND sym.qualified_name=hc.class_name || '.' || hm.method_name
6186
+ AND sym.start_line=hm.source_line
6187
+ WHERE hc.repo_id=?
6188
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
6189
+ CASE WHEN hm.decorator_kind='Event' THEN 'event'
6190
+ WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
6191
+ ELSE 'unsupported' END)='operation'
6192
+ AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
6193
+ CASE WHEN hm.decorator_kind IN ('Action','Func','On')
6194
+ THEN 1 ELSE 0 END)=1
6195
+ AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`
3926
6196
  ).all(op.repoId, operation, operation, op.operationName);
3927
6197
  return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
3928
6198
  }
@@ -3937,7 +6207,9 @@ function handlerMethodNode(db, methodId) {
3937
6207
  function implementationScope(db, operationId) {
3938
6208
  const edge = implementationEdge(db, operationId);
3939
6209
  if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
3940
- const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name WHERE hm.id=?").get(edge.to_id);
6210
+ const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(edge.to_id);
6211
+ if (!row || typeof row.symbolId !== "number")
6212
+ return { repoId: row?.repoId, files: /* @__PURE__ */ new Set(), edge };
3941
6213
  return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
3942
6214
  }
3943
6215
  function implementationMethodIdFromHint(edge, options) {
@@ -3973,8 +6245,8 @@ function contextImplementationMethodId(edge, callerRepoId, remoteEvidence, hintO
3973
6245
  return { blocksAutomatic: false, evidence: hinted.evidence.reason === "no_scoped_hint_matched_edge" ? hinted.evidence : { status: "tied", tieReason: scores.length > 1 ? "duplicate_helper_implementation_candidates" : "no_unique_materially_stronger_candidate", candidateScores: scores } };
3974
6246
  }
3975
6247
  function handlerScope(db, methodId) {
3976
- const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name WHERE hm.id=?").get(methodId);
3977
- if (!row) return void 0;
6248
+ const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(methodId);
6249
+ if (!row || typeof row.symbolId !== "number") return void 0;
3978
6250
  return { repoId: row.repoId, files: new Set(row.sourceFile ? [row.sourceFile] : []), symbolId: row.symbolId };
3979
6251
  }
3980
6252
  function traceEdgeType(call, row) {
@@ -4181,20 +6453,22 @@ function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRo
4181
6453
  }
4182
6454
  function trace(db, start, options) {
4183
6455
  const hintOptions = { implementationRepo: options.implementationRepo, implementationHints: options.implementationHints };
4184
- const scope = startScope(db, start, hintOptions);
4185
- const diagnostics = db.prepare(
4186
- "SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics WHERE (? IS NULL OR repo_id=?)"
4187
- ).all(scope.repo?.id, scope.repo?.id);
4188
- const stale = db.prepare("SELECT name,graph_stale_reason reason FROM repositories WHERE graph_stale_reason IS NOT NULL AND (? IS NULL OR id=?)").all(scope.repo?.id, scope.repo?.id);
6456
+ const scope = startScope(db, start, hintOptions, options.workspaceId);
6457
+ const hasSelector = Boolean(start.repo || start.handler || start.operation || start.operationPath || start.servicePath);
6458
+ const diagnosticRepoId = scope.executionRepoId ?? scope.repo?.id;
6459
+ const diagnostics = loadTraceDiagnostics(
6460
+ db,
6461
+ diagnosticRepoId,
6462
+ !hasSelector,
6463
+ options.workspaceId
6464
+ );
6465
+ const stale = diagnosticRepoId !== void 0 || !hasSelector ? db.prepare("SELECT name,graph_stale_reason reason FROM repositories WHERE graph_stale_reason IS NOT NULL AND (? IS NULL OR id=?) AND (? IS NULL OR workspace_id=?) ORDER BY name,id").all(diagnosticRepoId, diagnosticRepoId, options.workspaceId, options.workspaceId) : [];
4189
6466
  for (const row of stale)
4190
- diagnostics.unshift({ severity: "warning", code: "graph_stale", message: `Graph is stale for ${row.name ?? "repository"}: ${row.reason ?? "facts_changed"}. Run service-flow link.` });
4191
- for (const diagnostic of scope.startDiagnostics ?? []) diagnostics.unshift(diagnostic);
6467
+ prependTraceDiagnostic(diagnostics, { severity: "warning", code: "graph_stale", message: `Graph is stale for ${row.name ?? "repository"}: ${row.reason ?? "facts_changed"}. Run service-flow link.` });
6468
+ for (const diagnostic of scope.startDiagnostics ?? [])
6469
+ prependTraceDiagnostic(diagnostics, diagnostic);
4192
6470
  if (!scope.selectorMatched && !scope.startDiagnostics?.length)
4193
- diagnostics.unshift({
4194
- severity: "warning",
4195
- code: "trace_start_not_found",
4196
- message: start.servicePath && !start.operation && !start.operationPath && !start.handler ? "Service-only trace requires --operation or --path and will not broaden to the whole workspace" : "No handler source matched the requested trace start selector"
4197
- });
6471
+ prependTraceDiagnostic(diagnostics, selectorNotFoundDiagnostic(start));
4198
6472
  const maxDepth = positiveDepth(options.depth);
4199
6473
  const edges = [];
4200
6474
  const nodes = /* @__PURE__ */ new Map();
@@ -4223,8 +6497,8 @@ function trace(db, start, options) {
4223
6497
  if (seenScopes.has(key)) continue;
4224
6498
  seenScopes.add(key);
4225
6499
  const calls = db.prepare(
4226
- `SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) ORDER BY c.source_file,c.source_line`
4227
- ).all(current.repoId, current.repoId);
6500
+ `SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) AND (? IS NULL OR r.workspace_id=?) ORDER BY c.source_file,c.source_line`
6501
+ ).all(current.repoId, current.repoId, options.workspaceId, options.workspaceId);
4228
6502
  const filtered = calls.filter(
4229
6503
  (c) => (!current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
4230
6504
  );
@@ -4269,7 +6543,11 @@ function trace(db, start, options) {
4269
6543
  String(row.evidence_json || "{}")
4270
6544
  );
4271
6545
  const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
4272
- const effective = runtimeResolution(db, row, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)), contextual.unresolvedReason);
6546
+ const effective = runtimeResolution(db, row, rawEvidence, {
6547
+ vars: options.vars,
6548
+ dynamicMode: options.dynamicMode ?? "strict",
6549
+ maxDynamicCandidates: options.maxDynamicCandidates
6550
+ }, workspaceIdForCall(db, String(call.id)), contextual.unresolvedReason);
4273
6551
  const evidence = effective.evidence;
4274
6552
  const effectiveRow = effective.row;
4275
6553
  const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
@@ -4289,6 +6567,8 @@ function trace(db, start, options) {
4289
6567
  confidence: Number(effectiveRow.confidence ?? call.confidence),
4290
6568
  unresolvedReason: effective.unresolvedReason
4291
6569
  });
6570
+ if ((options.dynamicMode ?? "strict") === "candidates")
6571
+ edges.push(...dynamicCandidateBranches(current.depth, call, evidence));
4292
6572
  if (effectiveRow.to_kind === "operation") {
4293
6573
  const implementation = implementationScope(db, effectiveRow.to_id);
4294
6574
  const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence, hintOptions);
@@ -4297,7 +6577,7 @@ function trace(db, start, options) {
4297
6577
  if (implementation.edge) {
4298
6578
  const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
4299
6579
  const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence.implementationHintSuggestions);
4300
- if (hintDiagnostic) diagnostics.unshift(hintDiagnostic);
6580
+ if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
4301
6581
  const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
4302
6582
  const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
4303
6583
  if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
@@ -4343,16 +6623,35 @@ function trace(db, start, options) {
4343
6623
  }
4344
6624
  }
4345
6625
  const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
4346
- if (runtimeDiagnostic) diagnostics.unshift(runtimeDiagnostic);
6626
+ if (runtimeDiagnostic) prependTraceDiagnostic(diagnostics, runtimeDiagnostic);
6627
+ for (const diagnostic of runtimeNoCandidateDiagnostics(edges))
6628
+ prependTraceDiagnostic(diagnostics, diagnostic);
4347
6629
  return { start, nodes: [...nodes.values()], edges, diagnostics };
4348
6630
  }
4349
6631
 
4350
6632
  export {
6633
+ upsertWorkspace,
6634
+ getWorkspace,
6635
+ upsertRepository,
6636
+ listRepositories,
6637
+ reposByName,
6638
+ clearRepoFacts,
6639
+ insertRequires,
6640
+ insertService,
6641
+ insertHandler,
6642
+ handlerMethodIsExecutable,
6643
+ insertRegistrations,
6644
+ insertBindings,
6645
+ insertExecutableSymbols,
6646
+ insertSymbolCalls,
6647
+ insertCalls,
4351
6648
  normalizePath,
4352
6649
  stripQuotes,
4353
6650
  discoverRepositories,
4354
6651
  parsePackageJson,
6652
+ loadPackageJsonSnapshot,
4355
6653
  parseCdsFile,
6654
+ loadRepositorySourceContext,
4356
6655
  parseDecorators,
4357
6656
  parseHandlerRegistrations,
4358
6657
  redactText,
@@ -4369,6 +6668,7 @@ export {
4369
6668
  implementationHintSuggestions,
4370
6669
  linkWorkspace,
4371
6670
  parseVars,
6671
+ selectorRepoAmbiguousDiagnostic,
4372
6672
  trace
4373
6673
  };
4374
- //# sourceMappingURL=chunk-52OUS3MO.js.map
6674
+ //# sourceMappingURL=chunk-PTLDSHRC.js.map