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