@saptools/service-flow 0.1.11 → 0.1.12
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 +10 -0
- package/README.md +4 -2
- package/dist/{chunk-GG4XJGES.js → chunk-MXYYARUP.js} +67 -16
- package/dist/chunk-MXYYARUP.js.map +1 -0
- package/dist/cli.js +54 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-GG4XJGES.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.12
|
|
4
|
+
|
|
5
|
+
- Resolved same-repository local CAP service calls by qualified CDS name, simple service name, and service path, with explicit local transport and alias-chain evidence.
|
|
6
|
+
- Made implementation matching decorator-aware so generated `Func*`/`Action*` constants outrank method-name fallback and contradictory decorators are rejected without making edges ambiguous.
|
|
7
|
+
- Cleared stale unresolved reasons from resolved symbol calls and suppressed false trace unresolved reasons for symbol edges with concrete callee ids.
|
|
8
|
+
- Made local symbol-call collection conservative, added named export-list support, and indexed one-level object-literal helper methods as executable symbols so traces can reach helper database queries.
|
|
9
|
+
- Added first-class symbol nodes and readable symbol labels/locations to JSON, table, and Mermaid trace output.
|
|
10
|
+
- Deduplicated implementation candidates by method identity while retaining multiple registration rows as nested evidence, and kept default doctor output from failing on explainable source-ownership gaps.
|
|
11
|
+
- Documented the generated-constant decision: this patch uses deterministic decorator normalization for linking while `parseGeneratedConstants` remains a low-level parser export rather than a persisted graph fact.
|
|
12
|
+
|
|
3
13
|
## 0.1.11
|
|
4
14
|
|
|
5
15
|
- Added repository-owned executable symbols, source-symbol ownership for outbound calls, and local symbol-call facts so traces can follow reachable same-file/imported helpers without including unrelated calls from the same file.
|
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ npm install @saptools/service-flow
|
|
|
59
59
|
- Repository selectors on list, trace, graph, and inspect commands narrow scope. Unknown selectors return empty machine-readable diagnostics instead of falling back to the whole workspace.
|
|
60
60
|
- Helper-package dependency edges prefer exact indexed package names. Duplicate package-name candidates are persisted as ambiguous evidence rather than silently selecting one repository.
|
|
61
61
|
- Handler registration parsing is AST-based for common `createCombinedHandler({ handler: ... })` forms: direct arrays, arrays assembled with spreads, non-`handlers` array names, aliased class imports, default-imported arrays, named exported arrays, and safe relative re-exports. Class-level rows keep registration file/line and import evidence.
|
|
62
|
-
- Implementation edges require both operation compatibility and registration evidence. Same-repository registrations do not need a self-dependency edge; cross-package matches use registration or handler-package dependencies on the model package. Duplicate strong candidates are stored as ambiguous implementation edges.
|
|
62
|
+
- Implementation edges require both operation compatibility and registration evidence. Decorator operation signals are stronger than method-name fallback; common generated names such as `FuncGetConfiguration` and `ActionGetConfiguration` are normalized before comparison, and a contradictory decorator rejects the candidate even when the TypeScript method name collides. Same-repository registrations do not need a self-dependency edge; cross-package matches use registration or handler-package dependencies on the model package. Duplicate strong candidates are stored as ambiguous implementation edges.
|
|
63
63
|
- Traces render persisted `OPERATION_IMPLEMENTED_BY_HANDLER` hops after static or runtime remote operation resolution, including terminal handler nodes and ambiguous or unresolved implementation evidence when traversal cannot continue.
|
|
64
64
|
- Repository fingerprints include source content, package name/version, dependencies and devDependencies, scripts, normalized `cds.requires` (including nested credentials), package file content, and the analyzer version. Metadata-only changes therefore trigger reindexing.
|
|
65
65
|
- Index publication is designed around the last-good snapshot: failed parse or persistence attempts are recorded as diagnostics and must not be mixed with older graph facts. After indexing changes, relink before relying on graph/trace output; doctor reports stale or inconsistent stores where detectable.
|
|
@@ -152,7 +152,9 @@ target handler up to `--depth` instead of showing only calls in the first file.
|
|
|
152
152
|
|
|
153
153
|
`service-flow trace` starts from the selected handler method symbol, renders outbound calls owned by that symbol, and follows conservative local helper-call facts. Supported helper edges include same-file functions, `this.method()` calls, and exactly mapped relative imports/exports that resolve to an indexed executable symbol. Calls from unrelated functions in the same source file are not included merely because the file path matches.
|
|
154
154
|
|
|
155
|
-
Local CAP calls through `cds.services.<Service>.<operation>()`, bracket service lookups, and simple aliases are indexed as local operation calls. Entity accessors such as `cds.services.db.entities(...)` are treated as entity metadata access, not operation calls.
|
|
155
|
+
Local CAP calls through `cds.services.<Service>.<operation>()`, bracket service lookups, and simple aliases are indexed as local operation calls. Linking stays within the same repository by default and matches the target operation by exact qualified CDS service name, exact simple service name, exact service path, or an unambiguous service-path suffix. Entity accessors such as `cds.services.db.entities(...)` are treated as entity metadata access, not operation calls.
|
|
156
|
+
|
|
157
|
+
Conservative local symbol traversal intentionally excludes decorators, built-ins such as `JSON.parse`, collection methods, third-party APIs, and arbitrary property chains unless the callee can plausibly resolve to an indexed local symbol. Named export lists such as `export { loadTemplate as publicLoadTemplate }` are indexed with the public exported name so relative imports can resolve. One-level object-literal helpers are indexed as symbols named like `cacheHelper.getConfiguration`; nested object literals are not yet expanded beyond the first helper level. `parseGeneratedConstants` remains a public low-level parser export for callers that need it, but generated constants are not persisted as graph facts in this patch; linking uses the deterministic decorator normalizer described above.
|
|
156
158
|
JSON output includes typed nodes for calls, operations, database entities,
|
|
157
159
|
external destinations, and unresolved/dynamic candidates when edges exist.
|
|
158
160
|
|
|
@@ -1074,7 +1074,7 @@ function substituteVariables(template, vars) {
|
|
|
1074
1074
|
// src/linker/service-resolver.ts
|
|
1075
1075
|
function rows(db, operationPath, workspaceId) {
|
|
1076
1076
|
return db.prepare(
|
|
1077
|
-
`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,0 score,'' reasons
|
|
1077
|
+
`SELECT o.id operationId,r.name repoName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score,'' reasons
|
|
1078
1078
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
1079
1079
|
WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path=? OR o.operation_name=?) ORDER BY r.name,s.service_path,o.operation_name`
|
|
1080
1080
|
).all(
|
|
@@ -1110,7 +1110,7 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1110
1110
|
reasons: ["no_operation_candidates"]
|
|
1111
1111
|
};
|
|
1112
1112
|
const hasStrongSignal = Boolean(
|
|
1113
|
-
signals.servicePath || signals.alias || signals.destination || signals.hasExplicitOverride
|
|
1113
|
+
signals.servicePath || signals.serviceName || signals.alias || signals.destination || signals.hasExplicitOverride
|
|
1114
1114
|
);
|
|
1115
1115
|
for (const c of candidates) {
|
|
1116
1116
|
if (signals.servicePath && c.servicePath === signals.servicePath) {
|
|
@@ -1121,14 +1121,25 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1121
1121
|
c.score -= 0.1;
|
|
1122
1122
|
c.reasons.push("service_path_mismatch");
|
|
1123
1123
|
}
|
|
1124
|
-
if (signals.serviceName
|
|
1125
|
-
|
|
1126
|
-
c.
|
|
1124
|
+
if (signals.serviceName) {
|
|
1125
|
+
const simple = signals.serviceName.split(".").at(-1) ?? signals.serviceName;
|
|
1126
|
+
if (c.qualifiedName === signals.serviceName) {
|
|
1127
|
+
c.score += 0.8;
|
|
1128
|
+
c.reasons.push("exact_local_qualified_service_name");
|
|
1129
|
+
} else if (c.serviceName === signals.serviceName || c.serviceName === simple) {
|
|
1130
|
+
c.score += 0.75;
|
|
1131
|
+
c.reasons.push("exact_local_simple_service_name");
|
|
1132
|
+
} else if (c.servicePath === signals.serviceName || c.servicePath === `/${signals.serviceName}` || c.servicePath === `/${simple}`) {
|
|
1133
|
+
c.score += 0.7;
|
|
1134
|
+
c.reasons.push("exact_local_service_path");
|
|
1135
|
+
} else if (c.servicePath.endsWith(`/${simple}`)) {
|
|
1136
|
+
c.score += candidates.filter((candidate) => candidate.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2;
|
|
1137
|
+
c.reasons.push("suffix_local_service_path");
|
|
1138
|
+
} else c.reasons.push("local_service_name_mismatch");
|
|
1127
1139
|
}
|
|
1128
|
-
if (signals.serviceName && c.servicePath !== `/${signals.serviceName}` && !c.servicePath.endsWith(`/${signals.serviceName}`)) c.reasons.push("local_service_name_mismatch");
|
|
1129
1140
|
if (signals.hasExplicitOverride) {
|
|
1130
1141
|
c.score += 0.2;
|
|
1131
|
-
c.reasons.push("explicit_dynamic_override");
|
|
1142
|
+
c.reasons.push(signals.repoId !== void 0 ? "explicit_local_service_call" : "explicit_dynamic_override");
|
|
1132
1143
|
}
|
|
1133
1144
|
}
|
|
1134
1145
|
for (const c of candidates) c.score = Math.max(0, Math.min(1, c.score));
|
|
@@ -1149,7 +1160,7 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1149
1160
|
candidates,
|
|
1150
1161
|
reasons: ["operation_path_only_has_no_strong_target_signal"]
|
|
1151
1162
|
};
|
|
1152
|
-
if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName &&
|
|
1163
|
+
if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName && !best.reasons.includes("local_service_name_mismatch"))) && (best.operationPath === signals.operationPath || best.operationName === signals.operationPath.replace(/^\//, "")) && (!second || best.score - second.score >= 0.25))
|
|
1153
1164
|
return {
|
|
1154
1165
|
status: "resolved",
|
|
1155
1166
|
target: best,
|
|
@@ -1307,7 +1318,7 @@ function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
|
1307
1318
|
function deduplicateCandidates(rows2) {
|
|
1308
1319
|
const merged = /* @__PURE__ */ new Map();
|
|
1309
1320
|
for (const row of rows2) {
|
|
1310
|
-
const key = [row.methodId, row.classId, row.
|
|
1321
|
+
const key = [row.methodId, row.classId, row.handlerRepoId].join(":");
|
|
1311
1322
|
const registration = { file: row.registrationFile, line: row.registrationLine, kind: row.registrationKind, importSource: row.importSource };
|
|
1312
1323
|
const existing = merged.get(key);
|
|
1313
1324
|
if (!existing) {
|
|
@@ -1335,6 +1346,9 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1335
1346
|
const modelRepoGraphId = graphId(operation.modelRepoId);
|
|
1336
1347
|
return db.prepare(`SELECT DISTINCT
|
|
1337
1348
|
hm.id methodId,
|
|
1349
|
+
hm.method_name methodName,
|
|
1350
|
+
hm.decorator_value decoratorValue,
|
|
1351
|
+
hm.decorator_raw_expression decoratorRawExpression,
|
|
1338
1352
|
hc.id classId,
|
|
1339
1353
|
hc.class_name className,
|
|
1340
1354
|
hc.source_file sourceFile,
|
|
@@ -1361,7 +1375,7 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1361
1375
|
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
1362
1376
|
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id AND localService.service_path=?) THEN 1 ELSE 0 END localServicePathMatch,
|
|
1363
1377
|
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
1364
|
-
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON (localClass.id=localReg.handler_class_id OR localClass.class_name=localReg.class_name) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
1378
|
+
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON (localClass.id=localReg.handler_class_id OR localClass.class_name=localReg.class_name) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
1365
1379
|
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
|
|
1366
1380
|
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
|
|
1367
1381
|
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
|
|
@@ -1371,7 +1385,7 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1371
1385
|
JOIN handler_registrations hr ON (hr.handler_class_id=hc.id OR (hr.class_name=hc.class_name AND (hr.repo_id=hc.repo_id OR hr.import_source IS NOT NULL)))
|
|
1372
1386
|
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
1373
1387
|
WHERE appRepo.workspace_id=?
|
|
1374
|
-
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?)`).all(
|
|
1388
|
+
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
|
|
1375
1389
|
operation.modelRepoId,
|
|
1376
1390
|
operation.modelRepo,
|
|
1377
1391
|
operation.modelPackage,
|
|
@@ -1385,12 +1399,14 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1385
1399
|
normalizedOperation(String(operation.operationPath ?? "")),
|
|
1386
1400
|
operation.operationName,
|
|
1387
1401
|
operation.operationName,
|
|
1402
|
+
`%${upperFirst(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`,
|
|
1388
1403
|
modelRepoGraphId,
|
|
1389
1404
|
modelRepoGraphId,
|
|
1390
1405
|
workspaceId,
|
|
1391
1406
|
normalizedOperation(String(operation.operationPath ?? "")),
|
|
1392
1407
|
operation.operationName,
|
|
1393
|
-
operation.operationName
|
|
1408
|
+
operation.operationName,
|
|
1409
|
+
`%${upperFirst(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`
|
|
1394
1410
|
);
|
|
1395
1411
|
}
|
|
1396
1412
|
function scoreImplementationCandidate(row, operation) {
|
|
@@ -1408,7 +1424,10 @@ function scoreImplementationCandidate(row, operation) {
|
|
|
1408
1424
|
const importSource = typeof row.importSource === "string" && row.importSource.length > 0;
|
|
1409
1425
|
const sameRepoRegistration = flag(row.sameRepoRegistration);
|
|
1410
1426
|
const modelOriented = row.modelKind === "cap-db-model" || !applicationHasLocalRegistrationForOperation;
|
|
1411
|
-
const
|
|
1427
|
+
const methodSignal = implementationMethodSignal(row, operation);
|
|
1428
|
+
const methodMatches = methodSignal.matches;
|
|
1429
|
+
acceptedReasons.push(...methodSignal.acceptedReasons);
|
|
1430
|
+
rejectedReasons.push(...methodSignal.rejectedReasons);
|
|
1412
1431
|
const registeredAndLinked = sameRepoRegistration && importSource;
|
|
1413
1432
|
const helperOwned = modelOriented && methodMatches && registeredAndLinked && sameRepoRegistration && !applicationHasLocalServices && !modelIsApplicationRepo && !modelIsHandlerRepo && !localServicePathMatch && !appDependsOnModel && !appDependsOnHandler && !handlerDependsOnModel;
|
|
1414
1433
|
if (modelIsApplicationRepo) {
|
|
@@ -1449,7 +1468,7 @@ function scoreImplementationCandidate(row, operation) {
|
|
|
1449
1468
|
const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
|
|
1450
1469
|
const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
|
|
1451
1470
|
if (!hasOwnership && !localServicePathMatch && !hasCrossPackage && !helperOwned) rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
|
|
1452
|
-
const accepted = !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned);
|
|
1471
|
+
const accepted = methodMatches && !methodSignal.contradicted && !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned);
|
|
1453
1472
|
if (!accepted && rejectedReasons.length === 0) rejectedReasons.push("candidate did not meet implementation ownership policy");
|
|
1454
1473
|
return { ...row, methodId: Number(row.methodId), score, accepted, acceptedReasons, rejectedReasons };
|
|
1455
1474
|
}
|
|
@@ -1485,6 +1504,29 @@ function candidateEvidence(candidate, rank) {
|
|
|
1485
1504
|
}
|
|
1486
1505
|
};
|
|
1487
1506
|
}
|
|
1507
|
+
function implementationMethodSignal(row, operation) {
|
|
1508
|
+
const op = normalizedOperation(String(operation.operationPath ?? operation.operationName ?? ""));
|
|
1509
|
+
const decorator = normalizeDecoratorOperation(typeof row.decoratorValue === "string" ? row.decoratorValue : void 0, typeof row.decoratorRawExpression === "string" ? row.decoratorRawExpression : void 0);
|
|
1510
|
+
if (decorator && decorator === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
1511
|
+
if (decorator && decorator !== op) return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["method_name_matches_but_decorator_targets_different_operation"] };
|
|
1512
|
+
if (String(row.methodName ?? "") === op) return { matches: true, contradicted: false, acceptedReasons: ["method name fallback matched operation"], rejectedReasons: [] };
|
|
1513
|
+
return { matches: false, contradicted: false, acceptedReasons: [], rejectedReasons: ["method name does not match operation"] };
|
|
1514
|
+
}
|
|
1515
|
+
function normalizeDecoratorOperation(value, raw) {
|
|
1516
|
+
const candidate = value ?? raw?.split(".").filter(Boolean).at(-2);
|
|
1517
|
+
if (!candidate) return void 0;
|
|
1518
|
+
const cleaned = candidate.replace(/^['"`]|['"`]$/g, "");
|
|
1519
|
+
for (const prefix of ["Func", "Action"]) {
|
|
1520
|
+
if (cleaned.startsWith(prefix) && cleaned.length > prefix.length) return lowerFirst(cleaned.slice(prefix.length));
|
|
1521
|
+
}
|
|
1522
|
+
return normalizedOperation(cleaned);
|
|
1523
|
+
}
|
|
1524
|
+
function upperFirst(value) {
|
|
1525
|
+
return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
|
|
1526
|
+
}
|
|
1527
|
+
function lowerFirst(value) {
|
|
1528
|
+
return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
|
|
1529
|
+
}
|
|
1488
1530
|
function flag(value) {
|
|
1489
1531
|
return Boolean(Number(value ?? 0));
|
|
1490
1532
|
}
|
|
@@ -1634,6 +1676,12 @@ function evidenceWithRuntimeVariables(evidence, vars) {
|
|
|
1634
1676
|
if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)];
|
|
1635
1677
|
return next;
|
|
1636
1678
|
}
|
|
1679
|
+
function symbolNode(db, symbolId) {
|
|
1680
|
+
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);
|
|
1681
|
+
if (!row) return void 0;
|
|
1682
|
+
const fileName = String(row.sourceFile ?? "").split("/").at(-1) ?? String(row.sourceFile ?? "");
|
|
1683
|
+
return { id: `symbol:${symbolId}`, kind: "symbol", label: `${fileName}:${String(row.qualifiedName ?? row.symbolName)}`, ...row };
|
|
1684
|
+
}
|
|
1637
1685
|
function operationNode(db, operationId) {
|
|
1638
1686
|
const row = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_type operationType,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.id serviceId,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,r.id repoId,r.name repoName FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operationId);
|
|
1639
1687
|
if (!row) return void 0;
|
|
@@ -1728,7 +1776,10 @@ function trace(db, start, options) {
|
|
|
1728
1776
|
const nextFiles = /* @__PURE__ */ new Set([String(symbolCall.calleeFile)]);
|
|
1729
1777
|
const nextRepoId = Number(symbolCall.calleeRepoId);
|
|
1730
1778
|
const nextKey = `${nextRepoId}:${[...nextSymbols].join(",")}:${[...nextFiles].join(",")}`;
|
|
1731
|
-
|
|
1779
|
+
const calleeNode = symbolNode(db, Number(symbolCall.callee_symbol_id));
|
|
1780
|
+
if (calleeNode) nodes.set(String(calleeNode.id), calleeNode);
|
|
1781
|
+
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 };
|
|
1782
|
+
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 });
|
|
1732
1783
|
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" });
|
|
1733
1784
|
else queue.push({ repoId: nextRepoId, files: nextFiles, symbolIds: nextSymbols, depth: current.depth + 1 });
|
|
1734
1785
|
}
|
|
@@ -1842,4 +1893,4 @@ export {
|
|
|
1842
1893
|
linkWorkspace,
|
|
1843
1894
|
trace
|
|
1844
1895
|
};
|
|
1845
|
-
//# sourceMappingURL=chunk-
|
|
1896
|
+
//# sourceMappingURL=chunk-MXYYARUP.js.map
|