@saptools/service-flow 0.1.9 → 0.1.10
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 +8 -0
- package/README.md +3 -3
- package/dist/{chunk-HQMAFSRR.js → chunk-LQT67AU5.js} +87 -15
- package/dist/chunk-LQT67AU5.js.map +1 -0
- package/dist/cli.js +93 -81
- 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/dist/chunk-HQMAFSRR.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.10
|
|
4
|
+
|
|
5
|
+
- Persist unresolved `OPERATION_IMPLEMENTED_BY_HANDLER` audit edges when implementation candidates exist but all are rejected, including ranked candidate evidence and rejected reasons.
|
|
6
|
+
- Added a conservative helper-owned implementation path for unique registered helper handlers that implement model-oriented CDS operations without direct package dependency evidence, while keeping multiple helper matches ambiguous and local service-path contradictions rejected.
|
|
7
|
+
- Trace output now includes operation-to-handler implementation hops and terminal handler nodes in JSON/table/Mermaid-compatible edge data, including runtime-resolved operation targets.
|
|
8
|
+
- Doctor now reports rejected implementation candidates and strict remote-target implementation coverage gaps without making entity-only services noisy by default.
|
|
9
|
+
- Updated Node compatibility errors, package metadata, and README wording so current behavior is not described with stale release-specific version strings.
|
|
10
|
+
|
|
3
11
|
## 0.1.9
|
|
4
12
|
|
|
5
13
|
- Fixed implementation dependency matching by binding graph repository ids as text when comparing against `graph_edges.from_id` and `graph_edges.to_id`, restoring cross-package application-to-model and application-to-handler evidence under `node:sqlite`.
|
package/README.md
CHANGED
|
@@ -47,12 +47,12 @@ npm install @saptools/service-flow
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
> [!NOTE]
|
|
50
|
-
> Requires **Node.js ≥ 24.0.0** for the bundled `node:sqlite` runtime.
|
|
50
|
+
> Requires **Node.js ≥ 24.0.0** for the bundled `node:sqlite` runtime. The CLI uses a persistent SQLite driver (`node:sqlite` in supported Node builds) for bound parameters, transactions, WAL, busy timeouts, and read-only query commands. The analyzer is static: it reads files and package metadata, but it does not start CAP services, connect to SAP BTP, or execute application code.
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
### Correctness notes
|
|
55
|
+
### Correctness notes
|
|
56
56
|
|
|
57
57
|
- Runtime `--var` values are considered only for dynamic, ambiguous, or unresolved **remote** graph edges whose alias, destination, service path, or operation path expressions contain supplied placeholders. Local database, external HTTP, event, and already resolved static edges keep their persisted status, target, reason, and confidence. Partial substitutions remain dynamic and report the missing placeholder names.
|
|
58
58
|
- `trace` and `graph` both accept repeatable `--var key=value` options. Effective substitutions are rendered in trace evidence without mutating the persisted graph. Confidence values are bounded to `[0, 1]`.
|
|
@@ -60,7 +60,7 @@ npm install @saptools/service-flow
|
|
|
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
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.
|
|
63
|
-
- Traces
|
|
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.
|
|
66
66
|
- Source discovery, file reads, hashing, parsing, and publication are all inside the repository-level protected indexing flow. A failed read keeps the previous fingerprint and facts, marks the repository failed, and records a `source_read_failed` diagnostic; a later successful index clears superseded read-failure diagnostics during fact publication.
|
|
@@ -1177,7 +1177,7 @@ function linkWorkspace(db, workspaceId, vars = {}) {
|
|
|
1177
1177
|
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
1178
1178
|
const impl = linkImplementations(db, workspaceId, generation);
|
|
1179
1179
|
db.prepare("UPDATE repositories SET graph_generation=?, graph_stale_reason=NULL, graph_stale_at=NULL WHERE workspace_id=?").run(generation, workspaceId);
|
|
1180
|
-
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount };
|
|
1180
|
+
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount, implementationUnresolvedCount: impl.unresolvedCount };
|
|
1181
1181
|
});
|
|
1182
1182
|
}
|
|
1183
1183
|
function nextGraphGeneration(db, workspaceId) {
|
|
@@ -1225,14 +1225,15 @@ function callEvidence(call, resolution, servicePath, op, destination) {
|
|
|
1225
1225
|
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, targetRepo: resolution.target?.repoName, targetOperation: resolution.target?.operationName, helperChain: call.helperChainJson ? JSON.parse(String(call.helperChainJson)) : void 0, candidates: resolution.candidates, candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
1226
1226
|
}
|
|
1227
1227
|
function linkImplementations(db, workspaceId, generation) {
|
|
1228
|
-
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
1228
|
+
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
1229
1229
|
let edgeCount = 0;
|
|
1230
1230
|
let resolvedCount = 0;
|
|
1231
1231
|
let ambiguousCount = 0;
|
|
1232
|
+
let unresolvedCount = 0;
|
|
1232
1233
|
for (const operation of operations) {
|
|
1233
1234
|
const candidates = rankedImplementationCandidates(db, workspaceId, operation);
|
|
1235
|
+
if (candidates.length === 0) continue;
|
|
1234
1236
|
const accepted = candidates.filter((candidate) => candidate.accepted);
|
|
1235
|
-
if (accepted.length === 0) continue;
|
|
1236
1237
|
const topScore = accepted[0]?.score ?? 0;
|
|
1237
1238
|
const winners = accepted.filter((candidate) => candidate.score === topScore);
|
|
1238
1239
|
const unique = winners.length === 1 ? winners[0] : void 0;
|
|
@@ -1243,12 +1244,18 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
1243
1244
|
modelPackage: { id: operation.modelRepoId, name: operation.modelRepo, packageName: operation.modelPackage },
|
|
1244
1245
|
candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
1245
1246
|
};
|
|
1247
|
+
if (accepted.length === 0) {
|
|
1248
|
+
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", "unresolved", "operation", graphId(operation.operationId), "handler_method_candidates", candidates.map((row) => graphId(row.methodId)).join(","), 0, JSON.stringify(evidence), 0, "No implementation candidate passed policy", generation);
|
|
1249
|
+
edgeCount += 1;
|
|
1250
|
+
unresolvedCount += 1;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1246
1253
|
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : winners.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidence), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
|
|
1247
1254
|
edgeCount += 1;
|
|
1248
1255
|
if (unique) resolvedCount += 1;
|
|
1249
1256
|
else ambiguousCount += 1;
|
|
1250
1257
|
}
|
|
1251
|
-
return { edgeCount, resolvedCount, ambiguousCount };
|
|
1258
|
+
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
1252
1259
|
}
|
|
1253
1260
|
function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
1254
1261
|
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
@@ -1275,6 +1282,7 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1275
1282
|
? modelRepoId,
|
|
1276
1283
|
? modelRepo,
|
|
1277
1284
|
? modelPackage,
|
|
1285
|
+
? modelKind,
|
|
1278
1286
|
? servicePath,
|
|
1279
1287
|
? operationPath,
|
|
1280
1288
|
? operationName,
|
|
@@ -1283,6 +1291,7 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1283
1291
|
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
1284
1292
|
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,
|
|
1285
1293
|
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
1294
|
+
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,
|
|
1286
1295
|
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,
|
|
1287
1296
|
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,
|
|
1288
1297
|
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
|
|
@@ -1296,12 +1305,16 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1296
1305
|
operation.modelRepoId,
|
|
1297
1306
|
operation.modelRepo,
|
|
1298
1307
|
operation.modelPackage,
|
|
1308
|
+
operation.modelKind,
|
|
1299
1309
|
operation.servicePath,
|
|
1300
1310
|
operation.operationPath,
|
|
1301
1311
|
operation.operationName,
|
|
1302
1312
|
operation.modelRepoId,
|
|
1303
1313
|
operation.modelRepoId,
|
|
1304
1314
|
operation.servicePath,
|
|
1315
|
+
normalizedOperation(String(operation.operationPath ?? "")),
|
|
1316
|
+
operation.operationName,
|
|
1317
|
+
operation.operationName,
|
|
1305
1318
|
modelRepoGraphId,
|
|
1306
1319
|
modelRepoGraphId,
|
|
1307
1320
|
workspaceId,
|
|
@@ -1319,9 +1332,15 @@ function scoreImplementationCandidate(row, operation) {
|
|
|
1319
1332
|
const localServicePathMatch = flag(row.localServicePathMatch);
|
|
1320
1333
|
const applicationHasLocalServices = flag(row.applicationHasLocalServices);
|
|
1321
1334
|
const appDependsOnModel = flag(row.appDependsOnModel);
|
|
1335
|
+
const applicationHasLocalRegistrationForOperation = flag(row.applicationHasLocalRegistrationForOperation);
|
|
1322
1336
|
const appDependsOnHandler = flag(row.appDependsOnHandler);
|
|
1323
1337
|
const handlerDependsOnModel = flag(row.handlerDependsOnModel);
|
|
1324
1338
|
const importSource = typeof row.importSource === "string" && row.importSource.length > 0;
|
|
1339
|
+
const sameRepoRegistration = flag(row.sameRepoRegistration);
|
|
1340
|
+
const modelOriented = row.modelKind === "cap-db-model" || !applicationHasLocalRegistrationForOperation;
|
|
1341
|
+
const methodMatches = true;
|
|
1342
|
+
const registeredAndLinked = sameRepoRegistration && importSource;
|
|
1343
|
+
const helperOwned = modelOriented && methodMatches && registeredAndLinked && sameRepoRegistration && !applicationHasLocalServices && !modelIsApplicationRepo && !modelIsHandlerRepo && !localServicePathMatch && !appDependsOnModel && !appDependsOnHandler && !handlerDependsOnModel;
|
|
1325
1344
|
if (modelIsApplicationRepo) {
|
|
1326
1345
|
score += 100;
|
|
1327
1346
|
acceptedReasons.push("model package equals registration package");
|
|
@@ -1348,6 +1367,10 @@ function scoreImplementationCandidate(row, operation) {
|
|
|
1348
1367
|
score += 20;
|
|
1349
1368
|
acceptedReasons.push("handler package depends on model package");
|
|
1350
1369
|
}
|
|
1370
|
+
if (helperOwned) {
|
|
1371
|
+
score += 60;
|
|
1372
|
+
acceptedReasons.push("unique registered helper implementation for model-only operation");
|
|
1373
|
+
}
|
|
1351
1374
|
if (importSource) {
|
|
1352
1375
|
score += 10;
|
|
1353
1376
|
acceptedReasons.push("registration imports handler class");
|
|
@@ -1355,8 +1378,8 @@ function scoreImplementationCandidate(row, operation) {
|
|
|
1355
1378
|
const hasOwnership = modelIsApplicationRepo || modelIsHandlerRepo;
|
|
1356
1379
|
const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
|
|
1357
1380
|
const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
|
|
1358
|
-
if (!hasOwnership && !localServicePathMatch && !hasCrossPackage) rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
|
|
1359
|
-
const accepted = !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel);
|
|
1381
|
+
if (!hasOwnership && !localServicePathMatch && !hasCrossPackage && !helperOwned) rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
|
|
1382
|
+
const accepted = !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned);
|
|
1360
1383
|
if (!accepted && rejectedReasons.length === 0) rejectedReasons.push("candidate did not meet implementation ownership policy");
|
|
1361
1384
|
return { ...row, methodId: Number(row.methodId), score, accepted, acceptedReasons, rejectedReasons };
|
|
1362
1385
|
}
|
|
@@ -1383,6 +1406,7 @@ function candidateEvidence(candidate, rank) {
|
|
|
1383
1406
|
directOwnership: { modelIsApplicationRepo: flag(candidate.modelIsApplicationRepo), modelIsHandlerRepo: flag(candidate.modelIsHandlerRepo) },
|
|
1384
1407
|
localServicePathMatch: flag(candidate.localServicePathMatch),
|
|
1385
1408
|
applicationHasLocalServices: flag(candidate.applicationHasLocalServices),
|
|
1409
|
+
applicationHasLocalRegistrationForOperation: flag(candidate.applicationHasLocalRegistrationForOperation),
|
|
1386
1410
|
appDependsOnModel: flag(candidate.appDependsOnModel),
|
|
1387
1411
|
appDependsOnHandler: flag(candidate.appDependsOnHandler),
|
|
1388
1412
|
handlerDependsOnModel: flag(candidate.handlerDependsOnModel),
|
|
@@ -1435,9 +1459,17 @@ function sourceFilesForStart(db, repoId, start) {
|
|
|
1435
1459
|
operation,
|
|
1436
1460
|
operation
|
|
1437
1461
|
);
|
|
1438
|
-
if (
|
|
1439
|
-
if (
|
|
1440
|
-
|
|
1462
|
+
if (rows2.length > 0) return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
1463
|
+
if (start.servicePath && operation) {
|
|
1464
|
+
const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile
|
|
1465
|
+
FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
|
|
1466
|
+
JOIN graph_edges e ON e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved' AND e.from_kind='operation' AND e.from_id=CAST(o.id AS TEXT)
|
|
1467
|
+
JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
1468
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
1469
|
+
WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?)`).all(repoId, repoId, start.servicePath, operation, operation);
|
|
1470
|
+
if (implRows.length > 0) return new Set(implRows.map((row) => row.sourceFile).filter(Boolean));
|
|
1471
|
+
}
|
|
1472
|
+
return void 0;
|
|
1441
1473
|
}
|
|
1442
1474
|
function startScope(db, start) {
|
|
1443
1475
|
const repo = start.repo ? db.prepare(
|
|
@@ -1470,11 +1502,19 @@ function handlerFilesForOperation(db, operationId) {
|
|
|
1470
1502
|
).all(op.repoId, operation, operation, op.operationName);
|
|
1471
1503
|
return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
1472
1504
|
}
|
|
1505
|
+
function implementationEdge(db, operationId) {
|
|
1506
|
+
return db.prepare("SELECT * FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND from_kind='operation' AND from_id=? ORDER BY CASE status WHEN 'resolved' THEN 0 WHEN 'ambiguous' THEN 1 ELSE 2 END,id LIMIT 1").get(operationId);
|
|
1507
|
+
}
|
|
1508
|
+
function handlerMethodNode(db, methodId) {
|
|
1509
|
+
const row = db.prepare(`SELECT hm.id methodId,hm.method_name methodName,hm.decorator_value decoratorValue,hm.source_line sourceLine,hc.class_name className,hc.source_file sourceFile,r.name repoName,r.id repoId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id JOIN repositories r ON r.id=hc.repo_id WHERE hm.id=?`).get(methodId);
|
|
1510
|
+
if (!row) return void 0;
|
|
1511
|
+
return { id: `handler_method:${methodId}`, kind: "handler_method", label: `${String(row.repoName)}:${String(row.className)}.${String(row.methodName)}`, ...row };
|
|
1512
|
+
}
|
|
1473
1513
|
function implementationScope(db, operationId) {
|
|
1474
|
-
const edge = db
|
|
1475
|
-
if (!edge) return { files: /* @__PURE__ */ new Set() };
|
|
1514
|
+
const edge = implementationEdge(db, operationId);
|
|
1515
|
+
if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
|
|
1476
1516
|
const row = db.prepare("SELECT hc.repo_id repoId,hc.source_file sourceFile FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id WHERE hm.id=?").get(edge.to_id);
|
|
1477
|
-
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []) };
|
|
1517
|
+
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), edge };
|
|
1478
1518
|
}
|
|
1479
1519
|
function includeCall(type, options) {
|
|
1480
1520
|
if (!options.includeDb && type === "local_db_query") return false;
|
|
@@ -1573,6 +1613,22 @@ function trace(db, start, options) {
|
|
|
1573
1613
|
const edges = [];
|
|
1574
1614
|
const nodes = /* @__PURE__ */ new Map();
|
|
1575
1615
|
const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, depth: 1 }] : [];
|
|
1616
|
+
if (start.servicePath && (start.operation ?? start.operationPath)) {
|
|
1617
|
+
const startOperation = normalizeOperation(start.operation ?? start.operationPath);
|
|
1618
|
+
const row = db.prepare(`SELECT o.id operationId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE s.service_path=? AND (o.operation_path=? OR o.operation_name=?) ORDER BY o.id LIMIT 1`).get(start.servicePath, startOperation, startOperation);
|
|
1619
|
+
if (row?.operationId !== void 0) {
|
|
1620
|
+
const opId = String(row.operationId);
|
|
1621
|
+
const op = operationNode(db, opId);
|
|
1622
|
+
const impl = implementationScope(db, opId);
|
|
1623
|
+
if (op) nodes.set(String(op.id), op);
|
|
1624
|
+
if (impl.edge) {
|
|
1625
|
+
const implEvidence = JSON.parse(String(impl.edge.evidence_json || "{}"));
|
|
1626
|
+
const handlerNode = impl.edge.status === "resolved" ? handlerMethodNode(db, impl.edge.to_id) : void 0;
|
|
1627
|
+
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
1628
|
+
edges.push({ step: 1, type: "operation_implemented_by_handler", from: op?.label ? String(op.label) : `${start.servicePath}/${startOperation ?? ""}`, 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) });
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1576
1632
|
const seenScopes = /* @__PURE__ */ new Set();
|
|
1577
1633
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
1578
1634
|
while (queue.length > 0) {
|
|
@@ -1628,10 +1684,26 @@ function trace(db, start, options) {
|
|
|
1628
1684
|
confidence: Number(effectiveRow.confidence ?? call.confidence),
|
|
1629
1685
|
unresolvedReason: effective.unresolvedReason
|
|
1630
1686
|
});
|
|
1631
|
-
if (effectiveRow.to_kind === "operation"
|
|
1687
|
+
if (effectiveRow.to_kind === "operation") {
|
|
1632
1688
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
1689
|
+
if (implementation.edge) {
|
|
1690
|
+
const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
|
|
1691
|
+
const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : void 0;
|
|
1692
|
+
const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
1693
|
+
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
1694
|
+
edges.push({
|
|
1695
|
+
step: current.depth + 1,
|
|
1696
|
+
type: "operation_implemented_by_handler",
|
|
1697
|
+
from: to,
|
|
1698
|
+
to: implTo,
|
|
1699
|
+
evidence: implEvidence,
|
|
1700
|
+
confidence: Number(implementation.edge.confidence ?? 0),
|
|
1701
|
+
unresolvedReason: implementation.edge.status === "resolved" ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status)
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
if (current.depth >= maxDepth) continue;
|
|
1633
1705
|
const files = implementation.files.size > 0 ? implementation.files : handlerFilesForOperation(db, effectiveRow.to_id);
|
|
1634
|
-
if (files.size > 0) {
|
|
1706
|
+
if (implementation.edge?.status === "resolved" && files.size > 0) {
|
|
1635
1707
|
const targetRepoId = implementation.repoId ?? db.prepare(
|
|
1636
1708
|
"SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?"
|
|
1637
1709
|
).get(effectiveRow.to_id)?.repoId;
|
|
@@ -1678,4 +1750,4 @@ export {
|
|
|
1678
1750
|
linkWorkspace,
|
|
1679
1751
|
trace
|
|
1680
1752
|
};
|
|
1681
|
-
//# sourceMappingURL=chunk-
|
|
1753
|
+
//# sourceMappingURL=chunk-LQT67AU5.js.map
|