@saptools/service-flow 0.1.42 → 0.1.44

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.
@@ -577,10 +577,64 @@ function summarizeExpression(text) {
577
577
  return redactText(text).slice(0, 240);
578
578
  }
579
579
 
580
+ // src/parsers/outbound-call-parser.ts
581
+ import fs6 from "fs/promises";
582
+ import path7 from "path";
583
+ import ts4 from "typescript";
584
+
585
+ // src/linker/external-http-target.ts
586
+ import { createHash } from "crypto";
587
+ var sensitiveKeys = /* @__PURE__ */ new Set(["token", "access_token", "id_token", "api_key", "apikey", "key", "password", "passwd", "pwd", "secret", "client_secret", "authorization", "cookie", "signature"]);
588
+ function hash(value) {
589
+ return createHash("sha256").update(value).digest("hex").slice(0, 12);
590
+ }
591
+ function methodPrefix(method) {
592
+ return typeof method === "string" && method.length > 0 ? `${method.toUpperCase()} ` : "";
593
+ }
594
+ function redactUrl(value) {
595
+ try {
596
+ const url = new URL(value, value.startsWith("/") ? "https://relative.invalid" : void 0);
597
+ url.username = "";
598
+ url.password = "";
599
+ for (const key of [...url.searchParams.keys()]) url.searchParams.set(key, sensitiveKeys.has(key.toLowerCase()) ? "<redacted>" : "<redacted>");
600
+ const path10 = `${url.pathname}${url.search ? url.search : ""}`;
601
+ return value.startsWith("/") ? path10 : `${url.origin}${path10}`;
602
+ } catch {
603
+ return value.replace(/([?&][^=;&]*(?:token|key|password|secret|cookie|authorization)[^=;&]*=)[^&]*/gi, "$1<redacted>");
604
+ }
605
+ }
606
+ function externalHttpTarget(call) {
607
+ const evidence = typeof call.evidence_json === "string" ? safeParse(call.evidence_json) : {};
608
+ const target = evidence.externalTarget && typeof evidence.externalTarget === "object" && !Array.isArray(evidence.externalTarget) ? evidence.externalTarget : {};
609
+ const method = typeof call.method === "string" ? call.method : typeof target.method === "string" ? target.method : void 0;
610
+ const kind = typeof target.kind === "string" ? target.kind : "unknown";
611
+ const expression = typeof target.expression === "string" ? target.expression : void 0;
612
+ if (kind === "destination" && target.dynamic === true) {
613
+ const shape = typeof target.expressionShape === "string" ? target.expressionShape : "expression";
614
+ const candidates = Array.isArray(target.candidateLiterals) ? target.candidateLiterals.filter((item) => typeof item === "string") : [];
615
+ return { kind, toKind: "external_destination", toId: `destination:dynamic:${hash(`${shape}:${candidates.join("|")}`)}`, label: "External destination: dynamic destination", method, dynamic: true, expression: candidates.length ? `candidates:${candidates.join("|")}` : `shape:${shape}` };
616
+ }
617
+ if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
618
+ if (kind === "static_url" && expression) {
619
+ const redacted = redactUrl(expression);
620
+ return { kind, toKind: "external_endpoint", toId: `endpoint:${hash(`${method ?? ""}:${redacted}`)}`, label: `External endpoint: ${methodPrefix(method)}${redacted}`, method, dynamic: false, expression: redacted };
621
+ }
622
+ if (kind === "url_expression" && expression) return { kind, toKind: "external_endpoint", toId: `dynamic:${hash(expression)}`, label: `External endpoint: ${methodPrefix(method)}dynamic URL`, method, dynamic: true, expression: `expr:${hash(expression)}` };
623
+ return { kind: "unknown", toKind: "external_endpoint", toId: "unknown", label: "External endpoint: unknown", method, dynamic: false };
624
+ }
625
+ function safeParse(value) {
626
+ try {
627
+ const parsed = JSON.parse(value);
628
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
629
+ } catch {
630
+ return {};
631
+ }
632
+ }
633
+
580
634
  // src/linker/odata-path-normalizer.ts
581
- function normalizeODataOperationInvocationPath(path9) {
582
- if (path9 === void 0) return void 0;
583
- const raw = path9.trim();
635
+ function normalizeODataOperationInvocationPath(path10) {
636
+ if (path10 === void 0) return void 0;
637
+ const raw = path10.trim();
584
638
  if (!raw) return void 0;
585
639
  const rejected = (reason) => ({ rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false, invocationArgumentPlaceholderKeys: [], normalizationRejectedReason: reason });
586
640
  const open = raw.indexOf("(");
@@ -602,8 +656,8 @@ function normalizeODataOperationInvocationPath(path9) {
602
656
  normalizationReason: "balanced_top_level_operation_invocation"
603
657
  };
604
658
  }
605
- function classifyODataPathIntent(path9, method) {
606
- const rawPath = (path9 ?? "").trim();
659
+ function classifyODataPathIntent(path10, method) {
660
+ const rawPath = (path10 ?? "").trim();
607
661
  const normalizedMethod = (method ?? "GET").trim().toUpperCase() || "GET";
608
662
  const queryIndex = rawPath.indexOf("?");
609
663
  const pathWithoutQuery = queryIndex >= 0 ? rawPath.slice(0, queryIndex) : rawPath;
@@ -651,8 +705,8 @@ function classifyODataPathIntent(path9, method) {
651
705
  if (/^[A-Z][A-Za-z0-9_]*$/.test(firstSegment)) return { ...base, kind: "entity_candidate", reason: "uppercase_collection_segment_without_indexed_entity_evidence" };
652
706
  return { ...base, kind: "unknown", reason: "get_path_has_no_query_key_or_navigation_signal" };
653
707
  }
654
- function entitySegmentFromPath(path9) {
655
- const first = path9.replace(/^\//, "").split("/")[0]?.trim();
708
+ function entitySegmentFromPath(path10) {
709
+ const first = path10.replace(/^\//, "").split("/")[0]?.trim();
656
710
  if (!first) return void 0;
657
711
  const open = first.indexOf("(");
658
712
  const entity = (open >= 0 ? first.slice(0, open) : first).trim();
@@ -799,60 +853,6 @@ function topLevelQueryIndex(text) {
799
853
  return -1;
800
854
  }
801
855
 
802
- // src/parsers/outbound-call-parser.ts
803
- import fs6 from "fs/promises";
804
- import path7 from "path";
805
- import ts4 from "typescript";
806
-
807
- // src/linker/external-http-target.ts
808
- import { createHash } from "crypto";
809
- var sensitiveKeys = /* @__PURE__ */ new Set(["token", "access_token", "id_token", "api_key", "apikey", "key", "password", "passwd", "pwd", "secret", "client_secret", "authorization", "cookie", "signature"]);
810
- function hash(value) {
811
- return createHash("sha256").update(value).digest("hex").slice(0, 12);
812
- }
813
- function methodPrefix(method) {
814
- return typeof method === "string" && method.length > 0 ? `${method.toUpperCase()} ` : "";
815
- }
816
- function redactUrl(value) {
817
- try {
818
- const url = new URL(value, value.startsWith("/") ? "https://relative.invalid" : void 0);
819
- url.username = "";
820
- url.password = "";
821
- for (const key of [...url.searchParams.keys()]) url.searchParams.set(key, sensitiveKeys.has(key.toLowerCase()) ? "<redacted>" : "<redacted>");
822
- const path9 = `${url.pathname}${url.search ? url.search : ""}`;
823
- return value.startsWith("/") ? path9 : `${url.origin}${path9}`;
824
- } catch {
825
- return value.replace(/([?&][^=;&]*(?:token|key|password|secret|cookie|authorization)[^=;&]*=)[^&]*/gi, "$1<redacted>");
826
- }
827
- }
828
- function externalHttpTarget(call) {
829
- const evidence = typeof call.evidence_json === "string" ? safeParse(call.evidence_json) : {};
830
- const target = evidence.externalTarget && typeof evidence.externalTarget === "object" && !Array.isArray(evidence.externalTarget) ? evidence.externalTarget : {};
831
- const method = typeof call.method === "string" ? call.method : typeof target.method === "string" ? target.method : void 0;
832
- const kind = typeof target.kind === "string" ? target.kind : "unknown";
833
- const expression = typeof target.expression === "string" ? target.expression : void 0;
834
- if (kind === "destination" && target.dynamic === true) {
835
- const shape = typeof target.expressionShape === "string" ? target.expressionShape : "expression";
836
- const candidates = Array.isArray(target.candidateLiterals) ? target.candidateLiterals.filter((item) => typeof item === "string") : [];
837
- return { kind, toKind: "external_destination", toId: `destination:dynamic:${hash(`${shape}:${candidates.join("|")}`)}`, label: "External destination: dynamic destination", method, dynamic: true, expression: candidates.length ? `candidates:${candidates.join("|")}` : `shape:${shape}` };
838
- }
839
- if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
840
- if (kind === "static_url" && expression) {
841
- const redacted = redactUrl(expression);
842
- return { kind, toKind: "external_endpoint", toId: `endpoint:${hash(`${method ?? ""}:${redacted}`)}`, label: `External endpoint: ${methodPrefix(method)}${redacted}`, method, dynamic: false, expression: redacted };
843
- }
844
- if (kind === "url_expression" && expression) return { kind, toKind: "external_endpoint", toId: `dynamic:${hash(expression)}`, label: `External endpoint: ${methodPrefix(method)}dynamic URL`, method, dynamic: true, expression: `expr:${hash(expression)}` };
845
- return { kind: "unknown", toKind: "external_endpoint", toId: "unknown", label: "External endpoint: unknown", method, dynamic: false };
846
- }
847
- function safeParse(value) {
848
- try {
849
- const parsed = JSON.parse(value);
850
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
851
- } catch {
852
- return {};
853
- }
854
- }
855
-
856
856
  // src/parsers/outbound-call-parser.ts
857
857
  function lineOf3(text, idx) {
858
858
  return text.slice(0, idx).split("\n").length;
@@ -950,6 +950,7 @@ function nodeContains(parent, child) {
950
950
  }
951
951
  function declarationScope(node) {
952
952
  if (ts4.isParameter(node)) return node.parent;
953
+ if (ts4.isCatchClause(node.parent) && node.parent.variableDeclaration === node) return node.parent;
953
954
  const list = node.parent;
954
955
  const blockScoped = (list.flags & (ts4.NodeFlags.Const | ts4.NodeFlags.Let)) !== 0;
955
956
  let current = list.parent;
@@ -957,26 +958,23 @@ function declarationScope(node) {
957
958
  while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
958
959
  return current;
959
960
  }
960
- while (current.parent && !ts4.isBlock(current) && !ts4.isSourceFile(current) && !ts4.isModuleBlock(current) && !ts4.isCaseBlock(current) && !ts4.isCatchClause(current) && !ts4.isForStatement(current) && !ts4.isForInStatement(current) && !ts4.isForOfStatement(current) && !isFunctionLikeScope(current)) current = current.parent;
961
+ while (current.parent && !ts4.isBlock(current) && !ts4.isSourceFile(current) && !ts4.isModuleBlock(current) && !ts4.isCaseBlock(current) && !isLoopInitializerScope(node, current) && !isFunctionLikeScope(current)) current = current.parent;
961
962
  return current;
962
963
  }
963
- function declarationScopedAncestor(node) {
964
- let current = node.parent;
965
- while (current) {
966
- if (ts4.isCatchClause(current) || ts4.isForStatement(current) || ts4.isForInStatement(current) || ts4.isForOfStatement(current)) return current;
967
- if (ts4.isSourceFile(current) || ts4.isFunctionLike(current)) return void 0;
968
- current = current.parent;
969
- }
970
- return void 0;
964
+ function isLoopInitializerScope(declaration, scope) {
965
+ const list = declaration.parent;
966
+ return ts4.isForStatement(scope) && scope.initializer === list || (ts4.isForInStatement(scope) || ts4.isForOfStatement(scope)) && scope.initializer === list;
967
+ }
968
+ function catchBindingScope(declaration) {
969
+ if (ts4.isParameter(declaration)) return void 0;
970
+ return ts4.isCatchClause(declaration.parent) && declaration.parent.variableDeclaration === declaration ? declaration.parent : void 0;
971
971
  }
972
972
  function isAccessibleDeclaration(declaration, use) {
973
973
  const source = use.getSourceFile();
974
974
  if (declaration.name.getStart(source) >= use.getStart(source)) return false;
975
- const scopedAncestor = declarationScopedAncestor(declaration);
976
- if (scopedAncestor && ts4.isCatchClause(scopedAncestor)) return nodeContains(scopedAncestor.block, use);
977
- if (scopedAncestor && (ts4.isForStatement(scopedAncestor) || ts4.isForInStatement(scopedAncestor) || ts4.isForOfStatement(scopedAncestor))) return nodeContains(scopedAncestor.statement, use);
975
+ const catchScope = catchBindingScope(declaration);
976
+ if (catchScope) return nodeContains(catchScope.block, use);
978
977
  const scope = declarationScope(declaration);
979
- if (ts4.isCatchClause(scope)) return nodeContains(scope.block, use);
980
978
  if (ts4.isForStatement(scope) || ts4.isForInStatement(scope) || ts4.isForOfStatement(scope)) return nodeContains(scope.statement, use);
981
979
  return ts4.isSourceFile(scope) || nodeContains(scope, use);
982
980
  }
@@ -1084,6 +1082,11 @@ function httpMethodFromObject(object, use) {
1084
1082
  const text = resolveExpression(propertyInitializer(object, "method"), use, "literal").value;
1085
1083
  return text ? stripQuotes(text).toUpperCase() : void 0;
1086
1084
  }
1085
+ var supportedHttpMethods = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
1086
+ function safeOperationName(value) {
1087
+ if (!value || !/^[A-Za-z_$][\w$]*(?:[./][A-Za-z_$][\w$]*)*$/.test(value)) return void 0;
1088
+ return operationPathFromStatic(value);
1089
+ }
1087
1090
  function hasTemplatePlaceholder(value) {
1088
1091
  return /\$\{|%7B|%7D/i.test(value);
1089
1092
  }
@@ -1201,6 +1204,14 @@ function collectWrapperSpecs(source) {
1201
1204
  if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName, methodLiteral, start: node.getStart(source), end: node.getEnd() });
1202
1205
  }
1203
1206
  }
1207
+ if (ts4.isCallExpression(node) && ts4.isIdentifier(node.expression) && specs.has(node.expression.text)) {
1208
+ const nested = specs.get(node.expression.text);
1209
+ const pathArg = nested ? node.arguments[nested.pathIndex] : void 0;
1210
+ const clientArg = nested?.clientIndex === void 0 ? void 0 : node.arguments[nested.clientIndex];
1211
+ const pathName = pathArg && ts4.isIdentifier(pathArg) ? pathArg.text : void 0;
1212
+ const clientName = clientArg && ts4.isIdentifier(clientArg) ? clientArg.text : nested?.clientName;
1213
+ if (nested && pathName && clientName) sends.push({ client: clientName, path: pathName, method: nested.methodName, methodLiteral: nested.methodLiteral, nestedWrapperFunction: node.expression.text, start: node.getStart(source), end: node.getEnd() });
1214
+ }
1204
1215
  ts4.forEachChild(node, visit);
1205
1216
  };
1206
1217
  visit(fn);
@@ -1210,7 +1221,7 @@ function collectWrapperSpecs(source) {
1210
1221
  const pathIndex = params.indexOf(found.path);
1211
1222
  const methodIndex = found.method ? params.indexOf(found.method) : -1;
1212
1223
  const capturesKnownClient = serviceVariables.has(found.client) || /^(srv|service|serviceClient|client|.*Client)$/.test(found.client);
1213
- if (pathIndex >= 0 && (clientIndex >= 0 || capturesKnownClient)) specs.set(name, { clientIndex: clientIndex >= 0 ? clientIndex : void 0, clientName: clientIndex >= 0 ? void 0 : found.client, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodName: found.method, methodLiteral: found.methodLiteral, definitionLine: lineOf3(source.text, fn.getStart(source)), internalStart: found.start, internalEnd: found.end });
1224
+ if (pathIndex >= 0 && (clientIndex >= 0 || capturesKnownClient)) specs.set(name, { clientIndex: clientIndex >= 0 ? clientIndex : void 0, clientName: clientIndex >= 0 ? void 0 : found.client, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodName: found.method, methodLiteral: found.methodLiteral, nestedWrapperFunction: found.nestedWrapperFunction, definitionLine: lineOf3(source.text, fn.getStart(source)), internalStart: found.start, internalEnd: found.end });
1214
1225
  };
1215
1226
  const visitTop = (node) => {
1216
1227
  if (ts4.isFunctionDeclaration(node) && node.name) scanFunction(node.name.text, node);
@@ -1263,16 +1274,18 @@ function classifyOutboundCallsInSource(source, filePath) {
1263
1274
  } else {
1264
1275
  const receiver = receiverName(expr.expression);
1265
1276
  const rootReceiver = rootReceiverName(expr.expression);
1266
- const method = resolveExpression(node.arguments[0], node, "literal").value?.toUpperCase();
1277
+ const firstArg2 = resolveExpression(node.arguments[0], node, "literal");
1278
+ const method = firstArg2.value?.toUpperCase();
1267
1279
  const pathArg = node.arguments[1];
1268
- const supported = method && ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"].includes(method);
1280
+ const supported = method && supportedHttpMethods.has(method);
1269
1281
  if (receiver && supported && serviceVariables.has(rootReceiver ?? receiver)) {
1270
1282
  const resolvedPath = staticPathExpression(pathArg, node);
1271
1283
  const operationPathExpr = operationPathFromStatic(resolvedPath.text);
1272
1284
  const intent = classifyODataPathIntent(operationPathExpr, method);
1273
1285
  add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.8 : 0.45, unresolvedReason: operationPathExpr ? void 0 : "dynamic_operation_path_identifier" }, { receiver, rootReceiver, classifier: "service_client_send_method_path", rawPathExpression: resolvedPath.rawExpression, literalPathSource: resolvedPath.text ? resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : resolvedPath.sourceKind : void 0, odataPathIntent: operationPathExpr ? intent : void 0, parserWarning: operationPathExpr ? void 0 : "dynamic_operation_path_identifier" });
1274
1286
  } else if (receiver && serviceVariables.has(rootReceiver ?? receiver)) {
1275
- add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.35, unresolvedReason: "unsupported_cap_send_signature" }, { receiver, rootReceiver, classifier: "service_client_send_unsupported_signature" });
1287
+ const operationPathExpr = safeOperationName(firstArg2.value);
1288
+ add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.75 : 0.35, unresolvedReason: operationPathExpr ? void 0 : "unsupported_cap_send_signature" }, { receiver, rootReceiver, classifier: operationPathExpr ? "service_client_send_operation_event" : "service_client_send_unsupported_signature", rawOperationExpression: firstArg2.rawExpression, literalOperationSource: firstArg2.value ? firstArg2.sourceKind : void 0, parserWarning: operationPathExpr ? void 0 : "unsupported_cap_send_signature" });
1276
1289
  }
1277
1290
  }
1278
1291
  } else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts4.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
@@ -1288,7 +1301,7 @@ function classifyOutboundCallsInSource(source, filePath) {
1288
1301
  const operationPathExpr = operationPathFromStatic(resolvedPath.text);
1289
1302
  const normalizedOperationPath = operationPathExpr ? classifyODataPathIntent(operationPathExpr, method).topLevelOperationName : void 0;
1290
1303
  if (spec && receiver && operationPathExpr) {
1291
- add(node, { callType: "remote_action", serviceVariableName: receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver, classifier: resolvedPath.sourceKind === "literal" ? "higher_order_wrapper_literal_path" : "higher_order_wrapper_static_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf3(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, normalizedOperationPath, literalPathSource: resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : `wrapper_call_${resolvedPath.sourceKind}`, literalCallerArgumentDetected: true });
1304
+ add(node, { callType: "remote_action", serviceVariableName: receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver, classifier: resolvedPath.sourceKind === "literal" ? "higher_order_wrapper_literal_path" : "higher_order_wrapper_static_path", wrapperFunction: wrapperName, nestedWrapperFunction: spec.nestedWrapperFunction, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf3(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, normalizedOperationPath, literalPathSource: resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : `wrapper_call_${resolvedPath.sourceKind}`, literalCallerArgumentDetected: true });
1292
1305
  } else if (spec && receiver) {
1293
1306
  add(node, { callType: "remote_action", serviceVariableName: receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason: "dynamic_operation_path_identifier" }, { receiver, classifier: "higher_order_wrapper_dynamic_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf3(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, parserWarning: "dynamic_operation_path_identifier" });
1294
1307
  }
@@ -1383,6 +1396,10 @@ function serviceOperationCall(node, aliases) {
1383
1396
  }
1384
1397
 
1385
1398
  // src/parsers/service-binding-parser.ts
1399
+ import path9 from "path";
1400
+ import ts6 from "typescript";
1401
+
1402
+ // src/parsers/service-binding-parser-helpers.ts
1386
1403
  import fs7 from "fs/promises";
1387
1404
  import path8 from "path";
1388
1405
  import ts5 from "typescript";
@@ -1391,10 +1408,8 @@ function lineOf4(sf, node) {
1391
1408
  }
1392
1409
  function stringValue(node) {
1393
1410
  if (!node) return void 0;
1394
- if (ts5.isStringLiteralLike(node) || ts5.isNoSubstitutionTemplateLiteral(node))
1395
- return node.text;
1396
- if (ts5.isTemplateExpression(node))
1397
- return node.getText().replace(/^`|`$/g, "");
1411
+ if (ts5.isStringLiteralLike(node) || ts5.isNoSubstitutionTemplateLiteral(node)) return node.text;
1412
+ if (ts5.isTemplateExpression(node)) return node.getText().replace(/^`|`$/g, "");
1398
1413
  return node.getText();
1399
1414
  }
1400
1415
  function placeholders2(value) {
@@ -1402,57 +1417,36 @@ function placeholders2(value) {
1402
1417
  }
1403
1418
  function connectFactFromCall(call) {
1404
1419
  const expr = call.expression;
1405
- if (!ts5.isPropertyAccessExpression(expr) || expr.name.text !== "to")
1406
- return void 0;
1420
+ if (!ts5.isPropertyAccessExpression(expr) || expr.name.text !== "to") return void 0;
1407
1421
  const inner = expr.expression;
1408
- if (!ts5.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds")
1409
- return void 0;
1422
+ if (!ts5.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds") return void 0;
1410
1423
  const first = call.arguments[0];
1411
1424
  if (!first) return void 0;
1412
1425
  const second = call.arguments[1];
1413
1426
  const objectArg = ts5.isObjectLiteralExpression(first) ? first : second && ts5.isObjectLiteralExpression(second) ? second : void 0;
1414
1427
  let alias;
1415
1428
  let aliasExpr;
1416
- if (ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first))
1417
- alias = first.text;
1418
- else if (!ts5.isObjectLiteralExpression(first))
1419
- aliasExpr = stringValue(first);
1420
- if ((ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first)) && !objectArg)
1421
- return { alias: first.text, isDynamic: false, placeholders: [] };
1422
- if (!objectArg && aliasExpr)
1423
- return {
1424
- aliasExpr,
1425
- isDynamic: true,
1426
- placeholders: placeholders2(aliasExpr)
1427
- };
1428
- let destinationExpr;
1429
- let servicePathExpr;
1429
+ if (ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first)) alias = first.text;
1430
+ else if (!ts5.isObjectLiteralExpression(first)) aliasExpr = stringValue(first);
1431
+ if ((ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first)) && !objectArg) return { alias: first.text, isDynamic: false, placeholders: [] };
1432
+ if (!objectArg && aliasExpr) return { aliasExpr, isDynamic: true, placeholders: placeholders2(aliasExpr) };
1433
+ const expressions = objectArg ? objectExpressions(objectArg) : {};
1434
+ const ph = [...placeholders2(aliasExpr ?? alias), ...placeholders2(expressions.destinationExpr), ...placeholders2(expressions.servicePathExpr)];
1435
+ return { alias, aliasExpr, ...expressions, isDynamic: ph.length > 0 || !expressions.destinationExpr && !expressions.servicePathExpr, placeholders: ph };
1436
+ }
1437
+ function objectExpressions(objectArg) {
1438
+ const out = {};
1430
1439
  function visitObject(obj) {
1431
1440
  for (const prop of obj.properties) {
1432
1441
  if (!ts5.isPropertyAssignment(prop)) continue;
1433
1442
  const name = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1434
- if (name === "destination")
1435
- destinationExpr = stringValue(prop.initializer);
1436
- if (name === "path" || name === "servicePath")
1437
- servicePathExpr = stringValue(prop.initializer);
1438
- if (ts5.isObjectLiteralExpression(prop.initializer))
1439
- visitObject(prop.initializer);
1440
- }
1441
- }
1442
- if (objectArg) visitObject(objectArg);
1443
- const ph = [
1444
- ...placeholders2(aliasExpr ?? alias),
1445
- ...placeholders2(destinationExpr),
1446
- ...placeholders2(servicePathExpr)
1447
- ];
1448
- return {
1449
- alias,
1450
- aliasExpr,
1451
- destinationExpr,
1452
- servicePathExpr,
1453
- isDynamic: ph.length > 0 || !destinationExpr && !servicePathExpr,
1454
- placeholders: ph
1455
- };
1443
+ if (name === "destination") out.destinationExpr = stringValue(prop.initializer);
1444
+ if (name === "path" || name === "servicePath") out.servicePathExpr = stringValue(prop.initializer);
1445
+ if (ts5.isObjectLiteralExpression(prop.initializer)) visitObject(prop.initializer);
1446
+ }
1447
+ }
1448
+ visitObject(objectArg);
1449
+ return out;
1456
1450
  }
1457
1451
  function unwrapCall(expr) {
1458
1452
  if (ts5.isAwaitExpression(expr)) return unwrapCall(expr.expression);
@@ -1473,12 +1467,10 @@ function transactionReceiverName(expr) {
1473
1467
  const call = unwrapCall(expr);
1474
1468
  if (call && ts5.isPropertyAccessExpression(call.expression) && ["tx", "transaction"].includes(call.expression.name.text) && ts5.isIdentifier(call.expression.expression)) return call.expression.expression.text;
1475
1469
  const unwrapped = unwrapIdentityExpression(expr);
1476
- if (ts5.isConditionalExpression(unwrapped)) {
1477
- const left = transactionReceiverName(unwrapped.whenTrue);
1478
- const right = transactionReceiverName(unwrapped.whenFalse);
1479
- return left && left === right ? left : void 0;
1480
- }
1481
- return void 0;
1470
+ if (!ts5.isConditionalExpression(unwrapped)) return void 0;
1471
+ const left = transactionReceiverName(unwrapped.whenTrue);
1472
+ const right = transactionReceiverName(unwrapped.whenFalse);
1473
+ return left && left === right ? left : void 0;
1482
1474
  }
1483
1475
  function findConnectInExpression(expr) {
1484
1476
  const direct = unwrapCall(expr);
@@ -1498,13 +1490,7 @@ function findConnectInExpression(expr) {
1498
1490
  async function readSource(abs) {
1499
1491
  try {
1500
1492
  const text = await fs7.readFile(abs, "utf8");
1501
- return ts5.createSourceFile(
1502
- abs,
1503
- text,
1504
- ts5.ScriptTarget.Latest,
1505
- true,
1506
- ts5.ScriptKind.TS
1507
- );
1493
+ return ts5.createSourceFile(abs, text, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TS);
1508
1494
  } catch {
1509
1495
  return void 0;
1510
1496
  }
@@ -1514,56 +1500,34 @@ async function resolveImport(repoPath, fromFile, spec) {
1514
1500
  const rawBase = path8.resolve(repoPath, path8.dirname(fromFile), spec);
1515
1501
  const parsed = path8.parse(rawBase);
1516
1502
  const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ? path8.join(parsed.dir, parsed.name) : rawBase;
1517
- for (const candidate of [
1518
- base,
1519
- `${base}.ts`,
1520
- `${base}.js`,
1521
- path8.join(base, "index.ts"),
1522
- path8.join(base, "index.js")
1523
- ]) {
1524
- try {
1525
- const st = await fs7.stat(candidate);
1526
- if (st.isFile()) return normalizePath(path8.relative(repoPath, candidate));
1527
- } catch {
1528
- }
1503
+ for (const candidate of [base, `${base}.ts`, `${base}.js`, path8.join(base, "index.ts"), path8.join(base, "index.js")]) {
1504
+ const stat = await fs7.stat(candidate).catch(() => void 0);
1505
+ if (stat?.isFile()) return normalizePath(path8.relative(repoPath, candidate));
1529
1506
  }
1530
1507
  return void 0;
1531
1508
  }
1532
1509
  async function importsFor(repoPath, filePath, sf) {
1533
1510
  const imports = [];
1534
1511
  for (const stmt of sf.statements) {
1535
- if (!ts5.isImportDeclaration(stmt) || !ts5.isStringLiteralLike(stmt.moduleSpecifier))
1536
- continue;
1537
- const sourceFile = await resolveImport(
1538
- repoPath,
1539
- filePath,
1540
- stmt.moduleSpecifier.text
1541
- );
1512
+ if (!ts5.isImportDeclaration(stmt) || !ts5.isStringLiteralLike(stmt.moduleSpecifier)) continue;
1513
+ const sourceFile = await resolveImport(repoPath, filePath, stmt.moduleSpecifier.text);
1542
1514
  const clause = stmt.importClause;
1543
1515
  if (!clause) continue;
1544
- if (clause.name)
1545
- imports.push({
1546
- localName: clause.name.text,
1547
- exportedName: "default",
1548
- sourceFile
1549
- });
1516
+ if (clause.name) imports.push({ localName: clause.name.text, exportedName: "default", sourceFile });
1550
1517
  const bindings = clause.namedBindings;
1551
1518
  if (bindings && ts5.isNamedImports(bindings))
1552
- for (const el of bindings.elements)
1553
- imports.push({
1554
- localName: el.name.text,
1555
- exportedName: el.propertyName?.text ?? el.name.text,
1556
- sourceFile
1557
- });
1519
+ for (const el of bindings.elements) imports.push({ localName: el.name.text, exportedName: el.propertyName?.text ?? el.name.text, sourceFile });
1558
1520
  }
1559
1521
  return imports;
1560
1522
  }
1523
+
1524
+ // src/parsers/service-binding-parser.ts
1561
1525
  function collectLocalBindingFacts(fn) {
1562
1526
  const bindings = /* @__PURE__ */ new Map();
1563
1527
  function visit(node) {
1564
- if (node !== fn && (ts5.isFunctionDeclaration(node) || ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)))
1528
+ if (node !== fn && (ts6.isFunctionDeclaration(node) || ts6.isArrowFunction(node) || ts6.isFunctionExpression(node)))
1565
1529
  return;
1566
- if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
1530
+ if (ts6.isVariableDeclaration(node) && ts6.isIdentifier(node.name) && node.initializer) {
1567
1531
  const fact = findConnectInExpression(node.initializer);
1568
1532
  if (fact) bindings.set(node.name.text, fact);
1569
1533
  const sourceName = transactionReceiverName(node.initializer);
@@ -1572,7 +1536,7 @@ function collectLocalBindingFacts(fn) {
1572
1536
  if (source) bindings.set(node.name.text, { ...source, helperChain: [...source.helperChain ?? [], { aliasOf: sourceName, callerVariable: node.name.text, aliasKind: "transaction", transactionAliasSource: sourceName }] });
1573
1537
  }
1574
1538
  }
1575
- ts5.forEachChild(node, visit);
1539
+ ts6.forEachChild(node, visit);
1576
1540
  }
1577
1541
  visit(fn);
1578
1542
  return bindings;
@@ -1581,18 +1545,18 @@ function collectReturnedObjectBindings(fn) {
1581
1545
  const bindings = collectLocalBindingFacts(fn);
1582
1546
  const returns = /* @__PURE__ */ new Map();
1583
1547
  function visit(node) {
1584
- if (node !== fn && (ts5.isFunctionDeclaration(node) || ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)))
1548
+ if (node !== fn && (ts6.isFunctionDeclaration(node) || ts6.isArrowFunction(node) || ts6.isFunctionExpression(node)))
1585
1549
  return;
1586
- if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
1550
+ if (ts6.isReturnStatement(node) && node.expression && ts6.isObjectLiteralExpression(node.expression)) {
1587
1551
  for (const prop of node.expression.properties) {
1588
- if (ts5.isShorthandPropertyAssignment(prop)) returns.set(prop.name.text, prop.name.text);
1589
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
1590
- const propertyName = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1552
+ if (ts6.isShorthandPropertyAssignment(prop)) returns.set(prop.name.text, prop.name.text);
1553
+ if (ts6.isPropertyAssignment(prop) && ts6.isIdentifier(prop.initializer)) {
1554
+ const propertyName = ts6.isIdentifier(prop.name) || ts6.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1591
1555
  if (propertyName) returns.set(propertyName, prop.initializer.text);
1592
1556
  }
1593
1557
  }
1594
1558
  }
1595
- ts5.forEachChild(node, visit);
1559
+ ts6.forEachChild(node, visit);
1596
1560
  }
1597
1561
  visit(fn);
1598
1562
  const out = /* @__PURE__ */ new Map();
@@ -1604,65 +1568,65 @@ function collectReturnedObjectBindings(fn) {
1604
1568
  }
1605
1569
  function functionLikeInitializer(expr) {
1606
1570
  if (!expr) return void 0;
1607
- if (ts5.isArrowFunction(expr) || ts5.isFunctionExpression(expr)) return expr;
1571
+ if (ts6.isArrowFunction(expr) || ts6.isFunctionExpression(expr)) return expr;
1608
1572
  return void 0;
1609
1573
  }
1610
1574
  function directReturnConnectFact(fn) {
1611
1575
  const localBindings = collectLocalBindingFacts(fn);
1612
1576
  let returned;
1613
1577
  function visit(node) {
1614
- if (node !== fn && (ts5.isFunctionDeclaration(node) || ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)))
1578
+ if (node !== fn && (ts6.isFunctionDeclaration(node) || ts6.isArrowFunction(node) || ts6.isFunctionExpression(node)))
1615
1579
  return;
1616
- if (!returned && ts5.isReturnStatement(node) && node.expression)
1580
+ if (!returned && ts6.isReturnStatement(node) && node.expression)
1617
1581
  returned = node.expression;
1618
- if (!returned) ts5.forEachChild(node, visit);
1582
+ if (!returned) ts6.forEachChild(node, visit);
1619
1583
  }
1620
1584
  visit(fn);
1621
1585
  if (!returned) return void 0;
1622
- if (ts5.isIdentifier(returned)) return localBindings.get(returned.text);
1586
+ if (ts6.isIdentifier(returned)) return localBindings.get(returned.text);
1623
1587
  return findConnectInExpression(returned);
1624
1588
  }
1625
1589
  function directConnectFactFromFunctionLike(fn) {
1626
- if (ts5.isArrowFunction(fn) && fn.body && !ts5.isBlock(fn.body))
1590
+ if (ts6.isArrowFunction(fn) && fn.body && !ts6.isBlock(fn.body))
1627
1591
  return findConnectInExpression(fn.body);
1628
1592
  return directReturnConnectFact(fn);
1629
1593
  }
1630
1594
  function exportedLocalNames(sf) {
1631
1595
  const exports = /* @__PURE__ */ new Map();
1632
1596
  for (const stmt of sf.statements) {
1633
- const direct = ts5.canHaveModifiers(stmt) ? ts5.getModifiers(stmt)?.some(
1634
- (m) => m.kind === ts5.SyntaxKind.ExportKeyword
1597
+ const direct = ts6.canHaveModifiers(stmt) ? ts6.getModifiers(stmt)?.some(
1598
+ (m) => m.kind === ts6.SyntaxKind.ExportKeyword
1635
1599
  ) ?? false : false;
1636
- if (direct && ts5.isFunctionDeclaration(stmt) && stmt.name)
1600
+ if (direct && ts6.isFunctionDeclaration(stmt) && stmt.name)
1637
1601
  exports.set(stmt.name.text, stmt.name.text);
1638
- if (direct && ts5.isVariableStatement(stmt)) {
1602
+ if (direct && ts6.isVariableStatement(stmt)) {
1639
1603
  for (const decl of stmt.declarationList.declarations)
1640
- if (ts5.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
1604
+ if (ts6.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
1641
1605
  }
1642
- if (!ts5.isExportDeclaration(stmt) || !stmt.exportClause) continue;
1643
- if (!ts5.isNamedExports(stmt.exportClause)) continue;
1606
+ if (!ts6.isExportDeclaration(stmt) || !stmt.exportClause) continue;
1607
+ if (!ts6.isNamedExports(stmt.exportClause)) continue;
1644
1608
  for (const el of stmt.exportClause.elements)
1645
1609
  exports.set(el.name.text, el.propertyName?.text ?? el.name.text);
1646
1610
  }
1647
1611
  return exports;
1648
1612
  }
1649
1613
  async function helperBindings(repoPath, filePath) {
1650
- const sf = await readSource(path8.join(repoPath, filePath));
1614
+ const sf = await readSource(path9.join(repoPath, filePath));
1651
1615
  if (!sf) return [];
1652
1616
  const sourceFileAst = sf;
1653
1617
  const out = [];
1654
1618
  const exportedLocals = exportedLocalNames(sf);
1655
1619
  const factsByLocal = /* @__PURE__ */ new Map();
1656
1620
  for (const stmt of sf.statements) {
1657
- if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
1621
+ if (ts6.isFunctionDeclaration(stmt) && stmt.name) {
1658
1622
  const fact = directConnectFactFromFunctionLike(stmt);
1659
1623
  if (fact) factsByLocal.set(stmt.name.text, { ...fact, sourceLine: lineOf4(sf, stmt) });
1660
1624
  for (const [returnedProperty, objectFact] of collectReturnedObjectBindings(stmt))
1661
1625
  factsByLocal.set(`${stmt.name.text}#${returnedProperty}`, { ...objectFact, returnedProperty, sourceLine: lineOf4(sf, stmt) });
1662
1626
  }
1663
- if (ts5.isVariableStatement(stmt))
1627
+ if (ts6.isVariableStatement(stmt))
1664
1628
  for (const decl of stmt.declarationList.declarations) {
1665
- if (!ts5.isIdentifier(decl.name) || !decl.initializer) continue;
1629
+ if (!ts6.isIdentifier(decl.name) || !decl.initializer) continue;
1666
1630
  const helper = functionLikeInitializer(decl.initializer);
1667
1631
  if (helper) {
1668
1632
  const directReturn = directConnectFactFromFunctionLike(helper);
@@ -1708,7 +1672,7 @@ async function helperBindings(repoPath, filePath) {
1708
1672
  return out;
1709
1673
  }
1710
1674
  async function parseServiceBindings(repoPath, filePath) {
1711
- const sf = await readSource(path8.join(repoPath, filePath));
1675
+ const sf = await readSource(path9.join(repoPath, filePath));
1712
1676
  if (!sf) return [];
1713
1677
  const sourceFileAst = sf;
1714
1678
  const out = [];
@@ -1717,8 +1681,9 @@ async function parseServiceBindings(repoPath, filePath) {
1717
1681
  const classHelpers = collectClassHelpers(sourceFileAst);
1718
1682
  const localObjectHelpers = /* @__PURE__ */ new Map();
1719
1683
  const localDirectHelpers = /* @__PURE__ */ new Map();
1684
+ const objectHelperVariables = /* @__PURE__ */ new Map();
1720
1685
  for (const stmt of sourceFileAst.statements) {
1721
- if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
1686
+ if (ts6.isFunctionDeclaration(stmt) && stmt.name) {
1722
1687
  const directFact = directConnectFactFromFunctionLike(stmt);
1723
1688
  if (directFact) localDirectHelpers.set(stmt.name.text, { ...directFact, exportedName: stmt.name.text, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, stmt) });
1724
1689
  const rows2 = [];
@@ -1726,9 +1691,9 @@ async function parseServiceBindings(repoPath, filePath) {
1726
1691
  rows2.push({ ...fact, exportedName: stmt.name.text, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, stmt) });
1727
1692
  if (rows2.length > 0) localObjectHelpers.set(stmt.name.text, rows2);
1728
1693
  }
1729
- if (ts5.isVariableStatement(stmt)) {
1694
+ if (ts6.isVariableStatement(stmt)) {
1730
1695
  for (const decl of stmt.declarationList.declarations) {
1731
- if (!ts5.isIdentifier(decl.name)) continue;
1696
+ if (!ts6.isIdentifier(decl.name)) continue;
1732
1697
  const helper = functionLikeInitializer(decl.initializer);
1733
1698
  if (!helper) continue;
1734
1699
  const directFact = directConnectFactFromFunctionLike(helper);
@@ -1777,9 +1742,9 @@ async function parseServiceBindings(repoPath, filePath) {
1777
1742
  });
1778
1743
  }
1779
1744
  function recordIdentityAlias(decl) {
1780
- if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
1745
+ if (!ts6.isIdentifier(decl.name) || !decl.initializer) return;
1781
1746
  const unwrapped = unwrapIdentityExpression(decl.initializer);
1782
- if (!ts5.isIdentifier(unwrapped)) return;
1747
+ if (!ts6.isIdentifier(unwrapped)) return;
1783
1748
  cloneAliasBinding(decl.name.text, unwrapped.text, "identity", decl);
1784
1749
  }
1785
1750
  async function recordBindingFromExpression(targetName, expr, node, aliasKind) {
@@ -1794,7 +1759,7 @@ async function parseServiceBindings(repoPath, filePath) {
1794
1759
  sourceLine: lineOf4(sourceFileAst, node),
1795
1760
  helperChain: aliasKind === "assignment" ? [{ callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind, scopeRule: "same-file-source-order" }] : void 0
1796
1761
  });
1797
- else if (ts5.isIdentifier(call.expression)) {
1762
+ else if (ts6.isIdentifier(call.expression)) {
1798
1763
  const localDirect = localDirectHelpers.get(call.expression.text);
1799
1764
  const resolved2 = localDirect ? { helper: localDirect, imp: void 0 } : await importedHelper(call.expression.text);
1800
1765
  if (resolved2)
@@ -1824,24 +1789,64 @@ async function parseServiceBindings(repoPath, filePath) {
1824
1789
  }
1825
1790
  }
1826
1791
  async function recordVariable(decl) {
1827
- if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
1792
+ if (!ts6.isIdentifier(decl.name) || !decl.initializer) return;
1828
1793
  await recordBindingFromExpression(decl.name.text, decl.initializer, decl, "declaration");
1829
1794
  }
1830
1795
  async function helpersForCall(call) {
1831
- if (!ts5.isIdentifier(call.expression)) return [];
1796
+ if (!ts6.isIdentifier(call.expression)) return [];
1832
1797
  const local = localObjectHelpers.get(call.expression.text) ?? [];
1833
1798
  const imported = await importedHelpers(call.expression.text);
1834
1799
  return [...local.map((helper) => ({ helper })), ...imported];
1835
1800
  }
1801
+ async function rememberObjectHelperVariable(decl) {
1802
+ if (!ts6.isIdentifier(decl.name) || !decl.initializer) return;
1803
+ const call = unwrapCall(decl.initializer);
1804
+ if (!call) return;
1805
+ const helpers = (await helpersForCall(call)).filter((row) => row.helper.returnedProperty);
1806
+ if (helpers.length > 0) objectHelperVariables.set(decl.name.text, helpers);
1807
+ }
1808
+ function recordObjectPropertyBinding(targetName, expr, node) {
1809
+ const unwrapped = unwrapIdentityExpression(expr);
1810
+ if (!ts6.isPropertyAccessExpression(unwrapped) || !ts6.isIdentifier(unwrapped.expression)) return false;
1811
+ const helpers = objectHelperVariables.get(unwrapped.expression.text) ?? [];
1812
+ const matches = helpers.filter((row) => row.helper.returnedProperty === unwrapped.name.text);
1813
+ if (matches.length !== 1) return false;
1814
+ const resolved2 = matches[0];
1815
+ out.push({
1816
+ variableName: targetName,
1817
+ alias: resolved2.helper.alias,
1818
+ aliasExpr: resolved2.helper.aliasExpr,
1819
+ destinationExpr: resolved2.helper.destinationExpr,
1820
+ servicePathExpr: resolved2.helper.servicePathExpr,
1821
+ isDynamic: resolved2.helper.isDynamic,
1822
+ placeholders: resolved2.helper.placeholders,
1823
+ sourceFile: normalizePath(filePath),
1824
+ sourceLine: lineOf4(sourceFileAst, node),
1825
+ helperChain: [
1826
+ ...resolved2.helper.helperChain ?? [],
1827
+ {
1828
+ callerVariable: targetName,
1829
+ sourceVariable: unwrapped.expression.text,
1830
+ returnedProperty: unwrapped.name.text,
1831
+ assignedFromProperty: unwrapped.getText(sourceFileAst),
1832
+ importSource: resolved2.imp?.sourceFile,
1833
+ exportedSymbol: resolved2.imp?.exportedName,
1834
+ helperSourceFile: resolved2.helper.sourceFile,
1835
+ helperSourceLine: resolved2.helper.sourceLine
1836
+ }
1837
+ ]
1838
+ });
1839
+ return true;
1840
+ }
1836
1841
  async function recordDestructuredHelper(decl) {
1837
- if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
1842
+ if (!ts6.isObjectBindingPattern(decl.name) || !decl.initializer) return;
1838
1843
  const call = unwrapCall(decl.initializer);
1839
1844
  if (!call) return;
1840
1845
  const helpers = await helpersForCall(call);
1841
1846
  if (helpers.length === 0) return;
1842
1847
  for (const el of decl.name.elements) {
1843
- if (!ts5.isIdentifier(el.name)) continue;
1844
- const propertyName = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1848
+ if (!ts6.isIdentifier(el.name)) continue;
1849
+ const propertyName = el.propertyName && ts6.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1845
1850
  const matches = helpers.filter((row) => row.helper.returnedProperty === propertyName);
1846
1851
  if (matches.length !== 1) continue;
1847
1852
  const resolved2 = matches[0];
@@ -1867,12 +1872,12 @@ async function parseServiceBindings(repoPath, filePath) {
1867
1872
  for (const prop of pattern.properties) {
1868
1873
  let propertyName;
1869
1874
  let targetName;
1870
- if (ts5.isShorthandPropertyAssignment(prop)) {
1875
+ if (ts6.isShorthandPropertyAssignment(prop)) {
1871
1876
  propertyName = prop.name.text;
1872
1877
  targetName = prop.name.text;
1873
- } else if (ts5.isPropertyAssignment(prop)) {
1874
- propertyName = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1875
- targetName = ts5.isIdentifier(prop.initializer) ? prop.initializer.text : void 0;
1878
+ } else if (ts6.isPropertyAssignment(prop)) {
1879
+ propertyName = ts6.isIdentifier(prop.name) || ts6.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1880
+ targetName = ts6.isIdentifier(prop.initializer) ? prop.initializer.text : void 0;
1876
1881
  }
1877
1882
  if (!propertyName || !targetName) continue;
1878
1883
  const matches = helpers.filter((row) => row.helper.returnedProperty === propertyName);
@@ -1893,14 +1898,14 @@ async function parseServiceBindings(repoPath, filePath) {
1893
1898
  }
1894
1899
  }
1895
1900
  function recordDestructuredClassHelper(decl) {
1896
- if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
1901
+ if (!ts6.isObjectBindingPattern(decl.name) || !decl.initializer) return;
1897
1902
  const call = unwrapCall(decl.initializer);
1898
- if (!call || !ts5.isPropertyAccessExpression(call.expression)) return;
1903
+ if (!call || !ts6.isPropertyAccessExpression(call.expression)) return;
1899
1904
  const target = call.expression;
1900
- if (target.expression.kind !== ts5.SyntaxKind.ThisKeyword) return;
1905
+ if (target.expression.kind !== ts6.SyntaxKind.ThisKeyword) return;
1901
1906
  for (const el of decl.name.elements) {
1902
- if (!ts5.isIdentifier(el.name)) continue;
1903
- const propertyName = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1907
+ if (!ts6.isIdentifier(el.name)) continue;
1908
+ const propertyName = el.propertyName && ts6.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1904
1909
  const helper = classHelpers.find(
1905
1910
  (h) => h.helperName === target.name.text && h.propertyName === propertyName
1906
1911
  );
@@ -1926,14 +1931,14 @@ async function parseServiceBindings(repoPath, filePath) {
1926
1931
  }
1927
1932
  function arrayElementsFromExpression(expr) {
1928
1933
  const unwrapped = unwrapIdentityExpression(expr);
1929
- if (ts5.isArrayLiteralExpression(unwrapped)) return { elements: unwrapped.elements, promiseAll: false };
1934
+ if (ts6.isArrayLiteralExpression(unwrapped)) return { elements: unwrapped.elements, promiseAll: false };
1930
1935
  const call = unwrapCall(expr);
1931
1936
  if (!call) return void 0;
1932
- if (!ts5.isPropertyAccessExpression(call.expression) || call.expression.name.text !== "all" || call.expression.expression.getText(sourceFileAst) !== "Promise") return void 0;
1937
+ if (!ts6.isPropertyAccessExpression(call.expression) || call.expression.name.text !== "all" || call.expression.expression.getText(sourceFileAst) !== "Promise") return void 0;
1933
1938
  const first = call.arguments[0];
1934
1939
  if (!first) return void 0;
1935
1940
  const container = unwrapIdentityExpression(first);
1936
- if (!ts5.isArrayLiteralExpression(container)) return void 0;
1941
+ if (!ts6.isArrayLiteralExpression(container)) return void 0;
1937
1942
  return { elements: container.elements, promiseAll: true };
1938
1943
  }
1939
1944
  async function recordArrayElementBinding(targetName, expr, node, arrayIndex, promiseAll) {
@@ -1948,7 +1953,7 @@ async function parseServiceBindings(repoPath, filePath) {
1948
1953
  return;
1949
1954
  }
1950
1955
  const unwrapped = unwrapIdentityExpression(expr);
1951
- if (ts5.isIdentifier(unwrapped)) {
1956
+ if (ts6.isIdentifier(unwrapped)) {
1952
1957
  const existing = bindingForVariable(unwrapped.text);
1953
1958
  if (!existing) return;
1954
1959
  out.push({
@@ -1963,15 +1968,15 @@ async function parseServiceBindings(repoPath, filePath) {
1963
1968
  }
1964
1969
  }
1965
1970
  async function recordArrayDestructuredVariable(decl) {
1966
- if (!ts5.isArrayBindingPattern(decl.name) || !decl.initializer) return;
1971
+ if (!ts6.isArrayBindingPattern(decl.name) || !decl.initializer) return;
1967
1972
  const container = arrayElementsFromExpression(decl.initializer);
1968
1973
  if (!container) return;
1969
1974
  for (let index = 0; index < decl.name.elements.length; index += 1) {
1970
1975
  const el = decl.name.elements[index];
1971
- if (!el || ts5.isOmittedExpression(el) || ts5.isBindingElement(el) && el.dotDotDotToken) continue;
1972
- if (!ts5.isBindingElement(el) || !ts5.isIdentifier(el.name)) continue;
1976
+ if (!el || ts6.isOmittedExpression(el) || ts6.isBindingElement(el) && el.dotDotDotToken) continue;
1977
+ if (!ts6.isBindingElement(el) || !ts6.isIdentifier(el.name)) continue;
1973
1978
  const source = container.elements[index];
1974
- if (!source || ts5.isOmittedExpression(source)) continue;
1979
+ if (!source || ts6.isOmittedExpression(source)) continue;
1975
1980
  await recordArrayElementBinding(el.name.text, source, decl, index, container.promiseAll);
1976
1981
  }
1977
1982
  }
@@ -1980,49 +1985,52 @@ async function parseServiceBindings(repoPath, filePath) {
1980
1985
  if (!container) return;
1981
1986
  for (let index = 0; index < pattern.elements.length; index += 1) {
1982
1987
  const el = pattern.elements[index];
1983
- if (!el || ts5.isOmittedExpression(el) || ts5.isSpreadElement(el) || !ts5.isIdentifier(el)) continue;
1988
+ if (!el || ts6.isOmittedExpression(el) || ts6.isSpreadElement(el) || !ts6.isIdentifier(el)) continue;
1984
1989
  const source = container.elements[index];
1985
- if (!source || ts5.isOmittedExpression(source)) continue;
1990
+ if (!source || ts6.isOmittedExpression(source)) continue;
1986
1991
  await recordArrayElementBinding(el.text, source, node, index, container.promiseAll);
1987
1992
  }
1988
1993
  }
1989
1994
  const events = [];
1990
1995
  function collectEvents(node) {
1991
- if (ts5.isVariableDeclaration(node)) events.push({ pos: node.getStart(sourceFileAst), node });
1992
- if (ts5.isBinaryExpression(node) && node.operatorToken.kind === ts5.SyntaxKind.EqualsToken)
1996
+ if (ts6.isVariableDeclaration(node)) events.push({ pos: node.getStart(sourceFileAst), node });
1997
+ if (ts6.isBinaryExpression(node) && node.operatorToken.kind === ts6.SyntaxKind.EqualsToken)
1993
1998
  events.push({ pos: node.getStart(sourceFileAst), node });
1994
- ts5.forEachChild(node, collectEvents);
1999
+ ts6.forEachChild(node, collectEvents);
1995
2000
  }
1996
2001
  collectEvents(sourceFileAst);
1997
2002
  events.sort((a, b) => a.pos - b.pos);
1998
2003
  for (const event of events) {
1999
- if (ts5.isVariableDeclaration(event.node)) {
2004
+ if (ts6.isVariableDeclaration(event.node)) {
2000
2005
  const decl = event.node;
2001
2006
  await recordDestructuredHelper(decl);
2002
2007
  await recordArrayDestructuredVariable(decl);
2003
2008
  recordDestructuredClassHelper(decl);
2009
+ await rememberObjectHelperVariable(decl);
2010
+ if (ts6.isIdentifier(decl.name) && decl.initializer) recordObjectPropertyBinding(decl.name.text, decl.initializer, decl);
2004
2011
  await recordVariable(decl);
2005
2012
  recordIdentityAlias(decl);
2006
- if (ts5.isIdentifier(decl.name) && decl.initializer) {
2013
+ if (ts6.isIdentifier(decl.name) && decl.initializer) {
2007
2014
  const sourceName = transactionReceiverName(decl.initializer);
2008
2015
  if (sourceName) cloneAliasBinding(decl.name.text, sourceName, "transaction", decl);
2009
2016
  }
2010
2017
  continue;
2011
2018
  }
2012
2019
  const assignment = event.node;
2013
- if (ts5.isIdentifier(assignment.left)) {
2020
+ if (ts6.isIdentifier(assignment.left)) {
2014
2021
  const rhs = unwrapIdentityExpression(assignment.right);
2015
- if (ts5.isIdentifier(rhs)) {
2022
+ if (ts6.isIdentifier(rhs)) {
2016
2023
  cloneAliasBinding(assignment.left.text, rhs.text, "identity-assignment", assignment);
2017
2024
  continue;
2018
2025
  }
2026
+ if (recordObjectPropertyBinding(assignment.left.text, assignment.right, assignment)) continue;
2019
2027
  await recordBindingFromExpression(assignment.left.text, assignment.right, assignment, "assignment");
2020
2028
  continue;
2021
2029
  }
2022
- const left = ts5.isParenthesizedExpression(assignment.left) ? assignment.left.expression : assignment.left;
2023
- if (ts5.isObjectLiteralExpression(left))
2030
+ const left = ts6.isParenthesizedExpression(assignment.left) ? assignment.left.expression : assignment.left;
2031
+ if (ts6.isObjectLiteralExpression(left))
2024
2032
  await recordDestructuredAssignment(left, assignment.right, assignment);
2025
- if (ts5.isArrayLiteralExpression(left))
2033
+ if (ts6.isArrayLiteralExpression(left))
2026
2034
  await recordArrayDestructuredAssignment(left, assignment.right, assignment);
2027
2035
  }
2028
2036
  return out;
@@ -2030,16 +2038,16 @@ async function parseServiceBindings(repoPath, filePath) {
2030
2038
  function collectClassHelpers(sf) {
2031
2039
  const helpers = [];
2032
2040
  for (const stmt of sf.statements) {
2033
- if (!ts5.isClassDeclaration(stmt) || !stmt.name) continue;
2041
+ if (!ts6.isClassDeclaration(stmt) || !stmt.name) continue;
2034
2042
  for (const member of stmt.members) {
2035
2043
  let visit2 = function(node) {
2036
- if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
2044
+ if (ts6.isVariableDeclaration(node) && ts6.isIdentifier(node.name) && node.initializer) {
2037
2045
  const fact = findConnectInExpression(node.initializer);
2038
2046
  if (fact) bindings.set(node.name.text, fact);
2039
2047
  }
2040
- if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
2048
+ if (ts6.isReturnStatement(node) && node.expression && ts6.isObjectLiteralExpression(node.expression)) {
2041
2049
  for (const prop of node.expression.properties) {
2042
- if (ts5.isShorthandPropertyAssignment(prop)) {
2050
+ if (ts6.isShorthandPropertyAssignment(prop)) {
2043
2051
  const fact = bindings.get(prop.name.text);
2044
2052
  if (fact)
2045
2053
  helpers.push({
@@ -2051,8 +2059,8 @@ function collectClassHelpers(sf) {
2051
2059
  sourceLine: lineOf4(sf, prop)
2052
2060
  });
2053
2061
  }
2054
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
2055
- const propertyName = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
2062
+ if (ts6.isPropertyAssignment(prop) && ts6.isIdentifier(prop.initializer)) {
2063
+ const propertyName = ts6.isIdentifier(prop.name) || ts6.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
2056
2064
  const fact = propertyName ? bindings.get(prop.initializer.text) : void 0;
2057
2065
  if (propertyName && fact)
2058
2066
  helpers.push({
@@ -2066,15 +2074,15 @@ function collectClassHelpers(sf) {
2066
2074
  }
2067
2075
  }
2068
2076
  }
2069
- ts5.forEachChild(node, visit2);
2077
+ ts6.forEachChild(node, visit2);
2070
2078
  };
2071
2079
  var visit = visit2;
2072
- if (!ts5.isPropertyDeclaration(member) || !member.initializer) continue;
2073
- if (!ts5.isIdentifier(member.name)) continue;
2080
+ if (!ts6.isPropertyDeclaration(member) || !member.initializer) continue;
2081
+ if (!ts6.isIdentifier(member.name)) continue;
2074
2082
  const className = stmt.name.text;
2075
2083
  const helperName = member.name.text;
2076
2084
  const initializer = member.initializer;
2077
- if (!ts5.isArrowFunction(initializer) && !ts5.isFunctionExpression(initializer))
2085
+ if (!ts6.isArrowFunction(initializer) && !ts6.isFunctionExpression(initializer))
2078
2086
  continue;
2079
2087
  const bindings = /* @__PURE__ */ new Map();
2080
2088
  visit2(initializer);
@@ -2519,7 +2527,11 @@ function linkImplementations(db, workspaceId, generation) {
2519
2527
  const accepted = candidates.filter((candidate) => candidate.accepted);
2520
2528
  const topScore = accepted[0]?.score ?? 0;
2521
2529
  const winners = accepted.filter((candidate) => candidate.score === topScore);
2522
- const unique = winners.length === 1 ? winners[0] : void 0;
2530
+ const duplicateFamilies = duplicatePackageFamilies(accepted);
2531
+ const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
2532
+ const selected = duplicatePackageAmbiguous ? accepted : winners;
2533
+ const unique = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
2534
+ const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
2523
2535
  const evidence = {
2524
2536
  servicePath: operation.servicePath,
2525
2537
  operationPath: operation.operationPath,
@@ -2528,6 +2540,8 @@ function linkImplementations(db, workspaceId, generation) {
2528
2540
  implementationSource: implementationContext.operationId === operation.operationId ? "direct_or_concrete_override" : "inherited_from_base_operation",
2529
2541
  baseOperationId: operation.baseOperationId,
2530
2542
  implementationOperationId: implementationContext.operationId,
2543
+ ambiguityReasons,
2544
+ candidateFamilies: duplicateFamilies,
2531
2545
  candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
2532
2546
  };
2533
2547
  if (accepted.length === 0) {
@@ -2536,7 +2550,7 @@ function linkImplementations(db, workspaceId, generation) {
2536
2550
  unresolvedCount += 1;
2537
2551
  continue;
2538
2552
  }
2539
- 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", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : winners.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidence), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
2553
+ 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", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidence), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
2540
2554
  edgeCount += 1;
2541
2555
  if (unique) resolvedCount += 1;
2542
2556
  else ambiguousCount += 1;
@@ -2580,6 +2594,18 @@ function uniqueRegistrations(rows2) {
2580
2594
  return true;
2581
2595
  });
2582
2596
  }
2597
+ function duplicatePackageFamilies(candidates) {
2598
+ const byPackage = /* @__PURE__ */ new Map();
2599
+ for (const candidate of candidates) {
2600
+ const packageName = typeof candidate.handlerPackage === "string" ? candidate.handlerPackage : void 0;
2601
+ if (!packageName) continue;
2602
+ byPackage.set(packageName, [...byPackage.get(packageName) ?? [], candidate]);
2603
+ }
2604
+ return [...byPackage.entries()].filter(([, rows2]) => new Set(rows2.map((row) => Number(row.handlerRepoId))).size > 1).map(([packageName, rows2]) => ({ reason: "duplicate_package_name_candidates", packageName, count: rows2.length, repositories: rows2.map((row) => row.handlerRepo).sort() }));
2605
+ }
2606
+ function hasDirectOwnershipEvidence(candidate) {
2607
+ return candidate.acceptedReasons.some((reason) => reason === "model package equals registration package" || reason === "model package equals handler package" || reason === "registration package contains exact local service path");
2608
+ }
2583
2609
  function implementationCandidates(db, workspaceId, operation) {
2584
2610
  const modelRepoGraphId = graphId(operation.modelRepoId);
2585
2611
  return db.prepare(`SELECT DISTINCT
@@ -2763,6 +2789,154 @@ function normalizedOperation(value) {
2763
2789
  return value.startsWith("/") ? value.slice(1) : value;
2764
2790
  }
2765
2791
 
2792
+ // src/trace/evidence.ts
2793
+ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
2794
+ const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
2795
+ return {
2796
+ ...evidence,
2797
+ graphEdgeId: row.id,
2798
+ persistedGraphEdgeId: row.id > 0 ? row.id : void 0,
2799
+ outboundCallId: call.id,
2800
+ callSite: { sourceFile: call.source_file, sourceLine: call.source_line },
2801
+ sourceFile: call.source_file,
2802
+ sourceLine: call.source_line,
2803
+ file: call.source_file,
2804
+ line: call.source_line,
2805
+ persistedTarget: { kind: row.to_kind, id: row.to_id },
2806
+ contextualResolutionParticipated: Boolean(contextualEvidence?.contextualServiceBindingAttempted),
2807
+ persistedResolution: persistedResolution(row)
2808
+ };
2809
+ }
2810
+ function runtimeResolution(db, row, evidence, vars, workspaceId) {
2811
+ const substituted = evidenceWithRuntimeVariables(evidence, vars);
2812
+ if (!isRemoteRuntimeCandidate(row, evidence, vars)) {
2813
+ const withSections = withEffectiveResolution(substituted, row, row.unresolved_reason);
2814
+ return { row, evidence: withSections, unresolvedReason: row.unresolved_reason };
2815
+ }
2816
+ const resolution = resolveRuntimeOperation(db, substituted, workspaceId);
2817
+ if (resolution.target) {
2818
+ 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)) };
2819
+ return { row: resolvedRow, evidence: withEffectiveResolution(substituted, resolvedRow, void 0, resolution), target: resolution.target };
2820
+ }
2821
+ const unresolvedReason = runtimeUnresolvedReason(resolution);
2822
+ return { row, evidence: withEffectiveResolution(substituted, row, unresolvedReason, resolution), unresolvedReason };
2823
+ }
2824
+ function runtimeVariableDiagnostic(edges) {
2825
+ const missing = /* @__PURE__ */ new Set();
2826
+ for (const edge of edges) {
2827
+ const substitutions = edge.evidence.runtimeSubstitutions;
2828
+ if (!substitutions || typeof substitutions !== "object" || Array.isArray(substitutions)) continue;
2829
+ for (const value of Object.values(substitutions))
2830
+ for (const key of value.missing ?? []) missing.add(key);
2831
+ }
2832
+ const missingVariables = [...missing].sort();
2833
+ if (missingVariables.length === 0) return void 0;
2834
+ return {
2835
+ severity: "warning",
2836
+ code: "trace_runtime_variables_missing",
2837
+ message: `Runtime variables are required to resolve dynamic trace targets: ${missingVariables.join(", ")}`,
2838
+ missingVariables,
2839
+ suggestions: missingVariables.map((key) => `--var ${key}=<value>`)
2840
+ };
2841
+ }
2842
+ function edgeTarget(row, evidence) {
2843
+ const effective = parseObject(evidence.effectiveResolution);
2844
+ const targetServicePath = stringValue2(effective.targetServicePath ?? evidence.targetServicePath);
2845
+ const targetOperationPath = stringValue2(effective.targetOperationPath ?? evidence.targetOperationPath);
2846
+ if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
2847
+ const runtimeCandidate = evidence.runtimeResolvedCandidate;
2848
+ if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
2849
+ const servicePath = stringValue2(evidence.servicePath);
2850
+ const operationPath = stringValue2(evidence.operationPath);
2851
+ const targetOperation = stringValue2(evidence.targetOperation);
2852
+ const targetRepo = stringValue2(evidence.targetRepo) ?? "";
2853
+ if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
2854
+ if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue2(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
2855
+ if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
2856
+ const target = parseObject(evidence.externalTarget);
2857
+ return stringValue2(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
2858
+ }
2859
+ if (servicePath && operationPath) return `${servicePath}${operationPath}`;
2860
+ return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
2861
+ }
2862
+ function persistedResolution(row) {
2863
+ return {
2864
+ status: row.status,
2865
+ targetKind: row.to_kind,
2866
+ targetId: row.to_id,
2867
+ edgeId: row.id > 0 ? row.id : void 0,
2868
+ confidence: row.confidence,
2869
+ unresolvedReason: row.unresolved_reason,
2870
+ edgeType: row.edge_type
2871
+ };
2872
+ }
2873
+ function effectiveResolution(row, evidence, unresolvedReason, resolution) {
2874
+ const target = resolution?.target;
2875
+ return {
2876
+ status: target ? "resolved" : row.status,
2877
+ targetKind: target ? "operation" : row.to_kind,
2878
+ targetId: target ? String(target.operationId) : row.to_id,
2879
+ targetRepo: target?.repoName ?? evidence.targetRepo,
2880
+ targetServicePath: target?.servicePath ?? evidence.targetServicePath,
2881
+ targetOperationPath: target?.operationPath ?? evidence.targetOperationPath,
2882
+ targetOperation: target?.operationName ?? evidence.targetOperation,
2883
+ confidence: target?.score ?? row.confidence,
2884
+ reasons: resolution?.reasons ?? evidence.resolutionReasons,
2885
+ unresolvedReason,
2886
+ edgeType: target ? "REMOTE_CALL_RESOLVES_TO_OPERATION" : row.edge_type
2887
+ };
2888
+ }
2889
+ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
2890
+ const current = effectiveResolution(row, evidence, unresolvedReason, resolution);
2891
+ const rest = { ...evidence };
2892
+ delete rest.runtimeResolvedCandidate;
2893
+ return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
2894
+ }
2895
+ function resolveRuntimeOperation(db, evidence, workspaceId) {
2896
+ const servicePath = stringValue2(evidence.servicePath);
2897
+ const operationPath = stringValue2(evidence.normalizedOperationPath ?? evidence.operationPath);
2898
+ const alias = stringValue2(evidence.serviceAliasExpr ?? evidence.serviceAlias);
2899
+ const destination = stringValue2(evidence.destination);
2900
+ return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
2901
+ }
2902
+ function evidenceWithRuntimeVariables(evidence, vars) {
2903
+ const substitutions = runtimeSubstitutions(evidence, vars ?? {});
2904
+ const next = { ...evidence, runtimeSubstitutions: substitutions };
2905
+ for (const [key, value] of Object.entries(substitutions)) if (value.effective) next[key] = value.effective;
2906
+ const missing = Object.values(substitutions).flatMap((value) => value.missing);
2907
+ if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)].sort();
2908
+ if (Object.keys(vars ?? {}).length > 0) next.runtimeVariablesApplied = true;
2909
+ return next;
2910
+ }
2911
+ function runtimeSubstitutions(evidence, vars) {
2912
+ const substitutions = {};
2913
+ for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
2914
+ const substitution = substituteVariables(stringValue2(evidence[key]), vars);
2915
+ if (substitution.placeholders.length > 0) substitutions[key] = substitution;
2916
+ }
2917
+ return substitutions;
2918
+ }
2919
+ function isRemoteRuntimeCandidate(row, evidence, vars) {
2920
+ if (!vars || Object.keys(vars).length === 0) return false;
2921
+ if (!["dynamic", "ambiguous", "unresolved"].includes(String(row.status ?? ""))) return false;
2922
+ if (!["DYNAMIC_EDGE_CANDIDATE", "UNRESOLVED_EDGE", "REMOTE_CALL_RESOLVES_TO_OPERATION"].includes(row.edge_type)) return false;
2923
+ return ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"].some((key) => hasRuntimeVariable(evidence[key], vars));
2924
+ }
2925
+ function hasRuntimeVariable(value, vars) {
2926
+ return typeof value === "string" && extractPlaceholders(value).some((key) => Object.hasOwn(vars, key));
2927
+ }
2928
+ function runtimeUnresolvedReason(resolution) {
2929
+ if (resolution.status === "dynamic") return `Dynamic target is missing runtime variables: ${resolution.reasons.join(", ")}`;
2930
+ if (resolution.status === "ambiguous") return "Ambiguous runtime operation candidates";
2931
+ return "No runtime operation candidate matched substituted service and operation path";
2932
+ }
2933
+ function parseObject(value) {
2934
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2935
+ }
2936
+ function stringValue2(value) {
2937
+ return typeof value === "string" ? value : void 0;
2938
+ }
2939
+
2766
2940
  // src/trace/trace-engine.ts
2767
2941
  function normalizeOperation(value) {
2768
2942
  if (!value) return void 0;
@@ -2771,7 +2945,7 @@ function normalizeOperation(value) {
2771
2945
  function positiveDepth(value) {
2772
2946
  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
2773
2947
  }
2774
- function operationStartScope(db, repoId, start) {
2948
+ function operationStartScope(db, repoId, start, implementationRepo) {
2775
2949
  const requested = normalizeOperation(start.operationPath ?? start.operation);
2776
2950
  if (!requested) return void 0;
2777
2951
  const rows2 = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,s.service_path servicePath,r.id repoId,r.name repoName
@@ -2787,7 +2961,15 @@ function operationStartScope(db, repoId, start) {
2787
2961
  const operationId = String(rows2[0]?.operationId);
2788
2962
  const impl = implementationScope(db, operationId);
2789
2963
  if (impl.edge?.status === "resolved" && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? /* @__PURE__ */ new Set([impl.symbolId]) : void 0, operationId, diagnostics: [] };
2790
- if (impl.edge) return { operationId, diagnostics: [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved", message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? "unresolved")}`, resolutionStage: "implementation", resolutionStatus: impl.edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation", implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, candidates: parseEvidence(impl.edge.evidence_json).candidates }] };
2964
+ const hinted = implementationMethodIdFromHint(impl.edge, implementationRepo);
2965
+ if (hinted.methodId) {
2966
+ const hintedScope = handlerScope(db, hinted.methodId);
2967
+ if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? /* @__PURE__ */ new Set([hintedScope.symbolId]) : void 0, operationId, diagnostics: [] };
2968
+ }
2969
+ if (impl.edge) {
2970
+ const evidence = parseEvidence(impl.edge.evidence_json);
2971
+ return { operationId, diagnostics: [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved", message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? "unresolved")}`, resolutionStage: "implementation", resolutionStatus: impl.edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation", implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, implementationAmbiguityReasons: evidence.ambiguityReasons, candidates: evidence.candidates }] };
2972
+ }
2791
2973
  return { operationId, diagnostics: [{ severity: "warning", code: "trace_start_implementation_unresolved", message: "Indexed operation matched but no implementation candidate exists", resolutionStage: "implementation", resolutionStatus: "operation_without_implementation" }] };
2792
2974
  }
2793
2975
  function sourceFilesForStart(db, repoId, start) {
@@ -2830,12 +3012,12 @@ function sourceFilesForStart(db, repoId, start) {
2830
3012
  }
2831
3013
  return void 0;
2832
3014
  }
2833
- function startScope(db, start) {
3015
+ function startScope(db, start, implementationRepo) {
2834
3016
  const repo = start.repo ? db.prepare(
2835
3017
  "SELECT id,name FROM repositories WHERE name=? OR package_name=?"
2836
3018
  ).get(start.repo, start.repo) : void 0;
2837
3019
  if (start.repo && !repo) return { repo, selectorMatched: false };
2838
- const operationScope = operationStartScope(db, repo?.id, start);
3020
+ const operationScope = operationStartScope(db, repo?.id, start, implementationRepo);
2839
3021
  const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === "operation" || d.resolutionStage === "implementation");
2840
3022
  const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
2841
3023
  const sourceFiles = sourceScope?.files;
@@ -2882,7 +3064,26 @@ function implementationScope(db, operationId) {
2882
3064
  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);
2883
3065
  return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
2884
3066
  }
2885
- function contextImplementationMethodId(edge, callerRepoId, remoteEvidence = {}) {
3067
+ function implementationMethodIdFromHint(edge, implementationRepo) {
3068
+ if (!edge || edge.status !== "ambiguous" || !implementationRepo) return { evidence: { status: "not_applicable" } };
3069
+ const evidence = parseEvidence(edge.evidence_json);
3070
+ const matches = (evidence.candidates ?? []).filter((item) => item.accepted && (item.handlerPackage?.name === implementationRepo || item.handlerPackage?.packageName === implementationRepo || item.sourceFile?.startsWith(implementationRepo)));
3071
+ if (matches.length !== 1 || matches[0]?.methodId === void 0) return { evidence: { status: matches.length > 1 ? "tied" : "not_matched", strategy: "implementation_repo_hint", selectedRepo: implementationRepo, candidateCount: matches.length } };
3072
+ return {
3073
+ methodId: String(matches[0].methodId),
3074
+ evidence: {
3075
+ status: "selected",
3076
+ guided: true,
3077
+ strategy: "implementation_repo_hint",
3078
+ selectedRepo: implementationRepo,
3079
+ selectedMethodId: matches[0].methodId,
3080
+ ambiguityReason: evidence.ambiguityReasons?.[0]
3081
+ }
3082
+ };
3083
+ }
3084
+ function contextImplementationMethodId(edge, callerRepoId, remoteEvidence = {}, implementationRepo) {
3085
+ const hinted = implementationMethodIdFromHint(edge, implementationRepo);
3086
+ if (hinted.methodId) return hinted;
2886
3087
  if (!edge || edge.status !== "ambiguous" || callerRepoId === void 0) return { evidence: { status: "not_applicable" } };
2887
3088
  const evidence = JSON.parse(String(edge.evidence_json || "{}"));
2888
3089
  const scores = (evidence.candidates ?? []).filter((item) => item.accepted).map((item) => {
@@ -2935,31 +3136,6 @@ function graphForCalls(db, callIds) {
2935
3136
  }
2936
3137
  return map;
2937
3138
  }
2938
- function hasRuntimeVariable(value, vars) {
2939
- return typeof value === "string" && extractPlaceholders(value).some((key) => Object.hasOwn(vars, key));
2940
- }
2941
- function isRemoteRuntimeCandidate(row, evidence, vars) {
2942
- if (!vars || Object.keys(vars).length === 0) return false;
2943
- if (!["dynamic", "ambiguous", "unresolved"].includes(String(row.status ?? ""))) return false;
2944
- if (!["DYNAMIC_EDGE_CANDIDATE", "UNRESOLVED_EDGE", "REMOTE_CALL_RESOLVES_TO_OPERATION"].includes(row.edge_type)) return false;
2945
- if (row.status === "resolved") return false;
2946
- return ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"].some((key) => hasRuntimeVariable(evidence[key], vars));
2947
- }
2948
- function evidenceWithRuntimeVariables(evidence, vars) {
2949
- if (!vars || Object.keys(vars).length === 0) return evidence;
2950
- const substitutions = {};
2951
- for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
2952
- const substitution = substituteVariables(typeof evidence[key] === "string" ? String(evidence[key]) : void 0, vars);
2953
- if (substitution.placeholders.length > 0) substitutions[key] = substitution;
2954
- }
2955
- const next = { ...evidence, runtimeVariablesApplied: true, runtimeSubstitutions: substitutions };
2956
- for (const [key, value] of Object.entries(substitutions)) {
2957
- if (value.effective) next[key] = value.effective;
2958
- }
2959
- const missing = Object.values(substitutions).flatMap((value) => value.missing);
2960
- if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)];
2961
- return next;
2962
- }
2963
3139
  function symbolNode(db, symbolId) {
2964
3140
  const row = db.prepare(`SELECT s.id symbolId,s.name symbolName,s.qualified_name qualifiedName,s.source_file sourceFile,s.start_line startLine,s.end_line endLine,r.name repoName,r.id repoId FROM symbols s JOIN repositories r ON r.id=s.repo_id WHERE s.id=?`).get(symbolId);
2965
3141
  if (!row) return void 0;
@@ -2974,24 +3150,6 @@ function operationNode(db, operationId) {
2974
3150
  function workspaceIdForCall(db, callId) {
2975
3151
  return db.prepare("SELECT r.workspace_id workspaceId FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE c.id=?").get(callId)?.workspaceId;
2976
3152
  }
2977
- function runtimeResolution(db, row, evidence, vars) {
2978
- if (!isRemoteRuntimeCandidate(row, evidence, vars))
2979
- return { row, evidence, unresolvedReason: row.unresolved_reason };
2980
- const nextEvidence = evidenceWithRuntimeVariables(evidence, vars);
2981
- const servicePath = typeof nextEvidence.servicePath === "string" ? nextEvidence.servicePath : void 0;
2982
- const operationPath = typeof nextEvidence.normalizedOperationPath === "string" ? nextEvidence.normalizedOperationPath : typeof nextEvidence.operationPath === "string" ? nextEvidence.operationPath : void 0;
2983
- const alias = typeof nextEvidence.serviceAliasExpr === "string" ? nextEvidence.serviceAliasExpr : typeof nextEvidence.serviceAlias === "string" ? nextEvidence.serviceAlias : void 0;
2984
- const destination = typeof nextEvidence.destination === "string" ? nextEvidence.destination : void 0;
2985
- const resolution = resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceIdForCall(db, row.from_id));
2986
- nextEvidence.runtimeResolutionStatus = resolution.status;
2987
- nextEvidence.runtimeResolutionReasons = resolution.reasons;
2988
- if (resolution.target) {
2989
- nextEvidence.runtimeResolvedCandidate = resolution.target;
2990
- return { row: { ...row, to_kind: "operation", to_id: String(resolution.target.operationId), unresolved_reason: void 0, confidence: Math.max(0, Math.min(1, resolution.target.score)) }, evidence: nextEvidence, target: resolution.target };
2991
- }
2992
- const unresolvedReason = resolution.status === "dynamic" ? `Dynamic target is missing runtime variables: ${resolution.reasons.join(", ")}` : resolution.status === "ambiguous" ? "Ambiguous runtime operation candidates" : "No runtime operation candidate matched substituted service and operation path";
2993
- return { row, evidence: nextEvidence, unresolvedReason };
2994
- }
2995
3153
  function parseEvidence(value) {
2996
3154
  try {
2997
3155
  const parsed = JSON.parse(String(value || "{}"));
@@ -3091,27 +3249,8 @@ function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRo
3091
3249
  if (persistedResolved) return { row: void 0, evidence: { ...resolvedEvidence, contextualPreservedPersistedResolvedEdge: true }, unresolvedReason: void 0 };
3092
3250
  return { row: { id: -Number(call.id), edge_type: "REMOTE_CALL_RESOLVES_TO_OPERATION", from_id: String(call.id), to_kind: "operation", to_id: String(resolution.target.operationId), confidence: resolution.target.score, evidence_json: JSON.stringify(resolvedEvidence), status: "resolved" }, evidence: resolvedEvidence, unresolvedReason: void 0 };
3093
3251
  }
3094
- function edgeTarget(row, evidence) {
3095
- const runtimeCandidate = evidence.runtimeResolvedCandidate;
3096
- if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath)
3097
- return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
3098
- const targetServicePath = typeof evidence.targetServicePath === "string" ? evidence.targetServicePath : void 0;
3099
- const targetOperationPath = typeof evidence.targetOperationPath === "string" ? evidence.targetOperationPath : void 0;
3100
- if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
3101
- const servicePath = typeof evidence.servicePath === "string" ? evidence.servicePath : void 0;
3102
- const operationPath = typeof evidence.operationPath === "string" ? evidence.operationPath : void 0;
3103
- const targetOperation = typeof evidence.targetOperation === "string" ? evidence.targetOperation : void 0;
3104
- const targetRepo = typeof evidence.targetRepo === "string" ? evidence.targetRepo : "";
3105
- if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
3106
- if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return typeof evidence.remoteQueryTarget === "string" ? evidence.remoteQueryTarget : `Remote query: ${row.to_id || "unknown"}`;
3107
- if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
3108
- const target = evidence.externalTarget;
3109
- return typeof target?.label === "string" ? target.label : `External endpoint: ${row.to_id || "unknown"}`;
3110
- }
3111
- return servicePath && operationPath ? `${servicePath}${operationPath}` : targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
3112
- }
3113
3252
  function trace(db, start, options) {
3114
- const scope = startScope(db, start);
3253
+ const scope = startScope(db, start, options.implementationRepo);
3115
3254
  const diagnostics = db.prepare(
3116
3255
  "SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics WHERE (? IS NULL OR repo_id=?)"
3117
3256
  ).all(scope.repo?.id, scope.repo?.id);
@@ -3134,12 +3273,14 @@ function trace(db, start, options) {
3134
3273
  const op = operationNode(db, scope.startOperationId);
3135
3274
  const impl = implementationScope(db, scope.startOperationId);
3136
3275
  if (op) nodes.set(String(op.id), op);
3137
- if (impl.edge && impl.edge.status === "resolved") {
3138
- const implEvidence = { ...parseEvidence(impl.edge.evidence_json), startResolution: { strategy: "indexed_operation_graph", matchedOperationId: scope.startOperationId, implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, selectedHandlerMethodId: impl.edge.status === "resolved" ? impl.edge.to_id : void 0 } };
3139
- const handlerNode = impl.edge.status === "resolved" ? handlerMethodNode(db, impl.edge.to_id) : void 0;
3276
+ const startSelection = implementationMethodIdFromHint(impl.edge, options.implementationRepo);
3277
+ if (impl.edge && (impl.edge.status === "resolved" || startSelection.methodId)) {
3278
+ const selectedMethodId = impl.edge.status === "resolved" ? impl.edge.to_id : startSelection.methodId;
3279
+ const implEvidence = { ...parseEvidence(impl.edge.evidence_json), startResolution: { strategy: "indexed_operation_graph", matchedOperationId: scope.startOperationId, implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, selectedHandlerMethodId: selectedMethodId }, implementationSelection: startSelection.methodId ? startSelection.evidence : void 0 };
3280
+ const handlerNode = selectedMethodId ? handlerMethodNode(db, selectedMethodId) : void 0;
3140
3281
  if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
3141
3282
  seenEdges.add(Number(impl.edge.id));
3142
- edges.push({ step: 1, type: "operation_implemented_by_handler", from: op?.label ? String(op.label) : `operation:${scope.startOperationId}`, to: handlerNode?.label ? String(handlerNode.label) : `${impl.edge.to_kind}:${impl.edge.to_id}`, evidence: implEvidence, confidence: Number(impl.edge.confidence ?? 0), unresolvedReason: impl.edge.status === "resolved" ? void 0 : String(impl.edge.unresolved_reason ?? impl.edge.status) });
3283
+ edges.push({ step: 1, type: "operation_implemented_by_handler", from: op?.label ? String(op.label) : `operation:${scope.startOperationId}`, to: handlerNode?.label ? String(handlerNode.label) : `${impl.edge.to_kind}:${impl.edge.to_id}`, evidence: implEvidence, confidence: Number(impl.edge.confidence ?? 0), unresolvedReason: impl.edge.status === "resolved" || startSelection.methodId ? void 0 : String(impl.edge.unresolved_reason ?? impl.edge.status) });
3143
3284
  }
3144
3285
  }
3145
3286
  const seenScopes = /* @__PURE__ */ new Set();
@@ -3196,8 +3337,8 @@ function trace(db, start, options) {
3196
3337
  const persistedEvidence = JSON.parse(
3197
3338
  String(row.evidence_json || "{}")
3198
3339
  );
3199
- const rawEvidence = { ...persistedEvidence, ...contextual.evidence ?? {}, graphEdgeId: row.id, persistedGraphEdgeId: row.id > 0 ? row.id : void 0, outboundCallId: call.id, callSite: { sourceFile: call.source_file, sourceLine: call.source_line }, sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, linker: { status: row.status, confidence: row.confidence, reason: row.unresolved_reason, edgeType: row.edge_type }, persistedTarget: { kind: row.to_kind, id: row.to_id }, contextualResolutionParticipated: Boolean(contextual.evidence?.contextualServiceBindingAttempted) };
3200
- const effective = runtimeResolution(db, row, rawEvidence, options.vars);
3340
+ const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
3341
+ const effective = runtimeResolution(db, row, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)));
3201
3342
  const evidence = effective.evidence;
3202
3343
  const effectiveRow = effective.row;
3203
3344
  const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
@@ -3219,7 +3360,7 @@ function trace(db, start, options) {
3219
3360
  });
3220
3361
  if (effectiveRow.to_kind === "operation") {
3221
3362
  const implementation = implementationScope(db, effectiveRow.to_id);
3222
- const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence);
3363
+ const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence, options.implementationRepo);
3223
3364
  const contextMethodId = contextSelection.methodId;
3224
3365
  const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : void 0;
3225
3366
  if (implementation.edge) {
@@ -3232,7 +3373,7 @@ function trace(db, start, options) {
3232
3373
  type: "operation_implemented_by_handler",
3233
3374
  from: to,
3234
3375
  to: implTo,
3235
- evidence: contextMethodId ? { ...implEvidence, contextualImplementationSelected: true, contextualImplementation: contextSelection.evidence } : { ...implEvidence, contextualImplementation: contextSelection.evidence },
3376
+ evidence: contextMethodId ? { ...implEvidence, contextualImplementationSelected: contextSelection.evidence.strategy !== "implementation_repo_hint", contextualImplementation: contextSelection.evidence, implementationSelection: contextSelection.evidence } : { ...implEvidence, contextualImplementation: contextSelection.evidence },
3236
3377
  confidence: Number(implementation.edge.confidence ?? 0),
3237
3378
  unresolvedReason: implementation.edge.status === "resolved" || contextMethodId ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status)
3238
3379
  });
@@ -3268,6 +3409,8 @@ function trace(db, start, options) {
3268
3409
  }
3269
3410
  }
3270
3411
  }
3412
+ const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
3413
+ if (runtimeDiagnostic) diagnostics.unshift(runtimeDiagnostic);
3271
3414
  return { start, nodes: [...nodes.values()], edges, diagnostics };
3272
3415
  }
3273
3416
 
@@ -3292,4 +3435,4 @@ export {
3292
3435
  linkWorkspace,
3293
3436
  trace
3294
3437
  };
3295
- //# sourceMappingURL=chunk-RP3BJ64F.js.map
3438
+ //# sourceMappingURL=chunk-UKNPHTUS.js.map