@saptools/service-flow 0.1.43 → 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;
@@ -1204,6 +1204,14 @@ function collectWrapperSpecs(source) {
1204
1204
  if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName, methodLiteral, start: node.getStart(source), end: node.getEnd() });
1205
1205
  }
1206
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
+ }
1207
1215
  ts4.forEachChild(node, visit);
1208
1216
  };
1209
1217
  visit(fn);
@@ -1213,7 +1221,7 @@ function collectWrapperSpecs(source) {
1213
1221
  const pathIndex = params.indexOf(found.path);
1214
1222
  const methodIndex = found.method ? params.indexOf(found.method) : -1;
1215
1223
  const capturesKnownClient = serviceVariables.has(found.client) || /^(srv|service|serviceClient|client|.*Client)$/.test(found.client);
1216
- 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 });
1217
1225
  };
1218
1226
  const visitTop = (node) => {
1219
1227
  if (ts4.isFunctionDeclaration(node) && node.name) scanFunction(node.name.text, node);
@@ -1293,7 +1301,7 @@ function classifyOutboundCallsInSource(source, filePath) {
1293
1301
  const operationPathExpr = operationPathFromStatic(resolvedPath.text);
1294
1302
  const normalizedOperationPath = operationPathExpr ? classifyODataPathIntent(operationPathExpr, method).topLevelOperationName : void 0;
1295
1303
  if (spec && receiver && operationPathExpr) {
1296
- 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 });
1297
1305
  } else if (spec && receiver) {
1298
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" });
1299
1307
  }
@@ -1388,6 +1396,10 @@ function serviceOperationCall(node, aliases) {
1388
1396
  }
1389
1397
 
1390
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
1391
1403
  import fs7 from "fs/promises";
1392
1404
  import path8 from "path";
1393
1405
  import ts5 from "typescript";
@@ -1396,10 +1408,8 @@ function lineOf4(sf, node) {
1396
1408
  }
1397
1409
  function stringValue(node) {
1398
1410
  if (!node) return void 0;
1399
- if (ts5.isStringLiteralLike(node) || ts5.isNoSubstitutionTemplateLiteral(node))
1400
- return node.text;
1401
- if (ts5.isTemplateExpression(node))
1402
- 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, "");
1403
1413
  return node.getText();
1404
1414
  }
1405
1415
  function placeholders2(value) {
@@ -1407,57 +1417,36 @@ function placeholders2(value) {
1407
1417
  }
1408
1418
  function connectFactFromCall(call) {
1409
1419
  const expr = call.expression;
1410
- if (!ts5.isPropertyAccessExpression(expr) || expr.name.text !== "to")
1411
- return void 0;
1420
+ if (!ts5.isPropertyAccessExpression(expr) || expr.name.text !== "to") return void 0;
1412
1421
  const inner = expr.expression;
1413
- if (!ts5.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds")
1414
- return void 0;
1422
+ if (!ts5.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds") return void 0;
1415
1423
  const first = call.arguments[0];
1416
1424
  if (!first) return void 0;
1417
1425
  const second = call.arguments[1];
1418
1426
  const objectArg = ts5.isObjectLiteralExpression(first) ? first : second && ts5.isObjectLiteralExpression(second) ? second : void 0;
1419
1427
  let alias;
1420
1428
  let aliasExpr;
1421
- if (ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first))
1422
- alias = first.text;
1423
- else if (!ts5.isObjectLiteralExpression(first))
1424
- aliasExpr = stringValue(first);
1425
- if ((ts5.isStringLiteralLike(first) || ts5.isNoSubstitutionTemplateLiteral(first)) && !objectArg)
1426
- return { alias: first.text, isDynamic: false, placeholders: [] };
1427
- if (!objectArg && aliasExpr)
1428
- return {
1429
- aliasExpr,
1430
- isDynamic: true,
1431
- placeholders: placeholders2(aliasExpr)
1432
- };
1433
- let destinationExpr;
1434
- 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 = {};
1435
1439
  function visitObject(obj) {
1436
1440
  for (const prop of obj.properties) {
1437
1441
  if (!ts5.isPropertyAssignment(prop)) continue;
1438
1442
  const name = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1439
- if (name === "destination")
1440
- destinationExpr = stringValue(prop.initializer);
1441
- if (name === "path" || name === "servicePath")
1442
- servicePathExpr = stringValue(prop.initializer);
1443
- if (ts5.isObjectLiteralExpression(prop.initializer))
1444
- visitObject(prop.initializer);
1445
- }
1446
- }
1447
- if (objectArg) visitObject(objectArg);
1448
- const ph = [
1449
- ...placeholders2(aliasExpr ?? alias),
1450
- ...placeholders2(destinationExpr),
1451
- ...placeholders2(servicePathExpr)
1452
- ];
1453
- return {
1454
- alias,
1455
- aliasExpr,
1456
- destinationExpr,
1457
- servicePathExpr,
1458
- isDynamic: ph.length > 0 || !destinationExpr && !servicePathExpr,
1459
- placeholders: ph
1460
- };
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;
1461
1450
  }
1462
1451
  function unwrapCall(expr) {
1463
1452
  if (ts5.isAwaitExpression(expr)) return unwrapCall(expr.expression);
@@ -1478,12 +1467,10 @@ function transactionReceiverName(expr) {
1478
1467
  const call = unwrapCall(expr);
1479
1468
  if (call && ts5.isPropertyAccessExpression(call.expression) && ["tx", "transaction"].includes(call.expression.name.text) && ts5.isIdentifier(call.expression.expression)) return call.expression.expression.text;
1480
1469
  const unwrapped = unwrapIdentityExpression(expr);
1481
- if (ts5.isConditionalExpression(unwrapped)) {
1482
- const left = transactionReceiverName(unwrapped.whenTrue);
1483
- const right = transactionReceiverName(unwrapped.whenFalse);
1484
- return left && left === right ? left : void 0;
1485
- }
1486
- 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;
1487
1474
  }
1488
1475
  function findConnectInExpression(expr) {
1489
1476
  const direct = unwrapCall(expr);
@@ -1503,13 +1490,7 @@ function findConnectInExpression(expr) {
1503
1490
  async function readSource(abs) {
1504
1491
  try {
1505
1492
  const text = await fs7.readFile(abs, "utf8");
1506
- return ts5.createSourceFile(
1507
- abs,
1508
- text,
1509
- ts5.ScriptTarget.Latest,
1510
- true,
1511
- ts5.ScriptKind.TS
1512
- );
1493
+ return ts5.createSourceFile(abs, text, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TS);
1513
1494
  } catch {
1514
1495
  return void 0;
1515
1496
  }
@@ -1519,56 +1500,34 @@ async function resolveImport(repoPath, fromFile, spec) {
1519
1500
  const rawBase = path8.resolve(repoPath, path8.dirname(fromFile), spec);
1520
1501
  const parsed = path8.parse(rawBase);
1521
1502
  const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ? path8.join(parsed.dir, parsed.name) : rawBase;
1522
- for (const candidate of [
1523
- base,
1524
- `${base}.ts`,
1525
- `${base}.js`,
1526
- path8.join(base, "index.ts"),
1527
- path8.join(base, "index.js")
1528
- ]) {
1529
- try {
1530
- const st = await fs7.stat(candidate);
1531
- if (st.isFile()) return normalizePath(path8.relative(repoPath, candidate));
1532
- } catch {
1533
- }
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));
1534
1506
  }
1535
1507
  return void 0;
1536
1508
  }
1537
1509
  async function importsFor(repoPath, filePath, sf) {
1538
1510
  const imports = [];
1539
1511
  for (const stmt of sf.statements) {
1540
- if (!ts5.isImportDeclaration(stmt) || !ts5.isStringLiteralLike(stmt.moduleSpecifier))
1541
- continue;
1542
- const sourceFile = await resolveImport(
1543
- repoPath,
1544
- filePath,
1545
- stmt.moduleSpecifier.text
1546
- );
1512
+ if (!ts5.isImportDeclaration(stmt) || !ts5.isStringLiteralLike(stmt.moduleSpecifier)) continue;
1513
+ const sourceFile = await resolveImport(repoPath, filePath, stmt.moduleSpecifier.text);
1547
1514
  const clause = stmt.importClause;
1548
1515
  if (!clause) continue;
1549
- if (clause.name)
1550
- imports.push({
1551
- localName: clause.name.text,
1552
- exportedName: "default",
1553
- sourceFile
1554
- });
1516
+ if (clause.name) imports.push({ localName: clause.name.text, exportedName: "default", sourceFile });
1555
1517
  const bindings = clause.namedBindings;
1556
1518
  if (bindings && ts5.isNamedImports(bindings))
1557
- for (const el of bindings.elements)
1558
- imports.push({
1559
- localName: el.name.text,
1560
- exportedName: el.propertyName?.text ?? el.name.text,
1561
- sourceFile
1562
- });
1519
+ for (const el of bindings.elements) imports.push({ localName: el.name.text, exportedName: el.propertyName?.text ?? el.name.text, sourceFile });
1563
1520
  }
1564
1521
  return imports;
1565
1522
  }
1523
+
1524
+ // src/parsers/service-binding-parser.ts
1566
1525
  function collectLocalBindingFacts(fn) {
1567
1526
  const bindings = /* @__PURE__ */ new Map();
1568
1527
  function visit(node) {
1569
- 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)))
1570
1529
  return;
1571
- if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
1530
+ if (ts6.isVariableDeclaration(node) && ts6.isIdentifier(node.name) && node.initializer) {
1572
1531
  const fact = findConnectInExpression(node.initializer);
1573
1532
  if (fact) bindings.set(node.name.text, fact);
1574
1533
  const sourceName = transactionReceiverName(node.initializer);
@@ -1577,7 +1536,7 @@ function collectLocalBindingFacts(fn) {
1577
1536
  if (source) bindings.set(node.name.text, { ...source, helperChain: [...source.helperChain ?? [], { aliasOf: sourceName, callerVariable: node.name.text, aliasKind: "transaction", transactionAliasSource: sourceName }] });
1578
1537
  }
1579
1538
  }
1580
- ts5.forEachChild(node, visit);
1539
+ ts6.forEachChild(node, visit);
1581
1540
  }
1582
1541
  visit(fn);
1583
1542
  return bindings;
@@ -1586,18 +1545,18 @@ function collectReturnedObjectBindings(fn) {
1586
1545
  const bindings = collectLocalBindingFacts(fn);
1587
1546
  const returns = /* @__PURE__ */ new Map();
1588
1547
  function visit(node) {
1589
- 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)))
1590
1549
  return;
1591
- if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
1550
+ if (ts6.isReturnStatement(node) && node.expression && ts6.isObjectLiteralExpression(node.expression)) {
1592
1551
  for (const prop of node.expression.properties) {
1593
- if (ts5.isShorthandPropertyAssignment(prop)) returns.set(prop.name.text, prop.name.text);
1594
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
1595
- 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;
1596
1555
  if (propertyName) returns.set(propertyName, prop.initializer.text);
1597
1556
  }
1598
1557
  }
1599
1558
  }
1600
- ts5.forEachChild(node, visit);
1559
+ ts6.forEachChild(node, visit);
1601
1560
  }
1602
1561
  visit(fn);
1603
1562
  const out = /* @__PURE__ */ new Map();
@@ -1609,65 +1568,65 @@ function collectReturnedObjectBindings(fn) {
1609
1568
  }
1610
1569
  function functionLikeInitializer(expr) {
1611
1570
  if (!expr) return void 0;
1612
- if (ts5.isArrowFunction(expr) || ts5.isFunctionExpression(expr)) return expr;
1571
+ if (ts6.isArrowFunction(expr) || ts6.isFunctionExpression(expr)) return expr;
1613
1572
  return void 0;
1614
1573
  }
1615
1574
  function directReturnConnectFact(fn) {
1616
1575
  const localBindings = collectLocalBindingFacts(fn);
1617
1576
  let returned;
1618
1577
  function visit(node) {
1619
- 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)))
1620
1579
  return;
1621
- if (!returned && ts5.isReturnStatement(node) && node.expression)
1580
+ if (!returned && ts6.isReturnStatement(node) && node.expression)
1622
1581
  returned = node.expression;
1623
- if (!returned) ts5.forEachChild(node, visit);
1582
+ if (!returned) ts6.forEachChild(node, visit);
1624
1583
  }
1625
1584
  visit(fn);
1626
1585
  if (!returned) return void 0;
1627
- if (ts5.isIdentifier(returned)) return localBindings.get(returned.text);
1586
+ if (ts6.isIdentifier(returned)) return localBindings.get(returned.text);
1628
1587
  return findConnectInExpression(returned);
1629
1588
  }
1630
1589
  function directConnectFactFromFunctionLike(fn) {
1631
- if (ts5.isArrowFunction(fn) && fn.body && !ts5.isBlock(fn.body))
1590
+ if (ts6.isArrowFunction(fn) && fn.body && !ts6.isBlock(fn.body))
1632
1591
  return findConnectInExpression(fn.body);
1633
1592
  return directReturnConnectFact(fn);
1634
1593
  }
1635
1594
  function exportedLocalNames(sf) {
1636
1595
  const exports = /* @__PURE__ */ new Map();
1637
1596
  for (const stmt of sf.statements) {
1638
- const direct = ts5.canHaveModifiers(stmt) ? ts5.getModifiers(stmt)?.some(
1639
- (m) => m.kind === ts5.SyntaxKind.ExportKeyword
1597
+ const direct = ts6.canHaveModifiers(stmt) ? ts6.getModifiers(stmt)?.some(
1598
+ (m) => m.kind === ts6.SyntaxKind.ExportKeyword
1640
1599
  ) ?? false : false;
1641
- if (direct && ts5.isFunctionDeclaration(stmt) && stmt.name)
1600
+ if (direct && ts6.isFunctionDeclaration(stmt) && stmt.name)
1642
1601
  exports.set(stmt.name.text, stmt.name.text);
1643
- if (direct && ts5.isVariableStatement(stmt)) {
1602
+ if (direct && ts6.isVariableStatement(stmt)) {
1644
1603
  for (const decl of stmt.declarationList.declarations)
1645
- 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);
1646
1605
  }
1647
- if (!ts5.isExportDeclaration(stmt) || !stmt.exportClause) continue;
1648
- if (!ts5.isNamedExports(stmt.exportClause)) continue;
1606
+ if (!ts6.isExportDeclaration(stmt) || !stmt.exportClause) continue;
1607
+ if (!ts6.isNamedExports(stmt.exportClause)) continue;
1649
1608
  for (const el of stmt.exportClause.elements)
1650
1609
  exports.set(el.name.text, el.propertyName?.text ?? el.name.text);
1651
1610
  }
1652
1611
  return exports;
1653
1612
  }
1654
1613
  async function helperBindings(repoPath, filePath) {
1655
- const sf = await readSource(path8.join(repoPath, filePath));
1614
+ const sf = await readSource(path9.join(repoPath, filePath));
1656
1615
  if (!sf) return [];
1657
1616
  const sourceFileAst = sf;
1658
1617
  const out = [];
1659
1618
  const exportedLocals = exportedLocalNames(sf);
1660
1619
  const factsByLocal = /* @__PURE__ */ new Map();
1661
1620
  for (const stmt of sf.statements) {
1662
- if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
1621
+ if (ts6.isFunctionDeclaration(stmt) && stmt.name) {
1663
1622
  const fact = directConnectFactFromFunctionLike(stmt);
1664
1623
  if (fact) factsByLocal.set(stmt.name.text, { ...fact, sourceLine: lineOf4(sf, stmt) });
1665
1624
  for (const [returnedProperty, objectFact] of collectReturnedObjectBindings(stmt))
1666
1625
  factsByLocal.set(`${stmt.name.text}#${returnedProperty}`, { ...objectFact, returnedProperty, sourceLine: lineOf4(sf, stmt) });
1667
1626
  }
1668
- if (ts5.isVariableStatement(stmt))
1627
+ if (ts6.isVariableStatement(stmt))
1669
1628
  for (const decl of stmt.declarationList.declarations) {
1670
- if (!ts5.isIdentifier(decl.name) || !decl.initializer) continue;
1629
+ if (!ts6.isIdentifier(decl.name) || !decl.initializer) continue;
1671
1630
  const helper = functionLikeInitializer(decl.initializer);
1672
1631
  if (helper) {
1673
1632
  const directReturn = directConnectFactFromFunctionLike(helper);
@@ -1713,7 +1672,7 @@ async function helperBindings(repoPath, filePath) {
1713
1672
  return out;
1714
1673
  }
1715
1674
  async function parseServiceBindings(repoPath, filePath) {
1716
- const sf = await readSource(path8.join(repoPath, filePath));
1675
+ const sf = await readSource(path9.join(repoPath, filePath));
1717
1676
  if (!sf) return [];
1718
1677
  const sourceFileAst = sf;
1719
1678
  const out = [];
@@ -1722,8 +1681,9 @@ async function parseServiceBindings(repoPath, filePath) {
1722
1681
  const classHelpers = collectClassHelpers(sourceFileAst);
1723
1682
  const localObjectHelpers = /* @__PURE__ */ new Map();
1724
1683
  const localDirectHelpers = /* @__PURE__ */ new Map();
1684
+ const objectHelperVariables = /* @__PURE__ */ new Map();
1725
1685
  for (const stmt of sourceFileAst.statements) {
1726
- if (ts5.isFunctionDeclaration(stmt) && stmt.name) {
1686
+ if (ts6.isFunctionDeclaration(stmt) && stmt.name) {
1727
1687
  const directFact = directConnectFactFromFunctionLike(stmt);
1728
1688
  if (directFact) localDirectHelpers.set(stmt.name.text, { ...directFact, exportedName: stmt.name.text, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, stmt) });
1729
1689
  const rows2 = [];
@@ -1731,9 +1691,9 @@ async function parseServiceBindings(repoPath, filePath) {
1731
1691
  rows2.push({ ...fact, exportedName: stmt.name.text, returnedProperty, sourceFile: normalizePath(filePath), sourceLine: lineOf4(sourceFileAst, stmt) });
1732
1692
  if (rows2.length > 0) localObjectHelpers.set(stmt.name.text, rows2);
1733
1693
  }
1734
- if (ts5.isVariableStatement(stmt)) {
1694
+ if (ts6.isVariableStatement(stmt)) {
1735
1695
  for (const decl of stmt.declarationList.declarations) {
1736
- if (!ts5.isIdentifier(decl.name)) continue;
1696
+ if (!ts6.isIdentifier(decl.name)) continue;
1737
1697
  const helper = functionLikeInitializer(decl.initializer);
1738
1698
  if (!helper) continue;
1739
1699
  const directFact = directConnectFactFromFunctionLike(helper);
@@ -1782,9 +1742,9 @@ async function parseServiceBindings(repoPath, filePath) {
1782
1742
  });
1783
1743
  }
1784
1744
  function recordIdentityAlias(decl) {
1785
- if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
1745
+ if (!ts6.isIdentifier(decl.name) || !decl.initializer) return;
1786
1746
  const unwrapped = unwrapIdentityExpression(decl.initializer);
1787
- if (!ts5.isIdentifier(unwrapped)) return;
1747
+ if (!ts6.isIdentifier(unwrapped)) return;
1788
1748
  cloneAliasBinding(decl.name.text, unwrapped.text, "identity", decl);
1789
1749
  }
1790
1750
  async function recordBindingFromExpression(targetName, expr, node, aliasKind) {
@@ -1799,7 +1759,7 @@ async function parseServiceBindings(repoPath, filePath) {
1799
1759
  sourceLine: lineOf4(sourceFileAst, node),
1800
1760
  helperChain: aliasKind === "assignment" ? [{ callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind, scopeRule: "same-file-source-order" }] : void 0
1801
1761
  });
1802
- else if (ts5.isIdentifier(call.expression)) {
1762
+ else if (ts6.isIdentifier(call.expression)) {
1803
1763
  const localDirect = localDirectHelpers.get(call.expression.text);
1804
1764
  const resolved2 = localDirect ? { helper: localDirect, imp: void 0 } : await importedHelper(call.expression.text);
1805
1765
  if (resolved2)
@@ -1829,24 +1789,64 @@ async function parseServiceBindings(repoPath, filePath) {
1829
1789
  }
1830
1790
  }
1831
1791
  async function recordVariable(decl) {
1832
- if (!ts5.isIdentifier(decl.name) || !decl.initializer) return;
1792
+ if (!ts6.isIdentifier(decl.name) || !decl.initializer) return;
1833
1793
  await recordBindingFromExpression(decl.name.text, decl.initializer, decl, "declaration");
1834
1794
  }
1835
1795
  async function helpersForCall(call) {
1836
- if (!ts5.isIdentifier(call.expression)) return [];
1796
+ if (!ts6.isIdentifier(call.expression)) return [];
1837
1797
  const local = localObjectHelpers.get(call.expression.text) ?? [];
1838
1798
  const imported = await importedHelpers(call.expression.text);
1839
1799
  return [...local.map((helper) => ({ helper })), ...imported];
1840
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
+ }
1841
1841
  async function recordDestructuredHelper(decl) {
1842
- if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
1842
+ if (!ts6.isObjectBindingPattern(decl.name) || !decl.initializer) return;
1843
1843
  const call = unwrapCall(decl.initializer);
1844
1844
  if (!call) return;
1845
1845
  const helpers = await helpersForCall(call);
1846
1846
  if (helpers.length === 0) return;
1847
1847
  for (const el of decl.name.elements) {
1848
- if (!ts5.isIdentifier(el.name)) continue;
1849
- 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;
1850
1850
  const matches = helpers.filter((row) => row.helper.returnedProperty === propertyName);
1851
1851
  if (matches.length !== 1) continue;
1852
1852
  const resolved2 = matches[0];
@@ -1872,12 +1872,12 @@ async function parseServiceBindings(repoPath, filePath) {
1872
1872
  for (const prop of pattern.properties) {
1873
1873
  let propertyName;
1874
1874
  let targetName;
1875
- if (ts5.isShorthandPropertyAssignment(prop)) {
1875
+ if (ts6.isShorthandPropertyAssignment(prop)) {
1876
1876
  propertyName = prop.name.text;
1877
1877
  targetName = prop.name.text;
1878
- } else if (ts5.isPropertyAssignment(prop)) {
1879
- propertyName = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1880
- 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;
1881
1881
  }
1882
1882
  if (!propertyName || !targetName) continue;
1883
1883
  const matches = helpers.filter((row) => row.helper.returnedProperty === propertyName);
@@ -1898,14 +1898,14 @@ async function parseServiceBindings(repoPath, filePath) {
1898
1898
  }
1899
1899
  }
1900
1900
  function recordDestructuredClassHelper(decl) {
1901
- if (!ts5.isObjectBindingPattern(decl.name) || !decl.initializer) return;
1901
+ if (!ts6.isObjectBindingPattern(decl.name) || !decl.initializer) return;
1902
1902
  const call = unwrapCall(decl.initializer);
1903
- if (!call || !ts5.isPropertyAccessExpression(call.expression)) return;
1903
+ if (!call || !ts6.isPropertyAccessExpression(call.expression)) return;
1904
1904
  const target = call.expression;
1905
- if (target.expression.kind !== ts5.SyntaxKind.ThisKeyword) return;
1905
+ if (target.expression.kind !== ts6.SyntaxKind.ThisKeyword) return;
1906
1906
  for (const el of decl.name.elements) {
1907
- if (!ts5.isIdentifier(el.name)) continue;
1908
- 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;
1909
1909
  const helper = classHelpers.find(
1910
1910
  (h) => h.helperName === target.name.text && h.propertyName === propertyName
1911
1911
  );
@@ -1931,14 +1931,14 @@ async function parseServiceBindings(repoPath, filePath) {
1931
1931
  }
1932
1932
  function arrayElementsFromExpression(expr) {
1933
1933
  const unwrapped = unwrapIdentityExpression(expr);
1934
- if (ts5.isArrayLiteralExpression(unwrapped)) return { elements: unwrapped.elements, promiseAll: false };
1934
+ if (ts6.isArrayLiteralExpression(unwrapped)) return { elements: unwrapped.elements, promiseAll: false };
1935
1935
  const call = unwrapCall(expr);
1936
1936
  if (!call) return void 0;
1937
- 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;
1938
1938
  const first = call.arguments[0];
1939
1939
  if (!first) return void 0;
1940
1940
  const container = unwrapIdentityExpression(first);
1941
- if (!ts5.isArrayLiteralExpression(container)) return void 0;
1941
+ if (!ts6.isArrayLiteralExpression(container)) return void 0;
1942
1942
  return { elements: container.elements, promiseAll: true };
1943
1943
  }
1944
1944
  async function recordArrayElementBinding(targetName, expr, node, arrayIndex, promiseAll) {
@@ -1953,7 +1953,7 @@ async function parseServiceBindings(repoPath, filePath) {
1953
1953
  return;
1954
1954
  }
1955
1955
  const unwrapped = unwrapIdentityExpression(expr);
1956
- if (ts5.isIdentifier(unwrapped)) {
1956
+ if (ts6.isIdentifier(unwrapped)) {
1957
1957
  const existing = bindingForVariable(unwrapped.text);
1958
1958
  if (!existing) return;
1959
1959
  out.push({
@@ -1968,15 +1968,15 @@ async function parseServiceBindings(repoPath, filePath) {
1968
1968
  }
1969
1969
  }
1970
1970
  async function recordArrayDestructuredVariable(decl) {
1971
- if (!ts5.isArrayBindingPattern(decl.name) || !decl.initializer) return;
1971
+ if (!ts6.isArrayBindingPattern(decl.name) || !decl.initializer) return;
1972
1972
  const container = arrayElementsFromExpression(decl.initializer);
1973
1973
  if (!container) return;
1974
1974
  for (let index = 0; index < decl.name.elements.length; index += 1) {
1975
1975
  const el = decl.name.elements[index];
1976
- if (!el || ts5.isOmittedExpression(el) || ts5.isBindingElement(el) && el.dotDotDotToken) continue;
1977
- 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;
1978
1978
  const source = container.elements[index];
1979
- if (!source || ts5.isOmittedExpression(source)) continue;
1979
+ if (!source || ts6.isOmittedExpression(source)) continue;
1980
1980
  await recordArrayElementBinding(el.name.text, source, decl, index, container.promiseAll);
1981
1981
  }
1982
1982
  }
@@ -1985,49 +1985,52 @@ async function parseServiceBindings(repoPath, filePath) {
1985
1985
  if (!container) return;
1986
1986
  for (let index = 0; index < pattern.elements.length; index += 1) {
1987
1987
  const el = pattern.elements[index];
1988
- 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;
1989
1989
  const source = container.elements[index];
1990
- if (!source || ts5.isOmittedExpression(source)) continue;
1990
+ if (!source || ts6.isOmittedExpression(source)) continue;
1991
1991
  await recordArrayElementBinding(el.text, source, node, index, container.promiseAll);
1992
1992
  }
1993
1993
  }
1994
1994
  const events = [];
1995
1995
  function collectEvents(node) {
1996
- if (ts5.isVariableDeclaration(node)) events.push({ pos: node.getStart(sourceFileAst), node });
1997
- 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)
1998
1998
  events.push({ pos: node.getStart(sourceFileAst), node });
1999
- ts5.forEachChild(node, collectEvents);
1999
+ ts6.forEachChild(node, collectEvents);
2000
2000
  }
2001
2001
  collectEvents(sourceFileAst);
2002
2002
  events.sort((a, b) => a.pos - b.pos);
2003
2003
  for (const event of events) {
2004
- if (ts5.isVariableDeclaration(event.node)) {
2004
+ if (ts6.isVariableDeclaration(event.node)) {
2005
2005
  const decl = event.node;
2006
2006
  await recordDestructuredHelper(decl);
2007
2007
  await recordArrayDestructuredVariable(decl);
2008
2008
  recordDestructuredClassHelper(decl);
2009
+ await rememberObjectHelperVariable(decl);
2010
+ if (ts6.isIdentifier(decl.name) && decl.initializer) recordObjectPropertyBinding(decl.name.text, decl.initializer, decl);
2009
2011
  await recordVariable(decl);
2010
2012
  recordIdentityAlias(decl);
2011
- if (ts5.isIdentifier(decl.name) && decl.initializer) {
2013
+ if (ts6.isIdentifier(decl.name) && decl.initializer) {
2012
2014
  const sourceName = transactionReceiverName(decl.initializer);
2013
2015
  if (sourceName) cloneAliasBinding(decl.name.text, sourceName, "transaction", decl);
2014
2016
  }
2015
2017
  continue;
2016
2018
  }
2017
2019
  const assignment = event.node;
2018
- if (ts5.isIdentifier(assignment.left)) {
2020
+ if (ts6.isIdentifier(assignment.left)) {
2019
2021
  const rhs = unwrapIdentityExpression(assignment.right);
2020
- if (ts5.isIdentifier(rhs)) {
2022
+ if (ts6.isIdentifier(rhs)) {
2021
2023
  cloneAliasBinding(assignment.left.text, rhs.text, "identity-assignment", assignment);
2022
2024
  continue;
2023
2025
  }
2026
+ if (recordObjectPropertyBinding(assignment.left.text, assignment.right, assignment)) continue;
2024
2027
  await recordBindingFromExpression(assignment.left.text, assignment.right, assignment, "assignment");
2025
2028
  continue;
2026
2029
  }
2027
- const left = ts5.isParenthesizedExpression(assignment.left) ? assignment.left.expression : assignment.left;
2028
- if (ts5.isObjectLiteralExpression(left))
2030
+ const left = ts6.isParenthesizedExpression(assignment.left) ? assignment.left.expression : assignment.left;
2031
+ if (ts6.isObjectLiteralExpression(left))
2029
2032
  await recordDestructuredAssignment(left, assignment.right, assignment);
2030
- if (ts5.isArrayLiteralExpression(left))
2033
+ if (ts6.isArrayLiteralExpression(left))
2031
2034
  await recordArrayDestructuredAssignment(left, assignment.right, assignment);
2032
2035
  }
2033
2036
  return out;
@@ -2035,16 +2038,16 @@ async function parseServiceBindings(repoPath, filePath) {
2035
2038
  function collectClassHelpers(sf) {
2036
2039
  const helpers = [];
2037
2040
  for (const stmt of sf.statements) {
2038
- if (!ts5.isClassDeclaration(stmt) || !stmt.name) continue;
2041
+ if (!ts6.isClassDeclaration(stmt) || !stmt.name) continue;
2039
2042
  for (const member of stmt.members) {
2040
2043
  let visit2 = function(node) {
2041
- if (ts5.isVariableDeclaration(node) && ts5.isIdentifier(node.name) && node.initializer) {
2044
+ if (ts6.isVariableDeclaration(node) && ts6.isIdentifier(node.name) && node.initializer) {
2042
2045
  const fact = findConnectInExpression(node.initializer);
2043
2046
  if (fact) bindings.set(node.name.text, fact);
2044
2047
  }
2045
- if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
2048
+ if (ts6.isReturnStatement(node) && node.expression && ts6.isObjectLiteralExpression(node.expression)) {
2046
2049
  for (const prop of node.expression.properties) {
2047
- if (ts5.isShorthandPropertyAssignment(prop)) {
2050
+ if (ts6.isShorthandPropertyAssignment(prop)) {
2048
2051
  const fact = bindings.get(prop.name.text);
2049
2052
  if (fact)
2050
2053
  helpers.push({
@@ -2056,8 +2059,8 @@ function collectClassHelpers(sf) {
2056
2059
  sourceLine: lineOf4(sf, prop)
2057
2060
  });
2058
2061
  }
2059
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
2060
- 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;
2061
2064
  const fact = propertyName ? bindings.get(prop.initializer.text) : void 0;
2062
2065
  if (propertyName && fact)
2063
2066
  helpers.push({
@@ -2071,15 +2074,15 @@ function collectClassHelpers(sf) {
2071
2074
  }
2072
2075
  }
2073
2076
  }
2074
- ts5.forEachChild(node, visit2);
2077
+ ts6.forEachChild(node, visit2);
2075
2078
  };
2076
2079
  var visit = visit2;
2077
- if (!ts5.isPropertyDeclaration(member) || !member.initializer) continue;
2078
- if (!ts5.isIdentifier(member.name)) continue;
2080
+ if (!ts6.isPropertyDeclaration(member) || !member.initializer) continue;
2081
+ if (!ts6.isIdentifier(member.name)) continue;
2079
2082
  const className = stmt.name.text;
2080
2083
  const helperName = member.name.text;
2081
2084
  const initializer = member.initializer;
2082
- if (!ts5.isArrowFunction(initializer) && !ts5.isFunctionExpression(initializer))
2085
+ if (!ts6.isArrowFunction(initializer) && !ts6.isFunctionExpression(initializer))
2083
2086
  continue;
2084
2087
  const bindings = /* @__PURE__ */ new Map();
2085
2088
  visit2(initializer);
@@ -2524,7 +2527,11 @@ function linkImplementations(db, workspaceId, generation) {
2524
2527
  const accepted = candidates.filter((candidate) => candidate.accepted);
2525
2528
  const topScore = accepted[0]?.score ?? 0;
2526
2529
  const winners = accepted.filter((candidate) => candidate.score === topScore);
2527
- 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"] : [];
2528
2535
  const evidence = {
2529
2536
  servicePath: operation.servicePath,
2530
2537
  operationPath: operation.operationPath,
@@ -2533,6 +2540,8 @@ function linkImplementations(db, workspaceId, generation) {
2533
2540
  implementationSource: implementationContext.operationId === operation.operationId ? "direct_or_concrete_override" : "inherited_from_base_operation",
2534
2541
  baseOperationId: operation.baseOperationId,
2535
2542
  implementationOperationId: implementationContext.operationId,
2543
+ ambiguityReasons,
2544
+ candidateFamilies: duplicateFamilies,
2536
2545
  candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
2537
2546
  };
2538
2547
  if (accepted.length === 0) {
@@ -2541,7 +2550,7 @@ function linkImplementations(db, workspaceId, generation) {
2541
2550
  unresolvedCount += 1;
2542
2551
  continue;
2543
2552
  }
2544
- 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);
2545
2554
  edgeCount += 1;
2546
2555
  if (unique) resolvedCount += 1;
2547
2556
  else ambiguousCount += 1;
@@ -2585,6 +2594,18 @@ function uniqueRegistrations(rows2) {
2585
2594
  return true;
2586
2595
  });
2587
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
+ }
2588
2609
  function implementationCandidates(db, workspaceId, operation) {
2589
2610
  const modelRepoGraphId = graphId(operation.modelRepoId);
2590
2611
  return db.prepare(`SELECT DISTINCT
@@ -2768,6 +2789,154 @@ function normalizedOperation(value) {
2768
2789
  return value.startsWith("/") ? value.slice(1) : value;
2769
2790
  }
2770
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
+
2771
2940
  // src/trace/trace-engine.ts
2772
2941
  function normalizeOperation(value) {
2773
2942
  if (!value) return void 0;
@@ -2776,7 +2945,7 @@ function normalizeOperation(value) {
2776
2945
  function positiveDepth(value) {
2777
2946
  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
2778
2947
  }
2779
- function operationStartScope(db, repoId, start) {
2948
+ function operationStartScope(db, repoId, start, implementationRepo) {
2780
2949
  const requested = normalizeOperation(start.operationPath ?? start.operation);
2781
2950
  if (!requested) return void 0;
2782
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
@@ -2792,7 +2961,15 @@ function operationStartScope(db, repoId, start) {
2792
2961
  const operationId = String(rows2[0]?.operationId);
2793
2962
  const impl = implementationScope(db, operationId);
2794
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: [] };
2795
- 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
+ }
2796
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" }] };
2797
2974
  }
2798
2975
  function sourceFilesForStart(db, repoId, start) {
@@ -2835,12 +3012,12 @@ function sourceFilesForStart(db, repoId, start) {
2835
3012
  }
2836
3013
  return void 0;
2837
3014
  }
2838
- function startScope(db, start) {
3015
+ function startScope(db, start, implementationRepo) {
2839
3016
  const repo = start.repo ? db.prepare(
2840
3017
  "SELECT id,name FROM repositories WHERE name=? OR package_name=?"
2841
3018
  ).get(start.repo, start.repo) : void 0;
2842
3019
  if (start.repo && !repo) return { repo, selectorMatched: false };
2843
- const operationScope = operationStartScope(db, repo?.id, start);
3020
+ const operationScope = operationStartScope(db, repo?.id, start, implementationRepo);
2844
3021
  const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === "operation" || d.resolutionStage === "implementation");
2845
3022
  const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
2846
3023
  const sourceFiles = sourceScope?.files;
@@ -2887,7 +3064,26 @@ function implementationScope(db, operationId) {
2887
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);
2888
3065
  return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
2889
3066
  }
2890
- 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;
2891
3087
  if (!edge || edge.status !== "ambiguous" || callerRepoId === void 0) return { evidence: { status: "not_applicable" } };
2892
3088
  const evidence = JSON.parse(String(edge.evidence_json || "{}"));
2893
3089
  const scores = (evidence.candidates ?? []).filter((item) => item.accepted).map((item) => {
@@ -2940,31 +3136,6 @@ function graphForCalls(db, callIds) {
2940
3136
  }
2941
3137
  return map;
2942
3138
  }
2943
- function hasRuntimeVariable(value, vars) {
2944
- return typeof value === "string" && extractPlaceholders(value).some((key) => Object.hasOwn(vars, key));
2945
- }
2946
- function isRemoteRuntimeCandidate(row, evidence, vars) {
2947
- if (!vars || Object.keys(vars).length === 0) return false;
2948
- if (!["dynamic", "ambiguous", "unresolved"].includes(String(row.status ?? ""))) return false;
2949
- if (!["DYNAMIC_EDGE_CANDIDATE", "UNRESOLVED_EDGE", "REMOTE_CALL_RESOLVES_TO_OPERATION"].includes(row.edge_type)) return false;
2950
- if (row.status === "resolved") return false;
2951
- return ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"].some((key) => hasRuntimeVariable(evidence[key], vars));
2952
- }
2953
- function evidenceWithRuntimeVariables(evidence, vars) {
2954
- if (!vars || Object.keys(vars).length === 0) return evidence;
2955
- const substitutions = {};
2956
- for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
2957
- const substitution = substituteVariables(typeof evidence[key] === "string" ? String(evidence[key]) : void 0, vars);
2958
- if (substitution.placeholders.length > 0) substitutions[key] = substitution;
2959
- }
2960
- const next = { ...evidence, runtimeVariablesApplied: true, runtimeSubstitutions: substitutions };
2961
- for (const [key, value] of Object.entries(substitutions)) {
2962
- if (value.effective) next[key] = value.effective;
2963
- }
2964
- const missing = Object.values(substitutions).flatMap((value) => value.missing);
2965
- if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)];
2966
- return next;
2967
- }
2968
3139
  function symbolNode(db, symbolId) {
2969
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);
2970
3141
  if (!row) return void 0;
@@ -2979,24 +3150,6 @@ function operationNode(db, operationId) {
2979
3150
  function workspaceIdForCall(db, callId) {
2980
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;
2981
3152
  }
2982
- function runtimeResolution(db, row, evidence, vars) {
2983
- if (!isRemoteRuntimeCandidate(row, evidence, vars))
2984
- return { row, evidence, unresolvedReason: row.unresolved_reason };
2985
- const nextEvidence = evidenceWithRuntimeVariables(evidence, vars);
2986
- const servicePath = typeof nextEvidence.servicePath === "string" ? nextEvidence.servicePath : void 0;
2987
- const operationPath = typeof nextEvidence.normalizedOperationPath === "string" ? nextEvidence.normalizedOperationPath : typeof nextEvidence.operationPath === "string" ? nextEvidence.operationPath : void 0;
2988
- const alias = typeof nextEvidence.serviceAliasExpr === "string" ? nextEvidence.serviceAliasExpr : typeof nextEvidence.serviceAlias === "string" ? nextEvidence.serviceAlias : void 0;
2989
- const destination = typeof nextEvidence.destination === "string" ? nextEvidence.destination : void 0;
2990
- const resolution = resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceIdForCall(db, row.from_id));
2991
- nextEvidence.runtimeResolutionStatus = resolution.status;
2992
- nextEvidence.runtimeResolutionReasons = resolution.reasons;
2993
- if (resolution.target) {
2994
- nextEvidence.runtimeResolvedCandidate = resolution.target;
2995
- 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 };
2996
- }
2997
- 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";
2998
- return { row, evidence: nextEvidence, unresolvedReason };
2999
- }
3000
3153
  function parseEvidence(value) {
3001
3154
  try {
3002
3155
  const parsed = JSON.parse(String(value || "{}"));
@@ -3096,27 +3249,8 @@ function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRo
3096
3249
  if (persistedResolved) return { row: void 0, evidence: { ...resolvedEvidence, contextualPreservedPersistedResolvedEdge: true }, unresolvedReason: void 0 };
3097
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 };
3098
3251
  }
3099
- function edgeTarget(row, evidence) {
3100
- const runtimeCandidate = evidence.runtimeResolvedCandidate;
3101
- if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath)
3102
- return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
3103
- const targetServicePath = typeof evidence.targetServicePath === "string" ? evidence.targetServicePath : void 0;
3104
- const targetOperationPath = typeof evidence.targetOperationPath === "string" ? evidence.targetOperationPath : void 0;
3105
- if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
3106
- const servicePath = typeof evidence.servicePath === "string" ? evidence.servicePath : void 0;
3107
- const operationPath = typeof evidence.operationPath === "string" ? evidence.operationPath : void 0;
3108
- const targetOperation = typeof evidence.targetOperation === "string" ? evidence.targetOperation : void 0;
3109
- const targetRepo = typeof evidence.targetRepo === "string" ? evidence.targetRepo : "";
3110
- if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
3111
- if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return typeof evidence.remoteQueryTarget === "string" ? evidence.remoteQueryTarget : `Remote query: ${row.to_id || "unknown"}`;
3112
- if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
3113
- const target = evidence.externalTarget;
3114
- return typeof target?.label === "string" ? target.label : `External endpoint: ${row.to_id || "unknown"}`;
3115
- }
3116
- return servicePath && operationPath ? `${servicePath}${operationPath}` : targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
3117
- }
3118
3252
  function trace(db, start, options) {
3119
- const scope = startScope(db, start);
3253
+ const scope = startScope(db, start, options.implementationRepo);
3120
3254
  const diagnostics = db.prepare(
3121
3255
  "SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics WHERE (? IS NULL OR repo_id=?)"
3122
3256
  ).all(scope.repo?.id, scope.repo?.id);
@@ -3139,12 +3273,14 @@ function trace(db, start, options) {
3139
3273
  const op = operationNode(db, scope.startOperationId);
3140
3274
  const impl = implementationScope(db, scope.startOperationId);
3141
3275
  if (op) nodes.set(String(op.id), op);
3142
- if (impl.edge && impl.edge.status === "resolved") {
3143
- 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 } };
3144
- 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;
3145
3281
  if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
3146
3282
  seenEdges.add(Number(impl.edge.id));
3147
- 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) });
3148
3284
  }
3149
3285
  }
3150
3286
  const seenScopes = /* @__PURE__ */ new Set();
@@ -3201,8 +3337,8 @@ function trace(db, start, options) {
3201
3337
  const persistedEvidence = JSON.parse(
3202
3338
  String(row.evidence_json || "{}")
3203
3339
  );
3204
- 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) };
3205
- 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)));
3206
3342
  const evidence = effective.evidence;
3207
3343
  const effectiveRow = effective.row;
3208
3344
  const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
@@ -3224,7 +3360,7 @@ function trace(db, start, options) {
3224
3360
  });
3225
3361
  if (effectiveRow.to_kind === "operation") {
3226
3362
  const implementation = implementationScope(db, effectiveRow.to_id);
3227
- const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence);
3363
+ const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence, options.implementationRepo);
3228
3364
  const contextMethodId = contextSelection.methodId;
3229
3365
  const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : void 0;
3230
3366
  if (implementation.edge) {
@@ -3237,7 +3373,7 @@ function trace(db, start, options) {
3237
3373
  type: "operation_implemented_by_handler",
3238
3374
  from: to,
3239
3375
  to: implTo,
3240
- 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 },
3241
3377
  confidence: Number(implementation.edge.confidence ?? 0),
3242
3378
  unresolvedReason: implementation.edge.status === "resolved" || contextMethodId ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status)
3243
3379
  });
@@ -3273,6 +3409,8 @@ function trace(db, start, options) {
3273
3409
  }
3274
3410
  }
3275
3411
  }
3412
+ const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
3413
+ if (runtimeDiagnostic) diagnostics.unshift(runtimeDiagnostic);
3276
3414
  return { start, nodes: [...nodes.values()], edges, diagnostics };
3277
3415
  }
3278
3416
 
@@ -3297,4 +3435,4 @@ export {
3297
3435
  linkWorkspace,
3298
3436
  trace
3299
3437
  };
3300
- //# sourceMappingURL=chunk-KHQK7CFH.js.map
3438
+ //# sourceMappingURL=chunk-UKNPHTUS.js.map