@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.
- package/CHANGELOG.md +7 -1
- package/dist/{chunk-KHQK7CFH.js → chunk-UKNPHTUS.js} +445 -307
- package/dist/chunk-UKNPHTUS.js.map +1 -0
- package/dist/cli.js +417 -466
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +398 -0
- package/src/cli.ts +9 -452
- package/src/indexer/repository-indexer.ts +52 -26
- package/src/indexer/workspace-indexer.ts +17 -5
- package/src/linker/cross-repo-linker.ts +22 -2
- package/src/parsers/outbound-call-parser.ts +12 -4
- package/src/parsers/service-binding-parser-helpers.ts +160 -0
- package/src/parsers/service-binding-parser.ts +57 -242
- package/src/trace/evidence.ts +205 -0
- package/src/trace/trace-engine.ts +57 -106
- package/dist/chunk-KHQK7CFH.js.map +0 -1
|
@@ -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(
|
|
582
|
-
if (
|
|
583
|
-
const raw =
|
|
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(
|
|
606
|
-
const rawPath = (
|
|
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(
|
|
655
|
-
const first =
|
|
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
|
-
|
|
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
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
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
|
-
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
-
|
|
1524
|
-
|
|
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
|
-
|
|
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 && (
|
|
1528
|
+
if (node !== fn && (ts6.isFunctionDeclaration(node) || ts6.isArrowFunction(node) || ts6.isFunctionExpression(node)))
|
|
1570
1529
|
return;
|
|
1571
|
-
if (
|
|
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
|
-
|
|
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 && (
|
|
1548
|
+
if (node !== fn && (ts6.isFunctionDeclaration(node) || ts6.isArrowFunction(node) || ts6.isFunctionExpression(node)))
|
|
1590
1549
|
return;
|
|
1591
|
-
if (
|
|
1550
|
+
if (ts6.isReturnStatement(node) && node.expression && ts6.isObjectLiteralExpression(node.expression)) {
|
|
1592
1551
|
for (const prop of node.expression.properties) {
|
|
1593
|
-
if (
|
|
1594
|
-
if (
|
|
1595
|
-
const propertyName =
|
|
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
|
-
|
|
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 (
|
|
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 && (
|
|
1578
|
+
if (node !== fn && (ts6.isFunctionDeclaration(node) || ts6.isArrowFunction(node) || ts6.isFunctionExpression(node)))
|
|
1620
1579
|
return;
|
|
1621
|
-
if (!returned &&
|
|
1580
|
+
if (!returned && ts6.isReturnStatement(node) && node.expression)
|
|
1622
1581
|
returned = node.expression;
|
|
1623
|
-
if (!returned)
|
|
1582
|
+
if (!returned) ts6.forEachChild(node, visit);
|
|
1624
1583
|
}
|
|
1625
1584
|
visit(fn);
|
|
1626
1585
|
if (!returned) return void 0;
|
|
1627
|
-
if (
|
|
1586
|
+
if (ts6.isIdentifier(returned)) return localBindings.get(returned.text);
|
|
1628
1587
|
return findConnectInExpression(returned);
|
|
1629
1588
|
}
|
|
1630
1589
|
function directConnectFactFromFunctionLike(fn) {
|
|
1631
|
-
if (
|
|
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 =
|
|
1639
|
-
(m) => m.kind ===
|
|
1597
|
+
const direct = ts6.canHaveModifiers(stmt) ? ts6.getModifiers(stmt)?.some(
|
|
1598
|
+
(m) => m.kind === ts6.SyntaxKind.ExportKeyword
|
|
1640
1599
|
) ?? false : false;
|
|
1641
|
-
if (direct &&
|
|
1600
|
+
if (direct && ts6.isFunctionDeclaration(stmt) && stmt.name)
|
|
1642
1601
|
exports.set(stmt.name.text, stmt.name.text);
|
|
1643
|
-
if (direct &&
|
|
1602
|
+
if (direct && ts6.isVariableStatement(stmt)) {
|
|
1644
1603
|
for (const decl of stmt.declarationList.declarations)
|
|
1645
|
-
if (
|
|
1604
|
+
if (ts6.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
|
|
1646
1605
|
}
|
|
1647
|
-
if (!
|
|
1648
|
-
if (!
|
|
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(
|
|
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 (
|
|
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 (
|
|
1627
|
+
if (ts6.isVariableStatement(stmt))
|
|
1669
1628
|
for (const decl of stmt.declarationList.declarations) {
|
|
1670
|
-
if (!
|
|
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(
|
|
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 (
|
|
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 (
|
|
1694
|
+
if (ts6.isVariableStatement(stmt)) {
|
|
1735
1695
|
for (const decl of stmt.declarationList.declarations) {
|
|
1736
|
-
if (!
|
|
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 (!
|
|
1745
|
+
if (!ts6.isIdentifier(decl.name) || !decl.initializer) return;
|
|
1786
1746
|
const unwrapped = unwrapIdentityExpression(decl.initializer);
|
|
1787
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
1849
|
-
const propertyName = el.propertyName &&
|
|
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 (
|
|
1875
|
+
if (ts6.isShorthandPropertyAssignment(prop)) {
|
|
1876
1876
|
propertyName = prop.name.text;
|
|
1877
1877
|
targetName = prop.name.text;
|
|
1878
|
-
} else if (
|
|
1879
|
-
propertyName =
|
|
1880
|
-
targetName =
|
|
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 (!
|
|
1901
|
+
if (!ts6.isObjectBindingPattern(decl.name) || !decl.initializer) return;
|
|
1902
1902
|
const call = unwrapCall(decl.initializer);
|
|
1903
|
-
if (!call || !
|
|
1903
|
+
if (!call || !ts6.isPropertyAccessExpression(call.expression)) return;
|
|
1904
1904
|
const target = call.expression;
|
|
1905
|
-
if (target.expression.kind !==
|
|
1905
|
+
if (target.expression.kind !== ts6.SyntaxKind.ThisKeyword) return;
|
|
1906
1906
|
for (const el of decl.name.elements) {
|
|
1907
|
-
if (!
|
|
1908
|
-
const propertyName = el.propertyName &&
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 ||
|
|
1977
|
-
if (!
|
|
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 ||
|
|
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 ||
|
|
1988
|
+
if (!el || ts6.isOmittedExpression(el) || ts6.isSpreadElement(el) || !ts6.isIdentifier(el)) continue;
|
|
1989
1989
|
const source = container.elements[index];
|
|
1990
|
-
if (!source ||
|
|
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 (
|
|
1997
|
-
if (
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
2020
|
+
if (ts6.isIdentifier(assignment.left)) {
|
|
2019
2021
|
const rhs = unwrapIdentityExpression(assignment.right);
|
|
2020
|
-
if (
|
|
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 =
|
|
2028
|
-
if (
|
|
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 (
|
|
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 (!
|
|
2041
|
+
if (!ts6.isClassDeclaration(stmt) || !stmt.name) continue;
|
|
2039
2042
|
for (const member of stmt.members) {
|
|
2040
2043
|
let visit2 = function(node) {
|
|
2041
|
-
if (
|
|
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 (
|
|
2048
|
+
if (ts6.isReturnStatement(node) && node.expression && ts6.isObjectLiteralExpression(node.expression)) {
|
|
2046
2049
|
for (const prop of node.expression.properties) {
|
|
2047
|
-
if (
|
|
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 (
|
|
2060
|
-
const propertyName =
|
|
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
|
-
|
|
2077
|
+
ts6.forEachChild(node, visit2);
|
|
2075
2078
|
};
|
|
2076
2079
|
var visit = visit2;
|
|
2077
|
-
if (!
|
|
2078
|
-
if (!
|
|
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 (!
|
|
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
|
|
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) :
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3143
|
-
|
|
3144
|
-
const
|
|
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 =
|
|
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:
|
|
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-
|
|
3438
|
+
//# sourceMappingURL=chunk-UKNPHTUS.js.map
|