@saptools/service-flow 0.1.11 → 0.1.13
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 +20 -0
- package/README.md +6 -4
- package/TECHNICAL-NOTE.md +10 -1
- package/dist/{chunk-GG4XJGES.js → chunk-Q7W7TE3W.js} +158 -37
- package/dist/chunk-Q7W7TE3W.js.map +1 -0
- package/dist/cli.js +89 -14
- 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,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.13
|
|
4
|
+
|
|
5
|
+
- Added implementation-context fallback for local `cds.services.*` calls so helper packages can resolve model-package operations only when handler/dependency/registration evidence ties the caller repository to the target operation.
|
|
6
|
+
- Preserved same-repository local service resolution as the strongest path and added explicit evidence/reasons for implementation-context ownership, rejected candidates, and candidate-without-caller-ownership cases.
|
|
7
|
+
- Replaced fragile CAP DB query string extraction with AST traversal for chained `cds.run(SELECT/INSERT/UPDATE/DELETE...)` forms, including multiline `columns(...).where(...)` chains and `this.EntityName` targets.
|
|
8
|
+
- Kept genuinely dynamic DB queries terminal while exposing parser-warning evidence and rendering unknown targets as `Entity: unknown` instead of raw numeric call ids in table/Mermaid output.
|
|
9
|
+
- Further reduced symbol-call noise by filtering built-in collection/string methods, logger calls, global built-ins, third-party package property calls, and unindexed `this.container.method()` calls while retaining indexed local helpers and relative object-literal helper imports.
|
|
10
|
+
- Added doctor aggregate visibility for local service calls resolved by implementation context and calls left unresolved because candidates lack caller ownership.
|
|
11
|
+
- Updated README and technical notes to clarify generated constants remain low-level parser output rather than persisted graph facts.
|
|
12
|
+
|
|
13
|
+
## 0.1.12
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
- Made implementation matching decorator-aware so generated `Func*`/`Action*` constants outrank method-name fallback and contradictory decorators are rejected without making edges ambiguous.
|
|
17
|
+
- Cleared stale unresolved reasons from resolved symbol calls and suppressed false trace unresolved reasons for symbol edges with concrete callee ids.
|
|
18
|
+
- 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.
|
|
19
|
+
- Added first-class symbol nodes and readable symbol labels/locations to JSON, table, and Mermaid trace output.
|
|
20
|
+
- 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.
|
|
21
|
+
- 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.
|
|
22
|
+
|
|
3
23
|
## 0.1.11
|
|
4
24
|
|
|
5
25
|
- 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,9 +152,11 @@ 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 first stays within the same repository and matches the target operation by exact qualified CDS service name, exact simple service name, exact service path, or an unambiguous service-path suffix. If no same-repository service exists, the linker can use implementation-context evidence to resolve model-package operations for helper packages: a resolved/ambiguous implementation candidate, registration package, or dependency/import edge must tie the caller repository to the model operation. Name-only global matches are preserved as unresolved candidate evidence rather than guessed links. 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
|
-
external destinations, and unresolved/dynamic candidates when edges exist.
|
|
159
|
+
external destinations, and unresolved/dynamic candidates when edges exist. Chained CAP DB queries inside `cds.run(...)` are parsed with TypeScript AST evidence for `SELECT`, `INSERT`, `UPDATE`, and `DELETE` forms. When the query target is genuinely dynamic, graph status remains terminal and JSON retains `parserWarning` evidence, while table and Mermaid render the target as `Entity: unknown` rather than a numeric call id.
|
|
158
160
|
|
|
159
161
|
```bash
|
|
160
162
|
service-flow trace --workspace /path/to/workspace --repo facade-service --operation doWork
|
|
@@ -285,7 +287,7 @@ service-flow clean --workspace /path/to/workspace
|
|
|
285
287
|
| Local data access | `cds.run(SELECT...)` and local entity query evidence |
|
|
286
288
|
| Async channels | Event Mesh-style `emit`, `publish`, and `on` facts |
|
|
287
289
|
| External calls | Cloud SDK-style HTTP/destination calls and external edge evidence |
|
|
288
|
-
| Generated constants |
|
|
290
|
+
| Generated constants | low-level `parseGeneratedConstants` parser output for integrations; not persisted as first-class graph facts in this patch |
|
|
289
291
|
|
|
290
292
|
---
|
|
291
293
|
|
package/TECHNICAL-NOTE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Service Flow
|
|
1
|
+
# Service Flow Resolution Notes
|
|
2
2
|
|
|
3
3
|
- Imported helper bindings: TypeScript imports are resolved for relative modules. When a caller assigns `const client = await connectToService()`, the analyzer follows the imported symbol to an exported helper that returns `cds.connect.to(...)` and persists caller-variable evidence plus the helper source/export chain.
|
|
4
4
|
- Candidate ranking: operation-path matches start as weak candidates. A resolved operation edge requires a strong signal such as exact service path, CDS alias/destination context, or explicit dynamic variable overrides. Otherwise candidates are preserved in edge evidence as ambiguous or unresolved.
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
- Trace cycle safety: trace queues carry repository IDs, visited scope keys are independent of depth, graph edge IDs are emitted once, and revisiting an already-seen downstream operation scope creates a cycle marker instead of recursive expansion.
|
|
7
7
|
- SQLite reliability: the package uses a persistent SQLite connection per opened database, bound parameters, transactions, WAL, busy timeouts, read-only openings for query commands, and connection-local foreign-key enforcement. Native driver loading failures produce an actionable startup error before output rendering.
|
|
8
8
|
|
|
9
|
+
## 0.1.13 audit follow-up notes
|
|
10
|
+
|
|
11
|
+
- Local CAP service calls keep same-repository service ownership as the strongest resolution path. When the caller repository does not own the CDS service model, the linker searches workspace operations by service identity and operation name/path, then requires caller ownership evidence from implementation edges, ambiguous implementation candidates, registration packages, or resolved dependency/import edges before resolving. Candidate operations without caller evidence are retained with `local_service_candidate_without_caller_ownership` rather than guessed.
|
|
12
|
+
- Trace traversal can use local-call context to choose the caller repository's handler from an otherwise ambiguous global implementation edge. This is scoped to the local call and does not rewrite the global `OPERATION_IMPLEMENTED_BY_HANDLER` ambiguity.
|
|
13
|
+
- CAP DB entity extraction for local `cds.run(...)` calls uses TypeScript AST traversal across chained query expressions, including `SELECT.one.from(Entity).columns(...).where(...)`, `INSERT.into(Entity)`, `UPDATE(Entity)`, and `DELETE.from(Entity)`. Dynamic or unknowable query targets remain terminal graph edges with parser-warning evidence.
|
|
14
|
+
- Human output never labels an unknown DB query target with the raw call id. Table and Mermaid output use `Entity: unknown`; JSON keeps the parser warning and call id evidence for machines.
|
|
15
|
+
- Symbol-call indexing is intentionally conservative for property accesses. Built-in collection/string calls, global built-ins, logger calls, third-party package properties, and unindexed `this.someContainer.method()` calls are filtered unless indexed local helper or relative import evidence makes the edge actionable.
|
|
16
|
+
- Generated constants remain low-level parser output through `parseGeneratedConstants`. They are not persisted as graph facts; implementation linking only uses deterministic decorator normalization for common generated action/function names.
|
|
17
|
+
|
|
9
18
|
## 0.1.4 trace-correctness additions
|
|
10
19
|
|
|
11
20
|
- Helper exports are normalized through a public-to-local export map, so `export { helper }` and `export { helper as publicHelper }` both resolve to the local declaration that contains the `cds.connect.to(...)` call.
|
|
@@ -555,22 +555,39 @@ function matchingParen(text, open) {
|
|
|
555
555
|
}
|
|
556
556
|
return -1;
|
|
557
557
|
}
|
|
558
|
-
function
|
|
559
|
-
|
|
560
|
-
if (
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
558
|
+
function entityFromExpression(expr) {
|
|
559
|
+
if (!expr) return void 0;
|
|
560
|
+
if (ts4.isIdentifier(expr) || ts4.isStringLiteral(expr) || ts4.isNoSubstitutionTemplateLiteral(expr)) return expr.text;
|
|
561
|
+
if (ts4.isPropertyAccessExpression(expr) && expr.expression.kind === ts4.SyntaxKind.ThisKeyword) return expr.name.text;
|
|
562
|
+
return void 0;
|
|
563
|
+
}
|
|
564
|
+
function expressionName(expr) {
|
|
565
|
+
if (ts4.isIdentifier(expr)) return expr.text;
|
|
566
|
+
if (ts4.isPropertyAccessExpression(expr)) return `${expressionName(expr.expression)}.${expr.name.text}`;
|
|
567
|
+
return expr.getText();
|
|
568
|
+
}
|
|
569
|
+
function queryEntityFromAst(expr) {
|
|
570
|
+
if (ts4.isParenthesizedExpression(expr) || ts4.isAwaitExpression(expr)) return queryEntityFromAst(expr.expression);
|
|
571
|
+
if (ts4.isCallExpression(expr)) {
|
|
572
|
+
const name = expressionName(expr.expression);
|
|
573
|
+
if (name === "cds.run") return queryEntityFromAst(expr.arguments[0]);
|
|
574
|
+
if (name === "SELECT.one.from" || name === "SELECT.from" || name === "INSERT.into" || name === "DELETE.from") return entityFromExpression(expr.arguments[0]);
|
|
575
|
+
if (name === "UPDATE") return entityFromExpression(expr.arguments[0]);
|
|
576
|
+
const receiver = ts4.isPropertyAccessExpression(expr.expression) ? expr.expression.expression : void 0;
|
|
577
|
+
if (receiver) return queryEntityFromAst(receiver);
|
|
578
|
+
}
|
|
579
|
+
return void 0;
|
|
571
580
|
}
|
|
572
581
|
function extractQueryEntity(expr) {
|
|
573
|
-
|
|
582
|
+
const source = ts4.createSourceFile("query.ts", `const __query = (${expr});`, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
|
|
583
|
+
let found;
|
|
584
|
+
const visit = (node) => {
|
|
585
|
+
if (found) return;
|
|
586
|
+
if (ts4.isParenthesizedExpression(node)) found = queryEntityFromAst(node.expression);
|
|
587
|
+
ts4.forEachChild(node, visit);
|
|
588
|
+
};
|
|
589
|
+
visit(source);
|
|
590
|
+
return found;
|
|
574
591
|
}
|
|
575
592
|
async function parseOutboundCalls(repoPath, filePath) {
|
|
576
593
|
const text = await fs6.readFile(path7.join(repoPath, filePath), "utf8");
|
|
@@ -1074,7 +1091,7 @@ function substituteVariables(template, vars) {
|
|
|
1074
1091
|
// src/linker/service-resolver.ts
|
|
1075
1092
|
function rows(db, operationPath, workspaceId) {
|
|
1076
1093
|
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
|
|
1094
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,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
1095
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
1079
1096
|
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
1097
|
).all(
|
|
@@ -1098,11 +1115,21 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1098
1115
|
candidates: [],
|
|
1099
1116
|
reasons: ["missing_operation_path"]
|
|
1100
1117
|
};
|
|
1101
|
-
const
|
|
1118
|
+
const allCandidates = rows(db, signals.operationPath, workspaceId).map((c) => ({
|
|
1102
1119
|
...c,
|
|
1103
1120
|
score: 0.2,
|
|
1104
1121
|
reasons: ["operation_path_match"]
|
|
1105
1122
|
}));
|
|
1123
|
+
let candidates = allCandidates.filter((c) => matchesLocalRepo(db, c.operationId, signals.repoId));
|
|
1124
|
+
if (candidates.length === 0 && signals.repoId !== void 0 && signals.serviceName) {
|
|
1125
|
+
candidates = implementationContextCandidates(db, allCandidates, signals.repoId, signals.serviceName);
|
|
1126
|
+
if (candidates.length === 0)
|
|
1127
|
+
return {
|
|
1128
|
+
status: "unresolved",
|
|
1129
|
+
candidates: allCandidates.filter((c) => serviceMatches(c, signals.serviceName)),
|
|
1130
|
+
reasons: allCandidates.some((c) => serviceMatches(c, signals.serviceName)) ? ["local_service_candidate_without_caller_ownership"] : ["no_operation_candidates"]
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1106
1133
|
if (candidates.length === 0)
|
|
1107
1134
|
return {
|
|
1108
1135
|
status: "unresolved",
|
|
@@ -1110,7 +1137,7 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1110
1137
|
reasons: ["no_operation_candidates"]
|
|
1111
1138
|
};
|
|
1112
1139
|
const hasStrongSignal = Boolean(
|
|
1113
|
-
signals.servicePath || signals.alias || signals.destination || signals.hasExplicitOverride
|
|
1140
|
+
signals.servicePath || signals.serviceName || signals.alias || signals.destination || signals.hasExplicitOverride
|
|
1114
1141
|
);
|
|
1115
1142
|
for (const c of candidates) {
|
|
1116
1143
|
if (signals.servicePath && c.servicePath === signals.servicePath) {
|
|
@@ -1121,14 +1148,25 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1121
1148
|
c.score -= 0.1;
|
|
1122
1149
|
c.reasons.push("service_path_mismatch");
|
|
1123
1150
|
}
|
|
1124
|
-
if (signals.serviceName
|
|
1125
|
-
|
|
1126
|
-
c.
|
|
1151
|
+
if (signals.serviceName) {
|
|
1152
|
+
const simple = signals.serviceName.split(".").at(-1) ?? signals.serviceName;
|
|
1153
|
+
if (c.qualifiedName === signals.serviceName) {
|
|
1154
|
+
c.score += 0.8;
|
|
1155
|
+
c.reasons.push("exact_local_qualified_service_name");
|
|
1156
|
+
} else if (c.serviceName === signals.serviceName || c.serviceName === simple) {
|
|
1157
|
+
c.score += 0.75;
|
|
1158
|
+
c.reasons.push("exact_local_simple_service_name");
|
|
1159
|
+
} else if (c.servicePath === signals.serviceName || c.servicePath === `/${signals.serviceName}` || c.servicePath === `/${simple}`) {
|
|
1160
|
+
c.score += 0.7;
|
|
1161
|
+
c.reasons.push("exact_local_service_path");
|
|
1162
|
+
} else if (c.servicePath.endsWith(`/${simple}`)) {
|
|
1163
|
+
c.score += candidates.filter((candidate) => candidate.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2;
|
|
1164
|
+
c.reasons.push("suffix_local_service_path");
|
|
1165
|
+
} else c.reasons.push("local_service_name_mismatch");
|
|
1127
1166
|
}
|
|
1128
|
-
if (signals.serviceName && c.servicePath !== `/${signals.serviceName}` && !c.servicePath.endsWith(`/${signals.serviceName}`)) c.reasons.push("local_service_name_mismatch");
|
|
1129
1167
|
if (signals.hasExplicitOverride) {
|
|
1130
1168
|
c.score += 0.2;
|
|
1131
|
-
c.reasons.push("explicit_dynamic_override");
|
|
1169
|
+
c.reasons.push(signals.repoId !== void 0 ? "explicit_local_service_call" : "explicit_dynamic_override");
|
|
1132
1170
|
}
|
|
1133
1171
|
}
|
|
1134
1172
|
for (const c of candidates) c.score = Math.max(0, Math.min(1, c.score));
|
|
@@ -1149,7 +1187,7 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1149
1187
|
candidates,
|
|
1150
1188
|
reasons: ["operation_path_only_has_no_strong_target_signal"]
|
|
1151
1189
|
};
|
|
1152
|
-
if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName &&
|
|
1190
|
+
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
1191
|
return {
|
|
1154
1192
|
status: "resolved",
|
|
1155
1193
|
target: best,
|
|
@@ -1162,6 +1200,34 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1162
1200
|
reasons: ["candidate_score_below_resolution_threshold"]
|
|
1163
1201
|
};
|
|
1164
1202
|
}
|
|
1203
|
+
function serviceMatches(candidate, serviceName) {
|
|
1204
|
+
if (!serviceName) return false;
|
|
1205
|
+
const simple = serviceName.split(".").at(-1) ?? serviceName;
|
|
1206
|
+
return candidate.qualifiedName === serviceName || candidate.serviceName === serviceName || candidate.serviceName === simple || candidate.servicePath === serviceName || candidate.servicePath === `/${serviceName}` || candidate.servicePath === `/${simple}` || candidate.servicePath.endsWith(`/${simple}`);
|
|
1207
|
+
}
|
|
1208
|
+
function implementationContextCandidates(db, candidates, callerRepoId, serviceName) {
|
|
1209
|
+
const matching = candidates.filter((candidate) => serviceMatches(candidate, serviceName));
|
|
1210
|
+
const owned = matching.map((candidate) => ownershipReason(db, candidate, callerRepoId)).filter((item) => Boolean(item));
|
|
1211
|
+
if (owned.length === 0) return [];
|
|
1212
|
+
const direct = owned.filter((item) => item.reason !== "caller_depends_on_model_package");
|
|
1213
|
+
const chosen = direct.length > 0 ? direct : owned.length === 1 ? owned : [];
|
|
1214
|
+
return chosen.map((item) => ({ ...item.candidate, score: 0.95, reasons: [...item.candidate.reasons, "implementation_context_caller_ownership", item.reason] }));
|
|
1215
|
+
}
|
|
1216
|
+
function ownershipReason(db, candidate, callerRepoId) {
|
|
1217
|
+
const edge = db.prepare("SELECT status,evidence_json,to_id 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 LIMIT 1").get(String(candidate.operationId));
|
|
1218
|
+
if (edge?.status === "resolved") {
|
|
1219
|
+
const row = db.prepare("SELECT hc.repo_id repoId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id WHERE hm.id=?").get(edge.to_id);
|
|
1220
|
+
if (row?.repoId === callerRepoId) return { candidate, reason: "resolved_implementation_handler_repo_matches_caller" };
|
|
1221
|
+
}
|
|
1222
|
+
if (edge?.evidence_json) {
|
|
1223
|
+
const evidence = JSON.parse(edge.evidence_json);
|
|
1224
|
+
const hit = evidence.candidates?.find((item) => item.accepted && (Number(item.handlerPackage?.id) === callerRepoId || Number(item.applicationPackage?.id) === callerRepoId));
|
|
1225
|
+
if (hit) return { candidate, reason: edge.status === "ambiguous" ? "ambiguous_implementation_candidate_repo_matches_caller" : "registration_package_matches_caller" };
|
|
1226
|
+
}
|
|
1227
|
+
const dep = db.prepare("SELECT 1 FROM graph_edges WHERE edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND status='resolved' AND from_kind='repo' AND from_id=? AND to_id=?").get(String(callerRepoId), String(candidate.repoId));
|
|
1228
|
+
if (dep) return { candidate, reason: "caller_depends_on_model_package" };
|
|
1229
|
+
return void 0;
|
|
1230
|
+
}
|
|
1165
1231
|
function matchesLocalRepo(db, operationId, repoId) {
|
|
1166
1232
|
if (repoId === void 0) return true;
|
|
1167
1233
|
const row = db.prepare("SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?").get(operationId);
|
|
@@ -1216,8 +1282,8 @@ function linkWorkspace(db, workspaceId, vars = {}) {
|
|
|
1216
1282
|
const generation = nextGraphGeneration(db, workspaceId);
|
|
1217
1283
|
db.prepare("DELETE FROM graph_edges WHERE workspace_id=?").run(workspaceId);
|
|
1218
1284
|
const deps = linkHelperPackages(db, workspaceId, generation);
|
|
1219
|
-
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
1220
1285
|
const impl = linkImplementations(db, workspaceId, generation);
|
|
1286
|
+
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
1221
1287
|
db.prepare("UPDATE repositories SET graph_generation=?, graph_stale_reason=NULL, graph_stale_at=NULL WHERE workspace_id=?").run(generation, workspaceId);
|
|
1222
1288
|
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount, implementationUnresolvedCount: impl.unresolvedCount };
|
|
1223
1289
|
});
|
|
@@ -1307,7 +1373,7 @@ function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
|
1307
1373
|
function deduplicateCandidates(rows2) {
|
|
1308
1374
|
const merged = /* @__PURE__ */ new Map();
|
|
1309
1375
|
for (const row of rows2) {
|
|
1310
|
-
const key = [row.methodId, row.classId, row.
|
|
1376
|
+
const key = [row.methodId, row.classId, row.handlerRepoId].join(":");
|
|
1311
1377
|
const registration = { file: row.registrationFile, line: row.registrationLine, kind: row.registrationKind, importSource: row.importSource };
|
|
1312
1378
|
const existing = merged.get(key);
|
|
1313
1379
|
if (!existing) {
|
|
@@ -1335,6 +1401,9 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1335
1401
|
const modelRepoGraphId = graphId(operation.modelRepoId);
|
|
1336
1402
|
return db.prepare(`SELECT DISTINCT
|
|
1337
1403
|
hm.id methodId,
|
|
1404
|
+
hm.method_name methodName,
|
|
1405
|
+
hm.decorator_value decoratorValue,
|
|
1406
|
+
hm.decorator_raw_expression decoratorRawExpression,
|
|
1338
1407
|
hc.id classId,
|
|
1339
1408
|
hc.class_name className,
|
|
1340
1409
|
hc.source_file sourceFile,
|
|
@@ -1361,7 +1430,7 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1361
1430
|
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
1362
1431
|
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
1432
|
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,
|
|
1433
|
+
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
1434
|
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
1435
|
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
1436
|
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 +1440,7 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1371
1440
|
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
1441
|
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
1373
1442
|
WHERE appRepo.workspace_id=?
|
|
1374
|
-
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?)`).all(
|
|
1443
|
+
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
|
|
1375
1444
|
operation.modelRepoId,
|
|
1376
1445
|
operation.modelRepo,
|
|
1377
1446
|
operation.modelPackage,
|
|
@@ -1385,12 +1454,14 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
1385
1454
|
normalizedOperation(String(operation.operationPath ?? "")),
|
|
1386
1455
|
operation.operationName,
|
|
1387
1456
|
operation.operationName,
|
|
1457
|
+
`%${upperFirst(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`,
|
|
1388
1458
|
modelRepoGraphId,
|
|
1389
1459
|
modelRepoGraphId,
|
|
1390
1460
|
workspaceId,
|
|
1391
1461
|
normalizedOperation(String(operation.operationPath ?? "")),
|
|
1392
1462
|
operation.operationName,
|
|
1393
|
-
operation.operationName
|
|
1463
|
+
operation.operationName,
|
|
1464
|
+
`%${upperFirst(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`
|
|
1394
1465
|
);
|
|
1395
1466
|
}
|
|
1396
1467
|
function scoreImplementationCandidate(row, operation) {
|
|
@@ -1408,7 +1479,10 @@ function scoreImplementationCandidate(row, operation) {
|
|
|
1408
1479
|
const importSource = typeof row.importSource === "string" && row.importSource.length > 0;
|
|
1409
1480
|
const sameRepoRegistration = flag(row.sameRepoRegistration);
|
|
1410
1481
|
const modelOriented = row.modelKind === "cap-db-model" || !applicationHasLocalRegistrationForOperation;
|
|
1411
|
-
const
|
|
1482
|
+
const methodSignal = implementationMethodSignal(row, operation);
|
|
1483
|
+
const methodMatches = methodSignal.matches;
|
|
1484
|
+
acceptedReasons.push(...methodSignal.acceptedReasons);
|
|
1485
|
+
rejectedReasons.push(...methodSignal.rejectedReasons);
|
|
1412
1486
|
const registeredAndLinked = sameRepoRegistration && importSource;
|
|
1413
1487
|
const helperOwned = modelOriented && methodMatches && registeredAndLinked && sameRepoRegistration && !applicationHasLocalServices && !modelIsApplicationRepo && !modelIsHandlerRepo && !localServicePathMatch && !appDependsOnModel && !appDependsOnHandler && !handlerDependsOnModel;
|
|
1414
1488
|
if (modelIsApplicationRepo) {
|
|
@@ -1449,7 +1523,7 @@ function scoreImplementationCandidate(row, operation) {
|
|
|
1449
1523
|
const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
|
|
1450
1524
|
const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
|
|
1451
1525
|
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);
|
|
1526
|
+
const accepted = methodMatches && !methodSignal.contradicted && !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned);
|
|
1453
1527
|
if (!accepted && rejectedReasons.length === 0) rejectedReasons.push("candidate did not meet implementation ownership policy");
|
|
1454
1528
|
return { ...row, methodId: Number(row.methodId), score, accepted, acceptedReasons, rejectedReasons };
|
|
1455
1529
|
}
|
|
@@ -1485,6 +1559,29 @@ function candidateEvidence(candidate, rank) {
|
|
|
1485
1559
|
}
|
|
1486
1560
|
};
|
|
1487
1561
|
}
|
|
1562
|
+
function implementationMethodSignal(row, operation) {
|
|
1563
|
+
const op = normalizedOperation(String(operation.operationPath ?? operation.operationName ?? ""));
|
|
1564
|
+
const decorator = normalizeDecoratorOperation(typeof row.decoratorValue === "string" ? row.decoratorValue : void 0, typeof row.decoratorRawExpression === "string" ? row.decoratorRawExpression : void 0);
|
|
1565
|
+
if (decorator && decorator === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
1566
|
+
if (decorator && decorator !== op) return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["method_name_matches_but_decorator_targets_different_operation"] };
|
|
1567
|
+
if (String(row.methodName ?? "") === op) return { matches: true, contradicted: false, acceptedReasons: ["method name fallback matched operation"], rejectedReasons: [] };
|
|
1568
|
+
return { matches: false, contradicted: false, acceptedReasons: [], rejectedReasons: ["method name does not match operation"] };
|
|
1569
|
+
}
|
|
1570
|
+
function normalizeDecoratorOperation(value, raw) {
|
|
1571
|
+
const candidate = value ?? raw?.split(".").filter(Boolean).at(-2);
|
|
1572
|
+
if (!candidate) return void 0;
|
|
1573
|
+
const cleaned = candidate.replace(/^['"`]|['"`]$/g, "");
|
|
1574
|
+
for (const prefix of ["Func", "Action"]) {
|
|
1575
|
+
if (cleaned.startsWith(prefix) && cleaned.length > prefix.length) return lowerFirst(cleaned.slice(prefix.length));
|
|
1576
|
+
}
|
|
1577
|
+
return normalizedOperation(cleaned);
|
|
1578
|
+
}
|
|
1579
|
+
function upperFirst(value) {
|
|
1580
|
+
return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
|
|
1581
|
+
}
|
|
1582
|
+
function lowerFirst(value) {
|
|
1583
|
+
return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
|
|
1584
|
+
}
|
|
1488
1585
|
function flag(value) {
|
|
1489
1586
|
return Boolean(Number(value ?? 0));
|
|
1490
1587
|
}
|
|
@@ -1591,6 +1688,17 @@ function implementationScope(db, operationId) {
|
|
|
1591
1688
|
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);
|
|
1592
1689
|
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
1593
1690
|
}
|
|
1691
|
+
function contextImplementationMethodId(edge, callerRepoId) {
|
|
1692
|
+
if (!edge || edge.status !== "ambiguous" || callerRepoId === void 0) return void 0;
|
|
1693
|
+
const evidence = JSON.parse(String(edge.evidence_json || "{}"));
|
|
1694
|
+
const candidate = evidence.candidates?.find((item) => item.accepted && (Number(item.handlerPackage?.id) === callerRepoId || Number(item.applicationPackage?.id) === callerRepoId));
|
|
1695
|
+
return candidate?.methodId === void 0 ? void 0 : String(candidate.methodId);
|
|
1696
|
+
}
|
|
1697
|
+
function handlerScope(db, methodId) {
|
|
1698
|
+
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(methodId);
|
|
1699
|
+
if (!row) return void 0;
|
|
1700
|
+
return { repoId: row.repoId, files: new Set(row.sourceFile ? [row.sourceFile] : []), symbolId: row.symbolId };
|
|
1701
|
+
}
|
|
1594
1702
|
function includeCall(type, options) {
|
|
1595
1703
|
if (!options.includeDb && type === "local_db_query") return false;
|
|
1596
1704
|
if (!options.includeExternal && type === "external_http") return false;
|
|
@@ -1634,6 +1742,12 @@ function evidenceWithRuntimeVariables(evidence, vars) {
|
|
|
1634
1742
|
if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)];
|
|
1635
1743
|
return next;
|
|
1636
1744
|
}
|
|
1745
|
+
function symbolNode(db, symbolId) {
|
|
1746
|
+
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);
|
|
1747
|
+
if (!row) return void 0;
|
|
1748
|
+
const fileName = String(row.sourceFile ?? "").split("/").at(-1) ?? String(row.sourceFile ?? "");
|
|
1749
|
+
return { id: `symbol:${symbolId}`, kind: "symbol", label: `${fileName}:${String(row.qualifiedName ?? row.symbolName)}`, ...row };
|
|
1750
|
+
}
|
|
1637
1751
|
function operationNode(db, operationId) {
|
|
1638
1752
|
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
1753
|
if (!row) return void 0;
|
|
@@ -1668,6 +1782,7 @@ function edgeTarget(row, evidence) {
|
|
|
1668
1782
|
const operationPath = typeof evidence.operationPath === "string" ? evidence.operationPath : void 0;
|
|
1669
1783
|
const targetOperation = typeof evidence.targetOperation === "string" ? evidence.targetOperation : void 0;
|
|
1670
1784
|
const targetRepo = typeof evidence.targetRepo === "string" ? evidence.targetRepo : "";
|
|
1785
|
+
if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return /^\d+$/.test(row.to_id) ? "Entity: unknown" : `Entity: ${row.to_id}`;
|
|
1671
1786
|
return servicePath && operationPath ? `${servicePath}${operationPath}` : targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
|
|
1672
1787
|
}
|
|
1673
1788
|
function trace(db, start, options) {
|
|
@@ -1728,7 +1843,10 @@ function trace(db, start, options) {
|
|
|
1728
1843
|
const nextFiles = /* @__PURE__ */ new Set([String(symbolCall.calleeFile)]);
|
|
1729
1844
|
const nextRepoId = Number(symbolCall.calleeRepoId);
|
|
1730
1845
|
const nextKey = `${nextRepoId}:${[...nextSymbols].join(",")}:${[...nextFiles].join(",")}`;
|
|
1731
|
-
|
|
1846
|
+
const calleeNode = symbolNode(db, Number(symbolCall.callee_symbol_id));
|
|
1847
|
+
if (calleeNode) nodes.set(String(calleeNode.id), calleeNode);
|
|
1848
|
+
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 };
|
|
1849
|
+
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
1850
|
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
1851
|
else queue.push({ repoId: nextRepoId, files: nextFiles, symbolIds: nextSymbols, depth: current.depth + 1 });
|
|
1734
1852
|
}
|
|
@@ -1776,9 +1894,11 @@ function trace(db, start, options) {
|
|
|
1776
1894
|
});
|
|
1777
1895
|
if (effectiveRow.to_kind === "operation") {
|
|
1778
1896
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
1897
|
+
const contextMethodId = contextImplementationMethodId(implementation.edge, current.repoId);
|
|
1898
|
+
const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : void 0;
|
|
1779
1899
|
if (implementation.edge) {
|
|
1780
1900
|
const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
|
|
1781
|
-
const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) :
|
|
1901
|
+
const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
|
|
1782
1902
|
const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
1783
1903
|
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
1784
1904
|
edges.push({
|
|
@@ -1792,10 +1912,11 @@ function trace(db, start, options) {
|
|
|
1792
1912
|
});
|
|
1793
1913
|
}
|
|
1794
1914
|
if (current.depth >= maxDepth) continue;
|
|
1795
|
-
const
|
|
1796
|
-
const
|
|
1797
|
-
|
|
1798
|
-
|
|
1915
|
+
const contextScope = contextMethodId ? handlerScope(db, contextMethodId) : void 0;
|
|
1916
|
+
const files = contextScope?.files ?? (implementation.files.size > 0 ? implementation.files : handlerFilesForOperation(db, effectiveRow.to_id));
|
|
1917
|
+
const symbolIds = contextScope?.symbolId ? /* @__PURE__ */ new Set([contextScope.symbolId]) : implementation.symbolId ? /* @__PURE__ */ new Set([implementation.symbolId]) : void 0;
|
|
1918
|
+
if ((implementation.edge?.status === "resolved" || contextScope) && files.size > 0) {
|
|
1919
|
+
const targetRepoId = contextScope?.repoId ?? implementation.repoId ?? db.prepare(
|
|
1799
1920
|
"SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?"
|
|
1800
1921
|
).get(effectiveRow.to_id)?.repoId;
|
|
1801
1922
|
const nextKey = `${targetRepoId ?? "*"}:${[...symbolIds ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${[...files].sort().join(",")}`;
|
|
@@ -1842,4 +1963,4 @@ export {
|
|
|
1842
1963
|
linkWorkspace,
|
|
1843
1964
|
trace
|
|
1844
1965
|
};
|
|
1845
|
-
//# sourceMappingURL=chunk-
|
|
1966
|
+
//# sourceMappingURL=chunk-Q7W7TE3W.js.map
|