@saptools/service-flow 0.1.23 → 0.1.25
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 +19 -0
- package/dist/{chunk-LCXDOSWL.js → chunk-HS746OHV.js} +121 -24
- package/dist/chunk-HS746OHV.js.map +1 -0
- package/dist/cli.js +164 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LCXDOSWL.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.25
|
|
4
|
+
|
|
5
|
+
- Make class-instance symbol-call indexing conservative so only same-file and relatively imported helper classes are traced, while built-in collection, date, URL, error, typed-array, promise, and abort-controller instances are ignored.
|
|
6
|
+
- Persist identifier and one-level destructured object parameter metadata for executable symbols, including class methods, so contextual service-client binding propagation can map helper arguments to callee receiver names.
|
|
7
|
+
- Propagate contextual service bindings through positional helper arguments and destructured object helper parameters with auditable caller and callee evidence.
|
|
8
|
+
- Refine unresolved operation diagnostics when indexed candidates exist but service context is absent or the resolution score is below threshold.
|
|
9
|
+
|
|
10
|
+
## 0.1.24
|
|
11
|
+
|
|
12
|
+
- Add conservative class instance method symbol-call resolution for same-file and relatively imported helper classes, with auditable class-instance evidence.
|
|
13
|
+
- Propagate service-client binding context through resolved local symbol calls for explicit positional and object-literal helper arguments during trace rendering.
|
|
14
|
+
- Harden one-hop higher-order send wrapper literal path propagation for returned async closures and expose wrapper definition evidence.
|
|
15
|
+
- Refine strict doctor categories for no-binding remote actions and split ambiguous versus unresolved implementation diagnostics with capped examples.
|
|
16
|
+
|
|
17
|
+
## 0.1.23
|
|
18
|
+
|
|
19
|
+
- Retrospective note: preserve same-file late-assignment service binding extraction for connected clients, `cds.connect.to(...)` assignments, identity aliases, and simple transaction aliases before outbound `send(...)` calls.
|
|
20
|
+
- Retrospective note: preserve explicit object destructuring from safe helper-returned client objects so binding rows remain available for downstream remote action linking.
|
|
21
|
+
|
|
3
22
|
## 0.1.22
|
|
4
23
|
|
|
5
24
|
- Propagate conservative same-file identity aliases of connected service clients, including typed, `as`, `satisfies`, helper-returned, and transitive aliases, while preserving `.tx()` alias evidence.
|
|
@@ -642,28 +642,25 @@ function collectWrapperSpecs(source) {
|
|
|
642
642
|
const specs = /* @__PURE__ */ new Map();
|
|
643
643
|
const scanFunction = (name, fn) => {
|
|
644
644
|
const params = fn.parameters.map((param) => ts4.isIdentifier(param.name) ? param.name.text : void 0);
|
|
645
|
-
|
|
645
|
+
const closure = returnedClosure(fn) ?? fn;
|
|
646
|
+
const sends = [];
|
|
646
647
|
const visit = (node) => {
|
|
647
|
-
if (found) return;
|
|
648
|
-
if (node !== fn && (ts4.isFunctionDeclaration(node) || ts4.isArrowFunction(node) || ts4.isFunctionExpression(node))) {
|
|
649
|
-
ts4.forEachChild(node, visit);
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
648
|
if (ts4.isCallExpression(node) && ts4.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts4.isIdentifier(node.expression.expression)) {
|
|
653
649
|
const objectArg = node.arguments[0];
|
|
654
650
|
if (objectArg && ts4.isObjectLiteralExpression(objectArg)) {
|
|
655
651
|
const pathProp = objectArg.properties.find((property) => ts4.isShorthandPropertyAssignment(property) && property.name.text === "path");
|
|
656
|
-
if (pathProp)
|
|
652
|
+
if (pathProp) sends.push({ client: node.expression.expression.text, path: pathProp.name.text, method: objectPropertyText(objectArg, "method") });
|
|
657
653
|
}
|
|
658
654
|
}
|
|
659
655
|
ts4.forEachChild(node, visit);
|
|
660
656
|
};
|
|
661
|
-
visit(
|
|
662
|
-
if (
|
|
657
|
+
visit(closure);
|
|
658
|
+
if (sends.length !== 1) return;
|
|
659
|
+
const found = sends[0];
|
|
663
660
|
const clientIndex = params.indexOf(found.client);
|
|
664
661
|
const pathIndex = params.indexOf(found.path);
|
|
665
662
|
const methodIndex = found.method && params.includes(found.method) ? params.indexOf(found.method) : void 0;
|
|
666
|
-
if (clientIndex >= 0 && pathIndex >= 0) specs.set(name, { clientIndex, pathIndex, methodIndex });
|
|
663
|
+
if (clientIndex >= 0 && pathIndex >= 0) specs.set(name, { clientIndex, pathIndex, methodIndex, definitionLine: lineOf3(source.text, fn.getStart(source)) });
|
|
667
664
|
};
|
|
668
665
|
for (const stmt of source.statements) {
|
|
669
666
|
if (ts4.isFunctionDeclaration(stmt) && stmt.name) scanFunction(stmt.name.text, stmt);
|
|
@@ -673,6 +670,16 @@ function collectWrapperSpecs(source) {
|
|
|
673
670
|
}
|
|
674
671
|
return specs;
|
|
675
672
|
}
|
|
673
|
+
function returnedClosure(fn) {
|
|
674
|
+
const body = fn.body;
|
|
675
|
+
if (!body) return void 0;
|
|
676
|
+
if (ts4.isArrowFunction(fn) && !ts4.isBlock(body)) return ts4.isArrowFunction(body) || ts4.isFunctionExpression(body) ? body : void 0;
|
|
677
|
+
if (!ts4.isBlock(body)) return void 0;
|
|
678
|
+
const returns = body.statements.filter(ts4.isReturnStatement);
|
|
679
|
+
if (returns.length !== 1) return void 0;
|
|
680
|
+
const expr = returns[0]?.expression;
|
|
681
|
+
return expr && (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) ? expr : void 0;
|
|
682
|
+
}
|
|
676
683
|
function classifyOutboundCallsInSource(source, filePath) {
|
|
677
684
|
const calls = [];
|
|
678
685
|
const sourceFile = normalizePath(filePath);
|
|
@@ -691,22 +698,24 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
691
698
|
const entity = arg ? queryEntityFromAst(arg, initializers) : void 0;
|
|
692
699
|
const payload = arg?.getText(source) ?? "";
|
|
693
700
|
add(node, { callType: "local_db_query", queryEntity: entity, payloadSummary: summarizeExpression(payload), confidence: entity ? 0.9 : 0.55, unresolvedReason: entity ? void 0 : queryWarning(payload) });
|
|
694
|
-
} else if (ts4.isPropertyAccessExpression(expr) && expr.name.text === "send" && ts4.isIdentifier(expr.expression)) {
|
|
701
|
+
} else if (ts4.isPropertyAccessExpression(expr) && expr.name.text === "send" && (ts4.isIdentifier(expr.expression) || ts4.isPropertyAccessExpression(expr.expression))) {
|
|
695
702
|
const objectArg = node.arguments[0];
|
|
696
703
|
if (objectArg && ts4.isObjectLiteralExpression(objectArg)) {
|
|
697
|
-
const receiver = expr.expression
|
|
704
|
+
const receiver = receiverName(expr.expression);
|
|
698
705
|
const query = objectPropertyText(objectArg, "query");
|
|
699
706
|
const op = objectPropertyText(objectArg, "path") ?? objectPropertyText(objectArg, "event");
|
|
700
707
|
const shorthandPath = objectPropertyIsShorthand(objectArg, "path");
|
|
701
708
|
add(node, { callType: query ? "remote_query" : "remote_action", serviceVariableName: receiver, method: stripQuotes(objectPropertyText(objectArg, "method") ?? "POST"), operationPathExpr: op && !shorthandPath ? `/${stripQuotes(op).replace(/^\//, "")}` : void 0, queryEntity: query ? extractQueryEntity(query) : void 0, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || query ? 0.8 : 0.4, unresolvedReason: !query && shorthandPath ? "dynamic_operation_path_identifier" : void 0 }, { receiver, classifier: "service_client_send_object", operationPathExpression: shorthandPath ? op : void 0, parserWarning: shorthandPath ? "dynamic_operation_path_identifier" : void 0 });
|
|
702
709
|
}
|
|
703
|
-
} else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text)) {
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
const
|
|
710
|
+
} else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts4.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
|
|
711
|
+
const wrapperName = ts4.isIdentifier(expr) ? expr.text : ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) ? expr.expression.text : "";
|
|
712
|
+
const wrapperArgs = ts4.isIdentifier(expr) ? node.arguments : ts4.isCallExpression(expr) ? expr.arguments : node.arguments;
|
|
713
|
+
const spec = wrapperSpecs.get(wrapperName);
|
|
714
|
+
const clientArg = spec ? wrapperArgs[spec.clientIndex] : void 0;
|
|
715
|
+
const pathArg = spec ? wrapperArgs[spec.pathIndex] : void 0;
|
|
716
|
+
const methodArg = spec?.methodIndex === void 0 ? void 0 : wrapperArgs[spec.methodIndex];
|
|
708
717
|
if (spec && clientArg && ts4.isIdentifier(clientArg) && isStringLike(pathArg)) {
|
|
709
|
-
add(node, { callType: "remote_action", serviceVariableName: clientArg.text, method: stripQuotes(methodArg?.getText(source) ?? "POST"), operationPathExpr: `/${pathArg.text.replace(/^\//, "")}`, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver: clientArg.text, classifier: "
|
|
718
|
+
add(node, { callType: "remote_action", serviceVariableName: clientArg.text, method: stripQuotes(methodArg?.getText(source) ?? "POST"), operationPathExpr: `/${pathArg.text.replace(/^\//, "")}`, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver: clientArg.text, classifier: "higher_order_wrapper_literal_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, literalCallerArgumentDetected: true });
|
|
710
719
|
}
|
|
711
720
|
} else if (ts4.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
|
|
712
721
|
const receiver = receiverName(expr.expression);
|
|
@@ -1758,13 +1767,21 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
1758
1767
|
}
|
|
1759
1768
|
const edgeType = callType === "local_db_query" ? "HANDLER_RUNS_DB_QUERY" : callType === "external_http" ? "HANDLER_CALLS_EXTERNAL_HTTP" : callType === "async_emit" ? "HANDLER_EMITS_EVENT" : callType === "async_subscribe" ? "EVENT_CONSUMED_BY_HANDLER" : resolution.status === "dynamic" ? "DYNAMIC_EDGE_CANDIDATE" : "UNRESOLVED_EDGE";
|
|
1760
1769
|
const status = edgeType === "DYNAMIC_EDGE_CANDIDATE" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : edgeType === "UNRESOLVED_EDGE" ? "unresolved" : "terminal";
|
|
1761
|
-
const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? (resolution
|
|
1770
|
+
const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? unresolvedOperationReason(resolution));
|
|
1762
1771
|
const targetKind = callType === "local_db_query" ? "db_entity" : callType.startsWith("async_") ? "event" : "external";
|
|
1763
1772
|
const targetId = callType === "local_db_query" ? String(call.query_entity ?? "unknown") : callType === "remote_action" ? op ? `Remote action: ${op}` : call.unresolved_reason === "dynamic_operation_path_identifier" ? "Remote action: dynamic path" : "Remote action: unknown path" : String(call.event_name_expr ?? op ?? call.id);
|
|
1764
1773
|
const graphLevelDynamic = edgeType === "DYNAMIC_EDGE_CANDIDATE" && resolution.status === "dynamic";
|
|
1765
1774
|
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, edgeType, status, "call", String(call.id), targetKind, targetId, Number(call.confidence ?? 0.2), JSON.stringify(evidence), graphLevelDynamic ? 1 : 0, unresolvedReason, generation);
|
|
1766
1775
|
return { status, callType };
|
|
1767
1776
|
}
|
|
1777
|
+
function unresolvedOperationReason(resolution) {
|
|
1778
|
+
if (resolution.status === "dynamic") return `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}`;
|
|
1779
|
+
if (resolution.candidates.length === 0) return "No indexed target operation matched";
|
|
1780
|
+
if (resolution.reasons.includes("operation_path_only_has_no_strong_target_signal")) return "Operation candidates found but no strong service signal is available";
|
|
1781
|
+
if (resolution.reasons.includes("candidate_score_below_resolution_threshold")) return "Operation candidates found but resolution score is below threshold";
|
|
1782
|
+
if (resolution.status === "ambiguous") return "Ambiguous operation candidates require a strong service signal";
|
|
1783
|
+
return "Operation candidates found but resolution could not select a target";
|
|
1784
|
+
}
|
|
1768
1785
|
function parseJson(value) {
|
|
1769
1786
|
if (!value) return void 0;
|
|
1770
1787
|
try {
|
|
@@ -2237,6 +2254,84 @@ function runtimeResolution(db, row, evidence, vars) {
|
|
|
2237
2254
|
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";
|
|
2238
2255
|
return { row, evidence: nextEvidence, unresolvedReason };
|
|
2239
2256
|
}
|
|
2257
|
+
function parseEvidence(value) {
|
|
2258
|
+
try {
|
|
2259
|
+
const parsed = JSON.parse(String(value || "{}"));
|
|
2260
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
2261
|
+
} catch {
|
|
2262
|
+
return {};
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
function receiverFromEvidence(value) {
|
|
2266
|
+
const evidence = parseEvidence(value);
|
|
2267
|
+
return typeof evidence.receiver === "string" ? evidence.receiver : void 0;
|
|
2268
|
+
}
|
|
2269
|
+
function knownBindingsForCalls(db, calls) {
|
|
2270
|
+
const map = /* @__PURE__ */ new Map();
|
|
2271
|
+
for (const call of calls) {
|
|
2272
|
+
const receiver = receiverFromEvidence(call.evidence_json);
|
|
2273
|
+
const bindingId = Number(call.service_binding_id ?? 0);
|
|
2274
|
+
if (!receiver || !bindingId) continue;
|
|
2275
|
+
const row = db.prepare("SELECT id,alias,alias_expr aliasExpr,destination_expr destinationExpr,service_path_expr servicePathExpr,source_file sourceFile,source_line sourceLine FROM service_bindings WHERE id=?").get(bindingId);
|
|
2276
|
+
if (row) map.set(receiver, { ...row, bindingId, source: "local_service_binding", calleeReceiver: receiver });
|
|
2277
|
+
}
|
|
2278
|
+
return map;
|
|
2279
|
+
}
|
|
2280
|
+
function knownBindingsForScope(db, repoId, symbolIds, files) {
|
|
2281
|
+
const map = /* @__PURE__ */ new Map();
|
|
2282
|
+
if (repoId === void 0) return map;
|
|
2283
|
+
const rows2 = db.prepare("SELECT id,variable_name variableName,alias,alias_expr aliasExpr,destination_expr destinationExpr,service_path_expr servicePathExpr,source_file sourceFile,source_line sourceLine FROM service_bindings WHERE repo_id=?").all(repoId);
|
|
2284
|
+
for (const row of rows2) {
|
|
2285
|
+
if (!row.variableName) continue;
|
|
2286
|
+
if (files && !files.has(String(row.sourceFile))) continue;
|
|
2287
|
+
if (symbolIds && symbolIds.size > 0) {
|
|
2288
|
+
const owner = db.prepare("SELECT id FROM symbols WHERE id IN (" + [...symbolIds].map(() => "?").join(",") + ") AND source_file=? AND start_line<=? AND end_line>=? LIMIT 1").get(...symbolIds, row.sourceFile, row.sourceLine, row.sourceLine);
|
|
2289
|
+
if (!owner) continue;
|
|
2290
|
+
}
|
|
2291
|
+
map.set(row.variableName, { ...row, bindingId: Number(row.id), source: "local_service_binding", calleeReceiver: row.variableName });
|
|
2292
|
+
}
|
|
2293
|
+
return map;
|
|
2294
|
+
}
|
|
2295
|
+
function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
2296
|
+
const next = /* @__PURE__ */ new Map();
|
|
2297
|
+
if (callerBindings.size === 0) return next;
|
|
2298
|
+
const callEvidence2 = parseEvidence(symbolCall.evidence_json);
|
|
2299
|
+
const callee = db.prepare("SELECT evidence_json evidenceJson FROM symbols WHERE id=?").get(symbolCall.callee_symbol_id);
|
|
2300
|
+
const calleeEvidence = parseEvidence(callee?.evidenceJson);
|
|
2301
|
+
const params = Array.isArray(calleeEvidence.parameters) ? calleeEvidence.parameters.filter((item) => typeof item === "string") : [];
|
|
2302
|
+
const parameterBindings = Array.isArray(calleeEvidence.parameterBindings) ? calleeEvidence.parameterBindings.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
2303
|
+
const args = Array.isArray(callEvidence2.callArguments) ? callEvidence2.callArguments : [];
|
|
2304
|
+
args.forEach((arg, index) => {
|
|
2305
|
+
const paramBinding = parameterBindings.find((binding) => binding.index === index);
|
|
2306
|
+
const param = paramBinding?.kind === "identifier" && typeof paramBinding.name === "string" ? paramBinding.name : params[index];
|
|
2307
|
+
if (arg.kind === "identifier" && typeof arg.name === "string") {
|
|
2308
|
+
const binding = callerBindings.get(arg.name);
|
|
2309
|
+
if (binding && param) next.set(param, { ...binding, source: "local_symbol_argument", callerArgument: arg.name, calleeParameter: param, calleeReceiver: param });
|
|
2310
|
+
}
|
|
2311
|
+
if (arg.kind === "object_literal" && Array.isArray(arg.properties)) {
|
|
2312
|
+
for (const prop of arg.properties) {
|
|
2313
|
+
if (typeof prop.property !== "string" || typeof prop.argument !== "string") continue;
|
|
2314
|
+
const binding = callerBindings.get(prop.argument);
|
|
2315
|
+
if (!binding) continue;
|
|
2316
|
+
const destructured = paramBinding?.kind === "object_pattern" && Array.isArray(paramBinding.properties) ? paramBinding.properties.find((item) => item.property === prop.property && typeof item.local === "string") : void 0;
|
|
2317
|
+
if (destructured && typeof destructured.local === "string") next.set(destructured.local, { ...binding, source: "local_symbol_destructured_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: String(index), calleeReceiver: destructured.local });
|
|
2318
|
+
else if (param) next.set(`${param}.${prop.property}`, { ...binding, source: "local_symbol_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeReceiver: `${param}.${prop.property}` });
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2322
|
+
return next;
|
|
2323
|
+
}
|
|
2324
|
+
function contextualRuntimeResolution(db, call, binding, workspaceId) {
|
|
2325
|
+
if (!binding || String(call.call_type) !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null) return {};
|
|
2326
|
+
const op = normalizeODataOperationInvocationPathForTrace(String(call.operation_path_expr));
|
|
2327
|
+
const resolution = resolveOperation(db, { servicePath: binding.servicePathExpr, operationPath: op, alias: binding.aliasExpr ?? binding.alias, destination: binding.destinationExpr, hasExplicitOverride: true, isDynamic: false }, workspaceId);
|
|
2328
|
+
const evidence = { contextualServiceBindingSelected: true, contextualBinding: { source: binding.source, callerArgument: binding.callerArgument, callerProperty: binding.callerProperty, calleeParameter: binding.calleeParameter, calleeReceiver: binding.calleeReceiver, bindingSourceFile: binding.sourceFile, bindingSourceLine: binding.sourceLine }, operationPath: op, servicePath: binding.servicePathExpr, serviceAlias: binding.alias, serviceAliasExpr: binding.aliasExpr, destination: binding.destinationExpr, contextualResolutionStatus: resolution.status, candidates: resolution.candidates, resolutionReasons: resolution.reasons };
|
|
2329
|
+
if (!resolution.target) return { evidence, unresolvedReason: resolution.status === "ambiguous" ? "Ambiguous contextual operation candidates" : "No contextual operation candidate matched" };
|
|
2330
|
+
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(evidence), status: "resolved" }, evidence: { ...evidence, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName }, unresolvedReason: void 0 };
|
|
2331
|
+
}
|
|
2332
|
+
function normalizeODataOperationInvocationPathForTrace(raw) {
|
|
2333
|
+
return raw.startsWith("/") ? raw : `/${raw}`;
|
|
2334
|
+
}
|
|
2240
2335
|
function edgeTarget(row, evidence) {
|
|
2241
2336
|
const runtimeCandidate = evidence.runtimeResolvedCandidate;
|
|
2242
2337
|
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath)
|
|
@@ -2269,7 +2364,7 @@ function trace(db, start, options) {
|
|
|
2269
2364
|
const maxDepth = positiveDepth(options.depth);
|
|
2270
2365
|
const edges = [];
|
|
2271
2366
|
const nodes = /* @__PURE__ */ new Map();
|
|
2272
|
-
const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1 }] : [];
|
|
2367
|
+
const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1, context: /* @__PURE__ */ new Map() }] : [];
|
|
2273
2368
|
if (start.servicePath && (start.operation ?? start.operationPath)) {
|
|
2274
2369
|
const startOperation = normalizeOperation(start.operation ?? start.operationPath);
|
|
2275
2370
|
const startRows = db.prepare(`SELECT o.id operationId,r.name repoName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?) ORDER BY r.name,o.id LIMIT 2`).all(scope.repo?.id, scope.repo?.id, start.servicePath, startOperation, startOperation);
|
|
@@ -2302,6 +2397,7 @@ function trace(db, start, options) {
|
|
|
2302
2397
|
const filtered = calls.filter(
|
|
2303
2398
|
(c) => (!current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
|
|
2304
2399
|
);
|
|
2400
|
+
const callerBindings = new Map([...current.context ?? /* @__PURE__ */ new Map(), ...knownBindingsForScope(db, current.repoId, current.symbolIds, current.files), ...knownBindingsForCalls(db, filtered)]);
|
|
2305
2401
|
if (current.symbolIds && current.symbolIds.size > 0 && current.depth < maxDepth) {
|
|
2306
2402
|
const symbolRows = db.prepare(`SELECT sc.*,s.repo_id calleeRepoId,s.source_file calleeFile FROM symbol_calls sc LEFT JOIN symbols s ON s.id=sc.callee_symbol_id WHERE sc.caller_symbol_id IN (${[...current.symbolIds].map(() => "?").join(",")}) ORDER BY sc.source_file,sc.source_line`).all(...current.symbolIds);
|
|
2307
2403
|
for (const symbolCall of symbolRows) {
|
|
@@ -2315,7 +2411,7 @@ function trace(db, start, options) {
|
|
|
2315
2411
|
const evidence = { ...JSON.parse(String(symbolCall.evidence_json || "{}")), sourceFile: symbolCall.source_file, sourceLine: symbolCall.source_line, calleeSymbolId: symbolCall.callee_symbol_id, calleeSymbolName: calleeNode?.symbolName, calleeSymbolFile: calleeNode?.sourceFile, resolutionStatus: symbolCall.status };
|
|
2316
2412
|
edges.push({ step: current.depth, type: "local_symbol_call", from: String(symbolCall.callee_expression), to: calleeNode?.label ? String(calleeNode.label) : `symbol:${String(symbolCall.callee_symbol_id)}`, evidence, confidence: Number(symbolCall.confidence ?? 0.8), unresolvedReason: String(symbolCall.status) === "resolved" ? void 0 : symbolCall.unresolved_reason ? String(symbolCall.unresolved_reason) : void 0 });
|
|
2317
2413
|
if (seenScopes.has(nextKey)) edges.push({ step: current.depth, type: "cycle", from: String(symbolCall.callee_expression), to: nextKey, evidence: { cycle: true, symbolCallId: symbolCall.id }, confidence: 1, unresolvedReason: "Cycle detected; downstream symbol already visited" });
|
|
2318
|
-
else queue.push({ repoId: nextRepoId, files: nextFiles, symbolIds: nextSymbols, depth: current.depth + 1 });
|
|
2414
|
+
else queue.push({ repoId: nextRepoId, files: nextFiles, symbolIds: nextSymbols, depth: current.depth + 1, context: contextForSymbolCall(db, symbolCall, callerBindings) });
|
|
2319
2415
|
}
|
|
2320
2416
|
}
|
|
2321
2417
|
const graph = graphForCalls(
|
|
@@ -2332,11 +2428,12 @@ function trace(db, start, options) {
|
|
|
2332
2428
|
line: call.source_line,
|
|
2333
2429
|
callType: call.call_type
|
|
2334
2430
|
});
|
|
2335
|
-
const
|
|
2431
|
+
const contextual = contextualRuntimeResolution(db, call, (current.context ?? /* @__PURE__ */ new Map()).get(receiverFromEvidence(call.evidence_json) ?? ""), workspaceIdForCall(db, String(call.id)));
|
|
2432
|
+
const graphRows = contextual.row ? [contextual.row] : graph.get(Number(call.id)) ?? [];
|
|
2336
2433
|
for (const row of graphRows) {
|
|
2337
2434
|
if (seenEdges.has(Number(row.id))) continue;
|
|
2338
2435
|
seenEdges.add(Number(row.id));
|
|
2339
|
-
const rawEvidence = JSON.parse(
|
|
2436
|
+
const rawEvidence = contextual.evidence && row.id < 0 ? contextual.evidence : JSON.parse(
|
|
2340
2437
|
String(row.evidence_json || "{}")
|
|
2341
2438
|
);
|
|
2342
2439
|
const effective = runtimeResolution(db, row, rawEvidence, options.vars);
|
|
@@ -2433,4 +2530,4 @@ export {
|
|
|
2433
2530
|
linkWorkspace,
|
|
2434
2531
|
trace
|
|
2435
2532
|
};
|
|
2436
|
-
//# sourceMappingURL=chunk-
|
|
2533
|
+
//# sourceMappingURL=chunk-HS746OHV.js.map
|