@saptools/service-flow 0.1.51 → 0.1.52
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 +7 -0
- package/README.md +10 -5
- package/TECHNICAL-NOTE.md +8 -4
- package/dist/{chunk-YZJKE5UX.js → chunk-PTLDSHRC.js} +2412 -553
- package/dist/chunk-PTLDSHRC.js.map +1 -0
- package/dist/cli.js +280 -506
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +45 -7
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/000-clean.ts +82 -0
- package/src/cli.ts +75 -57
- package/src/db/connection.ts +1 -1
- package/src/db/migrations.ts +5 -3
- package/src/db/repositories.ts +120 -22
- package/src/db/schema.ts +7 -2
- package/src/indexer/repository-indexer.ts +57 -29
- package/src/indexer/workspace-indexer.ts +84 -6
- package/src/linker/cross-repo-linker.ts +13 -1
- package/src/output/table-output.ts +29 -3
- package/src/parsers/cds-parser.ts +8 -2
- package/src/parsers/decorator-parser.ts +382 -48
- package/src/parsers/handler-registration-parser.ts +38 -21
- package/src/parsers/imported-wrapper-parser.ts +18 -5
- package/src/parsers/outbound-call-parser.ts +25 -8
- package/src/parsers/package-json-parser.ts +36 -11
- package/src/parsers/service-binding-parser-helpers.ts +8 -1
- package/src/parsers/service-binding-parser.ts +6 -3
- package/src/parsers/symbol-parser.ts +13 -3
- package/src/parsers/ts-project.ts +54 -0
- package/src/trace/000-dynamic-target-types.ts +84 -0
- package/src/trace/001-dynamic-identity.ts +280 -0
- package/src/trace/002-trace-diagnostics.ts +54 -0
- package/src/trace/003-dynamic-references.ts +82 -0
- package/src/trace/dynamic-targets.ts +459 -229
- package/src/trace/evidence.ts +305 -46
- package/src/trace/selectors.ts +483 -0
- package/src/trace/trace-engine.ts +90 -83
- package/src/types.ts +27 -1
- package/dist/chunk-YZJKE5UX.js.map +0 -1
|
@@ -1,5 +1,468 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/db/repositories.ts
|
|
4
|
+
function upsertWorkspace(db, rootPath, dbPath) {
|
|
5
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6
|
+
db.prepare(
|
|
7
|
+
"INSERT INTO workspaces(root_path,db_path,created_at,updated_at) VALUES(?,?,?,?) ON CONFLICT(root_path) DO UPDATE SET db_path=excluded.db_path,updated_at=excluded.updated_at"
|
|
8
|
+
).run(rootPath, dbPath, now, now);
|
|
9
|
+
return Number(
|
|
10
|
+
db.prepare("SELECT id FROM workspaces WHERE root_path=?").get(rootPath)?.id
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function getWorkspace(db, rootPath) {
|
|
14
|
+
return db.prepare("SELECT * FROM workspaces WHERE root_path=?").get(rootPath);
|
|
15
|
+
}
|
|
16
|
+
function upsertRepository(db, workspaceId, r) {
|
|
17
|
+
db.prepare(
|
|
18
|
+
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,package_name,package_version,dependencies_json,kind,is_git_repo) VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET name=excluded.name,relative_path=excluded.relative_path,package_name=excluded.package_name,package_version=excluded.package_version,dependencies_json=excluded.dependencies_json,kind=excluded.kind`
|
|
19
|
+
).run(
|
|
20
|
+
workspaceId,
|
|
21
|
+
r.name,
|
|
22
|
+
r.absolutePath,
|
|
23
|
+
r.relativePath,
|
|
24
|
+
r.packageName,
|
|
25
|
+
r.packageVersion,
|
|
26
|
+
JSON.stringify(r.dependencies ?? {}),
|
|
27
|
+
r.kind ?? "unknown",
|
|
28
|
+
r.isGitRepo ? 1 : 0
|
|
29
|
+
);
|
|
30
|
+
return Number(
|
|
31
|
+
db.prepare(
|
|
32
|
+
"SELECT id FROM repositories WHERE workspace_id=? AND absolute_path=?"
|
|
33
|
+
).get(workspaceId, r.absolutePath)?.id
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function listRepositories(db, workspaceId) {
|
|
37
|
+
return db.prepare("SELECT * FROM repositories WHERE (? IS NULL OR workspace_id=?) ORDER BY name,absolute_path,id").all(workspaceId, workspaceId);
|
|
38
|
+
}
|
|
39
|
+
function reposByName(db, name, workspaceId) {
|
|
40
|
+
return db.prepare(`SELECT * FROM repositories
|
|
41
|
+
WHERE (? IS NULL OR workspace_id=?) AND (name=? OR package_name=?)
|
|
42
|
+
ORDER BY name,absolute_path,id`).all(workspaceId, workspaceId, name, name);
|
|
43
|
+
}
|
|
44
|
+
function clearRepoFacts(db, repoId) {
|
|
45
|
+
for (const t of [
|
|
46
|
+
"cds_requires",
|
|
47
|
+
"cds_services",
|
|
48
|
+
"handler_classes",
|
|
49
|
+
"outbound_calls",
|
|
50
|
+
"symbol_calls",
|
|
51
|
+
"handler_registrations",
|
|
52
|
+
"service_bindings",
|
|
53
|
+
"symbols",
|
|
54
|
+
"diagnostics",
|
|
55
|
+
"files"
|
|
56
|
+
])
|
|
57
|
+
db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
|
|
58
|
+
db.prepare("DELETE FROM search_index WHERE repo=?").run(String(repoId));
|
|
59
|
+
}
|
|
60
|
+
function insertRequires(db, repoId, rows2) {
|
|
61
|
+
const stmt = db.prepare(
|
|
62
|
+
"INSERT INTO cds_requires(repo_id,alias,kind,model,destination,service_path,request_timeout,raw_json) VALUES(?,?,?,?,?,?,?,?)"
|
|
63
|
+
);
|
|
64
|
+
for (const r of rows2)
|
|
65
|
+
stmt.run(
|
|
66
|
+
repoId,
|
|
67
|
+
r.alias,
|
|
68
|
+
r.kind,
|
|
69
|
+
r.model,
|
|
70
|
+
r.destination,
|
|
71
|
+
r.servicePath,
|
|
72
|
+
r.requestTimeout,
|
|
73
|
+
r.rawJson
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
function insertService(db, repoId, s) {
|
|
77
|
+
const id = Number(
|
|
78
|
+
db.prepare(
|
|
79
|
+
"INSERT INTO cds_services(repo_id,namespace,service_name,qualified_name,service_path,is_extend,source_file,source_line,extension_local_ref,extension_imported_symbol,extension_local_alias,extension_module_specifier,extension_import_kind) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id"
|
|
80
|
+
).get(
|
|
81
|
+
repoId,
|
|
82
|
+
s.namespace,
|
|
83
|
+
s.serviceName,
|
|
84
|
+
s.qualifiedName,
|
|
85
|
+
s.servicePath,
|
|
86
|
+
s.isExtend ? 1 : 0,
|
|
87
|
+
s.sourceFile,
|
|
88
|
+
s.sourceLine,
|
|
89
|
+
s.extension?.localReference,
|
|
90
|
+
s.extension?.importedSymbol,
|
|
91
|
+
s.extension?.localAlias,
|
|
92
|
+
s.extension?.moduleSpecifier,
|
|
93
|
+
s.extension?.importKind
|
|
94
|
+
)?.id
|
|
95
|
+
);
|
|
96
|
+
const stmt = db.prepare(
|
|
97
|
+
"INSERT INTO cds_operations(service_id,operation_type,operation_name,operation_path,params_json,return_type,source_file,source_line,provenance,base_operation_id) VALUES(?,?,?,?,?,?,?,?,?,?)"
|
|
98
|
+
);
|
|
99
|
+
db.prepare(
|
|
100
|
+
"INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
|
|
101
|
+
).run("service", s.qualifiedName, s.servicePath, String(repoId));
|
|
102
|
+
for (const o of s.operations)
|
|
103
|
+
stmt.run(
|
|
104
|
+
id,
|
|
105
|
+
o.operationType,
|
|
106
|
+
o.operationName,
|
|
107
|
+
o.operationPath,
|
|
108
|
+
o.paramsJson,
|
|
109
|
+
o.returnType,
|
|
110
|
+
o.sourceFile,
|
|
111
|
+
o.sourceLine,
|
|
112
|
+
o.provenance ?? "direct",
|
|
113
|
+
o.baseOperationId ?? null
|
|
114
|
+
);
|
|
115
|
+
const search = db.prepare(
|
|
116
|
+
"INSERT INTO search_index(kind,name,path,repo) VALUES(?,?,?,?)"
|
|
117
|
+
);
|
|
118
|
+
for (const o of s.operations)
|
|
119
|
+
search.run("operation", o.operationName, o.operationPath, String(repoId));
|
|
120
|
+
return id;
|
|
121
|
+
}
|
|
122
|
+
function insertHandler(db, repoId, h) {
|
|
123
|
+
const sid = insertHandlerClassSymbol(db, repoId, h);
|
|
124
|
+
const hid = Number(
|
|
125
|
+
db.prepare(
|
|
126
|
+
"INSERT INTO handler_classes(repo_id,symbol_id,class_name,source_file,source_line) VALUES(?,?,?,?,?) RETURNING id"
|
|
127
|
+
).get(repoId, sid, h.className, h.sourceFile, h.sourceLine)?.id
|
|
128
|
+
);
|
|
129
|
+
const stmt = db.prepare(
|
|
130
|
+
"INSERT INTO handler_methods(handler_class_id,method_name,decorator_kind,decorator_value,decorator_raw_expression,decorator_resolution_json,source_file,source_line) VALUES(?,?,?,?,?,?,?,?)"
|
|
131
|
+
);
|
|
132
|
+
for (const m of h.methods)
|
|
133
|
+
stmt.run(
|
|
134
|
+
hid,
|
|
135
|
+
m.methodName,
|
|
136
|
+
m.decoratorKind,
|
|
137
|
+
m.decoratorValue,
|
|
138
|
+
m.decoratorRawExpression,
|
|
139
|
+
JSON.stringify(canonicalHandlerMethodResolution(m)),
|
|
140
|
+
m.sourceFile,
|
|
141
|
+
m.sourceLine
|
|
142
|
+
);
|
|
143
|
+
insertHandlerIndexDiagnostic(db, repoId, h);
|
|
144
|
+
return hid;
|
|
145
|
+
}
|
|
146
|
+
function insertHandlerClassSymbol(db, repoId, h) {
|
|
147
|
+
const classEvidence = {
|
|
148
|
+
hasHandlerDecorator: h.hasHandlerDecorator ?? false,
|
|
149
|
+
classDecoratorNames: h.classDecoratorNames ?? [],
|
|
150
|
+
observedDecoratorNames: h.observedDecoratorNames ?? [],
|
|
151
|
+
unsupportedDecoratorNames: h.unsupportedDecoratorNames ?? [],
|
|
152
|
+
unsupportedMethods: h.methods.filter((method) => !handlerMethodIsExecutable(method)).map((method) => ({
|
|
153
|
+
methodName: method.methodName,
|
|
154
|
+
decoratorKind: method.decoratorKind,
|
|
155
|
+
sourceFile: method.sourceFile,
|
|
156
|
+
sourceLine: method.sourceLine,
|
|
157
|
+
reason: method.decoratorResolution.unresolvedReason
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
return Number(
|
|
161
|
+
db.prepare(
|
|
162
|
+
"INSERT INTO symbols(repo_id,kind,name,qualified_name,exported,start_line,end_line,source_file,evidence_json) VALUES(?,?,?,?,?,?,?,?,?) RETURNING id"
|
|
163
|
+
).get(
|
|
164
|
+
repoId,
|
|
165
|
+
"class",
|
|
166
|
+
h.className,
|
|
167
|
+
h.className,
|
|
168
|
+
1,
|
|
169
|
+
h.sourceLine,
|
|
170
|
+
h.sourceLine,
|
|
171
|
+
h.sourceFile,
|
|
172
|
+
JSON.stringify(classEvidence)
|
|
173
|
+
)?.id
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
function insertHandlerIndexDiagnostic(db, repoId, h) {
|
|
177
|
+
if (!h.hasHandlerDecorator) return;
|
|
178
|
+
const hasExecutable = h.methods.some(handlerMethodIsExecutable);
|
|
179
|
+
const unsupported = h.methods.filter((method) => !handlerMethodIsExecutable(method));
|
|
180
|
+
if (hasExecutable && unsupported.length === 0) return;
|
|
181
|
+
const code = hasExecutable ? "handler_decorators_not_indexed" : "handler_methods_not_indexed";
|
|
182
|
+
const names = unsupported.map((method) => method.decoratorKind).sort();
|
|
183
|
+
const detail = names.length > 0 ? ` Unsupported decorators: ${[...new Set(names)].join(", ")}.` : "";
|
|
184
|
+
db.prepare(
|
|
185
|
+
"INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line) VALUES(?,?,?,?,?,?)"
|
|
186
|
+
).run(
|
|
187
|
+
repoId,
|
|
188
|
+
"warning",
|
|
189
|
+
code,
|
|
190
|
+
hasExecutable ? `Handler class ${h.className} contains methods that were not indexed.${detail}` : `Handler class ${h.className} has no indexed executable methods; use a supported CAP handler decorator and re-index.${detail}`,
|
|
191
|
+
h.sourceFile,
|
|
192
|
+
h.sourceLine
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
function canonicalHandlerMethodResolution(method) {
|
|
196
|
+
const handlerKind = method.handlerKind ?? method.decoratorResolution.handlerKind ?? legacyHandlerKind(method.decoratorKind);
|
|
197
|
+
const executable = method.executable ?? method.decoratorResolution.executable ?? (handlerKind === "operation" || handlerKind === "event" || handlerKind === "entity_lifecycle");
|
|
198
|
+
return {
|
|
199
|
+
...method.decoratorResolution,
|
|
200
|
+
handlerKind,
|
|
201
|
+
executable,
|
|
202
|
+
lifecyclePhase: method.lifecyclePhase ?? method.decoratorResolution.lifecyclePhase,
|
|
203
|
+
lifecycleEvent: method.lifecycleEvent ?? method.decoratorResolution.lifecycleEvent
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function handlerMethodIsExecutable(method) {
|
|
207
|
+
return canonicalHandlerMethodResolution(method).executable === true;
|
|
208
|
+
}
|
|
209
|
+
function legacyHandlerKind(kind) {
|
|
210
|
+
if (kind === "Event") return "event";
|
|
211
|
+
if (["Action", "Func", "On"].includes(kind)) return "operation";
|
|
212
|
+
return "unsupported_decorator";
|
|
213
|
+
}
|
|
214
|
+
function insertRegistrations(db, repoId, rows2) {
|
|
215
|
+
const stmt = db.prepare(
|
|
216
|
+
"INSERT INTO handler_registrations(repo_id,handler_class_id,class_name,import_source,registration_file,registration_line,registration_kind,confidence) VALUES(?,?,?,?,?,?,?,?)"
|
|
217
|
+
);
|
|
218
|
+
for (const r of rows2) {
|
|
219
|
+
const handlerClass = r.className ? db.prepare(
|
|
220
|
+
"SELECT id FROM handler_classes WHERE repo_id=? AND class_name=? ORDER BY id"
|
|
221
|
+
).all(repoId, r.className) : [];
|
|
222
|
+
stmt.run(
|
|
223
|
+
repoId,
|
|
224
|
+
handlerClass.length === 1 ? handlerClass[0]?.id : null,
|
|
225
|
+
r.className,
|
|
226
|
+
r.importSource,
|
|
227
|
+
r.registrationFile,
|
|
228
|
+
r.registrationLine,
|
|
229
|
+
r.registrationKind,
|
|
230
|
+
r.confidence
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function insertBindings(db, repoId, rows2) {
|
|
235
|
+
const stmt = db.prepare(
|
|
236
|
+
"INSERT INTO service_bindings(repo_id,symbol_id,variable_name,alias,alias_expr,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,helper_chain_json) VALUES(?,(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1),?,?,?,?,?,?,?,?,?,?)"
|
|
237
|
+
);
|
|
238
|
+
for (const r of rows2)
|
|
239
|
+
stmt.run(
|
|
240
|
+
repoId,
|
|
241
|
+
repoId,
|
|
242
|
+
r.sourceFile,
|
|
243
|
+
r.sourceLine,
|
|
244
|
+
r.sourceLine,
|
|
245
|
+
r.variableName,
|
|
246
|
+
r.alias,
|
|
247
|
+
r.aliasExpr,
|
|
248
|
+
r.destinationExpr,
|
|
249
|
+
r.servicePathExpr,
|
|
250
|
+
r.isDynamic ? 1 : 0,
|
|
251
|
+
JSON.stringify(r.placeholders),
|
|
252
|
+
r.sourceFile,
|
|
253
|
+
r.sourceLine,
|
|
254
|
+
r.helperChain ? JSON.stringify(r.helperChain) : null
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
function insertExecutableSymbols(db, repoId, rows2) {
|
|
258
|
+
const stmt = db.prepare("INSERT INTO symbols(repo_id,file_id,kind,name,qualified_name,exported,start_line,end_line,start_offset,end_offset,source_file,exported_name,evidence_json) VALUES(?,(SELECT id FROM files WHERE repo_id=? AND relative_path=?),?,?,?,?,?,?,?,?,?,?,?)");
|
|
259
|
+
for (const r of rows2) stmt.run(repoId, repoId, r.sourceFile, r.kind, r.localName, r.qualifiedName, r.exported ? 1 : 0, r.startLine, r.endLine, r.startOffset, r.endOffset, r.sourceFile, r.exportedName, r.importExportEvidence ? JSON.stringify(r.importExportEvidence) : null);
|
|
260
|
+
}
|
|
261
|
+
function insertSymbolCalls(db, repoId, rows2) {
|
|
262
|
+
const callerStmt = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1");
|
|
263
|
+
const insertStmt = db.prepare("INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?)");
|
|
264
|
+
for (const r of rows2) {
|
|
265
|
+
const caller = callerStmt.get(repoId, r.sourceFile, r.callerQualifiedName);
|
|
266
|
+
const target = resolveSymbolCallTarget(db, repoId, r);
|
|
267
|
+
insertStmt.run(repoId, caller?.id, target.id, r.calleeExpression, r.importSource, r.sourceFile, r.sourceLine, target.status, 0.8, JSON.stringify({ ...r.evidence, candidateStrategy: target.strategy, candidateCount: target.candidateCount }), target.reason);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function isRelativeImportedSymbolCall(r) {
|
|
271
|
+
return Boolean(r.importSource?.startsWith("."));
|
|
272
|
+
}
|
|
273
|
+
function resolveSymbolCallTarget(db, repoId, r) {
|
|
274
|
+
const evidence = r.evidence;
|
|
275
|
+
const localRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName);
|
|
276
|
+
if (localRows.length === 1) return { id: localRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "same_file_exact", candidateCount: 1 };
|
|
277
|
+
if (localRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple same-file symbol targets matched exactly", strategy: "same_file_exact", candidateCount: localRows.length };
|
|
278
|
+
if (evidence.relation === "class_instance_method" && isRelativeImportedSymbolCall(r)) {
|
|
279
|
+
const classRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName);
|
|
280
|
+
if (classRows.length === 1) return { id: classRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "relative_import_class_instance_method", candidateCount: 1 };
|
|
281
|
+
if (classRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple relative class instance method targets matched exactly", strategy: "relative_import_class_instance_method", candidateCount: classRows.length };
|
|
282
|
+
}
|
|
283
|
+
const rows2 = db.prepare("SELECT id,kind,evidence_json evidenceJson FROM symbols WHERE repo_id=? AND source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName);
|
|
284
|
+
if (evidence.relation === "relative_import_proxy_member" && rows2.length > 1) {
|
|
285
|
+
const objectMapRows = rows2.filter((row) => String(row.evidenceJson ?? "").includes("exported_object_shorthand") || String(row.evidenceJson ?? "").includes("exported_object_literal"));
|
|
286
|
+
if (objectMapRows.length > 0) {
|
|
287
|
+
const concrete = rows2.find((row) => row.kind !== "object_alias") ?? objectMapRows[0];
|
|
288
|
+
return { id: concrete?.id ?? null, status: "resolved", reason: null, strategy: "proxy_member_exported_object_map", candidateCount: rows2.length };
|
|
289
|
+
}
|
|
290
|
+
return { id: null, status: "ambiguous", reason: "Proxy member target requires explicit factory/module/type evidence; global member name is ambiguous", strategy: "proxy_member_no_global_name_fallback", candidateCount: rows2.length };
|
|
291
|
+
}
|
|
292
|
+
if (rows2.length === 1) return { id: rows2[0]?.id ?? null, status: "resolved", reason: null, strategy: evidence.relation === "relative_import_proxy_member" ? "proxy_member_unique_exported_candidate" : "relative_import_exported_exact", candidateCount: 1 };
|
|
293
|
+
if (rows2.length > 1) return { id: null, status: "ambiguous", reason: "Multiple exported symbol targets matched exactly", strategy: "exported_exact", candidateCount: rows2.length };
|
|
294
|
+
return { id: null, status: "unresolved", reason: "No local symbol target matched exactly", strategy: evidence.relation === "relative_import_proxy_member" ? "proxy_member_no_global_name_fallback" : "exact_symbol_match", candidateCount: 0 };
|
|
295
|
+
}
|
|
296
|
+
function insertCalls(db, repoId, rows2) {
|
|
297
|
+
const stmt = db.prepare(
|
|
298
|
+
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,evidence_json,external_target_kind,external_target_id,external_target_label,external_target_dynamic,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
|
|
299
|
+
);
|
|
300
|
+
for (const r of rows2) {
|
|
301
|
+
const binding = resolvePersistedBinding(db, repoId, r);
|
|
302
|
+
const evidence = {
|
|
303
|
+
...r.evidence ?? {},
|
|
304
|
+
serviceBindingResolution: binding.evidence
|
|
305
|
+
};
|
|
306
|
+
stmt.run(
|
|
307
|
+
repoId,
|
|
308
|
+
repoId,
|
|
309
|
+
r.sourceFile,
|
|
310
|
+
r.sourceSymbolQualifiedName,
|
|
311
|
+
repoId,
|
|
312
|
+
r.sourceFile,
|
|
313
|
+
r.sourceLine,
|
|
314
|
+
r.sourceLine,
|
|
315
|
+
r.callType,
|
|
316
|
+
r.method,
|
|
317
|
+
r.operationPathExpr,
|
|
318
|
+
r.queryEntity,
|
|
319
|
+
r.eventNameExpr,
|
|
320
|
+
r.payloadSummary,
|
|
321
|
+
r.sourceFile,
|
|
322
|
+
r.sourceLine,
|
|
323
|
+
r.confidence,
|
|
324
|
+
r.unresolvedReason ?? binding.unresolvedReason,
|
|
325
|
+
r.localServiceName,
|
|
326
|
+
r.localServiceLookup,
|
|
327
|
+
r.aliasChain ? JSON.stringify(r.aliasChain) : null,
|
|
328
|
+
JSON.stringify(evidence),
|
|
329
|
+
r.externalTarget?.kind ?? null,
|
|
330
|
+
r.externalTarget?.stableId ?? null,
|
|
331
|
+
r.externalTarget?.label ?? null,
|
|
332
|
+
r.externalTarget?.dynamic ? 1 : 0,
|
|
333
|
+
binding.bindingId
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function resolvePersistedBinding(db, repoId, call) {
|
|
338
|
+
if (!call.serviceVariableName)
|
|
339
|
+
return { bindingId: null, evidence: { status: "not_applicable", candidateCount: 0 } };
|
|
340
|
+
const candidates = bindingCandidates(db, repoId, call);
|
|
341
|
+
const prior = candidates.filter((candidate) => candidate.sourceLine <= call.sourceLine);
|
|
342
|
+
const families = new Set(prior.map(bindingSignature));
|
|
343
|
+
if (prior.length > 0 && families.size === 1) {
|
|
344
|
+
const selected = prior.at(-1);
|
|
345
|
+
return {
|
|
346
|
+
bindingId: selected?.id ?? null,
|
|
347
|
+
evidence: bindingEvidence("selected", prior, selected)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (prior.length > 1) {
|
|
351
|
+
return {
|
|
352
|
+
bindingId: null,
|
|
353
|
+
unresolvedReason: "ambiguous_service_binding_candidates",
|
|
354
|
+
evidence: bindingEvidence("ambiguous", prior)
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (candidates.length > 0) {
|
|
358
|
+
return {
|
|
359
|
+
bindingId: null,
|
|
360
|
+
unresolvedReason: "service_binding_declared_after_call",
|
|
361
|
+
evidence: bindingEvidence("rejected_future_binding", candidates)
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
bindingId: null,
|
|
366
|
+
evidence: bindingEvidence("unrecoverable", [])
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function bindingCandidates(db, repoId, call) {
|
|
370
|
+
const ownerId = callSymbolId(db, repoId, call);
|
|
371
|
+
const rows2 = db.prepare(`
|
|
372
|
+
SELECT id,symbol_id symbolId,variable_name variableName,alias,alias_expr aliasExpr,
|
|
373
|
+
destination_expr destinationExpr,service_path_expr servicePathExpr,
|
|
374
|
+
source_file sourceFile,source_line sourceLine,helper_chain_json helperChainJson
|
|
375
|
+
FROM service_bindings
|
|
376
|
+
WHERE repo_id=? AND variable_name=? AND source_file=?
|
|
377
|
+
AND (? IS NULL OR symbol_id IS NULL OR symbol_id=?)
|
|
378
|
+
ORDER BY source_line,id
|
|
379
|
+
`).all(
|
|
380
|
+
repoId,
|
|
381
|
+
call.serviceVariableName,
|
|
382
|
+
call.sourceFile,
|
|
383
|
+
ownerId,
|
|
384
|
+
ownerId
|
|
385
|
+
);
|
|
386
|
+
return rows2.flatMap((row) => {
|
|
387
|
+
if (typeof row.id !== "number" || typeof row.variableName !== "string" || typeof row.sourceFile !== "string" || typeof row.sourceLine !== "number")
|
|
388
|
+
return [];
|
|
389
|
+
return [{
|
|
390
|
+
id: row.id,
|
|
391
|
+
symbolId: nullableNumber(row.symbolId),
|
|
392
|
+
variableName: row.variableName,
|
|
393
|
+
alias: nullableString(row.alias),
|
|
394
|
+
aliasExpr: nullableString(row.aliasExpr),
|
|
395
|
+
destinationExpr: nullableString(row.destinationExpr),
|
|
396
|
+
servicePathExpr: nullableString(row.servicePathExpr),
|
|
397
|
+
sourceFile: row.sourceFile,
|
|
398
|
+
sourceLine: row.sourceLine,
|
|
399
|
+
helperChainJson: nullableString(row.helperChainJson)
|
|
400
|
+
}];
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function nullableString(value) {
|
|
404
|
+
return value === null || typeof value === "string" ? value : void 0;
|
|
405
|
+
}
|
|
406
|
+
function nullableNumber(value) {
|
|
407
|
+
return value === null || typeof value === "number" ? value : void 0;
|
|
408
|
+
}
|
|
409
|
+
function callSymbolId(db, repoId, call) {
|
|
410
|
+
const row = db.prepare(`
|
|
411
|
+
SELECT id FROM symbols
|
|
412
|
+
WHERE repo_id=? AND source_file=?
|
|
413
|
+
AND ((? IS NOT NULL AND qualified_name=?)
|
|
414
|
+
OR (start_line<=? AND end_line>=?))
|
|
415
|
+
ORDER BY CASE WHEN qualified_name=? THEN 0 ELSE 1 END,
|
|
416
|
+
(end_line-start_line),id
|
|
417
|
+
LIMIT 1
|
|
418
|
+
`).get(
|
|
419
|
+
repoId,
|
|
420
|
+
call.sourceFile,
|
|
421
|
+
call.sourceSymbolQualifiedName,
|
|
422
|
+
call.sourceSymbolQualifiedName,
|
|
423
|
+
call.sourceLine,
|
|
424
|
+
call.sourceLine,
|
|
425
|
+
call.sourceSymbolQualifiedName
|
|
426
|
+
);
|
|
427
|
+
return typeof row?.id === "number" ? row.id : void 0;
|
|
428
|
+
}
|
|
429
|
+
function bindingEvidence(status, candidates, selected) {
|
|
430
|
+
return {
|
|
431
|
+
status,
|
|
432
|
+
candidateCount: candidates.length,
|
|
433
|
+
selectedBindingId: selected?.id,
|
|
434
|
+
sourceOrderRule: "binding_source_line_must_not_follow_call",
|
|
435
|
+
candidates: candidates.map((candidate) => ({
|
|
436
|
+
bindingId: candidate.id,
|
|
437
|
+
symbolId: candidate.symbolId,
|
|
438
|
+
variableName: candidate.variableName,
|
|
439
|
+
alias: candidate.alias,
|
|
440
|
+
aliasExpr: candidate.aliasExpr,
|
|
441
|
+
destinationExpr: candidate.destinationExpr,
|
|
442
|
+
servicePathExpr: candidate.servicePathExpr,
|
|
443
|
+
sourceFile: candidate.sourceFile,
|
|
444
|
+
sourceLine: candidate.sourceLine,
|
|
445
|
+
helperChain: parseBindingChain(candidate.helperChainJson)
|
|
446
|
+
}))
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function bindingSignature(candidate) {
|
|
450
|
+
return JSON.stringify([
|
|
451
|
+
candidate.alias,
|
|
452
|
+
candidate.aliasExpr,
|
|
453
|
+
candidate.destinationExpr,
|
|
454
|
+
candidate.servicePathExpr
|
|
455
|
+
]);
|
|
456
|
+
}
|
|
457
|
+
function parseBindingChain(value) {
|
|
458
|
+
if (!value) return void 0;
|
|
459
|
+
try {
|
|
460
|
+
return JSON.parse(value);
|
|
461
|
+
} catch {
|
|
462
|
+
return void 0;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
3
466
|
// src/discovery/discover-repositories.ts
|
|
4
467
|
import fs from "fs/promises";
|
|
5
468
|
import path2 from "path";
|
|
@@ -74,6 +537,9 @@ async function discoverRepositories(rootPath, ignore) {
|
|
|
74
537
|
// src/parsers/package-json-parser.ts
|
|
75
538
|
import fs2 from "fs/promises";
|
|
76
539
|
import path3 from "path";
|
|
540
|
+
function emptyPackageFacts() {
|
|
541
|
+
return { dependencies: {}, cdsRequires: [], scripts: {} };
|
|
542
|
+
}
|
|
77
543
|
function recordOfString(value) {
|
|
78
544
|
if (!value || typeof value !== "object") return {};
|
|
79
545
|
return Object.fromEntries(
|
|
@@ -100,22 +566,31 @@ function readRequires(cds) {
|
|
|
100
566
|
];
|
|
101
567
|
});
|
|
102
568
|
}
|
|
103
|
-
async function parsePackageJson(repoPath) {
|
|
569
|
+
async function parsePackageJson(repoPath, options = {}) {
|
|
570
|
+
return (await loadPackageJsonSnapshot(repoPath, options)).facts;
|
|
571
|
+
}
|
|
572
|
+
async function loadPackageJsonSnapshot(repoPath, options = {}) {
|
|
104
573
|
try {
|
|
105
574
|
const raw = await fs2.readFile(path3.join(repoPath, "package.json"), "utf8");
|
|
106
575
|
const json = JSON.parse(raw);
|
|
107
576
|
return {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
577
|
+
rawText: raw,
|
|
578
|
+
facts: {
|
|
579
|
+
packageName: typeof json.name === "string" ? json.name : void 0,
|
|
580
|
+
packageVersion: typeof json.version === "string" ? json.version : void 0,
|
|
581
|
+
dependencies: {
|
|
582
|
+
...recordOfString(json.dependencies),
|
|
583
|
+
...recordOfString(json.devDependencies)
|
|
584
|
+
},
|
|
585
|
+
cdsRequires: readRequires(json.cds),
|
|
586
|
+
scripts: recordOfString(json.scripts)
|
|
587
|
+
}
|
|
116
588
|
};
|
|
117
|
-
} catch {
|
|
118
|
-
|
|
589
|
+
} catch (error) {
|
|
590
|
+
const missing = typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
591
|
+
if (!options.strict || options.allowMissing && missing)
|
|
592
|
+
return { facts: emptyPackageFacts(), rawText: "" };
|
|
593
|
+
throw error;
|
|
119
594
|
}
|
|
120
595
|
}
|
|
121
596
|
|
|
@@ -259,9 +734,9 @@ function operationsFromBody(text, maskedBody, bodyOffset, filePath) {
|
|
|
259
734
|
sourceLine: lineOf(text, bodyOffset + (m.index ?? 0))
|
|
260
735
|
}));
|
|
261
736
|
}
|
|
262
|
-
async function parseCdsFile(repoPath, filePath) {
|
|
737
|
+
async function parseCdsFile(repoPath, filePath, context) {
|
|
263
738
|
const absolute = path4.join(repoPath, filePath);
|
|
264
|
-
const text = await fs3.readFile(absolute, "utf8");
|
|
739
|
+
const text = context?.get(filePath)?.text ?? await fs3.readFile(absolute, "utf8");
|
|
265
740
|
const masked = maskCommentsAndStrings(text);
|
|
266
741
|
const namespace = /namespace\s+([\w.]+)\s*;/.exec(masked)?.[1];
|
|
267
742
|
const services = [];
|
|
@@ -311,8 +786,8 @@ async function parseCdsFile(repoPath, filePath) {
|
|
|
311
786
|
}
|
|
312
787
|
|
|
313
788
|
// src/parsers/decorator-parser.ts
|
|
314
|
-
import
|
|
315
|
-
import
|
|
789
|
+
import fs5 from "fs/promises";
|
|
790
|
+
import path6 from "path";
|
|
316
791
|
import ts2 from "typescript";
|
|
317
792
|
|
|
318
793
|
// src/linker/operation-decorator-normalizer.ts
|
|
@@ -356,7 +831,34 @@ function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
|
|
|
356
831
|
}
|
|
357
832
|
|
|
358
833
|
// src/parsers/ts-project.ts
|
|
834
|
+
import fs4 from "fs/promises";
|
|
835
|
+
import path5 from "path";
|
|
359
836
|
import ts from "typescript";
|
|
837
|
+
async function loadRepositorySourceContext(repoPath, filePaths, instrumentation) {
|
|
838
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
839
|
+
for (const inputPath of filePaths) {
|
|
840
|
+
const filePath = normalizePath(inputPath);
|
|
841
|
+
await instrumentation?.onSourceRead?.(repoPath, filePath);
|
|
842
|
+
const text = await fs4.readFile(path5.join(repoPath, filePath), "utf8");
|
|
843
|
+
let ast;
|
|
844
|
+
snapshots.set(filePath, {
|
|
845
|
+
repoPath,
|
|
846
|
+
filePath,
|
|
847
|
+
text,
|
|
848
|
+
sizeBytes: Buffer.byteLength(text),
|
|
849
|
+
sourceFile: () => {
|
|
850
|
+
if (ast) return ast;
|
|
851
|
+
instrumentation?.onAstCreated?.(repoPath, filePath);
|
|
852
|
+
ast = createSourceFile(filePath, text);
|
|
853
|
+
return ast;
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
get: (filePath) => snapshots.get(normalizePath(filePath)),
|
|
859
|
+
entries: () => [...snapshots.values()]
|
|
860
|
+
};
|
|
861
|
+
}
|
|
360
862
|
function createSourceFile(filePath, text) {
|
|
361
863
|
return ts.createSourceFile(
|
|
362
864
|
filePath,
|
|
@@ -368,6 +870,22 @@ function createSourceFile(filePath, text) {
|
|
|
368
870
|
}
|
|
369
871
|
|
|
370
872
|
// src/parsers/decorator-parser.ts
|
|
873
|
+
var OPERATION_DECORATORS = /* @__PURE__ */ new Set(["Action", "Func", "On"]);
|
|
874
|
+
var EVENT_DECORATORS = /* @__PURE__ */ new Set(["Event"]);
|
|
875
|
+
var LIFECYCLE_DECORATORS = /* @__PURE__ */ new Map([
|
|
876
|
+
["BeforeCreate", { phase: "before", event: "CREATE" }],
|
|
877
|
+
["OnCreate", { phase: "on", event: "CREATE" }],
|
|
878
|
+
["AfterCreate", { phase: "after", event: "CREATE" }],
|
|
879
|
+
["BeforeRead", { phase: "before", event: "READ" }],
|
|
880
|
+
["OnRead", { phase: "on", event: "READ" }],
|
|
881
|
+
["AfterRead", { phase: "after", event: "READ" }],
|
|
882
|
+
["BeforeUpdate", { phase: "before", event: "UPDATE" }],
|
|
883
|
+
["OnUpdate", { phase: "on", event: "UPDATE" }],
|
|
884
|
+
["AfterUpdate", { phase: "after", event: "UPDATE" }],
|
|
885
|
+
["BeforeDelete", { phase: "before", event: "DELETE" }],
|
|
886
|
+
["OnDelete", { phase: "on", event: "DELETE" }],
|
|
887
|
+
["AfterDelete", { phase: "after", event: "DELETE" }]
|
|
888
|
+
]);
|
|
371
889
|
function line(sf, pos) {
|
|
372
890
|
return sf.getLineAndCharacterOfPosition(pos).line + 1;
|
|
373
891
|
}
|
|
@@ -382,6 +900,12 @@ function firstArg(d) {
|
|
|
382
900
|
const e = d.expression;
|
|
383
901
|
return ts2.isCallExpression(e) ? e.arguments[0] : void 0;
|
|
384
902
|
}
|
|
903
|
+
function decoratorArguments(d) {
|
|
904
|
+
return ts2.isCallExpression(d.expression) ? d.expression.arguments : void 0;
|
|
905
|
+
}
|
|
906
|
+
function methodName(name) {
|
|
907
|
+
return ts2.isIdentifier(name) || ts2.isStringLiteralLike(name) || ts2.isNumericLiteral(name) ? name.text : name.getText();
|
|
908
|
+
}
|
|
385
909
|
function unwrapExpression(expression) {
|
|
386
910
|
let current = expression;
|
|
387
911
|
while (ts2.isParenthesizedExpression(current) || ts2.isAsExpression(current) || ts2.isTypeAssertionExpression(current) || ts2.isSatisfiesExpression(current)) current = current.expression;
|
|
@@ -419,9 +943,12 @@ function collectStringLookups(source) {
|
|
|
419
943
|
const lookups = {
|
|
420
944
|
identifiers: /* @__PURE__ */ new Map(),
|
|
421
945
|
enumMembers: /* @__PURE__ */ new Map(),
|
|
422
|
-
objectProperties: /* @__PURE__ */ new Map()
|
|
946
|
+
objectProperties: /* @__PURE__ */ new Map(),
|
|
947
|
+
capDecoratorNames: /* @__PURE__ */ new Map(),
|
|
948
|
+
capDecoratorNamespaces: /* @__PURE__ */ new Set()
|
|
423
949
|
};
|
|
424
950
|
for (const statement of source.statements) {
|
|
951
|
+
collectCapDecoratorImports(statement, lookups);
|
|
425
952
|
if (ts2.isEnumDeclaration(statement)) collectEnumMembers(statement, lookups);
|
|
426
953
|
if (!ts2.isVariableStatement(statement) || !(statement.declarationList.flags & ts2.NodeFlags.Const)) continue;
|
|
427
954
|
for (const declaration of statement.declarationList.declarations) {
|
|
@@ -433,75 +960,277 @@ function collectStringLookups(source) {
|
|
|
433
960
|
}
|
|
434
961
|
return lookups;
|
|
435
962
|
}
|
|
436
|
-
function
|
|
437
|
-
|
|
963
|
+
function collectCapDecoratorImports(statement, lookups) {
|
|
964
|
+
if (!ts2.isImportDeclaration(statement) || !ts2.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "cds-routing-handlers") return;
|
|
965
|
+
const clause = statement.importClause;
|
|
966
|
+
if (!clause || clause.isTypeOnly) return;
|
|
967
|
+
const bindings = clause.namedBindings;
|
|
968
|
+
if (bindings && ts2.isNamedImports(bindings)) {
|
|
969
|
+
for (const element of bindings.elements) {
|
|
970
|
+
if (element.isTypeOnly) continue;
|
|
971
|
+
lookups.capDecoratorNames.set(
|
|
972
|
+
element.name.text,
|
|
973
|
+
element.propertyName?.text ?? element.name.text
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (bindings && ts2.isNamespaceImport(bindings))
|
|
978
|
+
lookups.capDecoratorNamespaces.add(bindings.name.text);
|
|
979
|
+
}
|
|
980
|
+
function capDecoratorName(decorator, lookups) {
|
|
981
|
+
const expression = ts2.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
|
|
982
|
+
if (ts2.isIdentifier(expression))
|
|
983
|
+
return lookups.capDecoratorNames.get(expression.text);
|
|
984
|
+
if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression) && lookups.capDecoratorNamespaces.has(expression.expression.text))
|
|
985
|
+
return expression.name.text;
|
|
986
|
+
return void 0;
|
|
987
|
+
}
|
|
988
|
+
function unresolved(rawExpression, reason, argumentExpression) {
|
|
989
|
+
return {
|
|
990
|
+
rawExpression,
|
|
991
|
+
argumentExpression,
|
|
992
|
+
resolutionKind: "unresolved",
|
|
993
|
+
unresolvedReason: reason
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function resolved2(rawExpression, argumentExpression, resolvedValue, resolutionKind) {
|
|
997
|
+
return { rawExpression, argumentExpression, resolvedValue, resolutionKind };
|
|
438
998
|
}
|
|
439
999
|
function generatedConstant(rawExpression) {
|
|
440
1000
|
const match = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(rawExpression.trim());
|
|
441
1001
|
return match?.[1] ? generatedOperationNameFromConstant(match[1]) : void 0;
|
|
442
1002
|
}
|
|
443
|
-
function resolveDecoratorArgument(argument, lookups) {
|
|
444
|
-
if (!argument) return unresolved(
|
|
445
|
-
const
|
|
1003
|
+
function resolveDecoratorArgument(argument, lookups, rawExpression) {
|
|
1004
|
+
if (!argument) return unresolved(rawExpression, "decorator_argument_missing");
|
|
1005
|
+
const argumentExpression = argument.getText();
|
|
446
1006
|
const expression = unwrapExpression(argument);
|
|
447
1007
|
if (ts2.isStringLiteralLike(expression))
|
|
448
|
-
return
|
|
1008
|
+
return resolved2(rawExpression, argumentExpression, expression.text, "literal");
|
|
449
1009
|
if (ts2.isIdentifier(expression)) {
|
|
450
1010
|
const value = lookups.identifiers.get(expression.text);
|
|
451
|
-
return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string") :
|
|
1011
|
+
return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string", argumentExpression) : resolved2(rawExpression, argumentExpression, value, "const_identifier");
|
|
452
1012
|
}
|
|
453
1013
|
if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression)) {
|
|
454
1014
|
const key = `${expression.expression.text}.${expression.name.text}`;
|
|
455
1015
|
const enumValue = lookups.enumMembers.get(key);
|
|
456
1016
|
if (enumValue !== void 0)
|
|
457
|
-
return
|
|
1017
|
+
return resolved2(rawExpression, argumentExpression, enumValue, "enum_member");
|
|
458
1018
|
const objectValue2 = lookups.objectProperties.get(key);
|
|
459
1019
|
if (objectValue2 !== void 0)
|
|
460
|
-
return
|
|
1020
|
+
return resolved2(rawExpression, argumentExpression, objectValue2, "const_object_property");
|
|
461
1021
|
}
|
|
462
|
-
const generatedValue = generatedConstant(
|
|
1022
|
+
const generatedValue = generatedConstant(argumentExpression);
|
|
463
1023
|
if (generatedValue !== void 0)
|
|
464
|
-
return
|
|
1024
|
+
return resolved2(
|
|
465
1025
|
rawExpression,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1026
|
+
argumentExpression,
|
|
1027
|
+
generatedValue,
|
|
1028
|
+
"generated_constant_name"
|
|
1029
|
+
);
|
|
469
1030
|
if (ts2.isPropertyAccessExpression(expression))
|
|
470
|
-
return unresolved(
|
|
471
|
-
|
|
1031
|
+
return unresolved(
|
|
1032
|
+
rawExpression,
|
|
1033
|
+
"property_access_not_resolved_to_local_string",
|
|
1034
|
+
argumentExpression
|
|
1035
|
+
);
|
|
1036
|
+
return unresolved(rawExpression, "unsupported_decorator_expression", argumentExpression);
|
|
1037
|
+
}
|
|
1038
|
+
function classificationFor(name) {
|
|
1039
|
+
if (OPERATION_DECORATORS.has(name))
|
|
1040
|
+
return { handlerKind: "operation", executable: true };
|
|
1041
|
+
if (EVENT_DECORATORS.has(name))
|
|
1042
|
+
return { handlerKind: "event", executable: true };
|
|
1043
|
+
const lifecycle = LIFECYCLE_DECORATORS.get(name);
|
|
1044
|
+
if (!lifecycle) return void 0;
|
|
1045
|
+
return {
|
|
1046
|
+
handlerKind: "entity_lifecycle",
|
|
1047
|
+
executable: true,
|
|
1048
|
+
lifecyclePhase: lifecycle.phase,
|
|
1049
|
+
lifecycleEvent: lifecycle.event
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function lifecycleLikePhase(name) {
|
|
1053
|
+
if (/^On[A-Z]/.test(name)) return "on";
|
|
1054
|
+
if (/^Before[A-Z]/.test(name)) return "before";
|
|
1055
|
+
if (/^After[A-Z]/.test(name)) return "after";
|
|
1056
|
+
return void 0;
|
|
1057
|
+
}
|
|
1058
|
+
function withClassification(resolution, classification) {
|
|
1059
|
+
return {
|
|
1060
|
+
...resolution,
|
|
1061
|
+
handlerKind: classification.handlerKind,
|
|
1062
|
+
executable: classification.executable,
|
|
1063
|
+
lifecyclePhase: classification.lifecyclePhase,
|
|
1064
|
+
lifecycleEvent: classification.lifecycleEvent
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function withDecoratorEvidence(resolution, decorator, resolvedDecoratorKind) {
|
|
1068
|
+
return {
|
|
1069
|
+
...resolution,
|
|
1070
|
+
decoratorExpression: decorator.expression.getText(),
|
|
1071
|
+
resolvedDecoratorKind,
|
|
1072
|
+
decoratorImportSource: resolvedDecoratorKind ? "cds-routing-handlers" : void 0
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
function lifecycleDecoratorResolution(decorator, lookups, classification) {
|
|
1076
|
+
const rawExpression = decorator.expression.getText();
|
|
1077
|
+
const args = decoratorArguments(decorator);
|
|
1078
|
+
if (args?.length === 0)
|
|
1079
|
+
return {
|
|
1080
|
+
classification,
|
|
1081
|
+
resolution: withClassification({
|
|
1082
|
+
rawExpression,
|
|
1083
|
+
resolutionKind: "lifecycle_implicit"
|
|
1084
|
+
}, classification)
|
|
1085
|
+
};
|
|
1086
|
+
const resolution = args?.length === 1 ? resolveDecoratorArgument(args[0], lookups, rawExpression) : unresolved(rawExpression, args ? "unsupported_lifecycle_argument_count" : "lifecycle_decorator_call_required");
|
|
1087
|
+
const unsupported = { ...classification, handlerKind: "unsupported_lifecycle", executable: false };
|
|
1088
|
+
const unsupportedResolution = args?.length === 1 ? { ...resolution, unresolvedReason: "lifecycle_decorator_arguments_not_supported" } : resolution;
|
|
1089
|
+
return {
|
|
1090
|
+
classification: unsupported,
|
|
1091
|
+
resolution: withClassification(unsupportedResolution, unsupported)
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
function unsupportedLifecycleResolution(decorator, phase) {
|
|
1095
|
+
const classification = {
|
|
1096
|
+
handlerKind: "unsupported_lifecycle",
|
|
1097
|
+
executable: false,
|
|
1098
|
+
lifecyclePhase: phase
|
|
1099
|
+
};
|
|
1100
|
+
const resolution = unresolved(
|
|
1101
|
+
decorator.expression.getText(),
|
|
1102
|
+
"lifecycle_decorator_not_allowlisted"
|
|
1103
|
+
);
|
|
1104
|
+
return { classification, resolution: withClassification(resolution, classification) };
|
|
1105
|
+
}
|
|
1106
|
+
function unsupportedDecoratorResolution(decorator, reason, phase) {
|
|
1107
|
+
const classification = {
|
|
1108
|
+
handlerKind: phase ? "unsupported_lifecycle" : "unsupported_decorator",
|
|
1109
|
+
executable: false,
|
|
1110
|
+
lifecyclePhase: phase
|
|
1111
|
+
};
|
|
1112
|
+
return {
|
|
1113
|
+
classification,
|
|
1114
|
+
resolution: withClassification(
|
|
1115
|
+
unresolved(decorator.expression.getText(), reason),
|
|
1116
|
+
classification
|
|
1117
|
+
)
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function parseMethodDecorator(decorator, lookups, handlerClass, allowLifecycle) {
|
|
1121
|
+
const decoratorKind = callName(decorator);
|
|
1122
|
+
const importedKind = capDecoratorName(decorator, lookups);
|
|
1123
|
+
const resolvedKind = importedKind ?? decoratorKind;
|
|
1124
|
+
const base = classificationFor(resolvedKind);
|
|
1125
|
+
const phase = lifecycleLikePhase(resolvedKind);
|
|
1126
|
+
const isLifecycle = base?.handlerKind === "entity_lifecycle" || Boolean(phase && !base);
|
|
1127
|
+
if (!handlerClass && isLifecycle) return void 0;
|
|
1128
|
+
const parsed = isLifecycle && (!allowLifecycle || !importedKind) ? unsupportedDecoratorResolution(
|
|
1129
|
+
decorator,
|
|
1130
|
+
"lifecycle_decorator_import_not_supported",
|
|
1131
|
+
phase
|
|
1132
|
+
) : base?.handlerKind === "entity_lifecycle" ? lifecycleDecoratorResolution(decorator, lookups, base) : phase && !base ? unsupportedLifecycleResolution(decorator, phase) : !base && handlerClass ? unsupportedDecoratorResolution(decorator, "decorator_not_allowlisted") : {
|
|
1133
|
+
classification: base,
|
|
1134
|
+
resolution: resolveDecoratorArgument(
|
|
1135
|
+
firstArg(decorator),
|
|
1136
|
+
lookups,
|
|
1137
|
+
firstArg(decorator)?.getText() ?? decorator.expression.getText()
|
|
1138
|
+
)
|
|
1139
|
+
};
|
|
1140
|
+
if (!parsed.classification) return void 0;
|
|
1141
|
+
return {
|
|
1142
|
+
classification: parsed.classification,
|
|
1143
|
+
resolution: parsed.resolution,
|
|
1144
|
+
decoratorKind,
|
|
1145
|
+
importedKind
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
function methodDecoratorFact(method, decorator, lookups, source, filePath, handlerClass, allowLifecycle) {
|
|
1149
|
+
const parsed = parseMethodDecorator(decorator, lookups, handlerClass, allowLifecycle);
|
|
1150
|
+
if (!parsed) return void 0;
|
|
1151
|
+
const classification = method.body ? parsed.classification : { ...parsed.classification, executable: false };
|
|
1152
|
+
const resolution = method.body ? parsed.resolution : { ...parsed.resolution, unresolvedReason: "handler_method_body_missing" };
|
|
1153
|
+
const argumentExpression = firstArg(decorator)?.getText();
|
|
1154
|
+
return {
|
|
1155
|
+
methodName: methodName(method.name),
|
|
1156
|
+
decoratorKind: parsed.decoratorKind,
|
|
1157
|
+
decoratorValue: parsed.resolution.resolvedValue,
|
|
1158
|
+
decoratorRawExpression: argumentExpression ?? decorator.expression.getText(),
|
|
1159
|
+
handlerKind: classification.handlerKind,
|
|
1160
|
+
executable: classification.executable,
|
|
1161
|
+
lifecyclePhase: classification.lifecyclePhase,
|
|
1162
|
+
lifecycleEvent: classification.lifecycleEvent,
|
|
1163
|
+
decoratorResolution: withDecoratorEvidence(
|
|
1164
|
+
withClassification(resolution, classification),
|
|
1165
|
+
decorator,
|
|
1166
|
+
parsed.importedKind
|
|
1167
|
+
),
|
|
1168
|
+
sourceFile: normalizePath(filePath),
|
|
1169
|
+
sourceLine: line(source, method.getStart())
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
function unique(values) {
|
|
1173
|
+
return [...new Set(values)];
|
|
1174
|
+
}
|
|
1175
|
+
function parseClassMethods(node, lookups, source, filePath, handlerClass, allowLifecycle) {
|
|
1176
|
+
const methods = [];
|
|
1177
|
+
const observed = [];
|
|
1178
|
+
const unsupported = [];
|
|
1179
|
+
for (const method of node.members.filter(ts2.isMethodDeclaration)) {
|
|
1180
|
+
for (const decorator of decs(method)) {
|
|
1181
|
+
observed.push(callName(decorator));
|
|
1182
|
+
const fact = methodDecoratorFact(
|
|
1183
|
+
method,
|
|
1184
|
+
decorator,
|
|
1185
|
+
lookups,
|
|
1186
|
+
source,
|
|
1187
|
+
filePath,
|
|
1188
|
+
handlerClass,
|
|
1189
|
+
allowLifecycle
|
|
1190
|
+
);
|
|
1191
|
+
if (fact) methods.push(fact);
|
|
1192
|
+
if (!fact?.executable) unsupported.push(callName(decorator));
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return {
|
|
1196
|
+
methods,
|
|
1197
|
+
observedDecoratorNames: unique(observed),
|
|
1198
|
+
unsupportedDecoratorNames: unique(unsupported)
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function parseHandlerClass(node, lookups, source, filePath) {
|
|
1202
|
+
const classDecoratorNames = unique(decs(node).map(callName));
|
|
1203
|
+
const classDecorators = decs(node);
|
|
1204
|
+
const hasImportedHandler = classDecorators.some((decorator) => capDecoratorName(decorator, lookups) === "Handler");
|
|
1205
|
+
const hasHandlerDecorator = hasImportedHandler || classDecoratorNames.includes("Handler");
|
|
1206
|
+
const parsed = parseClassMethods(
|
|
1207
|
+
node,
|
|
1208
|
+
lookups,
|
|
1209
|
+
source,
|
|
1210
|
+
filePath,
|
|
1211
|
+
hasHandlerDecorator,
|
|
1212
|
+
hasImportedHandler
|
|
1213
|
+
);
|
|
1214
|
+
if (!hasHandlerDecorator && parsed.methods.length === 0) return void 0;
|
|
1215
|
+
return {
|
|
1216
|
+
className: node.name?.text ?? "AnonymousHandler",
|
|
1217
|
+
sourceFile: normalizePath(filePath),
|
|
1218
|
+
sourceLine: line(source, node.getStart()),
|
|
1219
|
+
...parsed,
|
|
1220
|
+
hasHandlerDecorator,
|
|
1221
|
+
classDecoratorNames
|
|
1222
|
+
};
|
|
472
1223
|
}
|
|
473
|
-
async function parseDecorators(repoPath, filePath) {
|
|
474
|
-
const
|
|
475
|
-
const
|
|
1224
|
+
async function parseDecorators(repoPath, filePath, context) {
|
|
1225
|
+
const snapshot = context?.get(filePath);
|
|
1226
|
+
const text = snapshot?.text ?? await fs5.readFile(path6.join(repoPath, filePath), "utf8");
|
|
1227
|
+
const sf = snapshot?.sourceFile() ?? createSourceFile(filePath, text);
|
|
476
1228
|
const lookups = collectStringLookups(sf);
|
|
477
1229
|
const handlers = [];
|
|
478
1230
|
function visit(node) {
|
|
479
1231
|
if (ts2.isClassDeclaration(node)) {
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
const methods = node.members.filter(ts2.isMethodDeclaration).flatMap(
|
|
483
|
-
(m) => decs(m).filter(
|
|
484
|
-
(d) => ["Func", "Action", "On", "Event"].includes(callName(d))
|
|
485
|
-
).map((d) => {
|
|
486
|
-
const decoratorResolution = resolveDecoratorArgument(firstArg(d), lookups);
|
|
487
|
-
return {
|
|
488
|
-
methodName: m.name.getText(),
|
|
489
|
-
decoratorKind: callName(d),
|
|
490
|
-
decoratorValue: decoratorResolution.resolvedValue,
|
|
491
|
-
decoratorRawExpression: decoratorResolution.rawExpression,
|
|
492
|
-
decoratorResolution,
|
|
493
|
-
sourceFile: normalizePath(filePath),
|
|
494
|
-
sourceLine: line(sf, m.getStart())
|
|
495
|
-
};
|
|
496
|
-
})
|
|
497
|
-
);
|
|
498
|
-
if (hasHandler || methods.length > 0)
|
|
499
|
-
handlers.push({
|
|
500
|
-
className,
|
|
501
|
-
sourceFile: normalizePath(filePath),
|
|
502
|
-
sourceLine: line(sf, node.getStart()),
|
|
503
|
-
methods
|
|
504
|
-
});
|
|
1232
|
+
const handler = parseHandlerClass(node, lookups, sf, filePath);
|
|
1233
|
+
if (handler) handlers.push(handler);
|
|
505
1234
|
}
|
|
506
1235
|
ts2.forEachChild(node, visit);
|
|
507
1236
|
}
|
|
@@ -511,8 +1240,8 @@ async function parseDecorators(repoPath, filePath) {
|
|
|
511
1240
|
|
|
512
1241
|
// src/parsers/handler-registration-parser.ts
|
|
513
1242
|
import fsSync from "fs";
|
|
514
|
-
import
|
|
515
|
-
import
|
|
1243
|
+
import fs6 from "fs/promises";
|
|
1244
|
+
import path7 from "path";
|
|
516
1245
|
import ts3 from "typescript";
|
|
517
1246
|
var MAX_EXPORT_DEPTH = 5;
|
|
518
1247
|
function lineOf2(sourceFile, node) {
|
|
@@ -529,15 +1258,37 @@ function importSourceFor(identifier, imports) {
|
|
|
529
1258
|
const evidence = imports.get(identifier);
|
|
530
1259
|
return evidence ? `${evidence.source}#${evidence.importedName}` : void 0;
|
|
531
1260
|
}
|
|
532
|
-
async function parseHandlerRegistrations(repoPath, filePath) {
|
|
533
|
-
const absolutePath =
|
|
534
|
-
const
|
|
535
|
-
const
|
|
1261
|
+
async function parseHandlerRegistrations(repoPath, filePath, context) {
|
|
1262
|
+
const absolutePath = path7.join(repoPath, filePath);
|
|
1263
|
+
const snapshot = context?.get(filePath);
|
|
1264
|
+
const text = snapshot?.text ?? await fs6.readFile(absolutePath, "utf8");
|
|
1265
|
+
const sourceFile = snapshot?.sourceFile() ?? ts3.createSourceFile(
|
|
1266
|
+
filePath,
|
|
1267
|
+
text,
|
|
1268
|
+
ts3.ScriptTarget.Latest,
|
|
1269
|
+
true,
|
|
1270
|
+
ts3.ScriptKind.TS
|
|
1271
|
+
);
|
|
536
1272
|
const imports = collectImports(sourceFile);
|
|
537
|
-
const localArrays = collectLocalArrays(
|
|
1273
|
+
const localArrays = collectLocalArrays(
|
|
1274
|
+
sourceFile,
|
|
1275
|
+
imports,
|
|
1276
|
+
/* @__PURE__ */ new Map(),
|
|
1277
|
+
repoPath,
|
|
1278
|
+
filePath,
|
|
1279
|
+
context
|
|
1280
|
+
);
|
|
538
1281
|
const out = [];
|
|
539
1282
|
function emitFromExpression(expression, call) {
|
|
540
|
-
const classes = resolveArrayExpression(
|
|
1283
|
+
const classes = resolveArrayExpression(
|
|
1284
|
+
expression,
|
|
1285
|
+
localArrays,
|
|
1286
|
+
imports,
|
|
1287
|
+
repoPath,
|
|
1288
|
+
filePath,
|
|
1289
|
+
/* @__PURE__ */ new Set(),
|
|
1290
|
+
context
|
|
1291
|
+
);
|
|
541
1292
|
for (const cls of classes) {
|
|
542
1293
|
out.push({
|
|
543
1294
|
className: cls.className,
|
|
@@ -598,70 +1349,86 @@ function collectImports(sourceFile) {
|
|
|
598
1349
|
}
|
|
599
1350
|
return imports;
|
|
600
1351
|
}
|
|
601
|
-
function collectLocalArrays(sourceFile, imports, seed, repoPath = "", fromFile = "") {
|
|
1352
|
+
function collectLocalArrays(sourceFile, imports, seed, repoPath = "", fromFile = "", context) {
|
|
602
1353
|
const arrays = new Map(seed);
|
|
603
1354
|
for (const statement of sourceFile.statements) {
|
|
604
1355
|
if (ts3.isVariableStatement(statement)) {
|
|
605
1356
|
for (const decl of statement.declarationList.declarations) {
|
|
606
1357
|
if (ts3.isIdentifier(decl.name) && decl.initializer && ts3.isArrayLiteralExpression(decl.initializer)) {
|
|
607
|
-
arrays.set(decl.name.text, resolveArrayLiteral(
|
|
1358
|
+
arrays.set(decl.name.text, resolveArrayLiteral(
|
|
1359
|
+
decl.initializer,
|
|
1360
|
+
arrays,
|
|
1361
|
+
imports,
|
|
1362
|
+
repoPath,
|
|
1363
|
+
fromFile,
|
|
1364
|
+
/* @__PURE__ */ new Set(),
|
|
1365
|
+
context
|
|
1366
|
+
));
|
|
608
1367
|
}
|
|
609
1368
|
}
|
|
610
1369
|
}
|
|
611
1370
|
}
|
|
612
1371
|
return arrays;
|
|
613
1372
|
}
|
|
614
|
-
function resolveArrayExpression(expr, arrays, imports, repoPath, fromFile, seen) {
|
|
615
|
-
if (ts3.isArrayLiteralExpression(expr)) return resolveArrayLiteral(expr, arrays, imports, repoPath, fromFile, seen);
|
|
1373
|
+
function resolveArrayExpression(expr, arrays, imports, repoPath, fromFile, seen, context) {
|
|
1374
|
+
if (ts3.isArrayLiteralExpression(expr)) return resolveArrayLiteral(expr, arrays, imports, repoPath, fromFile, seen, context);
|
|
616
1375
|
if (ts3.isIdentifier(expr)) {
|
|
617
1376
|
const local = arrays.get(expr.text);
|
|
618
1377
|
if (local) return local;
|
|
619
1378
|
const evidence = imports.get(expr.text);
|
|
620
|
-
if (evidence && isRelative(evidence.source)) return resolveImportedArray(repoPath, fromFile, evidence, seen);
|
|
1379
|
+
if (evidence && isRelative(evidence.source)) return resolveImportedArray(repoPath, fromFile, evidence, seen, context);
|
|
621
1380
|
if (evidence) return [{ className: evidence.importedName === "default" ? expr.text : evidence.importedName, importSource: `${evidence.source}#${evidence.importedName}` }];
|
|
622
1381
|
}
|
|
623
1382
|
return [];
|
|
624
1383
|
}
|
|
625
|
-
function resolveArrayLiteral(array, arrays, imports, repoPath, fromFile, seen) {
|
|
1384
|
+
function resolveArrayLiteral(array, arrays, imports, repoPath, fromFile, seen, context) {
|
|
626
1385
|
const out = [];
|
|
627
1386
|
for (const element of array.elements) {
|
|
628
|
-
if (ts3.isSpreadElement(element)) out.push(...resolveArrayExpression(element.expression, arrays, imports, repoPath, fromFile, seen));
|
|
1387
|
+
if (ts3.isSpreadElement(element)) out.push(...resolveArrayExpression(element.expression, arrays, imports, repoPath, fromFile, seen, context));
|
|
629
1388
|
else if (ts3.isIdentifier(element)) out.push({ className: element.text, importSource: importSourceFor(element.text, imports) });
|
|
630
1389
|
}
|
|
631
1390
|
return out;
|
|
632
1391
|
}
|
|
633
|
-
function resolveImportedArray(repoPath, fromFile, evidence, seen) {
|
|
1392
|
+
function resolveImportedArray(repoPath, fromFile, evidence, seen, context) {
|
|
634
1393
|
const moduleFile = resolveRelativeModule(repoPath, fromFile, evidence.source);
|
|
635
1394
|
if (!moduleFile) return [];
|
|
636
1395
|
const key = `${moduleFile}:${evidence.importedName}`;
|
|
637
1396
|
if (seen.has(key) || seen.size > MAX_EXPORT_DEPTH) return [];
|
|
638
1397
|
seen.add(key);
|
|
639
|
-
const exports = readExports(repoPath, moduleFile, seen);
|
|
1398
|
+
const exports = readExports(repoPath, moduleFile, seen, context);
|
|
640
1399
|
if (evidence.importedName === "default") return exports.defaultArray ?? [];
|
|
641
1400
|
return exports.arrays.get(evidence.importedName) ?? exports.arrays.get(exports.aliases.get(evidence.importedName) ?? evidence.importedName) ?? [];
|
|
642
1401
|
}
|
|
643
1402
|
function resolveRelativeModule(repoPath, fromFile, specifier) {
|
|
644
|
-
const base =
|
|
645
|
-
for (const candidate of [base, `${base}.ts`, `${base}.js`,
|
|
1403
|
+
const base = path7.resolve(repoPath, path7.dirname(fromFile), specifier);
|
|
1404
|
+
for (const candidate of [base, `${base}.ts`, `${base}.js`, path7.join(base, "index.ts"), path7.join(base, "index.js")]) {
|
|
646
1405
|
try {
|
|
647
1406
|
const stat = fsSync.statSync(candidate);
|
|
648
|
-
if (stat.isFile()) return normalizePath(
|
|
1407
|
+
if (stat.isFile()) return normalizePath(path7.relative(repoPath, candidate));
|
|
649
1408
|
} catch {
|
|
650
1409
|
}
|
|
651
1410
|
}
|
|
652
1411
|
return void 0;
|
|
653
1412
|
}
|
|
654
|
-
function readExports(repoPath, filePath, seen) {
|
|
655
|
-
const absolute =
|
|
1413
|
+
function readExports(repoPath, filePath, seen, context) {
|
|
1414
|
+
const absolute = path7.join(repoPath, filePath);
|
|
656
1415
|
let text;
|
|
1416
|
+
const snapshot = context?.get(filePath);
|
|
657
1417
|
try {
|
|
658
|
-
text = fsSync.readFileSync(absolute, "utf8");
|
|
1418
|
+
text = snapshot?.text ?? fsSync.readFileSync(absolute, "utf8");
|
|
659
1419
|
} catch {
|
|
660
1420
|
return { arrays: /* @__PURE__ */ new Map(), aliases: /* @__PURE__ */ new Map() };
|
|
661
1421
|
}
|
|
662
|
-
const sourceFile = ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
1422
|
+
const sourceFile = snapshot?.sourceFile() ?? ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
663
1423
|
const imports = collectImports(sourceFile);
|
|
664
|
-
const arrays = collectLocalArrays(
|
|
1424
|
+
const arrays = collectLocalArrays(
|
|
1425
|
+
sourceFile,
|
|
1426
|
+
imports,
|
|
1427
|
+
/* @__PURE__ */ new Map(),
|
|
1428
|
+
repoPath,
|
|
1429
|
+
filePath,
|
|
1430
|
+
context
|
|
1431
|
+
);
|
|
665
1432
|
const aliases = /* @__PURE__ */ new Map();
|
|
666
1433
|
let defaultArray;
|
|
667
1434
|
for (const statement of sourceFile.statements) {
|
|
@@ -672,7 +1439,13 @@ function readExports(repoPath, filePath, seen) {
|
|
|
672
1439
|
const local = element.propertyName?.text ?? element.name.text;
|
|
673
1440
|
aliases.set(element.name.text, local);
|
|
674
1441
|
if (module && isRelative(module)) {
|
|
675
|
-
const imported = resolveImportedArray(
|
|
1442
|
+
const imported = resolveImportedArray(
|
|
1443
|
+
repoPath,
|
|
1444
|
+
filePath,
|
|
1445
|
+
{ source: module, importedName: local },
|
|
1446
|
+
seen,
|
|
1447
|
+
context
|
|
1448
|
+
);
|
|
676
1449
|
if (imported.length > 0) arrays.set(element.name.text, imported);
|
|
677
1450
|
}
|
|
678
1451
|
}
|
|
@@ -704,12 +1477,12 @@ function summarizeExpression(text) {
|
|
|
704
1477
|
}
|
|
705
1478
|
|
|
706
1479
|
// src/parsers/service-binding-parser.ts
|
|
707
|
-
import
|
|
1480
|
+
import path9 from "path";
|
|
708
1481
|
import ts5 from "typescript";
|
|
709
1482
|
|
|
710
1483
|
// src/parsers/service-binding-parser-helpers.ts
|
|
711
|
-
import
|
|
712
|
-
import
|
|
1484
|
+
import fs7 from "fs/promises";
|
|
1485
|
+
import path8 from "path";
|
|
713
1486
|
import ts4 from "typescript";
|
|
714
1487
|
function lineOf3(sf, node) {
|
|
715
1488
|
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
@@ -795,9 +1568,11 @@ function findConnectInExpression(expr) {
|
|
|
795
1568
|
visit(expr);
|
|
796
1569
|
return found;
|
|
797
1570
|
}
|
|
798
|
-
async function readSource(abs) {
|
|
1571
|
+
async function readSource(abs, context, filePath) {
|
|
1572
|
+
const snapshot = filePath ? context?.get(filePath) : void 0;
|
|
1573
|
+
if (snapshot) return snapshot.sourceFile();
|
|
799
1574
|
try {
|
|
800
|
-
const text = await
|
|
1575
|
+
const text = await fs7.readFile(abs, "utf8");
|
|
801
1576
|
return ts4.createSourceFile(abs, text, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
|
|
802
1577
|
} catch {
|
|
803
1578
|
return void 0;
|
|
@@ -805,12 +1580,12 @@ async function readSource(abs) {
|
|
|
805
1580
|
}
|
|
806
1581
|
async function resolveImport(repoPath, fromFile, spec) {
|
|
807
1582
|
if (!spec.startsWith(".")) return void 0;
|
|
808
|
-
const rawBase =
|
|
809
|
-
const parsed =
|
|
810
|
-
const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ?
|
|
811
|
-
for (const candidate of [base, `${base}.ts`, `${base}.js`,
|
|
812
|
-
const stat = await
|
|
813
|
-
if (stat?.isFile()) return normalizePath(
|
|
1583
|
+
const rawBase = path8.resolve(repoPath, path8.dirname(fromFile), spec);
|
|
1584
|
+
const parsed = path8.parse(rawBase);
|
|
1585
|
+
const base = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"].includes(parsed.ext) ? path8.join(parsed.dir, parsed.name) : rawBase;
|
|
1586
|
+
for (const candidate of [base, `${base}.ts`, `${base}.js`, path8.join(base, "index.ts"), path8.join(base, "index.js")]) {
|
|
1587
|
+
const stat = await fs7.stat(candidate).catch(() => void 0);
|
|
1588
|
+
if (stat?.isFile()) return normalizePath(path8.relative(repoPath, candidate));
|
|
814
1589
|
}
|
|
815
1590
|
return void 0;
|
|
816
1591
|
}
|
|
@@ -918,8 +1693,8 @@ function exportedLocalNames(sf) {
|
|
|
918
1693
|
}
|
|
919
1694
|
return exports;
|
|
920
1695
|
}
|
|
921
|
-
async function helperBindings(repoPath, filePath) {
|
|
922
|
-
const sf = await readSource(
|
|
1696
|
+
async function helperBindings(repoPath, filePath, context) {
|
|
1697
|
+
const sf = await readSource(path9.join(repoPath, filePath), context, filePath);
|
|
923
1698
|
if (!sf) return [];
|
|
924
1699
|
const sourceFileAst = sf;
|
|
925
1700
|
const out = [];
|
|
@@ -979,8 +1754,8 @@ async function helperBindings(repoPath, filePath) {
|
|
|
979
1754
|
}
|
|
980
1755
|
return out;
|
|
981
1756
|
}
|
|
982
|
-
async function parseServiceBindings(repoPath, filePath) {
|
|
983
|
-
const sf = await readSource(
|
|
1757
|
+
async function parseServiceBindings(repoPath, filePath, context) {
|
|
1758
|
+
const sf = await readSource(path9.join(repoPath, filePath), context, filePath);
|
|
984
1759
|
if (!sf) return [];
|
|
985
1760
|
const sourceFileAst = sf;
|
|
986
1761
|
const out = [];
|
|
@@ -1019,7 +1794,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1019
1794
|
if (!helperCache.has(imp.sourceFile))
|
|
1020
1795
|
helperCache.set(
|
|
1021
1796
|
imp.sourceFile,
|
|
1022
|
-
await helperBindings(repoPath, imp.sourceFile)
|
|
1797
|
+
await helperBindings(repoPath, imp.sourceFile, context)
|
|
1023
1798
|
);
|
|
1024
1799
|
return (helperCache.get(imp.sourceFile) ?? []).filter((h) => h.exportedName === imp.exportedName).map((helper) => ({ imp, helper }));
|
|
1025
1800
|
}
|
|
@@ -1069,28 +1844,28 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1069
1844
|
});
|
|
1070
1845
|
else if (ts5.isIdentifier(call.expression)) {
|
|
1071
1846
|
const localDirect = localDirectHelpers.get(call.expression.text);
|
|
1072
|
-
const
|
|
1073
|
-
if (
|
|
1847
|
+
const resolved3 = localDirect ? { helper: localDirect, imp: void 0 } : await importedHelper(call.expression.text);
|
|
1848
|
+
if (resolved3)
|
|
1074
1849
|
out.push({
|
|
1075
1850
|
variableName: targetName,
|
|
1076
|
-
alias:
|
|
1077
|
-
aliasExpr:
|
|
1078
|
-
destinationExpr:
|
|
1079
|
-
servicePathExpr:
|
|
1080
|
-
isDynamic:
|
|
1081
|
-
placeholders:
|
|
1851
|
+
alias: resolved3.helper.alias,
|
|
1852
|
+
aliasExpr: resolved3.helper.aliasExpr,
|
|
1853
|
+
destinationExpr: resolved3.helper.destinationExpr,
|
|
1854
|
+
servicePathExpr: resolved3.helper.servicePathExpr,
|
|
1855
|
+
isDynamic: resolved3.helper.isDynamic,
|
|
1856
|
+
placeholders: resolved3.helper.placeholders,
|
|
1082
1857
|
sourceFile: normalizePath(filePath),
|
|
1083
1858
|
sourceLine: lineOf3(sourceFileAst, node),
|
|
1084
1859
|
helperChain: [
|
|
1085
|
-
...
|
|
1860
|
+
...resolved3.helper.helperChain ?? [],
|
|
1086
1861
|
{
|
|
1087
1862
|
callerVariable: targetName,
|
|
1088
1863
|
...aliasKind === "assignment" ? { assignedFrom: call.expression.text, aliasKind, scopeRule: "same-file-source-order" } : {},
|
|
1089
1864
|
importedHelper: call.expression.text,
|
|
1090
|
-
importSource:
|
|
1091
|
-
exportedSymbol:
|
|
1092
|
-
helperSourceFile:
|
|
1093
|
-
helperSourceLine:
|
|
1865
|
+
importSource: resolved3.imp?.sourceFile,
|
|
1866
|
+
exportedSymbol: resolved3.imp?.exportedName ?? resolved3.helper.exportedName,
|
|
1867
|
+
helperSourceFile: resolved3.helper.sourceFile,
|
|
1868
|
+
helperSourceLine: resolved3.helper.sourceLine
|
|
1094
1869
|
}
|
|
1095
1870
|
]
|
|
1096
1871
|
});
|
|
@@ -1119,28 +1894,28 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1119
1894
|
const helpers = objectHelperVariables.get(unwrapped.expression.text) ?? [];
|
|
1120
1895
|
const matches2 = helpers.filter((row) => row.helper.returnedProperty === unwrapped.name.text);
|
|
1121
1896
|
if (matches2.length !== 1) return false;
|
|
1122
|
-
const
|
|
1897
|
+
const resolved3 = matches2[0];
|
|
1123
1898
|
out.push({
|
|
1124
1899
|
variableName: targetName,
|
|
1125
|
-
alias:
|
|
1126
|
-
aliasExpr:
|
|
1127
|
-
destinationExpr:
|
|
1128
|
-
servicePathExpr:
|
|
1129
|
-
isDynamic:
|
|
1130
|
-
placeholders:
|
|
1900
|
+
alias: resolved3.helper.alias,
|
|
1901
|
+
aliasExpr: resolved3.helper.aliasExpr,
|
|
1902
|
+
destinationExpr: resolved3.helper.destinationExpr,
|
|
1903
|
+
servicePathExpr: resolved3.helper.servicePathExpr,
|
|
1904
|
+
isDynamic: resolved3.helper.isDynamic,
|
|
1905
|
+
placeholders: resolved3.helper.placeholders,
|
|
1131
1906
|
sourceFile: normalizePath(filePath),
|
|
1132
1907
|
sourceLine: lineOf3(sourceFileAst, node),
|
|
1133
1908
|
helperChain: [
|
|
1134
|
-
...
|
|
1909
|
+
...resolved3.helper.helperChain ?? [],
|
|
1135
1910
|
{
|
|
1136
1911
|
callerVariable: targetName,
|
|
1137
1912
|
sourceVariable: unwrapped.expression.text,
|
|
1138
1913
|
returnedProperty: unwrapped.name.text,
|
|
1139
1914
|
assignedFromProperty: unwrapped.getText(sourceFileAst),
|
|
1140
|
-
importSource:
|
|
1141
|
-
exportedSymbol:
|
|
1142
|
-
helperSourceFile:
|
|
1143
|
-
helperSourceLine:
|
|
1915
|
+
importSource: resolved3.imp?.sourceFile,
|
|
1916
|
+
exportedSymbol: resolved3.imp?.exportedName,
|
|
1917
|
+
helperSourceFile: resolved3.helper.sourceFile,
|
|
1918
|
+
helperSourceLine: resolved3.helper.sourceLine
|
|
1144
1919
|
}
|
|
1145
1920
|
]
|
|
1146
1921
|
});
|
|
@@ -1157,18 +1932,18 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1157
1932
|
const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
1158
1933
|
const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
|
|
1159
1934
|
if (matches2.length !== 1) continue;
|
|
1160
|
-
const
|
|
1935
|
+
const resolved3 = matches2[0];
|
|
1161
1936
|
out.push({
|
|
1162
1937
|
variableName: el.name.text,
|
|
1163
|
-
alias:
|
|
1164
|
-
aliasExpr:
|
|
1165
|
-
destinationExpr:
|
|
1166
|
-
servicePathExpr:
|
|
1167
|
-
isDynamic:
|
|
1168
|
-
placeholders:
|
|
1938
|
+
alias: resolved3.helper.alias,
|
|
1939
|
+
aliasExpr: resolved3.helper.aliasExpr,
|
|
1940
|
+
destinationExpr: resolved3.helper.destinationExpr,
|
|
1941
|
+
servicePathExpr: resolved3.helper.servicePathExpr,
|
|
1942
|
+
isDynamic: resolved3.helper.isDynamic,
|
|
1943
|
+
placeholders: resolved3.helper.placeholders,
|
|
1169
1944
|
sourceFile: normalizePath(filePath),
|
|
1170
1945
|
sourceLine: lineOf3(sourceFileAst, decl),
|
|
1171
|
-
helperChain: [...
|
|
1946
|
+
helperChain: [...resolved3.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName3, importSource: resolved3.imp?.sourceFile, exportedSymbol: resolved3.imp?.exportedName, helperSourceFile: resolved3.helper.sourceFile, helperSourceLine: resolved3.helper.sourceLine }]
|
|
1172
1947
|
});
|
|
1173
1948
|
}
|
|
1174
1949
|
}
|
|
@@ -1190,18 +1965,18 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1190
1965
|
if (!propertyName3 || !targetName) continue;
|
|
1191
1966
|
const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
|
|
1192
1967
|
if (matches2.length !== 1) continue;
|
|
1193
|
-
const
|
|
1968
|
+
const resolved3 = matches2[0];
|
|
1194
1969
|
out.push({
|
|
1195
1970
|
variableName: targetName,
|
|
1196
|
-
alias:
|
|
1197
|
-
aliasExpr:
|
|
1198
|
-
destinationExpr:
|
|
1199
|
-
servicePathExpr:
|
|
1200
|
-
isDynamic:
|
|
1201
|
-
placeholders:
|
|
1971
|
+
alias: resolved3.helper.alias,
|
|
1972
|
+
aliasExpr: resolved3.helper.aliasExpr,
|
|
1973
|
+
destinationExpr: resolved3.helper.destinationExpr,
|
|
1974
|
+
servicePathExpr: resolved3.helper.servicePathExpr,
|
|
1975
|
+
isDynamic: resolved3.helper.isDynamic,
|
|
1976
|
+
placeholders: resolved3.helper.placeholders,
|
|
1202
1977
|
sourceFile: normalizePath(filePath),
|
|
1203
1978
|
sourceLine: lineOf3(sourceFileAst, node),
|
|
1204
|
-
helperChain: [...
|
|
1979
|
+
helperChain: [...resolved3.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName3, importSource: resolved3.imp?.sourceFile, exportedSymbol: resolved3.imp?.exportedName, helperSourceFile: resolved3.helper.sourceFile, helperSourceLine: resolved3.helper.sourceLine }]
|
|
1205
1980
|
});
|
|
1206
1981
|
}
|
|
1207
1982
|
}
|
|
@@ -1400,8 +2175,8 @@ function collectClassHelpers(sf) {
|
|
|
1400
2175
|
}
|
|
1401
2176
|
|
|
1402
2177
|
// src/parsers/outbound-call-parser.ts
|
|
1403
|
-
import
|
|
1404
|
-
import
|
|
2178
|
+
import fs8 from "fs/promises";
|
|
2179
|
+
import path11 from "path";
|
|
1405
2180
|
import ts8 from "typescript";
|
|
1406
2181
|
|
|
1407
2182
|
// src/linker/external-http-target.ts
|
|
@@ -1419,8 +2194,8 @@ function redactUrl(value) {
|
|
|
1419
2194
|
url.username = "";
|
|
1420
2195
|
url.password = "";
|
|
1421
2196
|
for (const key of [...url.searchParams.keys()]) url.searchParams.set(key, sensitiveKeys.has(key.toLowerCase()) ? "<redacted>" : "<redacted>");
|
|
1422
|
-
const
|
|
1423
|
-
return value.startsWith("/") ?
|
|
2197
|
+
const path12 = `${url.pathname}${url.search ? url.search : ""}`;
|
|
2198
|
+
return value.startsWith("/") ? path12 : `${url.origin}${path12}`;
|
|
1424
2199
|
} catch {
|
|
1425
2200
|
return value.replace(/([?&][^=;&]*(?:token|key|password|secret|cookie|authorization)[^=;&]*=)[^&]*/gi, "$1<redacted>");
|
|
1426
2201
|
}
|
|
@@ -1454,9 +2229,9 @@ function safeParse(value) {
|
|
|
1454
2229
|
}
|
|
1455
2230
|
|
|
1456
2231
|
// src/linker/odata-path-normalizer.ts
|
|
1457
|
-
function normalizeODataOperationInvocationPath(
|
|
1458
|
-
if (
|
|
1459
|
-
const raw =
|
|
2232
|
+
function normalizeODataOperationInvocationPath(path12) {
|
|
2233
|
+
if (path12 === void 0) return void 0;
|
|
2234
|
+
const raw = path12.trim();
|
|
1460
2235
|
if (!raw) return void 0;
|
|
1461
2236
|
const rejected = (reason) => ({ rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false, invocationArgumentPlaceholderKeys: [], normalizationRejectedReason: reason });
|
|
1462
2237
|
const open = raw.indexOf("(");
|
|
@@ -1479,8 +2254,8 @@ function normalizeODataOperationInvocationPath(path11) {
|
|
|
1479
2254
|
normalizationReason: "balanced_top_level_operation_invocation"
|
|
1480
2255
|
};
|
|
1481
2256
|
}
|
|
1482
|
-
function classifyODataPathIntent(
|
|
1483
|
-
const rawPath = (
|
|
2257
|
+
function classifyODataPathIntent(path12, method) {
|
|
2258
|
+
const rawPath = (path12 ?? "").trim();
|
|
1484
2259
|
const normalizedMethod = (method ?? "GET").trim().toUpperCase() || "GET";
|
|
1485
2260
|
const queryIndex = rawPath.indexOf("?");
|
|
1486
2261
|
const pathWithoutQuery = queryIndex >= 0 ? rawPath.slice(0, queryIndex) : rawPath;
|
|
@@ -1528,8 +2303,8 @@ function classifyODataPathIntent(path11, method) {
|
|
|
1528
2303
|
if (/^[A-Z][A-Za-z0-9_]*$/.test(firstSegment)) return { ...base, kind: "entity_candidate", reason: "uppercase_collection_segment_without_indexed_entity_evidence" };
|
|
1529
2304
|
return { ...base, kind: "unknown", reason: "get_path_has_no_query_key_or_navigation_signal" };
|
|
1530
2305
|
}
|
|
1531
|
-
function entitySegmentFromPath(
|
|
1532
|
-
const first =
|
|
2306
|
+
function entitySegmentFromPath(path12) {
|
|
2307
|
+
const first = path12.replace(/^\//, "").split("/")[0]?.trim();
|
|
1533
2308
|
if (!first) return void 0;
|
|
1534
2309
|
const open = first.indexOf("(");
|
|
1535
2310
|
const entity = (open >= 0 ? first.slice(0, open) : first).trim();
|
|
@@ -1677,7 +2452,7 @@ function topLevelQueryIndex(text) {
|
|
|
1677
2452
|
}
|
|
1678
2453
|
|
|
1679
2454
|
// src/parsers/imported-wrapper-parser.ts
|
|
1680
|
-
import
|
|
2455
|
+
import path10 from "path";
|
|
1681
2456
|
import ts7 from "typescript";
|
|
1682
2457
|
|
|
1683
2458
|
// src/parsers/operation-path-analysis.ts
|
|
@@ -1686,8 +2461,8 @@ var maxAliasDepth = 6;
|
|
|
1686
2461
|
function analyzeOperationPath(expression, use, method = "POST") {
|
|
1687
2462
|
if (!expression) return emptyAnalysis();
|
|
1688
2463
|
const state = collectExpressionState(expression, use, 0, /* @__PURE__ */ new Set());
|
|
1689
|
-
const paths =
|
|
1690
|
-
const normalized =
|
|
2464
|
+
const paths = unique2(state.paths.map(normalizeRawPath));
|
|
2465
|
+
const normalized = unique2(paths.flatMap((value) => normalizedCandidate(value, method)));
|
|
1691
2466
|
const status = pathStatus(paths, state.placeholders, state.dynamic);
|
|
1692
2467
|
const runtimeIdentifier = state.dynamic.at(-1)?.expression;
|
|
1693
2468
|
return {
|
|
@@ -1696,8 +2471,8 @@ function analyzeOperationPath(expression, use, method = "POST") {
|
|
|
1696
2471
|
normalizedOperationPath: status === "static" && normalized.length === 1 ? normalized[0] : void 0,
|
|
1697
2472
|
candidateRawPaths: paths,
|
|
1698
2473
|
candidateNormalizedOperationPaths: normalized,
|
|
1699
|
-
placeholderKeys:
|
|
1700
|
-
sourceKind:
|
|
2474
|
+
placeholderKeys: unique2([...state.placeholders, ...runtimeIdentifier ? [runtimeIdentifier] : []]),
|
|
2475
|
+
sourceKind: unique2(state.sourceKinds).join("+") || "unknown",
|
|
1701
2476
|
candidateIdentifier: ts6.isIdentifier(expression) ? expression.text : void 0,
|
|
1702
2477
|
runtimeIdentifier,
|
|
1703
2478
|
dynamicReassignments: state.dynamic,
|
|
@@ -1828,8 +2603,8 @@ function templateState(expression) {
|
|
|
1828
2603
|
sourceKinds: ["template_with_placeholders"]
|
|
1829
2604
|
};
|
|
1830
2605
|
}
|
|
1831
|
-
function staticState(
|
|
1832
|
-
return { paths: [
|
|
2606
|
+
function staticState(path12, sourceKind) {
|
|
2607
|
+
return { paths: [path12], placeholders: [], dynamic: [], sourceKinds: [sourceKind] };
|
|
1833
2608
|
}
|
|
1834
2609
|
function dynamicState(expression, sourceKind = "dynamic_expression") {
|
|
1835
2610
|
return {
|
|
@@ -1898,7 +2673,7 @@ function sourceLine(node) {
|
|
|
1898
2673
|
const source = node.getSourceFile();
|
|
1899
2674
|
return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
1900
2675
|
}
|
|
1901
|
-
function
|
|
2676
|
+
function unique2(values) {
|
|
1902
2677
|
return [...new Set(values)].sort();
|
|
1903
2678
|
}
|
|
1904
2679
|
function emptyAnalysis() {
|
|
@@ -1914,7 +2689,7 @@ function emptyAnalysis() {
|
|
|
1914
2689
|
}
|
|
1915
2690
|
|
|
1916
2691
|
// src/parsers/imported-wrapper-parser.ts
|
|
1917
|
-
async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings) {
|
|
2692
|
+
async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings, context) {
|
|
1918
2693
|
const imports = await importsFor(repoPath, filePath, source);
|
|
1919
2694
|
const importedByLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
|
|
1920
2695
|
const calls = collectImportedCalls(source, importedByLocal);
|
|
@@ -1924,7 +2699,7 @@ async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBind
|
|
|
1924
2699
|
if (!ts7.isIdentifier(call.expression)) continue;
|
|
1925
2700
|
const imported = importedByLocal.get(call.expression.text);
|
|
1926
2701
|
if (!imported?.sourceFile) continue;
|
|
1927
|
-
const spec = await loadWrapperSpec(repoPath, imported, cache, 0);
|
|
2702
|
+
const spec = await loadWrapperSpec(repoPath, imported, cache, 0, context);
|
|
1928
2703
|
const fact = spec ? wrapperCallFact(source, filePath, call, spec, serviceBindings) : void 0;
|
|
1929
2704
|
if (fact) out.push(fact);
|
|
1930
2705
|
}
|
|
@@ -1939,23 +2714,43 @@ function collectImportedCalls(source, imports) {
|
|
|
1939
2714
|
visit(source);
|
|
1940
2715
|
return calls;
|
|
1941
2716
|
}
|
|
1942
|
-
async function loadWrapperSpec(repoPath, imported, cache, depth) {
|
|
2717
|
+
async function loadWrapperSpec(repoPath, imported, cache, depth, context) {
|
|
1943
2718
|
if (!imported.sourceFile || depth > 5) return void 0;
|
|
1944
2719
|
const key = `${imported.sourceFile}#${imported.exportedName}`;
|
|
1945
2720
|
const existing = cache.get(key);
|
|
1946
2721
|
if (existing) return existing;
|
|
1947
|
-
const pending = inspectWrapper(
|
|
2722
|
+
const pending = inspectWrapper(
|
|
2723
|
+
repoPath,
|
|
2724
|
+
imported.sourceFile,
|
|
2725
|
+
imported.exportedName,
|
|
2726
|
+
cache,
|
|
2727
|
+
depth,
|
|
2728
|
+
context
|
|
2729
|
+
);
|
|
1948
2730
|
cache.set(key, pending);
|
|
1949
2731
|
return pending;
|
|
1950
2732
|
}
|
|
1951
|
-
async function inspectWrapper(repoPath, sourceFile, exportedName, cache, depth) {
|
|
1952
|
-
const source = await readSource(
|
|
2733
|
+
async function inspectWrapper(repoPath, sourceFile, exportedName, cache, depth, context) {
|
|
2734
|
+
const source = await readSource(
|
|
2735
|
+
path10.join(repoPath, sourceFile),
|
|
2736
|
+
context,
|
|
2737
|
+
sourceFile
|
|
2738
|
+
);
|
|
1953
2739
|
if (!source) return void 0;
|
|
1954
2740
|
const named = findFunction(source, exportedName);
|
|
1955
2741
|
if (!named) return void 0;
|
|
1956
2742
|
const direct = directSendSpec(source, sourceFile, named.name, named.fn);
|
|
1957
2743
|
if (direct) return direct;
|
|
1958
|
-
return nestedSendSpec(
|
|
2744
|
+
return nestedSendSpec(
|
|
2745
|
+
repoPath,
|
|
2746
|
+
sourceFile,
|
|
2747
|
+
source,
|
|
2748
|
+
named.name,
|
|
2749
|
+
named.fn,
|
|
2750
|
+
cache,
|
|
2751
|
+
depth,
|
|
2752
|
+
context
|
|
2753
|
+
);
|
|
1959
2754
|
}
|
|
1960
2755
|
function directSendSpec(source, sourceFile, name, fn) {
|
|
1961
2756
|
const params = parameterNames(fn);
|
|
@@ -1974,11 +2769,11 @@ function directSendSpec(source, sourceFile, name, fn) {
|
|
|
1974
2769
|
const clientIndex = params.indexOf(receiver);
|
|
1975
2770
|
const pathIndex = pathName ? params.indexOf(pathName) : -1;
|
|
1976
2771
|
if (clientIndex < 0 || pathIndex < 0) return void 0;
|
|
1977
|
-
const
|
|
1978
|
-
const methodIndex =
|
|
2772
|
+
const methodName2 = methodExpr && ts7.isIdentifier(methodExpr) ? methodExpr.text : void 0;
|
|
2773
|
+
const methodIndex = methodName2 ? params.indexOf(methodName2) : -1;
|
|
1979
2774
|
return { clientIndex, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodLiteral: literal(methodExpr), sourceFile, sourceLine: lineOf3(source, fn), chain: [name] };
|
|
1980
2775
|
}
|
|
1981
|
-
async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, depth) {
|
|
2776
|
+
async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, depth, context) {
|
|
1982
2777
|
const imports = await importsFor(repoPath, sourceFile, source);
|
|
1983
2778
|
const byLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
|
|
1984
2779
|
const calls = [];
|
|
@@ -1988,7 +2783,7 @@ async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, dep
|
|
|
1988
2783
|
if (calls.length !== 1) return void 0;
|
|
1989
2784
|
const call = calls[0];
|
|
1990
2785
|
const imported = call && ts7.isIdentifier(call.expression) ? byLocal.get(call.expression.text) : void 0;
|
|
1991
|
-
const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1) : void 0;
|
|
2786
|
+
const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1, context) : void 0;
|
|
1992
2787
|
if (!call || !nested) return void 0;
|
|
1993
2788
|
const params = parameterNames(fn);
|
|
1994
2789
|
const clientIndex = mappedParameterIndex(call.arguments[nested.clientIndex], params);
|
|
@@ -2235,8 +3030,8 @@ function resolveExpression(expr, use, policy, depth = 0, seen = /* @__PURE__ */
|
|
|
2235
3030
|
if (!binding.declaration || !binding.initializer || !binding.immutable) return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: binding.evidence, constName: expr.text };
|
|
2236
3031
|
if (seen.has(binding.declaration)) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_cycle_detected"], constName: expr.text };
|
|
2237
3032
|
seen.add(binding.declaration);
|
|
2238
|
-
const
|
|
2239
|
-
return { ...
|
|
3033
|
+
const resolved3 = resolveExpression(binding.initializer, binding.declaration, policy, depth + 1, seen);
|
|
3034
|
+
return { ...resolved3, sourceKind: "const_alias", rawExpression: safeRaw(expr), constName: expr.text, evidence: [...binding.evidence, ...resolved3.evidence] };
|
|
2240
3035
|
}
|
|
2241
3036
|
return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: [`unsupported_${ts8.SyntaxKind[expr.kind] ?? "expression"}`] };
|
|
2242
3037
|
}
|
|
@@ -2260,10 +3055,10 @@ function destinationExpressionShape(expr) {
|
|
|
2260
3055
|
return ts8.SyntaxKind[expr.kind] ?? "expression";
|
|
2261
3056
|
}
|
|
2262
3057
|
function staticConditionalCandidates(expr, initializers) {
|
|
2263
|
-
const
|
|
2264
|
-
if (!
|
|
2265
|
-
const left = staticExpressionText(
|
|
2266
|
-
const right = staticExpressionText(
|
|
3058
|
+
const resolved3 = expr && ts8.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
|
|
3059
|
+
if (!resolved3 || !ts8.isConditionalExpression(resolved3)) return void 0;
|
|
3060
|
+
const left = staticExpressionText(resolved3.whenTrue, initializers);
|
|
3061
|
+
const right = staticExpressionText(resolved3.whenFalse, initializers);
|
|
2267
3062
|
if (!left || !right) return void 0;
|
|
2268
3063
|
return [.../* @__PURE__ */ new Set([left, right])];
|
|
2269
3064
|
}
|
|
@@ -2311,15 +3106,15 @@ function hasTemplatePlaceholder(value) {
|
|
|
2311
3106
|
return /\$\{|%7B|%7D/i.test(value);
|
|
2312
3107
|
}
|
|
2313
3108
|
function urlTargetFromExpression(expr, use) {
|
|
2314
|
-
const
|
|
2315
|
-
if (
|
|
2316
|
-
if (expr) return { kind: "url_expression", dynamic: true, expression: `${
|
|
3109
|
+
const resolved3 = resolveExpression(expr, use, "external");
|
|
3110
|
+
if (resolved3.status === "static" && resolved3.value && !hasTemplatePlaceholder(resolved3.value)) return { kind: "static_url", expression: resolved3.value, dynamic: false, sourceKind: resolved3.sourceKind };
|
|
3111
|
+
if (expr) return { kind: "url_expression", dynamic: true, expression: `${resolved3.sourceKind}:${resolved3.placeholderKeys.join("|")}`, expressionShape: resolved3.sourceKind, placeholderKeys: resolved3.placeholderKeys };
|
|
2317
3112
|
return { kind: "unknown", dynamic: false };
|
|
2318
3113
|
}
|
|
2319
3114
|
function destinationTargetFromExpression(expr, use) {
|
|
2320
|
-
const
|
|
2321
|
-
const text =
|
|
2322
|
-
if (
|
|
3115
|
+
const resolved3 = resolveExpression(expr, use, "external");
|
|
3116
|
+
const text = resolved3.value;
|
|
3117
|
+
if (resolved3.status === "static" && text && !hasTemplatePlaceholder(text)) return { kind: "destination", expression: text, dynamic: false, sourceKind: resolved3.sourceKind };
|
|
2323
3118
|
const candidates = staticConditionalCandidates(expr, /* @__PURE__ */ new Map());
|
|
2324
3119
|
if (candidates) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates };
|
|
2325
3120
|
const shape = destinationExpressionShape(expr);
|
|
@@ -2419,9 +3214,9 @@ function collectWrapperSpecs(source) {
|
|
|
2419
3214
|
const pathProp = propertyInitializer(objectArg, "path");
|
|
2420
3215
|
const methodProp = propertyInitializer(objectArg, "method");
|
|
2421
3216
|
const pathName = pathProp && ts8.isIdentifier(pathProp) ? pathProp.text : void 0;
|
|
2422
|
-
const
|
|
3217
|
+
const methodName2 = methodProp && ts8.isIdentifier(methodProp) ? methodProp.text : void 0;
|
|
2423
3218
|
const methodLiteral = resolveExpression(methodProp, node, "literal").value;
|
|
2424
|
-
if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method:
|
|
3219
|
+
if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName2, methodLiteral, start: node.getStart(source), end: node.getEnd() });
|
|
2425
3220
|
}
|
|
2426
3221
|
}
|
|
2427
3222
|
if (ts8.isCallExpression(node) && ts8.isIdentifier(node.expression) && specs.has(node.expression.text)) {
|
|
@@ -2558,15 +3353,37 @@ function containsSupportedOutboundCall(node) {
|
|
|
2558
3353
|
const end = node.getEnd();
|
|
2559
3354
|
return classifyOutboundCallsInSource(source, source.fileName).some((call) => call.node.getStart(source) >= start && call.node.getEnd() <= end);
|
|
2560
3355
|
}
|
|
2561
|
-
async function parseOutboundCalls(repoPath, filePath) {
|
|
2562
|
-
const
|
|
2563
|
-
const
|
|
2564
|
-
const
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
3356
|
+
async function parseOutboundCalls(repoPath, filePath, context) {
|
|
3357
|
+
const snapshot = context?.get(filePath);
|
|
3358
|
+
const text = snapshot?.text ?? await fs8.readFile(path11.join(repoPath, filePath), "utf8");
|
|
3359
|
+
const source = snapshot?.sourceFile() ?? ts8.createSourceFile(
|
|
3360
|
+
filePath,
|
|
3361
|
+
text,
|
|
3362
|
+
ts8.ScriptTarget.Latest,
|
|
3363
|
+
true,
|
|
3364
|
+
filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
|
|
3365
|
+
);
|
|
3366
|
+
const bindingNames = new Set((await parseServiceBindings(
|
|
3367
|
+
repoPath,
|
|
3368
|
+
filePath,
|
|
3369
|
+
context
|
|
3370
|
+
)).map((binding) => binding.variableName));
|
|
3371
|
+
const importedWrappers = await parseImportedWrapperCalls(
|
|
3372
|
+
repoPath,
|
|
3373
|
+
filePath,
|
|
3374
|
+
source,
|
|
3375
|
+
bindingNames,
|
|
3376
|
+
context
|
|
3377
|
+
);
|
|
3378
|
+
return [...classifyOutboundCallsInSource(source, filePath).map((call) => call.fact), ...importedWrappers, ...parseLocalServiceCalls(text, filePath, source)];
|
|
3379
|
+
}
|
|
3380
|
+
function parseLocalServiceCalls(text, filePath, source = ts8.createSourceFile(
|
|
3381
|
+
filePath,
|
|
3382
|
+
text,
|
|
3383
|
+
ts8.ScriptTarget.Latest,
|
|
3384
|
+
true,
|
|
3385
|
+
filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS
|
|
3386
|
+
)) {
|
|
2570
3387
|
const aliases = /* @__PURE__ */ new Map();
|
|
2571
3388
|
const calls = [];
|
|
2572
3389
|
const visit = (node) => {
|
|
@@ -3277,7 +4094,7 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
3277
4094
|
const duplicateFamilies = duplicatePackageFamilies(accepted);
|
|
3278
4095
|
const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
|
|
3279
4096
|
const selected = duplicatePackageAmbiguous ? accepted : winners;
|
|
3280
|
-
const
|
|
4097
|
+
const unique3 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
|
|
3281
4098
|
const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
|
|
3282
4099
|
const evidence = {
|
|
3283
4100
|
servicePath: operation.servicePath,
|
|
@@ -3291,16 +4108,16 @@ function linkImplementations(db, workspaceId, generation) {
|
|
|
3291
4108
|
candidateFamilies: duplicateFamilies,
|
|
3292
4109
|
candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
3293
4110
|
};
|
|
3294
|
-
const evidenceWithHints =
|
|
4111
|
+
const evidenceWithHints = unique3 ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
|
|
3295
4112
|
if (accepted.length === 0) {
|
|
3296
4113
|
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(evidenceWithHints), 0, "No implementation candidate passed policy", generation);
|
|
3297
4114
|
edgeCount += 1;
|
|
3298
4115
|
unresolvedCount += 1;
|
|
3299
4116
|
continue;
|
|
3300
4117
|
}
|
|
3301
|
-
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",
|
|
4118
|
+
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", unique3 ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique3 ? "handler_method" : "handler_method_candidates", unique3 ? graphId(unique3.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique3 ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique3 ? null : "Ambiguous registered handler implementation candidates", generation);
|
|
3302
4119
|
edgeCount += 1;
|
|
3303
|
-
if (
|
|
4120
|
+
if (unique3) resolvedCount += 1;
|
|
3304
4121
|
else ambiguousCount += 1;
|
|
3305
4122
|
}
|
|
3306
4123
|
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
@@ -3443,7 +4260,7 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
3443
4260
|
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
3444
4261
|
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,
|
|
3445
4262
|
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
3446
|
-
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) 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,
|
|
4263
|
+
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.handlerKind'),CASE WHEN localMethod.decorator_kind='Event' THEN 'event' WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 'operation' ELSE 'unsupported' END)='operation' AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.executable'),CASE WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 1 ELSE 0 END)=1 AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
3447
4264
|
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,
|
|
3448
4265
|
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,
|
|
3449
4266
|
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
|
|
@@ -3475,6 +4292,13 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
3475
4292
|
)
|
|
3476
4293
|
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
3477
4294
|
WHERE appRepo.workspace_id=?
|
|
4295
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
4296
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
4297
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4298
|
+
ELSE 'unsupported' END)='operation'
|
|
4299
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4300
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
4301
|
+
THEN 1 ELSE 0 END)=1
|
|
3478
4302
|
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
|
|
3479
4303
|
operation.modelRepoId,
|
|
3480
4304
|
operation.modelRepo,
|
|
@@ -3603,6 +4427,11 @@ function candidateEvidence(candidate, rank) {
|
|
|
3603
4427
|
};
|
|
3604
4428
|
}
|
|
3605
4429
|
function implementationMethodSignal(row, operation) {
|
|
4430
|
+
const resolution = objectJson(row.decoratorResolutionJson) ?? {};
|
|
4431
|
+
if (resolution.handlerKind && resolution.handlerKind !== "operation")
|
|
4432
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["non_operation_handler_kind"] };
|
|
4433
|
+
if (resolution.executable === false)
|
|
4434
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["handler_method_not_executable"] };
|
|
3606
4435
|
const op = normalizedOperationName(String(operation.operationPath ?? operation.operationName ?? ""));
|
|
3607
4436
|
const decorator = normalizeDecoratorOperationSignal(typeof row.decoratorValue === "string" ? row.decoratorValue : void 0, typeof row.decoratorRawExpression === "string" ? row.decoratorRawExpression : void 0, op);
|
|
3608
4437
|
if (decorator.status === "resolved" && decorator.operationName === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
@@ -3632,91 +4461,730 @@ function parseVars(values) {
|
|
|
3632
4461
|
}
|
|
3633
4462
|
return out;
|
|
3634
4463
|
}
|
|
3635
|
-
function
|
|
3636
|
-
const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
|
|
4464
|
+
function selectorRepoNotFoundDiagnostic(requested) {
|
|
3637
4465
|
return {
|
|
3638
4466
|
severity: "warning",
|
|
3639
|
-
code: "
|
|
3640
|
-
message
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
resolutionStatus: "ambiguous_operation",
|
|
3644
|
-
candidates,
|
|
3645
|
-
serviceSuggestions,
|
|
3646
|
-
selectorSuggestions: fullSelectorSuggestions(candidates)
|
|
4467
|
+
code: "selector_repo_not_found",
|
|
4468
|
+
message: `No indexed repository matched selector: ${requested}`,
|
|
4469
|
+
selectorKind: "repo",
|
|
4470
|
+
requestedRepository: requested
|
|
3647
4471
|
};
|
|
3648
4472
|
}
|
|
3649
|
-
function
|
|
3650
|
-
const
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
];
|
|
3657
|
-
})
|
|
3658
|
-
}
|
|
3659
|
-
|
|
3660
|
-
// src/trace/dynamic-targets.ts
|
|
3661
|
-
function analyzeDynamicTargetCandidates(db, evidence, workspaceId, mode, maxCandidates) {
|
|
3662
|
-
const templates = templatesFromEvidence(evidence);
|
|
3663
|
-
const missingVariables = allMissingVariables(evidence, templates);
|
|
3664
|
-
if (missingVariables.length === 0) return void 0;
|
|
3665
|
-
const order = variableOrder(templates, missingVariables);
|
|
3666
|
-
const candidates = rankedCandidates(
|
|
3667
|
-
db,
|
|
3668
|
-
candidateTargets(db, evidence, workspaceId),
|
|
3669
|
-
referenceRows(db, workspaceId),
|
|
3670
|
-
templates,
|
|
3671
|
-
order
|
|
3672
|
-
);
|
|
3673
|
-
const inference = inferenceDecision(candidates);
|
|
3674
|
-
const shownCandidates = candidates.slice(0, maxCandidates);
|
|
4473
|
+
function selectorRepoAmbiguousDiagnostic(requested, candidates) {
|
|
4474
|
+
const uniqueName = (value) => candidates.filter((candidate) => candidate.name === value).length === 1;
|
|
4475
|
+
const uniquePackage = (value) => candidates.filter((candidate) => candidate.packageName === value).length === 1;
|
|
4476
|
+
const suggestions = candidates.flatMap((candidate) => {
|
|
4477
|
+
if (uniqueName(candidate.name)) return [`--repo ${candidate.name}`];
|
|
4478
|
+
if (candidate.packageName && uniquePackage(candidate.packageName))
|
|
4479
|
+
return [`--repo ${candidate.packageName}`];
|
|
4480
|
+
return [];
|
|
4481
|
+
});
|
|
3675
4482
|
return {
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
4483
|
+
severity: "warning",
|
|
4484
|
+
code: "selector_repo_ambiguous",
|
|
4485
|
+
message: `Repository selector matched multiple indexed repositories: ${requested}`,
|
|
4486
|
+
selectorKind: "repo",
|
|
4487
|
+
requestedRepository: requested,
|
|
3681
4488
|
candidates,
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
inference
|
|
4489
|
+
selectorSuggestions: [...new Set(suggestions)].sort(),
|
|
4490
|
+
remediation: suggestions.length > 0 ? "Use one of the unique --repo selectors shown." : "Repository names and package names must be unique before this selector can be traced safely."
|
|
3685
4491
|
};
|
|
3686
4492
|
}
|
|
3687
|
-
function
|
|
3688
|
-
const
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
const
|
|
3699
|
-
|
|
3700
|
-
const
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
4493
|
+
function selectorNotFoundDiagnostic(start) {
|
|
4494
|
+
const serviceOnly = start.servicePath && !start.operation && !start.operationPath && !start.handler;
|
|
4495
|
+
return {
|
|
4496
|
+
severity: "warning",
|
|
4497
|
+
code: "trace_start_not_found",
|
|
4498
|
+
message: serviceOnly ? "Service-only trace requires --operation or --path and will not broaden to the whole workspace" : "No handler source matched the requested trace start selector",
|
|
4499
|
+
selectorKind: start.handler ? "handler" : start.operation || start.operationPath ? "operation" : start.servicePath ? "service" : void 0
|
|
4500
|
+
};
|
|
4501
|
+
}
|
|
4502
|
+
function sourceScopeForSelector(db, repoId, start, workspaceId) {
|
|
4503
|
+
if (start.handler) {
|
|
4504
|
+
const classRows = handlerClassRows(db, repoId, start.handler, workspaceId);
|
|
4505
|
+
if (classRows.length > 0) return handlerClassScope(classRows, start.handler);
|
|
4506
|
+
const methodRows = handlerMethodRows(db, repoId, start.handler, workspaceId);
|
|
4507
|
+
if (methodRows.length > 0)
|
|
4508
|
+
return handlerMethodScope(methodRows, repoId, start.handler);
|
|
4509
|
+
}
|
|
4510
|
+
const operation = normalizeOperation(start.operation ?? start.operationPath);
|
|
4511
|
+
if (!operation) return void 0;
|
|
4512
|
+
const operationRows = operationHandlerRows(
|
|
4513
|
+
db,
|
|
4514
|
+
repoId,
|
|
4515
|
+
operation,
|
|
4516
|
+
start.servicePath,
|
|
4517
|
+
workspaceId
|
|
4518
|
+
);
|
|
4519
|
+
if (operationRows.length > 0)
|
|
4520
|
+
return operationHandlerScope(operationRows, repoId, operation);
|
|
4521
|
+
if (!start.servicePath) return void 0;
|
|
4522
|
+
const implementationRows = implementationHandlerRows(
|
|
4523
|
+
db,
|
|
4524
|
+
repoId,
|
|
4525
|
+
start.servicePath,
|
|
4526
|
+
operation,
|
|
4527
|
+
workspaceId
|
|
4528
|
+
);
|
|
4529
|
+
return implementationRows.length > 0 ? executableScope(implementationRows, repoId) : void 0;
|
|
4530
|
+
}
|
|
4531
|
+
function handlerClassRows(db, repoId, handler, workspaceId) {
|
|
4532
|
+
return db.prepare(`SELECT hc.id handlerClassId,hc.repo_id repoId,
|
|
4533
|
+
r.name repoName,hc.class_name className,
|
|
4534
|
+
hc.source_file sourceFile,hc.source_line sourceLine,hm.id methodId,
|
|
4535
|
+
sym.id symbolId,COALESCE(classSym.evidence_json,
|
|
4536
|
+
(SELECT fallback.evidence_json FROM symbols fallback
|
|
4537
|
+
WHERE fallback.repo_id=hc.repo_id AND fallback.kind='class'
|
|
4538
|
+
AND fallback.name=hc.class_name AND fallback.source_file=hc.source_file
|
|
4539
|
+
ORDER BY fallback.id LIMIT 1)) classEvidence
|
|
4540
|
+
FROM handler_classes hc
|
|
4541
|
+
JOIN repositories r ON r.id=hc.repo_id
|
|
4542
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4543
|
+
AND sym.source_file=hc.source_file
|
|
4544
|
+
AND sym.kind='method'
|
|
4545
|
+
AND substr(sym.qualified_name,1,length(hc.class_name)+1)=hc.class_name || '.'
|
|
4546
|
+
AND (NOT EXISTS (SELECT 1 FROM handler_methods declared
|
|
4547
|
+
WHERE declared.handler_class_id=hc.id
|
|
4548
|
+
AND declared.method_name=sym.name)
|
|
4549
|
+
OR EXISTS (SELECT 1 FROM handler_methods executable
|
|
4550
|
+
WHERE executable.handler_class_id=hc.id
|
|
4551
|
+
AND executable.method_name=sym.name
|
|
4552
|
+
AND COALESCE(json_extract(executable.decorator_resolution_json,
|
|
4553
|
+
'$.executable'),CASE WHEN executable.decorator_kind IN
|
|
4554
|
+
('Action','Func','On','Event') THEN 1 ELSE 0 END)=1))
|
|
4555
|
+
LEFT JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
4556
|
+
AND hm.method_name=sym.name
|
|
4557
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4558
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On','Event')
|
|
4559
|
+
THEN 1 ELSE 0 END)=1
|
|
4560
|
+
LEFT JOIN symbols classSym ON classSym.id=hc.symbol_id
|
|
4561
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
4562
|
+
AND (? IS NULL OR hc.repo_id=?) AND hc.class_name=?
|
|
4563
|
+
ORDER BY hc.repo_id,hc.id,hm.id`).all(
|
|
4564
|
+
workspaceId,
|
|
4565
|
+
workspaceId,
|
|
4566
|
+
repoId,
|
|
4567
|
+
repoId,
|
|
4568
|
+
handler
|
|
4569
|
+
);
|
|
4570
|
+
}
|
|
4571
|
+
function handlerMethodRows(db, repoId, handler, workspaceId) {
|
|
4572
|
+
return db.prepare(`SELECT hc.id handlerClassId,hc.repo_id repoId,
|
|
4573
|
+
r.name repoName,hc.class_name className,hc.source_file sourceFile,
|
|
4574
|
+
hm.source_line sourceLine,hm.id methodId,sym.id symbolId
|
|
4575
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4576
|
+
JOIN repositories r ON r.id=hc.repo_id
|
|
4577
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4578
|
+
AND sym.source_file=hc.source_file
|
|
4579
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
4580
|
+
AND sym.start_line=hm.source_line
|
|
4581
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
4582
|
+
AND (? IS NULL OR hc.repo_id=?) AND hm.method_name=?
|
|
4583
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4584
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On','Event')
|
|
4585
|
+
THEN 1 ELSE 0 END)=1
|
|
4586
|
+
ORDER BY hc.repo_id,hc.id,hm.id`).all(
|
|
4587
|
+
workspaceId,
|
|
4588
|
+
workspaceId,
|
|
4589
|
+
repoId,
|
|
4590
|
+
repoId,
|
|
4591
|
+
handler
|
|
4592
|
+
);
|
|
4593
|
+
}
|
|
4594
|
+
function operationHandlerRows(db, repoId, operation, servicePath, workspaceId) {
|
|
4595
|
+
return db.prepare(`SELECT DISTINCT hc.id handlerClassId,hc.repo_id repoId,
|
|
4596
|
+
r.name repoName,hc.class_name className,hc.source_file sourceFile,
|
|
4597
|
+
hm.source_line sourceLine,hm.id methodId,sym.id symbolId
|
|
4598
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4599
|
+
JOIN repositories r ON r.id=hc.repo_id
|
|
4600
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4601
|
+
AND sym.source_file=hc.source_file
|
|
4602
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
4603
|
+
AND sym.start_line=hm.source_line
|
|
4604
|
+
WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR hc.repo_id=?)
|
|
4605
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
4606
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
4607
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4608
|
+
ELSE 'unsupported' END)='operation'
|
|
4609
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4610
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
4611
|
+
THEN 1 ELSE 0 END)=1
|
|
4612
|
+
AND (hm.decorator_value=? OR hm.method_name=?)
|
|
4613
|
+
AND (? IS NULL OR EXISTS (
|
|
4614
|
+
SELECT 1 FROM cds_services svc JOIN cds_operations op ON op.service_id=svc.id
|
|
4615
|
+
WHERE svc.repo_id=hc.repo_id AND svc.service_path=?
|
|
4616
|
+
AND (op.operation_path=? OR op.operation_name=?)))
|
|
4617
|
+
ORDER BY hc.repo_id,hc.id,hm.id`).all(
|
|
4618
|
+
workspaceId,
|
|
4619
|
+
workspaceId,
|
|
4620
|
+
repoId,
|
|
4621
|
+
repoId,
|
|
4622
|
+
operation,
|
|
4623
|
+
operation,
|
|
4624
|
+
servicePath,
|
|
4625
|
+
servicePath,
|
|
4626
|
+
operation,
|
|
4627
|
+
operation
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4630
|
+
function operationHandlerScope(rows2, fallbackRepoId, requested) {
|
|
4631
|
+
const candidates = handlerSelectorCandidates(rows2, "method");
|
|
4632
|
+
if (candidates.length < 2) return executableScope(rows2, fallbackRepoId);
|
|
4633
|
+
const classes = /* @__PURE__ */ new Map();
|
|
4634
|
+
for (const candidate of candidates) {
|
|
4635
|
+
const key = `${String(candidate.repoName)}:${String(candidate.className)}`;
|
|
4636
|
+
classes.set(key, /* @__PURE__ */ new Set([
|
|
4637
|
+
...classes.get(key) ?? [],
|
|
4638
|
+
String(candidate.handlerClassId)
|
|
4639
|
+
]));
|
|
4640
|
+
}
|
|
4641
|
+
const suggestions = candidates.flatMap((candidate) => {
|
|
4642
|
+
if (typeof candidate.repoName !== "string" || typeof candidate.className !== "string") return [];
|
|
4643
|
+
const key = `${candidate.repoName}:${candidate.className}`;
|
|
4644
|
+
return classes.get(key)?.size === 1 ? [`--repo ${candidate.repoName} --handler ${candidate.className}`] : [];
|
|
4645
|
+
});
|
|
4646
|
+
return { diagnostics: [{
|
|
4647
|
+
severity: "warning",
|
|
4648
|
+
code: "trace_start_ambiguous",
|
|
4649
|
+
message: "Operation selector matched multiple handler-only executable scopes",
|
|
4650
|
+
selectorKind: "operation",
|
|
4651
|
+
normalizedSelectorValue: requested,
|
|
4652
|
+
resolutionStage: "handler",
|
|
4653
|
+
resolutionStatus: "ambiguous_handler_operation",
|
|
4654
|
+
candidates,
|
|
4655
|
+
selectorSuggestions: [...new Set(suggestions)].sort(),
|
|
4656
|
+
remediation: "Select one handler class explicitly; no operation was chosen automatically."
|
|
4657
|
+
}] };
|
|
4658
|
+
}
|
|
4659
|
+
function implementationHandlerRows(db, repoId, servicePath, operation, workspaceId) {
|
|
4660
|
+
return db.prepare(`SELECT DISTINCT hc.repo_id repoId,
|
|
4661
|
+
hc.source_file sourceFile,hm.id methodId,sym.id symbolId
|
|
4662
|
+
FROM cds_services svc JOIN cds_operations op ON op.service_id=svc.id
|
|
4663
|
+
JOIN repositories r ON r.id=svc.repo_id
|
|
4664
|
+
JOIN graph_edges edge ON edge.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER'
|
|
4665
|
+
AND edge.status='resolved' AND edge.from_kind='operation'
|
|
4666
|
+
AND edge.from_id=CAST(op.id AS TEXT)
|
|
4667
|
+
JOIN handler_methods hm ON hm.id=CAST(edge.to_id AS INTEGER)
|
|
4668
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4669
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4670
|
+
AND sym.source_file=hc.source_file
|
|
4671
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
4672
|
+
AND sym.start_line=hm.source_line
|
|
4673
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
4674
|
+
AND (? IS NULL OR svc.repo_id=?) AND svc.service_path=?
|
|
4675
|
+
AND (op.operation_path=? OR op.operation_name=?)
|
|
4676
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
4677
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
4678
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4679
|
+
ELSE 'unsupported' END)='operation'
|
|
4680
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4681
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
4682
|
+
THEN 1 ELSE 0 END)=1
|
|
4683
|
+
ORDER BY hc.repo_id,hc.id,hm.id`).all(
|
|
4684
|
+
workspaceId,
|
|
4685
|
+
workspaceId,
|
|
4686
|
+
repoId,
|
|
4687
|
+
repoId,
|
|
4688
|
+
servicePath,
|
|
4689
|
+
operation,
|
|
4690
|
+
operation
|
|
4691
|
+
);
|
|
4692
|
+
}
|
|
4693
|
+
function handlerClassScope(rows2, requested) {
|
|
4694
|
+
const ambiguity = handlerSelectorAmbiguity(rows2, requested, "class");
|
|
4695
|
+
if (ambiguity) return { diagnostics: [ambiguity] };
|
|
4696
|
+
const executable = rows2.filter((row) => typeof row.symbolId === "number");
|
|
4697
|
+
const repoId = numericValue(rows2[0]?.repoId);
|
|
4698
|
+
if (executable.length > 0) {
|
|
4699
|
+
const scope = executableScope(executable, repoId);
|
|
4700
|
+
const warning = executable.some((row) => typeof row.methodId === "number") ? handlerDecoratorsNotIndexedDiagnostic(rows2[0]) : handlerMethodsNotIndexedDiagnostic(rows2[0]);
|
|
4701
|
+
return warning ? { ...scope, diagnostics: [warning] } : scope;
|
|
4702
|
+
}
|
|
4703
|
+
const first = rows2[0];
|
|
4704
|
+
return {
|
|
4705
|
+
repoId,
|
|
4706
|
+
diagnostics: [handlerMethodsNotIndexedDiagnostic(first)]
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
function handlerMethodScope(rows2, fallbackRepoId, requested) {
|
|
4710
|
+
const ambiguity = handlerSelectorAmbiguity(rows2, requested, "method");
|
|
4711
|
+
return ambiguity ? { diagnostics: [ambiguity] } : executableScope(rows2, fallbackRepoId);
|
|
4712
|
+
}
|
|
4713
|
+
function handlerSelectorAmbiguity(rows2, requested, matchKind) {
|
|
4714
|
+
const candidates = handlerSelectorCandidates(rows2, matchKind);
|
|
4715
|
+
if (candidates.length < 2) return void 0;
|
|
4716
|
+
const repoCounts = /* @__PURE__ */ new Map();
|
|
4717
|
+
for (const candidate of candidates) {
|
|
4718
|
+
if (typeof candidate.repoName !== "string") continue;
|
|
4719
|
+
repoCounts.set(
|
|
4720
|
+
candidate.repoName,
|
|
4721
|
+
(repoCounts.get(candidate.repoName) ?? 0) + 1
|
|
4722
|
+
);
|
|
4723
|
+
}
|
|
4724
|
+
const suggestions = candidates.flatMap((candidate) => {
|
|
4725
|
+
const repoName = typeof candidate.repoName === "string" ? candidate.repoName : void 0;
|
|
4726
|
+
if (repoName && repoCounts.get(repoName) === 1)
|
|
4727
|
+
return [`--repo ${repoName} --handler ${requested}`];
|
|
4728
|
+
if (matchKind === "method" && typeof candidate.className === "string")
|
|
4729
|
+
return [`${repoName ? `--repo ${repoName} ` : ""}--handler ${candidate.className}`];
|
|
4730
|
+
return [];
|
|
4731
|
+
});
|
|
4732
|
+
return {
|
|
4733
|
+
severity: "warning",
|
|
4734
|
+
code: "trace_start_ambiguous",
|
|
4735
|
+
message: "Handler selector matched multiple executable scopes and was not selected automatically",
|
|
4736
|
+
selectorKind: "handler",
|
|
4737
|
+
requestedHandler: requested,
|
|
4738
|
+
resolutionStage: "handler",
|
|
4739
|
+
resolutionStatus: "ambiguous_handler",
|
|
4740
|
+
candidates,
|
|
4741
|
+
selectorSuggestions: [...new Set(suggestions)].sort(),
|
|
4742
|
+
remediation: suggestions.length > 0 ? "Use one of the scoped handler selectors shown." : "No current CLI selector uniquely identifies these duplicate handler classes."
|
|
4743
|
+
};
|
|
4744
|
+
}
|
|
4745
|
+
function handlerSelectorCandidates(rows2, matchKind) {
|
|
4746
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
4747
|
+
for (const row of rows2) {
|
|
4748
|
+
const identity = matchKind === "class" ? `class:${String(row.handlerClassId)}` : `method:${String(row.repoId)}:${String(row.symbolId ?? row.methodId)}`;
|
|
4749
|
+
candidates.set(identity, {
|
|
4750
|
+
handlerClassId: row.handlerClassId,
|
|
4751
|
+
repoId: row.repoId,
|
|
4752
|
+
repoName: row.repoName,
|
|
4753
|
+
className: row.className,
|
|
4754
|
+
sourceFile: row.sourceFile,
|
|
4755
|
+
sourceLine: row.sourceLine,
|
|
4756
|
+
matchKind
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4759
|
+
return [...candidates.values()].sort((left, right) => String(left.repoName ?? "").localeCompare(String(right.repoName ?? "")) || String(left.className ?? "").localeCompare(String(right.className ?? "")) || String(left.sourceFile ?? "").localeCompare(String(right.sourceFile ?? "")));
|
|
4760
|
+
}
|
|
4761
|
+
function executableScope(rows2, fallbackRepoId) {
|
|
4762
|
+
const files = rows2.flatMap((row) => row.sourceFile ? [row.sourceFile] : []);
|
|
4763
|
+
const symbols = rows2.flatMap((row) => typeof row.symbolId === "number" ? [row.symbolId] : []);
|
|
4764
|
+
if (files.length === 0 || symbols.length === 0) return { repoId: fallbackRepoId };
|
|
4765
|
+
return {
|
|
4766
|
+
files: new Set(files),
|
|
4767
|
+
symbols: new Set(symbols),
|
|
4768
|
+
repoId: numericValue(rows2[0]?.repoId) ?? fallbackRepoId
|
|
4769
|
+
};
|
|
4770
|
+
}
|
|
4771
|
+
function handlerMethodsNotIndexedDiagnostic(row) {
|
|
4772
|
+
return {
|
|
4773
|
+
severity: "warning",
|
|
4774
|
+
code: "handler_methods_not_indexed",
|
|
4775
|
+
message: `Handler class ${row?.className ?? "unknown"} has no indexed executable methods`,
|
|
4776
|
+
selectorKind: "handler",
|
|
4777
|
+
className: row?.className,
|
|
4778
|
+
sourceFile: row?.sourceFile,
|
|
4779
|
+
sourceLine: row?.sourceLine,
|
|
4780
|
+
observedDecoratorNames: stringEvidenceArray(
|
|
4781
|
+
row?.classEvidence,
|
|
4782
|
+
"observedDecoratorNames"
|
|
4783
|
+
),
|
|
4784
|
+
unsupportedDecoratorNames: stringEvidenceArray(
|
|
4785
|
+
row?.classEvidence,
|
|
4786
|
+
"unsupportedDecoratorNames"
|
|
4787
|
+
),
|
|
4788
|
+
remediation: "Use a supported CAP handler decorator on at least one class method and re-index the workspace."
|
|
4789
|
+
};
|
|
4790
|
+
}
|
|
4791
|
+
function handlerDecoratorsNotIndexedDiagnostic(row) {
|
|
4792
|
+
const names = stringEvidenceArray(
|
|
4793
|
+
row?.classEvidence,
|
|
4794
|
+
"unsupportedDecoratorNames"
|
|
4795
|
+
);
|
|
4796
|
+
const methods = arrayEvidence(row?.classEvidence, "unsupportedMethods");
|
|
4797
|
+
if (names.length === 0 && methods.length === 0) return void 0;
|
|
4798
|
+
return {
|
|
4799
|
+
severity: "warning",
|
|
4800
|
+
code: "handler_decorators_not_indexed",
|
|
4801
|
+
message: `Handler class ${row?.className ?? "unknown"} contains methods that were not indexed`,
|
|
4802
|
+
selectorKind: "handler",
|
|
4803
|
+
className: row?.className,
|
|
4804
|
+
sourceFile: row?.sourceFile,
|
|
4805
|
+
sourceLine: row?.sourceLine,
|
|
4806
|
+
unsupportedDecoratorNames: names,
|
|
4807
|
+
unsupportedMethods: methods,
|
|
4808
|
+
remediation: "Use a supported CAP handler decorator shape and re-index the workspace."
|
|
4809
|
+
};
|
|
4810
|
+
}
|
|
4811
|
+
function evidenceRecord(evidenceJson) {
|
|
4812
|
+
if (!evidenceJson) return {};
|
|
4813
|
+
try {
|
|
4814
|
+
const parsed = JSON.parse(evidenceJson);
|
|
4815
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4816
|
+
} catch {
|
|
4817
|
+
return {};
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
function stringEvidenceArray(evidenceJson, key) {
|
|
4821
|
+
const value = evidenceRecord(evidenceJson)[key];
|
|
4822
|
+
return Array.isArray(value) ? [...new Set(value.filter((item) => typeof item === "string"))].sort() : [];
|
|
4823
|
+
}
|
|
4824
|
+
function arrayEvidence(evidenceJson, key) {
|
|
4825
|
+
const value = evidenceRecord(evidenceJson)[key];
|
|
4826
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
4827
|
+
}
|
|
4828
|
+
function numericValue(value) {
|
|
4829
|
+
return typeof value === "number" ? value : void 0;
|
|
4830
|
+
}
|
|
4831
|
+
function normalizeOperation(value) {
|
|
4832
|
+
if (!value) return void 0;
|
|
4833
|
+
return value.startsWith("/") ? value.slice(1) : value;
|
|
4834
|
+
}
|
|
4835
|
+
function ambiguousStartDiagnostic(requested, candidates, message) {
|
|
4836
|
+
const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
|
|
4837
|
+
return {
|
|
4838
|
+
severity: "warning",
|
|
4839
|
+
code: "trace_start_ambiguous",
|
|
4840
|
+
message,
|
|
4841
|
+
normalizedSelectorValue: requested,
|
|
4842
|
+
resolutionStage: "operation",
|
|
4843
|
+
resolutionStatus: "ambiguous_operation",
|
|
4844
|
+
candidates,
|
|
4845
|
+
serviceSuggestions,
|
|
4846
|
+
selectorSuggestions: fullSelectorSuggestions(candidates)
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
function fullSelectorSuggestions(candidates) {
|
|
4850
|
+
const includeRepo = new Set(candidates.map((row) => row.repoName)).size > 1;
|
|
4851
|
+
return [...new Set(candidates.flatMap((row) => {
|
|
4852
|
+
if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
|
|
4853
|
+
const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
|
|
4854
|
+
return [
|
|
4855
|
+
`${repoSelector}--service ${row.servicePath} --path ${row.operationPath}`
|
|
4856
|
+
];
|
|
4857
|
+
}))].sort();
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4860
|
+
// src/trace/001-dynamic-identity.ts
|
|
4861
|
+
function uniqueIdentityDerivations(db, candidates, templates) {
|
|
4862
|
+
const identities = workspaceIdentities(db, candidates);
|
|
4863
|
+
const proposals = candidates.flatMap((candidate) => {
|
|
4864
|
+
const owner = implementationOwner(db, candidate.candidateOperationId);
|
|
4865
|
+
const identity = identities.find((item) => item.repoId === candidate.repoId);
|
|
4866
|
+
return ownerAgrees(candidate, owner) && identity ? identityProposals(candidate, identity, templates) : [];
|
|
4867
|
+
});
|
|
4868
|
+
const matches2 = workspaceIdentityMatches(identities, templates);
|
|
4869
|
+
const competing = competingIdentityKeys(matches2);
|
|
4870
|
+
const duplicates = duplicateNormalizedIdentities(identities);
|
|
4871
|
+
return proposals.filter((proposal) => !competing.has(`${proposal.key}:${proposal.value}`)).filter((proposal) => !duplicates.has(proposal.normalizedIdentity)).map((proposal) => ({
|
|
4872
|
+
operationId: proposal.operationId,
|
|
4873
|
+
key: proposal.key,
|
|
4874
|
+
value: proposal.value,
|
|
4875
|
+
provenance: proposal.provenance
|
|
4876
|
+
}));
|
|
4877
|
+
}
|
|
4878
|
+
function identityProposals(candidate, identity, templates) {
|
|
4879
|
+
const routeTemplates = [templates.alias, templates.destination].filter((value) => Boolean(value));
|
|
4880
|
+
const identities = [
|
|
4881
|
+
{ name: identity.packageName, sourceKind: "package_identity", npmPackage: true },
|
|
4882
|
+
{ name: identity.repoName, sourceKind: "repository_identity", npmPackage: false }
|
|
4883
|
+
].filter((item) => Boolean(item.name));
|
|
4884
|
+
const proposals = routeTemplates.flatMap((template) => identities.flatMap((identity2) => proposalForIdentity(
|
|
4885
|
+
candidate,
|
|
4886
|
+
template,
|
|
4887
|
+
identity2.name,
|
|
4888
|
+
identity2.sourceKind,
|
|
4889
|
+
identity2.npmPackage
|
|
4890
|
+
)));
|
|
4891
|
+
return deduplicateProposals(proposals);
|
|
4892
|
+
}
|
|
4893
|
+
function proposalForIdentity(candidate, template, identity, sourceKind, npmPackage) {
|
|
4894
|
+
const match = matchIdentityTemplate(template, identity, npmPackage);
|
|
4895
|
+
if (!match) return [];
|
|
4896
|
+
return [{
|
|
4897
|
+
operationId: candidate.candidateOperationId,
|
|
4898
|
+
key: match.key,
|
|
4899
|
+
value: match.value,
|
|
4900
|
+
normalizedIdentity: match.normalizedIdentity,
|
|
4901
|
+
provenance: {
|
|
4902
|
+
sourceKind,
|
|
4903
|
+
value: match.value,
|
|
4904
|
+
rule: "exact_normalized_identity_template_match",
|
|
4905
|
+
template,
|
|
4906
|
+
matchedName: identity,
|
|
4907
|
+
normalizedForm: match.normalizedIdentity,
|
|
4908
|
+
sourceRepo: candidate.repoName
|
|
4909
|
+
}
|
|
4910
|
+
}];
|
|
4911
|
+
}
|
|
4912
|
+
function matchIdentityTemplate(template, identity, npmPackage) {
|
|
4913
|
+
const matches2 = [...template.matchAll(/\$\{([^}]*)\}/g)];
|
|
4914
|
+
if (matches2.length !== 1 || !matches2[0]?.[1]) return void 0;
|
|
4915
|
+
const placeholder = matches2[0][0];
|
|
4916
|
+
const sentinel = "dynamicplaceholdertoken";
|
|
4917
|
+
const normalizedTemplate = normalizeIdentity(template.replace(placeholder, sentinel));
|
|
4918
|
+
const [prefix, suffix, extra] = normalizedTemplate.split(sentinel);
|
|
4919
|
+
if (!prefix || !suffix || extra !== void 0) return void 0;
|
|
4920
|
+
const normalizedIdentity = normalizeIdentity(identity, npmPackage);
|
|
4921
|
+
const match = new RegExp(`^${escapeRegex(prefix)}([a-z0-9]+)${escapeRegex(suffix)}$`).exec(normalizedIdentity);
|
|
4922
|
+
if (!match?.[1]) return void 0;
|
|
4923
|
+
return { key: matches2[0][1].trim(), value: match[1], normalizedIdentity };
|
|
4924
|
+
}
|
|
4925
|
+
function competingIdentityKeys(proposals) {
|
|
4926
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4927
|
+
for (const proposal of proposals) {
|
|
4928
|
+
const key = `${proposal.key}:${proposal.value}`;
|
|
4929
|
+
owners.set(key, /* @__PURE__ */ new Set([...owners.get(key) ?? [], proposal.ownerKey]));
|
|
4930
|
+
}
|
|
4931
|
+
return new Set([...owners.entries()].filter(([, repos]) => repos.size > 1).map(([key]) => key));
|
|
4932
|
+
}
|
|
4933
|
+
function duplicateNormalizedIdentities(identities) {
|
|
4934
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4935
|
+
for (const identity of identities) {
|
|
4936
|
+
for (const [name, npmPackage] of [
|
|
4937
|
+
[identity.packageName, true],
|
|
4938
|
+
[identity.repoName, false]
|
|
4939
|
+
]) {
|
|
4940
|
+
if (!name) continue;
|
|
4941
|
+
const normalized = normalizeIdentity(name, npmPackage);
|
|
4942
|
+
owners.set(normalized, /* @__PURE__ */ new Set([
|
|
4943
|
+
...owners.get(normalized) ?? [],
|
|
4944
|
+
`repository:${identity.repoId}`
|
|
4945
|
+
]));
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
return new Set([...owners.entries()].filter(([, repos]) => repos.size > 1).map(([identity]) => identity));
|
|
4949
|
+
}
|
|
4950
|
+
function workspaceIdentityMatches(identities, templates) {
|
|
4951
|
+
const routeTemplates = [templates.alias, templates.destination].filter((value) => Boolean(value));
|
|
4952
|
+
return identities.flatMap((identity) => {
|
|
4953
|
+
const names = [
|
|
4954
|
+
{ name: identity.packageName, npmPackage: true },
|
|
4955
|
+
{ name: identity.repoName, npmPackage: false }
|
|
4956
|
+
];
|
|
4957
|
+
return routeTemplates.flatMap((template) => names.flatMap(({ name, npmPackage }) => {
|
|
4958
|
+
if (!name) return [];
|
|
4959
|
+
const match = matchIdentityTemplate(template, name, npmPackage);
|
|
4960
|
+
return match ? [{
|
|
4961
|
+
ownerKey: `repository:${identity.repoId}`,
|
|
4962
|
+
key: match.key,
|
|
4963
|
+
value: match.value,
|
|
4964
|
+
normalizedIdentity: match.normalizedIdentity
|
|
4965
|
+
}] : [];
|
|
4966
|
+
}));
|
|
4967
|
+
});
|
|
4968
|
+
}
|
|
4969
|
+
function workspaceIdentities(db, candidates) {
|
|
4970
|
+
const repoIds = [...new Set(candidates.flatMap((candidate) => candidate.repoId === void 0 ? [] : [candidate.repoId]))].sort((a, b) => a - b);
|
|
4971
|
+
if (repoIds.length === 0) return [];
|
|
4972
|
+
const placeholders3 = repoIds.map(() => "?").join(",");
|
|
4973
|
+
const rows2 = db.prepare(`SELECT id repoId,name repoName,package_name packageName
|
|
4974
|
+
FROM repositories WHERE workspace_id IN (
|
|
4975
|
+
SELECT DISTINCT workspace_id FROM repositories WHERE id IN (${placeholders3})
|
|
4976
|
+
) ORDER BY workspace_id,name,absolute_path,id`).all(...repoIds);
|
|
4977
|
+
return rows2.flatMap((row) => {
|
|
4978
|
+
const repoId = numberValue(row.repoId);
|
|
4979
|
+
const repoName = stringValue4(row.repoName);
|
|
4980
|
+
return repoId === void 0 || !repoName ? [] : [{
|
|
4981
|
+
repoId,
|
|
4982
|
+
repoName,
|
|
4983
|
+
packageName: stringValue4(row.packageName)
|
|
4984
|
+
}];
|
|
4985
|
+
});
|
|
4986
|
+
}
|
|
4987
|
+
function deduplicateProposals(rows2) {
|
|
4988
|
+
const sorted = [...rows2].sort((left, right) => left.operationId - right.operationId || left.key.localeCompare(right.key) || left.value.localeCompare(right.value) || left.provenance.sourceKind.localeCompare(right.provenance.sourceKind));
|
|
4989
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4990
|
+
return sorted.filter((row) => {
|
|
4991
|
+
const key = [row.operationId, row.key, row.value, row.normalizedIdentity].join(":");
|
|
4992
|
+
if (seen.has(key)) return false;
|
|
4993
|
+
seen.add(key);
|
|
4994
|
+
return true;
|
|
4995
|
+
});
|
|
4996
|
+
}
|
|
4997
|
+
function ownerAgrees(candidate, owner) {
|
|
4998
|
+
return candidate.repoId !== void 0 && owner?.repoId !== void 0 && owner.repoId === candidate.repoId;
|
|
4999
|
+
}
|
|
5000
|
+
function implementationOwner(db, operationId) {
|
|
5001
|
+
const rows2 = db.prepare(
|
|
5002
|
+
`SELECT r.id repoId
|
|
5003
|
+
FROM graph_edges e JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
5004
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
5005
|
+
JOIN repositories r ON r.id=hc.repo_id
|
|
5006
|
+
WHERE e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved'
|
|
5007
|
+
AND e.from_kind='operation' AND e.from_id=?
|
|
5008
|
+
ORDER BY r.id,hm.id,e.id`
|
|
5009
|
+
).all(String(operationId));
|
|
5010
|
+
if (rows2.length !== 1) return void 0;
|
|
5011
|
+
const row = rows2[0];
|
|
5012
|
+
if (!row) return void 0;
|
|
5013
|
+
return {
|
|
5014
|
+
repoId: numberValue(row.repoId)
|
|
5015
|
+
};
|
|
5016
|
+
}
|
|
5017
|
+
function normalizeIdentity(value, npmPackage = false) {
|
|
5018
|
+
const unscoped = npmPackage && /^@[^/]+\/[^/]+$/.test(value) ? value.slice(value.indexOf("/") + 1) : value;
|
|
5019
|
+
return unscoped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
5020
|
+
}
|
|
5021
|
+
function stringValue4(value) {
|
|
5022
|
+
return typeof value === "string" ? value : void 0;
|
|
5023
|
+
}
|
|
5024
|
+
function numberValue(value) {
|
|
5025
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5026
|
+
}
|
|
5027
|
+
function escapeRegex(value) {
|
|
5028
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
// src/trace/003-dynamic-references.ts
|
|
5032
|
+
function dynamicReferenceRows(db, workspaceId, callerRepoId, callerRepo) {
|
|
5033
|
+
if (callerRepoId === void 0 && callerRepo === void 0) return [];
|
|
5034
|
+
const rows2 = db.prepare(
|
|
5035
|
+
`SELECT COALESCE(b.alias,b.alias_expr) alias,b.destination_expr destination,
|
|
5036
|
+
b.service_path_expr servicePath,'service_binding' sourceKind,r.name repoName,
|
|
5037
|
+
b.source_file sourceFile,b.source_line sourceLine,0 sourcePriority
|
|
5038
|
+
FROM service_bindings b JOIN repositories r ON r.id=b.repo_id
|
|
5039
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5040
|
+
AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
|
|
5041
|
+
UNION ALL
|
|
5042
|
+
SELECT req.alias,req.destination,req.service_path,'cds_require',r.name,
|
|
5043
|
+
'package.json',1,1 FROM cds_requires req JOIN repositories r ON r.id=req.repo_id
|
|
5044
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5045
|
+
AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
|
|
5046
|
+
ORDER BY sourcePriority,repoName,sourceFile,sourceLine`
|
|
5047
|
+
).all(
|
|
5048
|
+
workspaceId,
|
|
5049
|
+
workspaceId,
|
|
5050
|
+
callerRepoId,
|
|
5051
|
+
callerRepoId,
|
|
5052
|
+
callerRepoId,
|
|
5053
|
+
callerRepo,
|
|
5054
|
+
workspaceId,
|
|
5055
|
+
workspaceId,
|
|
5056
|
+
callerRepoId,
|
|
5057
|
+
callerRepoId,
|
|
5058
|
+
callerRepoId,
|
|
5059
|
+
callerRepo
|
|
5060
|
+
);
|
|
5061
|
+
return rows2.flatMap(referenceFromRow);
|
|
5062
|
+
}
|
|
5063
|
+
function dynamicReferenceProvenance(reference, kind, template, value) {
|
|
5064
|
+
return {
|
|
5065
|
+
sourceKind: `${reference.sourceKind}.${kind}`,
|
|
5066
|
+
value,
|
|
5067
|
+
rule: "exact_indexed_reference_template_match",
|
|
5068
|
+
template,
|
|
5069
|
+
sourceRepo: reference.repoName,
|
|
5070
|
+
sourceFile: reference.sourceFile,
|
|
5071
|
+
sourceLine: reference.sourceLine
|
|
5072
|
+
};
|
|
5073
|
+
}
|
|
5074
|
+
function referenceFromRow(row) {
|
|
5075
|
+
const sourceKind = row.sourceKind;
|
|
5076
|
+
const repoName = stringValue5(row.repoName);
|
|
5077
|
+
if (sourceKind !== "service_binding" && sourceKind !== "cds_require" || !repoName) return [];
|
|
5078
|
+
return [{
|
|
5079
|
+
alias: stringValue5(row.alias),
|
|
5080
|
+
destination: stringValue5(row.destination),
|
|
5081
|
+
servicePath: stringValue5(row.servicePath),
|
|
5082
|
+
sourceKind,
|
|
5083
|
+
repoName,
|
|
5084
|
+
sourceFile: stringValue5(row.sourceFile),
|
|
5085
|
+
sourceLine: numberValue2(row.sourceLine)
|
|
5086
|
+
}];
|
|
5087
|
+
}
|
|
5088
|
+
function stringValue5(value) {
|
|
5089
|
+
return typeof value === "string" ? value : void 0;
|
|
5090
|
+
}
|
|
5091
|
+
function numberValue2(value) {
|
|
5092
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5093
|
+
}
|
|
5094
|
+
|
|
5095
|
+
// src/trace/dynamic-targets.ts
|
|
5096
|
+
function analyzeDynamicTargetCandidates(db, evidence, workspaceId, mode, maxCandidates) {
|
|
5097
|
+
const inputs = analysisInputs(evidence);
|
|
5098
|
+
if (inputs.required.length === 0) return void 0;
|
|
5099
|
+
const targets = candidateTargets(db, evidence, workspaceId);
|
|
5100
|
+
const references = dynamicReferenceRows(
|
|
5101
|
+
db,
|
|
5102
|
+
workspaceId,
|
|
5103
|
+
inputs.callerRepoId,
|
|
5104
|
+
inputs.callerRepo
|
|
5105
|
+
);
|
|
5106
|
+
const candidates = buildCandidates(db, targets, references, inputs);
|
|
5107
|
+
applyUniqueIdentityEvidence(db, candidates, inputs);
|
|
5108
|
+
finalizeCandidates(candidates, inputs.order);
|
|
5109
|
+
const ranked = stableRank(candidates);
|
|
5110
|
+
const inference = inferenceDecision(ranked);
|
|
5111
|
+
applyModeState(ranked, mode, inference);
|
|
5112
|
+
const viable = ranked.filter((candidate) => candidate.viable);
|
|
5113
|
+
const rejected = ranked.filter((candidate) => candidate.rejected);
|
|
5114
|
+
const shown = viable.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
|
|
5115
|
+
const shownRejected = rejected.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
|
|
5116
|
+
return {
|
|
5117
|
+
mode,
|
|
5118
|
+
maxCandidates,
|
|
5119
|
+
candidateCount: ranked.length,
|
|
5120
|
+
viableCandidateCount: viable.length,
|
|
5121
|
+
rejectedCandidateCount: rejected.length,
|
|
5122
|
+
shownCandidateCount: shown.length,
|
|
5123
|
+
omittedCandidateCount: Math.max(0, viable.length - shown.length),
|
|
5124
|
+
shownRejectedCandidateCount: shownRejected.length,
|
|
5125
|
+
omittedRejectedCandidateCount: Math.max(0, rejected.length - shownRejected.length),
|
|
5126
|
+
missingVariables: inputs.required.filter((key) => inputs.supplied[key] === void 0),
|
|
5127
|
+
requiredVariables: inputs.required,
|
|
5128
|
+
suppliedVariables: inputs.supplied,
|
|
5129
|
+
appliedSuppliedVariables: requiredSuppliedVariables(inputs),
|
|
5130
|
+
substitutedSignals: inputs.effective,
|
|
5131
|
+
candidates: shown,
|
|
5132
|
+
shownCandidates: shown,
|
|
5133
|
+
rejectedCandidates: shownRejected,
|
|
5134
|
+
suggestedVarSets: suggestedVarSets(viable, inputs.order, maxCandidates),
|
|
5135
|
+
inference
|
|
5136
|
+
};
|
|
5137
|
+
}
|
|
5138
|
+
function analysisInputs(evidence) {
|
|
5139
|
+
const original = templatesFromEvidence(evidence, "original");
|
|
5140
|
+
const effective = templatesFromEvidence(evidence, "effective");
|
|
5141
|
+
const requiredSources = placeholderSources(original);
|
|
5142
|
+
const required = Object.keys(requiredSources);
|
|
5143
|
+
const supplied = stringRecord(evidence.suppliedRuntimeVariables);
|
|
5144
|
+
return {
|
|
5145
|
+
original,
|
|
5146
|
+
effective,
|
|
5147
|
+
required,
|
|
5148
|
+
requiredSources,
|
|
5149
|
+
supplied,
|
|
5150
|
+
order: variableOrder(original, required),
|
|
5151
|
+
callerRepo: stringValue6(evidence.repo),
|
|
5152
|
+
callerRepoId: numberValue3(evidence.repoId)
|
|
5153
|
+
};
|
|
5154
|
+
}
|
|
5155
|
+
function candidateTargets(db, evidence, workspaceId) {
|
|
5156
|
+
const embedded = rowsFromEvidence(evidence.candidates);
|
|
5157
|
+
if (embedded.length > 0) return embedded;
|
|
5158
|
+
const operationPath = effectiveSignal(evidence, "operationPath");
|
|
5159
|
+
if (!operationPath || extractPlaceholders(operationPath).length > 0) return [];
|
|
5160
|
+
return queryOperationTargets(db, operationPath, workspaceId);
|
|
5161
|
+
}
|
|
5162
|
+
function rowsFromEvidence(value) {
|
|
5163
|
+
if (!Array.isArray(value)) return [];
|
|
5164
|
+
return value.flatMap((item) => {
|
|
5165
|
+
const row = record(item);
|
|
5166
|
+
const operationId = numberValue3(row.operationId);
|
|
5167
|
+
const repoName = stringValue6(row.repoName);
|
|
5168
|
+
const servicePath = stringValue6(row.servicePath);
|
|
5169
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5170
|
+
const operationName = stringValue6(row.operationName) ?? operationPath?.replace(/^\//, "");
|
|
5171
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
|
|
5172
|
+
return [{
|
|
5173
|
+
operationId,
|
|
5174
|
+
repoId: numberValue3(row.repoId),
|
|
5175
|
+
repoName,
|
|
5176
|
+
packageName: stringValue6(row.packageName),
|
|
5177
|
+
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5178
|
+
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5179
|
+
servicePath,
|
|
5180
|
+
operationPath,
|
|
5181
|
+
operationName,
|
|
5182
|
+
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5183
|
+
sourceLine: numberValue3(row.sourceLine) ?? 0,
|
|
5184
|
+
score: numberValue3(row.score) ?? 0,
|
|
5185
|
+
reasons: stringArray(row.reasons)
|
|
5186
|
+
}];
|
|
5187
|
+
});
|
|
3720
5188
|
}
|
|
3721
5189
|
function queryOperationTargets(db, operationPath, workspaceId) {
|
|
3722
5190
|
const simple = operationPath.replace(/^\//, "").split(".").at(-1) ?? operationPath;
|
|
@@ -3724,169 +5192,291 @@ function queryOperationTargets(db, operationPath, workspaceId) {
|
|
|
3724
5192
|
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
|
|
3725
5193
|
s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
|
|
3726
5194
|
o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
|
|
3727
|
-
o.source_line sourceLine
|
|
3728
|
-
|
|
3729
|
-
JOIN repositories r ON r.id=s.repo_id
|
|
5195
|
+
o.source_line sourceLine FROM cds_operations o
|
|
5196
|
+
JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
3730
5197
|
WHERE (? IS NULL OR r.workspace_id=?)
|
|
3731
5198
|
AND (o.operation_path IN (?,?) OR o.operation_name=?)
|
|
3732
|
-
ORDER BY r.name,s.service_path,o.operation_name`
|
|
5199
|
+
ORDER BY r.name,s.service_path,o.operation_name,o.id`
|
|
3733
5200
|
).all(workspaceId, workspaceId, operationPath, `/${simple}`, simple);
|
|
3734
|
-
return rows2.
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
5201
|
+
return rows2.flatMap(operationTargetFromRow);
|
|
5202
|
+
}
|
|
5203
|
+
function operationTargetFromRow(row) {
|
|
5204
|
+
const operationId = numberValue3(row.operationId);
|
|
5205
|
+
const repoName = stringValue6(row.repoName);
|
|
5206
|
+
const servicePath = stringValue6(row.servicePath);
|
|
5207
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5208
|
+
const operationName = stringValue6(row.operationName);
|
|
5209
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
|
|
5210
|
+
return [{
|
|
5211
|
+
operationId,
|
|
5212
|
+
repoId: numberValue3(row.repoId),
|
|
5213
|
+
repoName,
|
|
5214
|
+
packageName: stringValue6(row.packageName),
|
|
5215
|
+
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5216
|
+
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5217
|
+
servicePath,
|
|
5218
|
+
operationPath,
|
|
5219
|
+
operationName,
|
|
5220
|
+
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5221
|
+
sourceLine: numberValue3(row.sourceLine) ?? 0,
|
|
3746
5222
|
score: 0.2,
|
|
3747
5223
|
reasons: ["operation_path_match"]
|
|
3748
|
-
}
|
|
5224
|
+
}];
|
|
5225
|
+
}
|
|
5226
|
+
function buildCandidates(db, targets, references, inputs) {
|
|
5227
|
+
return targets.map((target) => {
|
|
5228
|
+
const state = emptyCandidate(target, inputs);
|
|
5229
|
+
applyDirectSignal(state, inputs, "operationPath", target.operationPath, 0.25);
|
|
5230
|
+
applyDirectSignal(state, inputs, "servicePath", target.servicePath, 0.35);
|
|
5231
|
+
const matchingReferences = references.filter((reference) => reference.servicePath === target.servicePath);
|
|
5232
|
+
applyReferenceSignal(state, inputs, matchingReferences, "alias");
|
|
5233
|
+
applyReferenceSignal(state, inputs, matchingReferences, "destination");
|
|
5234
|
+
if (hasResolvedImplementation(db, target.operationId))
|
|
5235
|
+
addScore(state, 0.1, "implementation_edge_resolved");
|
|
5236
|
+
return state;
|
|
5237
|
+
});
|
|
3749
5238
|
}
|
|
3750
|
-
function
|
|
3751
|
-
const ranked = candidates.map((candidate) => candidateEvidence2(db, candidate, references, templates, order));
|
|
3752
|
-
return ranked.sort((a, b) => b.score - a.score || a.repoName.localeCompare(b.repoName) || a.servicePath.localeCompare(b.servicePath));
|
|
3753
|
-
}
|
|
3754
|
-
function candidateEvidence2(db, candidate, references, templates, order) {
|
|
3755
|
-
const state = emptyCandidate(candidate);
|
|
3756
|
-
applyDirectTemplate(state, templates.operationPath, candidate.operationPath, "operation_path");
|
|
3757
|
-
applyDirectTemplate(state, templates.servicePath, candidate.servicePath, "service_path");
|
|
3758
|
-
const refs = references.filter((item) => item.servicePath === candidate.servicePath);
|
|
3759
|
-
applyReferenceTemplate(state, templates.alias, refs, "alias");
|
|
3760
|
-
applyReferenceTemplate(state, templates.destination, refs, "destination");
|
|
3761
|
-
if (hasResolvedImplementation(db, candidate.operationId)) addScore(state, 0.1, "implementation_edge_resolved");
|
|
3762
|
-
state.missingVariables = order.filter((key) => state.derivedVariables[key] === void 0);
|
|
3763
|
-
if (state.missingVariables.length === 0) addScore(state, 0.15, "all_runtime_variables_derived");
|
|
3764
|
-
else state.rejectedReasons.push("missing_required_runtime_variable");
|
|
3765
|
-
state.score = Math.max(0, Math.min(1, state.score));
|
|
3766
|
-
state.cli = state.missingVariables.length === 0 ? cliFor(state.derivedVariables, order) : void 0;
|
|
3767
|
-
return state;
|
|
3768
|
-
}
|
|
3769
|
-
function emptyCandidate(candidate) {
|
|
5239
|
+
function emptyCandidate(target, inputs) {
|
|
3770
5240
|
return {
|
|
3771
|
-
candidateOperationId:
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
5241
|
+
candidateOperationId: target.operationId,
|
|
5242
|
+
repoId: target.repoId,
|
|
5243
|
+
repoName: target.repoName,
|
|
5244
|
+
packageName: target.packageName ?? void 0,
|
|
5245
|
+
serviceName: target.serviceName,
|
|
5246
|
+
qualifiedName: target.qualifiedName,
|
|
5247
|
+
servicePath: target.servicePath,
|
|
5248
|
+
operationPath: target.operationPath,
|
|
5249
|
+
operationName: target.operationName,
|
|
5250
|
+
sourceFile: target.sourceFile,
|
|
5251
|
+
sourceLine: target.sourceLine,
|
|
5252
|
+
originalTemplates: inputs.original,
|
|
5253
|
+
effectiveValues: inputs.effective,
|
|
5254
|
+
requiredVariables: inputs.required,
|
|
5255
|
+
requiredVariableSources: inputs.requiredSources,
|
|
5256
|
+
suppliedVariables: inputs.supplied,
|
|
5257
|
+
completeVariables: { ...inputs.supplied },
|
|
3777
5258
|
derivedVariables: {},
|
|
3778
5259
|
derivedVariableSources: {},
|
|
5260
|
+
derivationProvenance: {},
|
|
3779
5261
|
missingVariables: [],
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
5262
|
+
conflicts: [],
|
|
5263
|
+
score: Math.max(0.2, Number(target.score ?? 0)),
|
|
5264
|
+
explicitSignalStrength: 0,
|
|
5265
|
+
reasons: nonEmptyStrings(target.reasons, ["operation_path_match"]),
|
|
5266
|
+
rejectedReasons: [],
|
|
5267
|
+
inferenceBlockReasons: [],
|
|
5268
|
+
viable: true,
|
|
5269
|
+
rejected: false,
|
|
5270
|
+
selected: false,
|
|
5271
|
+
exploratory: false
|
|
3783
5272
|
};
|
|
3784
5273
|
}
|
|
3785
|
-
function
|
|
3786
|
-
const
|
|
3787
|
-
|
|
3788
|
-
if (!
|
|
3789
|
-
state
|
|
5274
|
+
function applyDirectSignal(state, inputs, kind, concrete, score) {
|
|
5275
|
+
const effective = inputs.effective[kind];
|
|
5276
|
+
const original = inputs.original[kind];
|
|
5277
|
+
if (effective && !matchTemplate(effective, concrete)) {
|
|
5278
|
+
reject(state, `${signalCode(kind)}_contradicts_runtime_substitution`);
|
|
3790
5279
|
return;
|
|
3791
5280
|
}
|
|
3792
|
-
|
|
3793
|
-
|
|
5281
|
+
if (!effective) return;
|
|
5282
|
+
const suppliedKeys = extractPlaceholders(original).filter((key) => inputs.supplied[key] !== void 0);
|
|
5283
|
+
state.explicitSignalStrength += suppliedKeys.length;
|
|
5284
|
+
const matched = matchTemplate(original, concrete) ?? {};
|
|
5285
|
+
for (const [key, value] of Object.entries(matched)) {
|
|
5286
|
+
addDerivation(state, key, value, {
|
|
5287
|
+
sourceKind: `${signalCode(kind)}_template`,
|
|
5288
|
+
value,
|
|
5289
|
+
rule: "exact_template_match",
|
|
5290
|
+
template: original
|
|
5291
|
+
});
|
|
5292
|
+
}
|
|
5293
|
+
addScore(state, score, `${signalCode(kind)}_template_match`);
|
|
3794
5294
|
}
|
|
3795
|
-
function
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
5295
|
+
function applyReferenceSignal(state, inputs, references, kind) {
|
|
5296
|
+
const original = inputs.original[kind];
|
|
5297
|
+
const effective = inputs.effective[kind];
|
|
5298
|
+
if (!original || extractPlaceholders(original).length === 0) return;
|
|
5299
|
+
const values = references.flatMap((reference) => {
|
|
5300
|
+
const concrete = kind === "alias" ? reference.alias : reference.destination;
|
|
5301
|
+
return isConcrete(concrete) ? [{ reference, concrete }] : [];
|
|
5302
|
+
});
|
|
5303
|
+
if (effective && extractPlaceholders(effective).length === 0 && values.length > 0 && !values.some(({ concrete }) => concrete === effective)) {
|
|
5304
|
+
reject(state, `${kind}_contradicts_runtime_substitution`);
|
|
5305
|
+
}
|
|
5306
|
+
let matchedSignal = false;
|
|
5307
|
+
for (const { reference, concrete } of values) {
|
|
5308
|
+
const matched = matchTemplate(original, concrete);
|
|
3800
5309
|
if (!matched) continue;
|
|
3801
|
-
|
|
5310
|
+
matchedSignal = true;
|
|
5311
|
+
for (const [key, value] of Object.entries(matched)) {
|
|
5312
|
+
addDerivation(
|
|
5313
|
+
state,
|
|
5314
|
+
key,
|
|
5315
|
+
value,
|
|
5316
|
+
dynamicReferenceProvenance(reference, kind, original, value)
|
|
5317
|
+
);
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
if (matchedSignal) {
|
|
5321
|
+
state.explicitSignalStrength += extractPlaceholders(original).filter((key) => inputs.supplied[key] !== void 0).length;
|
|
3802
5322
|
addScore(state, 0.2, `${kind}_template_match`);
|
|
5323
|
+
}
|
|
5324
|
+
}
|
|
5325
|
+
function applyUniqueIdentityEvidence(db, candidates, inputs) {
|
|
5326
|
+
for (const derivation of uniqueIdentityDerivations(db, candidates, inputs.original)) {
|
|
5327
|
+
const candidate = candidates.find((item) => item.candidateOperationId === derivation.operationId);
|
|
5328
|
+
if (!candidate) continue;
|
|
5329
|
+
addDerivation(candidate, derivation.key, derivation.value, derivation.provenance);
|
|
5330
|
+
addScore(candidate, 0.2, "exact_identity_template_match");
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
function addDerivation(state, key, value, provenance) {
|
|
5334
|
+
const priorProvenance = state.derivationProvenance[key] ?? [];
|
|
5335
|
+
state.derivationProvenance[key] = uniqueProvenance([...priorProvenance, provenance]);
|
|
5336
|
+
const supplied = state.suppliedVariables[key];
|
|
5337
|
+
if (supplied !== void 0 && supplied !== value) {
|
|
5338
|
+
addConflict(state, key, [supplied, value], "explicit_value_conflicts_with_derived_value");
|
|
5339
|
+
return;
|
|
5340
|
+
}
|
|
5341
|
+
const prior = state.derivedVariables[key];
|
|
5342
|
+
if (prior !== void 0 && prior !== value) {
|
|
5343
|
+
addConflict(state, key, [prior, value], "conflicting_strong_derivations");
|
|
3803
5344
|
return;
|
|
3804
5345
|
}
|
|
3805
|
-
if (
|
|
3806
|
-
|
|
5346
|
+
if (supplied === void 0) state.derivedVariables[key] = value;
|
|
5347
|
+
state.completeVariables[key] = supplied ?? value;
|
|
5348
|
+
state.derivedVariableSources[key] ??= provenance;
|
|
5349
|
+
}
|
|
5350
|
+
function addConflict(state, key, values, reason) {
|
|
5351
|
+
const sources = (state.derivationProvenance[key] ?? []).map((item) => item.sourceKind).sort();
|
|
5352
|
+
state.conflicts.push({ key, values: [...new Set(values)].sort(), reason, sources });
|
|
5353
|
+
reject(state, reason);
|
|
5354
|
+
}
|
|
5355
|
+
function uniqueProvenance(rows2) {
|
|
5356
|
+
const sorted = [...rows2].sort((left, right) => left.sourceKind.localeCompare(right.sourceKind) || String(left.matchedName ?? "").localeCompare(String(right.matchedName ?? "")) || left.value.localeCompare(right.value));
|
|
5357
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5358
|
+
return sorted.filter((row) => {
|
|
5359
|
+
const key = JSON.stringify(row);
|
|
5360
|
+
if (seen.has(key)) return false;
|
|
5361
|
+
seen.add(key);
|
|
5362
|
+
return true;
|
|
5363
|
+
});
|
|
5364
|
+
}
|
|
5365
|
+
function finalizeCandidates(candidates, order) {
|
|
5366
|
+
for (const candidate of candidates) {
|
|
5367
|
+
candidate.missingVariables = order.filter((key) => candidate.completeVariables[key] === void 0);
|
|
5368
|
+
candidate.viable = candidate.rejectedReasons.length === 0;
|
|
5369
|
+
candidate.rejected = !candidate.viable;
|
|
5370
|
+
if (candidate.missingVariables.length === 0 && candidate.viable)
|
|
5371
|
+
addScore(candidate, 0.15, "all_runtime_variables_derived");
|
|
5372
|
+
else if (candidate.missingVariables.length > 0)
|
|
5373
|
+
addReason(candidate, "missing_required_runtime_variable");
|
|
5374
|
+
candidate.score = Math.max(0, Math.min(1, candidate.score));
|
|
5375
|
+
candidate.cli = candidate.missingVariables.length === 0 && candidate.viable ? cliFor(candidate.completeVariables, order) : void 0;
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
function stableRank(candidates) {
|
|
5379
|
+
return [...candidates].sort((left, right) => Number(right.viable) - Number(left.viable) || right.score - left.score || right.explicitSignalStrength - left.explicitSignalStrength || left.repoName.localeCompare(right.repoName) || String(left.packageName ?? "").localeCompare(String(right.packageName ?? "")) || left.servicePath.localeCompare(right.servicePath) || left.operationPath.localeCompare(right.operationPath) || left.operationName.localeCompare(right.operationName) || left.candidateOperationId - right.candidateOperationId);
|
|
3807
5380
|
}
|
|
3808
5381
|
function inferenceDecision(candidates) {
|
|
3809
|
-
const
|
|
3810
|
-
const first =
|
|
3811
|
-
const second =
|
|
3812
|
-
if (!first
|
|
3813
|
-
|
|
3814
|
-
if (
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
5382
|
+
const viable = candidates.filter((candidate) => candidate.viable);
|
|
5383
|
+
const first = viable[0];
|
|
5384
|
+
const second = viable[1];
|
|
5385
|
+
if (!first || first.missingVariables.length > 0)
|
|
5386
|
+
return { status: "unresolved", reason: "missing_required_runtime_variable" };
|
|
5387
|
+
if (first.score < 0.85)
|
|
5388
|
+
return { status: "unresolved", reason: "candidate_score_below_inference_threshold" };
|
|
5389
|
+
const scoreGap = second ? Number((first.score - second.score).toFixed(12)) : void 0;
|
|
5390
|
+
if (second && scoreGap !== void 0 && scoreGap <= 0.05) {
|
|
5391
|
+
const reason = scoreGap === 0 ? "candidate_tied_with_equal_score" : "candidate_within_inference_margin";
|
|
5392
|
+
for (const candidate of viable.filter((item) => first.score - item.score <= 0.05))
|
|
5393
|
+
addInferenceBlock(candidate, reason);
|
|
5394
|
+
return { status: "ambiguous", reason, scoreGap, requiredMargin: 0.05 };
|
|
3818
5395
|
}
|
|
3819
5396
|
return {
|
|
3820
5397
|
status: "resolved",
|
|
3821
5398
|
candidateOperationId: first.candidateOperationId,
|
|
3822
|
-
inferredVariables: first.
|
|
5399
|
+
inferredVariables: first.completeVariables,
|
|
3823
5400
|
score: first.score,
|
|
3824
5401
|
reasons: first.reasons
|
|
3825
5402
|
};
|
|
3826
5403
|
}
|
|
3827
|
-
function
|
|
5404
|
+
function boundedCandidate(candidate, limit) {
|
|
5405
|
+
const derivationProvenance = Object.fromEntries(
|
|
5406
|
+
Object.entries(candidate.derivationProvenance).sort(([left], [right]) => left.localeCompare(right)).map(([key, rows2]) => [key, rows2.slice(0, limit)])
|
|
5407
|
+
);
|
|
5408
|
+
const conflicts = candidate.conflicts.slice(0, limit).map((conflict) => ({
|
|
5409
|
+
...conflict,
|
|
5410
|
+
sources: conflict.sources.slice(0, limit)
|
|
5411
|
+
}));
|
|
5412
|
+
return { ...candidate, derivationProvenance, conflicts };
|
|
5413
|
+
}
|
|
5414
|
+
function applyModeState(candidates, mode, inference) {
|
|
5415
|
+
const selectedId = mode === "infer" && inference.status === "resolved" ? numberValue3(inference.candidateOperationId) : void 0;
|
|
5416
|
+
for (const candidate of candidates) {
|
|
5417
|
+
candidate.selected = selectedId === candidate.candidateOperationId;
|
|
5418
|
+
candidate.exploratory = mode === "candidates" && candidate.viable;
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
function suggestedVarSets(candidates, order, limit) {
|
|
3828
5422
|
const seen = /* @__PURE__ */ new Set();
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
if (seen.has(cli))
|
|
3833
|
-
seen.add(cli);
|
|
3834
|
-
|
|
3835
|
-
|
|
5423
|
+
const rows2 = [];
|
|
5424
|
+
for (const candidate of candidates) {
|
|
5425
|
+
if (!candidate.cli || candidate.missingVariables.length > 0) continue;
|
|
5426
|
+
if (seen.has(candidate.cli)) continue;
|
|
5427
|
+
seen.add(candidate.cli);
|
|
5428
|
+
rows2.push({ variables: orderedVariables(candidate.completeVariables, order), cli: candidate.cli });
|
|
5429
|
+
if (rows2.length >= limit) break;
|
|
5430
|
+
}
|
|
5431
|
+
return rows2;
|
|
3836
5432
|
}
|
|
3837
|
-
function
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
FROM cds_requires req JOIN repositories r ON r.id=req.repo_id
|
|
3842
|
-
WHERE (? IS NULL OR r.workspace_id=?)
|
|
3843
|
-
UNION ALL
|
|
3844
|
-
SELECT COALESCE(b.alias,b.alias_expr) alias,b.destination_expr destination,
|
|
3845
|
-
b.service_path_expr servicePath,'service_binding' sourceKind,r.name repoName
|
|
3846
|
-
FROM service_bindings b JOIN repositories r ON r.id=b.repo_id
|
|
3847
|
-
WHERE (? IS NULL OR r.workspace_id=?)`
|
|
3848
|
-
).all(workspaceId, workspaceId, workspaceId, workspaceId);
|
|
3849
|
-
return rows2.map((row) => ({
|
|
3850
|
-
alias: stringValue4(row.alias),
|
|
3851
|
-
destination: stringValue4(row.destination),
|
|
3852
|
-
servicePath: stringValue4(row.servicePath),
|
|
3853
|
-
sourceKind: String(row.sourceKind),
|
|
3854
|
-
repoName: String(row.repoName)
|
|
3855
|
-
}));
|
|
5433
|
+
function hasResolvedImplementation(db, operationId) {
|
|
5434
|
+
return Boolean(db.prepare(
|
|
5435
|
+
"SELECT 1 FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status='resolved' AND from_kind='operation' AND from_id=? LIMIT 1"
|
|
5436
|
+
).get(String(operationId)));
|
|
3856
5437
|
}
|
|
3857
|
-
function
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
5438
|
+
function templatesFromEvidence(evidence, phase) {
|
|
5439
|
+
return {
|
|
5440
|
+
servicePath: substitutionSignal(evidence, "servicePath", phase),
|
|
5441
|
+
operationPath: substitutionSignal(evidence, "operationPath", phase),
|
|
5442
|
+
alias: substitutionSignal(
|
|
5443
|
+
evidence,
|
|
5444
|
+
evidence.serviceAliasExpr !== void 0 ? "serviceAliasExpr" : "serviceAlias",
|
|
5445
|
+
phase
|
|
5446
|
+
),
|
|
5447
|
+
destination: substitutionSignal(evidence, "destination", phase)
|
|
5448
|
+
};
|
|
3866
5449
|
}
|
|
3867
|
-
function
|
|
5450
|
+
function substitutionSignal(evidence, key, phase) {
|
|
5451
|
+
const substitution = record(record(evidence.runtimeSubstitutions)[key]);
|
|
5452
|
+
return stringValue6(substitution[phase]) ?? stringValue6(evidence[key]);
|
|
5453
|
+
}
|
|
5454
|
+
function effectiveSignal(evidence, key) {
|
|
5455
|
+
return substitutionSignal(evidence, key, "effective");
|
|
5456
|
+
}
|
|
5457
|
+
function placeholderSources(templates) {
|
|
5458
|
+
const sources = {};
|
|
5459
|
+
for (const [kind, template] of Object.entries(templates)) {
|
|
5460
|
+
if (typeof template !== "string") continue;
|
|
5461
|
+
for (const key of extractPlaceholders(template))
|
|
5462
|
+
sources[key] = [.../* @__PURE__ */ new Set([...sources[key] ?? [], `${kind}:${template}`])].sort();
|
|
5463
|
+
}
|
|
5464
|
+
return Object.fromEntries(Object.entries(sources).sort(([left], [right]) => left.localeCompare(right)));
|
|
5465
|
+
}
|
|
5466
|
+
function variableOrder(templates, required) {
|
|
3868
5467
|
const ordered = [
|
|
3869
5468
|
templates.servicePath,
|
|
3870
5469
|
templates.operationPath,
|
|
3871
5470
|
templates.alias,
|
|
3872
5471
|
templates.destination
|
|
3873
5472
|
].flatMap((value) => extractPlaceholders(value));
|
|
3874
|
-
return [.../* @__PURE__ */ new Set([...ordered, ...
|
|
3875
|
-
}
|
|
3876
|
-
function templatesFromEvidence(evidence) {
|
|
3877
|
-
return {
|
|
3878
|
-
servicePath: stringValue4(evidence.servicePath),
|
|
3879
|
-
operationPath: stringValue4(evidence.operationPath),
|
|
3880
|
-
alias: stringValue4(evidence.serviceAliasExpr ?? evidence.serviceAlias),
|
|
3881
|
-
destination: stringValue4(evidence.destination)
|
|
3882
|
-
};
|
|
5473
|
+
return [.../* @__PURE__ */ new Set([...ordered, ...required])];
|
|
3883
5474
|
}
|
|
3884
5475
|
function matchTemplate(template, concrete) {
|
|
3885
5476
|
if (!template || !concrete) return void 0;
|
|
3886
5477
|
const keys = extractPlaceholders(template);
|
|
3887
5478
|
if (keys.length === 0) return template === concrete ? {} : void 0;
|
|
3888
|
-
const
|
|
3889
|
-
const match = regex.exec(concrete);
|
|
5479
|
+
const match = new RegExp(`^${templateToPattern(template)}$`).exec(concrete);
|
|
3890
5480
|
if (!match) return void 0;
|
|
3891
5481
|
const values = {};
|
|
3892
5482
|
for (let index = 0; index < keys.length; index += 1) {
|
|
@@ -3902,42 +5492,58 @@ function templateToPattern(template) {
|
|
|
3902
5492
|
let pattern = "";
|
|
3903
5493
|
let lastIndex = 0;
|
|
3904
5494
|
for (const match of template.matchAll(/\$\{([^}]*)\}/g)) {
|
|
3905
|
-
pattern +=
|
|
5495
|
+
pattern += escapeRegex2(template.slice(lastIndex, match.index));
|
|
3906
5496
|
pattern += "([^/]+?)";
|
|
3907
5497
|
lastIndex = (match.index ?? 0) + match[0].length;
|
|
3908
5498
|
}
|
|
3909
|
-
return `${pattern}${
|
|
5499
|
+
return `${pattern}${escapeRegex2(template.slice(lastIndex))}`;
|
|
3910
5500
|
}
|
|
3911
|
-
function
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
}
|
|
5501
|
+
function cliFor(variables, order) {
|
|
5502
|
+
return order.filter((key) => variables[key] !== void 0).map((key) => `--var ${shellArgument(`${key}=${variables[key]}`)}`).join(" ");
|
|
5503
|
+
}
|
|
5504
|
+
function shellArgument(value) {
|
|
5505
|
+
return /^[A-Za-z0-9_./:=+-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
5506
|
+
}
|
|
5507
|
+
function orderedVariables(variables, order) {
|
|
5508
|
+
return Object.fromEntries(order.flatMap((key) => variables[key] === void 0 ? [] : [[key, variables[key]]]));
|
|
3920
5509
|
}
|
|
3921
5510
|
function addScore(state, amount, reason) {
|
|
3922
5511
|
state.score += amount;
|
|
5512
|
+
addReason(state, reason);
|
|
5513
|
+
}
|
|
5514
|
+
function addReason(state, reason) {
|
|
3923
5515
|
if (!state.reasons.includes(reason)) state.reasons.push(reason);
|
|
3924
5516
|
}
|
|
3925
|
-
function
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
return Boolean(row);
|
|
5517
|
+
function reject(state, reason) {
|
|
5518
|
+
rejectReasonOnly(state, reason);
|
|
5519
|
+
state.viable = false;
|
|
5520
|
+
state.rejected = true;
|
|
3930
5521
|
}
|
|
3931
|
-
function
|
|
3932
|
-
|
|
5522
|
+
function rejectReasonOnly(state, reason) {
|
|
5523
|
+
if (!state.rejectedReasons.includes(reason)) state.rejectedReasons.push(reason);
|
|
5524
|
+
}
|
|
5525
|
+
function addInferenceBlock(state, reason) {
|
|
5526
|
+
addReason(state, reason);
|
|
5527
|
+
if (!state.inferenceBlockReasons.includes(reason))
|
|
5528
|
+
state.inferenceBlockReasons.push(reason);
|
|
5529
|
+
}
|
|
5530
|
+
function requiredSuppliedVariables(inputs) {
|
|
5531
|
+
return Object.fromEntries(inputs.required.flatMap((key) => inputs.supplied[key] === void 0 ? [] : [[key, inputs.supplied[key]]]));
|
|
5532
|
+
}
|
|
5533
|
+
function signalCode(kind) {
|
|
5534
|
+
return kind === "servicePath" ? "service_path" : "operation_path";
|
|
3933
5535
|
}
|
|
3934
5536
|
function record(value) {
|
|
3935
5537
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3936
5538
|
}
|
|
3937
|
-
function
|
|
5539
|
+
function stringRecord(value) {
|
|
5540
|
+
const entries = Object.entries(record(value)).filter((entry) => typeof entry[1] === "string").sort(([left], [right]) => left.localeCompare(right));
|
|
5541
|
+
return Object.fromEntries(entries);
|
|
5542
|
+
}
|
|
5543
|
+
function stringValue6(value) {
|
|
3938
5544
|
return typeof value === "string" ? value : void 0;
|
|
3939
5545
|
}
|
|
3940
|
-
function
|
|
5546
|
+
function numberValue3(value) {
|
|
3941
5547
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3942
5548
|
}
|
|
3943
5549
|
function stringArray(value) {
|
|
@@ -3950,7 +5556,7 @@ function nonEmptyStrings(value, fallback) {
|
|
|
3950
5556
|
function isConcrete(value) {
|
|
3951
5557
|
return typeof value === "string" && value.length > 0 && extractPlaceholders(value).length === 0;
|
|
3952
5558
|
}
|
|
3953
|
-
function
|
|
5559
|
+
function escapeRegex2(value) {
|
|
3954
5560
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3955
5561
|
}
|
|
3956
5562
|
|
|
@@ -3963,6 +5569,8 @@ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
|
3963
5569
|
persistedGraphEdgeId: row.id > 0 ? row.id : void 0,
|
|
3964
5570
|
outboundCallId: call.id,
|
|
3965
5571
|
callSite: { sourceFile: call.source_file, sourceLine: call.source_line },
|
|
5572
|
+
callType: call.call_type,
|
|
5573
|
+
repoId: call.repo_id,
|
|
3966
5574
|
sourceFile: call.source_file,
|
|
3967
5575
|
sourceLine: call.source_line,
|
|
3968
5576
|
file: call.source_file,
|
|
@@ -3973,43 +5581,123 @@ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
|
3973
5581
|
};
|
|
3974
5582
|
}
|
|
3975
5583
|
function runtimeResolution(db, row, evidence, options, workspaceId, contextualUnresolvedReason) {
|
|
3976
|
-
const substituted = evidenceWithRuntimeVariables(evidence, options.vars);
|
|
3977
5584
|
const dynamicMode = options.dynamicMode ?? "strict";
|
|
5585
|
+
const candidateCap = positiveCandidateCap(options.maxDynamicCandidates);
|
|
5586
|
+
if (!isDynamicRemoteOperationEdge(row, evidence))
|
|
5587
|
+
return unchangedRuntimeResolution(
|
|
5588
|
+
row,
|
|
5589
|
+
boundDynamicEvidence(evidence, candidateCap),
|
|
5590
|
+
contextualUnresolvedReason
|
|
5591
|
+
);
|
|
5592
|
+
const substituted = evidenceWithRuntimeVariables(evidence, options.vars);
|
|
3978
5593
|
const analysis = analyzeDynamicTargetCandidates(
|
|
3979
5594
|
db,
|
|
3980
5595
|
substituted,
|
|
3981
5596
|
workspaceId,
|
|
3982
5597
|
dynamicMode,
|
|
3983
|
-
|
|
5598
|
+
candidateCap
|
|
5599
|
+
);
|
|
5600
|
+
const enriched = boundDynamicEvidence(
|
|
5601
|
+
analysis ? evidenceWithDynamicAnalysis(substituted, analysis) : substituted,
|
|
5602
|
+
candidateCap
|
|
3984
5603
|
);
|
|
3985
|
-
|
|
5604
|
+
if (analysis && analysis.candidateCount > 0 && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
5605
|
+
return noCandidateRuntimeResolution(row, enriched);
|
|
3986
5606
|
if (dynamicMode === "infer") {
|
|
3987
5607
|
const inferred = inferredTarget(analysis);
|
|
3988
|
-
if (inferred)
|
|
3989
|
-
|
|
3990
|
-
const resolution2 = { status: "resolved", target: inferred, candidates: [], reasons: inferred.reasons };
|
|
3991
|
-
return { row: resolvedRow, evidence: withEffectiveResolution(enriched, resolvedRow, void 0, resolution2), target: inferred };
|
|
3992
|
-
}
|
|
5608
|
+
if (inferred)
|
|
5609
|
+
return resolvedRuntimeResolution(row, enriched, inferred, inferred.reasons);
|
|
3993
5610
|
}
|
|
3994
|
-
if (!
|
|
5611
|
+
if (!hasApplicableRuntimeVariables(evidence, options.vars)) {
|
|
3995
5612
|
const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason;
|
|
3996
5613
|
const withSections = withEffectiveResolution(enriched, row, unresolvedReason2);
|
|
3997
5614
|
return { row, evidence: withSections, unresolvedReason: unresolvedReason2 };
|
|
3998
5615
|
}
|
|
3999
5616
|
const resolution = resolveRuntimeOperation(db, enriched, workspaceId);
|
|
4000
|
-
if (resolution.target)
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
5617
|
+
if (resolution.target)
|
|
5618
|
+
return resolvedRuntimeResolution(
|
|
5619
|
+
row,
|
|
5620
|
+
enriched,
|
|
5621
|
+
resolution.target,
|
|
5622
|
+
resolution.reasons
|
|
5623
|
+
);
|
|
5624
|
+
if (analysis && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
5625
|
+
return noCandidateRuntimeResolution(row, enriched);
|
|
4004
5626
|
const unresolvedReason = runtimeUnresolvedReason(resolution);
|
|
4005
5627
|
return { row, evidence: withEffectiveResolution(enriched, row, unresolvedReason, resolution), unresolvedReason };
|
|
4006
5628
|
}
|
|
5629
|
+
function unchangedRuntimeResolution(row, evidence, contextualUnresolvedReason) {
|
|
5630
|
+
const unresolvedReason = contextualUnresolvedReason ?? row.unresolved_reason;
|
|
5631
|
+
return {
|
|
5632
|
+
row,
|
|
5633
|
+
evidence: withEffectiveResolution(evidence, row, unresolvedReason),
|
|
5634
|
+
unresolvedReason
|
|
5635
|
+
};
|
|
5636
|
+
}
|
|
5637
|
+
function noCandidateRuntimeResolution(row, evidence) {
|
|
5638
|
+
const unresolvedReason = "No candidate remained after runtime substitution";
|
|
5639
|
+
return {
|
|
5640
|
+
row,
|
|
5641
|
+
evidence: withEffectiveResolution(evidence, row, unresolvedReason),
|
|
5642
|
+
unresolvedReason
|
|
5643
|
+
};
|
|
5644
|
+
}
|
|
5645
|
+
function resolvedRuntimeResolution(row, evidence, target, reasons) {
|
|
5646
|
+
const resolvedRow = {
|
|
5647
|
+
...row,
|
|
5648
|
+
status: "resolved",
|
|
5649
|
+
to_kind: "operation",
|
|
5650
|
+
to_id: String(target.operationId),
|
|
5651
|
+
unresolved_reason: void 0,
|
|
5652
|
+
confidence: Math.max(0, Math.min(1, target.score))
|
|
5653
|
+
};
|
|
5654
|
+
const resolution = {
|
|
5655
|
+
status: "resolved",
|
|
5656
|
+
target,
|
|
5657
|
+
candidates: [],
|
|
5658
|
+
reasons
|
|
5659
|
+
};
|
|
5660
|
+
return {
|
|
5661
|
+
row: resolvedRow,
|
|
5662
|
+
evidence: withEffectiveResolution(evidence, resolvedRow, void 0, resolution),
|
|
5663
|
+
target
|
|
5664
|
+
};
|
|
5665
|
+
}
|
|
4007
5666
|
function runtimeVariableDiagnostic(edges) {
|
|
5667
|
+
const totals = runtimeDiagnosticTotals(edges);
|
|
5668
|
+
const missingVariables = [...totals.missing].sort();
|
|
5669
|
+
if (missingVariables.length === 0) return void 0;
|
|
5670
|
+
const shownSuggestions = totals.candidateSuggestions.slice(0, totals.maxCandidates);
|
|
5671
|
+
const shownRejected = totals.rejectedCandidates.slice(0, totals.maxCandidates);
|
|
5672
|
+
const shownCandidateCount = shownSuggestions.length;
|
|
5673
|
+
return {
|
|
5674
|
+
severity: "warning",
|
|
5675
|
+
code: "trace_runtime_variables_missing",
|
|
5676
|
+
message: `Runtime variables are required to resolve dynamic trace targets: ${missingVariables.join(", ")}`,
|
|
5677
|
+
missingVariables,
|
|
5678
|
+
suggestions: missingVariables.map((key) => `--var ${key}=<value>`),
|
|
5679
|
+
candidateCount: totals.candidateCount,
|
|
5680
|
+
viableCandidateCount: totals.viableCandidateCount,
|
|
5681
|
+
rejectedCandidateCount: totals.rejectedCandidateCount,
|
|
5682
|
+
shownCandidateCount,
|
|
5683
|
+
omittedCandidateCount: Math.max(0, totals.viableCandidateCount - shownCandidateCount),
|
|
5684
|
+
maxDynamicCandidates: totals.maxCandidates,
|
|
5685
|
+
shownRejectedCandidateCount: shownRejected.length,
|
|
5686
|
+
omittedRejectedCandidateCount: Math.max(0, totals.rejectedCandidateCount - shownRejected.length),
|
|
5687
|
+
candidateSuggestions: shownSuggestions,
|
|
5688
|
+
rejectedCandidates: shownRejected,
|
|
5689
|
+
suggestedVarSets: uniqueCliRows(totals.suggestedVarSets).slice(0, totals.maxCandidates),
|
|
5690
|
+
copyableExamples: copyableExamples(totals.suggestedVarSets, totals.candidateCount, totals.maxCandidates)
|
|
5691
|
+
};
|
|
5692
|
+
}
|
|
5693
|
+
function runtimeDiagnosticTotals(edges) {
|
|
4008
5694
|
const missing = /* @__PURE__ */ new Set();
|
|
4009
5695
|
let candidateCount = 0;
|
|
4010
|
-
let
|
|
4011
|
-
let
|
|
5696
|
+
let viableCandidateCount = 0;
|
|
5697
|
+
let rejectedCandidateCount = 0;
|
|
5698
|
+
let maxCandidates = 5;
|
|
4012
5699
|
const candidateSuggestions = [];
|
|
5700
|
+
const rejectedCandidates = [];
|
|
4013
5701
|
const suggestedVarSets2 = [];
|
|
4014
5702
|
for (const edge of edges) {
|
|
4015
5703
|
const effective = parseObject(edge.evidence.effectiveResolution);
|
|
@@ -4020,47 +5708,90 @@ function runtimeVariableDiagnostic(edges) {
|
|
|
4020
5708
|
for (const value of Object.values(substitutions))
|
|
4021
5709
|
for (const key of value.missing ?? []) missing.add(key);
|
|
4022
5710
|
const exploration = parseObject(edge.evidence.dynamicTargetExploration);
|
|
5711
|
+
maxCandidates = positiveCandidateCap(numeric(exploration.maxCandidates) || maxCandidates);
|
|
4023
5712
|
candidateCount += numeric(exploration.candidateCount);
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
5713
|
+
viableCandidateCount += numeric(exploration.viableCandidateCount);
|
|
5714
|
+
rejectedCandidateCount += numeric(exploration.rejectedCandidateCount);
|
|
5715
|
+
appendBounded(
|
|
5716
|
+
candidateSuggestions,
|
|
5717
|
+
recordArray(edge.evidence.dynamicTargetCandidateSuggestions),
|
|
5718
|
+
maxCandidates
|
|
5719
|
+
);
|
|
5720
|
+
appendBounded(
|
|
5721
|
+
rejectedCandidates,
|
|
5722
|
+
recordArray(exploration.rejectedCandidates),
|
|
5723
|
+
maxCandidates
|
|
5724
|
+
);
|
|
5725
|
+
appendBounded(
|
|
5726
|
+
suggestedVarSets2,
|
|
5727
|
+
recordArray(exploration.suggestedVarSets),
|
|
5728
|
+
maxCandidates
|
|
5729
|
+
);
|
|
4028
5730
|
}
|
|
4029
|
-
const missingVariables = [...missing].sort();
|
|
4030
|
-
if (missingVariables.length === 0) return void 0;
|
|
4031
5731
|
return {
|
|
4032
|
-
|
|
4033
|
-
code: "trace_runtime_variables_missing",
|
|
4034
|
-
message: `Runtime variables are required to resolve dynamic trace targets: ${missingVariables.join(", ")}`,
|
|
4035
|
-
missingVariables,
|
|
4036
|
-
suggestions: missingVariables.map((key) => `--var ${key}=<value>`),
|
|
5732
|
+
missing,
|
|
4037
5733
|
candidateCount,
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
...candidateCount > 0 ? ["--dynamic-mode candidates --max-dynamic-candidates 20"] : []
|
|
4045
|
-
]
|
|
5734
|
+
viableCandidateCount,
|
|
5735
|
+
rejectedCandidateCount,
|
|
5736
|
+
maxCandidates,
|
|
5737
|
+
candidateSuggestions,
|
|
5738
|
+
rejectedCandidates,
|
|
5739
|
+
suggestedVarSets: suggestedVarSets2
|
|
4046
5740
|
};
|
|
4047
5741
|
}
|
|
5742
|
+
function runtimeNoCandidateDiagnostics(edges) {
|
|
5743
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5744
|
+
return edges.flatMap((edge) => {
|
|
5745
|
+
const exploration = parseObject(edge.evidence.dynamicTargetExploration);
|
|
5746
|
+
const suppliedVariables = parseObject(exploration.suppliedVariables);
|
|
5747
|
+
const appliedSuppliedVariables = parseObject(
|
|
5748
|
+
exploration.appliedSuppliedVariables
|
|
5749
|
+
);
|
|
5750
|
+
if (numeric(exploration.viableCandidateCount) !== 0 || Object.keys(appliedSuppliedVariables).length === 0) return [];
|
|
5751
|
+
const callSite = parseObject(edge.evidence.callSite);
|
|
5752
|
+
const key = JSON.stringify([callSite, suppliedVariables]);
|
|
5753
|
+
if (seen.has(key)) return [];
|
|
5754
|
+
seen.add(key);
|
|
5755
|
+
const maxCandidates = positiveCandidateCap(numeric(exploration.maxCandidates));
|
|
5756
|
+
return [{
|
|
5757
|
+
severity: "warning",
|
|
5758
|
+
code: "no_candidate_after_runtime_substitution",
|
|
5759
|
+
message: "No dynamic target candidate remained after applying runtime variables",
|
|
5760
|
+
suppliedVariables,
|
|
5761
|
+
appliedSuppliedVariables,
|
|
5762
|
+
substitutedSignals: parseObject(exploration.substitutedSignals),
|
|
5763
|
+
candidateCount: numeric(exploration.candidateCount),
|
|
5764
|
+
viableCandidateCount: 0,
|
|
5765
|
+
rejectedCandidateCount: numeric(exploration.rejectedCandidateCount),
|
|
5766
|
+
shownCandidateCount: numeric(exploration.shownCandidateCount),
|
|
5767
|
+
omittedCandidateCount: numeric(exploration.omittedCandidateCount),
|
|
5768
|
+
shownRejectedCandidateCount: numeric(
|
|
5769
|
+
exploration.shownRejectedCandidateCount
|
|
5770
|
+
),
|
|
5771
|
+
omittedRejectedCandidateCount: numeric(
|
|
5772
|
+
exploration.omittedRejectedCandidateCount
|
|
5773
|
+
),
|
|
5774
|
+
rejectedCandidates: recordArray(exploration.rejectedCandidates).slice(0, maxCandidates),
|
|
5775
|
+
callSite
|
|
5776
|
+
}];
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
4048
5779
|
function edgeTarget(row, evidence) {
|
|
4049
5780
|
const effective = parseObject(evidence.effectiveResolution);
|
|
4050
|
-
const targetServicePath =
|
|
4051
|
-
const targetOperationPath =
|
|
5781
|
+
const targetServicePath = stringValue7(effective.targetServicePath ?? evidence.targetServicePath);
|
|
5782
|
+
const targetOperationPath = stringValue7(effective.targetOperationPath ?? evidence.targetOperationPath);
|
|
4052
5783
|
if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
|
|
4053
5784
|
const runtimeCandidate = evidence.runtimeResolvedCandidate;
|
|
4054
5785
|
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
|
|
4055
|
-
const servicePath =
|
|
4056
|
-
const operationPath =
|
|
4057
|
-
const targetOperation =
|
|
4058
|
-
const targetRepo =
|
|
5786
|
+
const servicePath = stringValue7(evidence.servicePath);
|
|
5787
|
+
const operationPath = stringValue7(evidence.operationPath);
|
|
5788
|
+
const targetOperation = stringValue7(evidence.targetOperation);
|
|
5789
|
+
const targetRepo = stringValue7(evidence.targetRepo) ?? "";
|
|
4059
5790
|
if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
|
|
4060
|
-
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return
|
|
5791
|
+
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue7(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
|
|
4061
5792
|
if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
|
|
4062
5793
|
const target = parseObject(evidence.externalTarget);
|
|
4063
|
-
return
|
|
5794
|
+
return stringValue7(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
|
|
4064
5795
|
}
|
|
4065
5796
|
if (servicePath && operationPath) return `${servicePath}${operationPath}`;
|
|
4066
5797
|
return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
|
|
@@ -4099,39 +5830,102 @@ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
|
|
|
4099
5830
|
return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
|
|
4100
5831
|
}
|
|
4101
5832
|
function resolveRuntimeOperation(db, evidence, workspaceId) {
|
|
4102
|
-
const servicePath =
|
|
4103
|
-
const rawOperationPath =
|
|
5833
|
+
const servicePath = stringValue7(evidence.servicePath);
|
|
5834
|
+
const rawOperationPath = stringValue7(evidence.operationPath);
|
|
4104
5835
|
const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
|
|
4105
|
-
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath :
|
|
4106
|
-
const alias =
|
|
4107
|
-
const destination =
|
|
5836
|
+
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue7(evidence.normalizedOperationPath) ?? rawOperationPath;
|
|
5837
|
+
const alias = stringValue7(evidence.serviceAliasExpr ?? evidence.serviceAlias);
|
|
5838
|
+
const destination = stringValue7(evidence.destination);
|
|
4108
5839
|
return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
|
|
4109
5840
|
}
|
|
4110
5841
|
function evidenceWithRuntimeVariables(evidence, vars) {
|
|
4111
5842
|
const substitutions = runtimeSubstitutions(evidence, vars ?? {});
|
|
4112
|
-
const
|
|
5843
|
+
const suppliedRuntimeVariables = Object.fromEntries(
|
|
5844
|
+
Object.entries(vars ?? {}).sort(([left], [right]) => left.localeCompare(right))
|
|
5845
|
+
);
|
|
5846
|
+
const next = {
|
|
5847
|
+
...evidence,
|
|
5848
|
+
runtimeSubstitutions: substitutions,
|
|
5849
|
+
suppliedRuntimeVariables
|
|
5850
|
+
};
|
|
4113
5851
|
for (const [key, value] of Object.entries(substitutions)) if (value.effective) next[key] = value.effective;
|
|
4114
5852
|
const missing = Object.values(substitutions).flatMap((value) => value.missing);
|
|
4115
5853
|
if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)].sort();
|
|
4116
|
-
if (Object.keys(
|
|
5854
|
+
if (Object.keys(suppliedRuntimeVariables).length > 0) next.runtimeVariablesApplied = true;
|
|
4117
5855
|
return next;
|
|
4118
5856
|
}
|
|
4119
5857
|
function evidenceWithDynamicAnalysis(evidence, analysis) {
|
|
5858
|
+
const persistedCandidates = recordArray(evidence.candidates);
|
|
5859
|
+
const persistedScores = recordArray(evidence.candidateScores);
|
|
4120
5860
|
return {
|
|
4121
5861
|
...evidence,
|
|
5862
|
+
candidates: persistedCandidates.slice(0, analysis.maxCandidates),
|
|
5863
|
+
candidateScores: persistedScores.slice(0, analysis.maxCandidates),
|
|
5864
|
+
persistedCandidateCount: persistedCandidates.length,
|
|
5865
|
+
persistedCandidateOmittedCount: Math.max(
|
|
5866
|
+
0,
|
|
5867
|
+
persistedCandidates.length - analysis.maxCandidates
|
|
5868
|
+
),
|
|
4122
5869
|
dynamicTargetExploration: {
|
|
4123
5870
|
mode: analysis.mode,
|
|
5871
|
+
maxCandidates: analysis.maxCandidates,
|
|
4124
5872
|
missingVariables: analysis.missingVariables,
|
|
5873
|
+
requiredVariables: analysis.requiredVariables,
|
|
5874
|
+
suppliedVariables: analysis.suppliedVariables,
|
|
5875
|
+
appliedSuppliedVariables: analysis.appliedSuppliedVariables,
|
|
5876
|
+
substitutedSignals: analysis.substitutedSignals,
|
|
4125
5877
|
candidateCount: analysis.candidateCount,
|
|
5878
|
+
viableCandidateCount: analysis.viableCandidateCount,
|
|
5879
|
+
rejectedCandidateCount: analysis.rejectedCandidateCount,
|
|
4126
5880
|
shownCandidateCount: analysis.shownCandidateCount,
|
|
4127
5881
|
omittedCandidateCount: analysis.omittedCandidateCount,
|
|
5882
|
+
shownRejectedCandidateCount: analysis.shownRejectedCandidateCount,
|
|
5883
|
+
omittedRejectedCandidateCount: analysis.omittedRejectedCandidateCount,
|
|
5884
|
+
rejectedCandidates: analysis.rejectedCandidates,
|
|
4128
5885
|
suggestedVarSets: analysis.suggestedVarSets
|
|
4129
5886
|
},
|
|
4130
5887
|
dynamicTargetCandidates: analysis.candidates,
|
|
4131
5888
|
dynamicTargetCandidateSuggestions: analysis.shownCandidates,
|
|
5889
|
+
rejectedCandidates: analysis.rejectedCandidates,
|
|
4132
5890
|
dynamicTargetInference: analysis.inference
|
|
4133
5891
|
};
|
|
4134
5892
|
}
|
|
5893
|
+
var boundedDynamicListKeys = /* @__PURE__ */ new Set([
|
|
5894
|
+
"candidates",
|
|
5895
|
+
"candidateScores",
|
|
5896
|
+
"dynamicTargetCandidates",
|
|
5897
|
+
"dynamicTargetCandidateSuggestions",
|
|
5898
|
+
"candidateSuggestions",
|
|
5899
|
+
"suggestedVarSets",
|
|
5900
|
+
"rejectedCandidates",
|
|
5901
|
+
"rejectedCandidateSuggestions",
|
|
5902
|
+
"copyableExamples",
|
|
5903
|
+
"conflicts"
|
|
5904
|
+
]);
|
|
5905
|
+
function boundDynamicEvidence(evidence, limit) {
|
|
5906
|
+
const candidateCount = numeric(evidence.persistedCandidateCount) || (Array.isArray(evidence.candidates) ? evidence.candidates.length : 0);
|
|
5907
|
+
const projected = boundDynamicValue(evidence, limit);
|
|
5908
|
+
const next = parseObject(projected);
|
|
5909
|
+
if (candidateCount === 0) return next;
|
|
5910
|
+
return {
|
|
5911
|
+
...next,
|
|
5912
|
+
persistedCandidateCount: candidateCount,
|
|
5913
|
+
persistedCandidateOmittedCount: Math.max(0, candidateCount - limit)
|
|
5914
|
+
};
|
|
5915
|
+
}
|
|
5916
|
+
function boundDynamicValue(value, limit, key, parentKey) {
|
|
5917
|
+
if (Array.isArray(value)) {
|
|
5918
|
+
const bounded = Boolean(key && (boundedDynamicListKeys.has(key) || parentKey === "derivationProvenance" || parentKey === "conflicts" && key === "sources"));
|
|
5919
|
+
const input = bounded ? value.slice(0, limit) : value;
|
|
5920
|
+
const projected = input.map((item) => boundDynamicValue(item, limit, void 0, key ?? parentKey));
|
|
5921
|
+
return projected;
|
|
5922
|
+
}
|
|
5923
|
+
if (!value || typeof value !== "object") return value;
|
|
5924
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
|
|
5925
|
+
childKey,
|
|
5926
|
+
boundDynamicValue(child, limit, childKey, key ?? parentKey)
|
|
5927
|
+
]));
|
|
5928
|
+
}
|
|
4135
5929
|
function runtimeSubstitutions(evidence, vars) {
|
|
4136
5930
|
const substitutions = {};
|
|
4137
5931
|
for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
|
|
@@ -4141,15 +5935,18 @@ function runtimeSubstitutions(evidence, vars) {
|
|
|
4141
5935
|
return substitutions;
|
|
4142
5936
|
}
|
|
4143
5937
|
function substitutionValue(evidence, key) {
|
|
4144
|
-
const value =
|
|
5938
|
+
const value = stringValue7(evidence[key]);
|
|
4145
5939
|
if (key !== "operationPath") return value;
|
|
4146
5940
|
const normalized = normalizeODataOperationInvocationPath(value);
|
|
4147
5941
|
return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
|
|
4148
5942
|
}
|
|
4149
|
-
function
|
|
4150
|
-
if (
|
|
5943
|
+
function isDynamicRemoteOperationEdge(row, evidence) {
|
|
5944
|
+
if (evidence.callType !== "remote_action") return false;
|
|
4151
5945
|
if (!["dynamic", "ambiguous", "unresolved"].includes(String(row.status ?? ""))) return false;
|
|
4152
|
-
|
|
5946
|
+
return ["DYNAMIC_EDGE_CANDIDATE", "UNRESOLVED_EDGE", "REMOTE_CALL_RESOLVES_TO_OPERATION"].includes(row.edge_type);
|
|
5947
|
+
}
|
|
5948
|
+
function hasApplicableRuntimeVariables(evidence, vars) {
|
|
5949
|
+
if (!vars || Object.keys(vars).length === 0) return false;
|
|
4153
5950
|
return ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"].some((key) => hasRuntimeVariable(evidence[key], vars));
|
|
4154
5951
|
}
|
|
4155
5952
|
function inferredTarget(analysis) {
|
|
@@ -4168,11 +5965,12 @@ function targetFromCandidate(candidate) {
|
|
|
4168
5965
|
servicePath: candidate.servicePath,
|
|
4169
5966
|
operationPath: candidate.operationPath,
|
|
4170
5967
|
operationName: candidate.operationName,
|
|
5968
|
+
repoId: candidate.repoId,
|
|
4171
5969
|
packageName: candidate.packageName,
|
|
4172
5970
|
score: candidate.score,
|
|
4173
5971
|
reasons: candidate.reasons,
|
|
4174
|
-
sourceFile:
|
|
4175
|
-
sourceLine:
|
|
5972
|
+
sourceFile: candidate.sourceFile,
|
|
5973
|
+
sourceLine: candidate.sourceLine
|
|
4176
5974
|
};
|
|
4177
5975
|
}
|
|
4178
5976
|
function hasRuntimeVariable(value, vars) {
|
|
@@ -4186,7 +5984,7 @@ function runtimeUnresolvedReason(resolution) {
|
|
|
4186
5984
|
function parseObject(value) {
|
|
4187
5985
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
4188
5986
|
}
|
|
4189
|
-
function
|
|
5987
|
+
function stringValue7(value) {
|
|
4190
5988
|
return typeof value === "string" ? value : void 0;
|
|
4191
5989
|
}
|
|
4192
5990
|
function numeric(value) {
|
|
@@ -4195,6 +5993,10 @@ function numeric(value) {
|
|
|
4195
5993
|
function recordArray(value) {
|
|
4196
5994
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
4197
5995
|
}
|
|
5996
|
+
function appendBounded(target, values, limit) {
|
|
5997
|
+
const remaining = Math.max(0, limit - target.length);
|
|
5998
|
+
if (remaining > 0) target.push(...values.slice(0, remaining));
|
|
5999
|
+
}
|
|
4198
6000
|
function uniqueCliRows(rows2) {
|
|
4199
6001
|
const seen = /* @__PURE__ */ new Set();
|
|
4200
6002
|
return rows2.filter((row) => {
|
|
@@ -4204,6 +6006,11 @@ function uniqueCliRows(rows2) {
|
|
|
4204
6006
|
return true;
|
|
4205
6007
|
});
|
|
4206
6008
|
}
|
|
6009
|
+
function copyableExamples(suggestedVarSets2, candidateCount, limit) {
|
|
6010
|
+
const variableExamples = uniqueCliRows(suggestedVarSets2).flatMap((row) => typeof row.cli === "string" ? [row.cli] : []);
|
|
6011
|
+
const exploration = candidateCount > 0 ? ["--dynamic-mode candidates --max-dynamic-candidates 20"] : [];
|
|
6012
|
+
return [...variableExamples, ...exploration].slice(0, limit);
|
|
6013
|
+
}
|
|
4207
6014
|
function positiveCandidateCap(value) {
|
|
4208
6015
|
return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : 5;
|
|
4209
6016
|
}
|
|
@@ -4221,9 +6028,9 @@ function dynamicCandidateBranches(depth, call, evidence) {
|
|
|
4221
6028
|
exploratory: true,
|
|
4222
6029
|
dynamicMode: String(exploration.mode ?? "candidates"),
|
|
4223
6030
|
selected: false,
|
|
4224
|
-
omittedCandidateCount:
|
|
6031
|
+
omittedCandidateCount: numericValue2(exploration.omittedCandidateCount)
|
|
4225
6032
|
},
|
|
4226
|
-
confidence:
|
|
6033
|
+
confidence: numericValue2(candidate.score),
|
|
4227
6034
|
unresolvedReason: "Exploratory dynamic target candidate; provide runtime variables to select it"
|
|
4228
6035
|
}));
|
|
4229
6036
|
}
|
|
@@ -4233,25 +6040,62 @@ function objectRecord(value) {
|
|
|
4233
6040
|
function recordArray2(value) {
|
|
4234
6041
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
4235
6042
|
}
|
|
4236
|
-
function
|
|
6043
|
+
function numericValue2(value) {
|
|
4237
6044
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
4238
6045
|
}
|
|
4239
6046
|
|
|
6047
|
+
// src/trace/002-trace-diagnostics.ts
|
|
6048
|
+
function loadTraceDiagnostics(db, repoId, includeWorkspaceDiagnostics, workspaceId) {
|
|
6049
|
+
if (repoId === void 0 && !includeWorkspaceDiagnostics) return [];
|
|
6050
|
+
return db.prepare(`SELECT d.repo_id repoId,d.severity,d.code,d.message,
|
|
6051
|
+
d.source_file sourceFile,d.source_line sourceLine
|
|
6052
|
+
FROM diagnostics d LEFT JOIN repositories r ON r.id=d.repo_id
|
|
6053
|
+
WHERE (? IS NULL OR d.repo_id=?)
|
|
6054
|
+
AND (? IS NULL OR d.repo_id IS NULL OR r.workspace_id=?)
|
|
6055
|
+
ORDER BY severity,code,COALESCE(source_file,''),
|
|
6056
|
+
COALESCE(source_line,0),d.id`).all(
|
|
6057
|
+
repoId,
|
|
6058
|
+
repoId,
|
|
6059
|
+
workspaceId,
|
|
6060
|
+
workspaceId
|
|
6061
|
+
);
|
|
6062
|
+
}
|
|
6063
|
+
function prependTraceDiagnostic(diagnostics, diagnostic) {
|
|
6064
|
+
const duplicate = diagnostics.findIndex((item) => sameDiagnosticLocation(item, diagnostic));
|
|
6065
|
+
if (duplicate >= 0) diagnostics.splice(duplicate, 1);
|
|
6066
|
+
diagnostics.unshift(diagnostic);
|
|
6067
|
+
}
|
|
6068
|
+
function sameDiagnosticLocation(left, right) {
|
|
6069
|
+
if (left.code !== right.code) return false;
|
|
6070
|
+
const leftFile = stringValue8(left.sourceFile);
|
|
6071
|
+
const rightFile = stringValue8(right.sourceFile);
|
|
6072
|
+
if (leftFile || rightFile)
|
|
6073
|
+
return leftFile === rightFile && numericValue3(left.sourceLine) === numericValue3(right.sourceLine);
|
|
6074
|
+
return String(left.message ?? "") === String(right.message ?? "");
|
|
6075
|
+
}
|
|
6076
|
+
function stringValue8(value) {
|
|
6077
|
+
return typeof value === "string" ? value : void 0;
|
|
6078
|
+
}
|
|
6079
|
+
function numericValue3(value) {
|
|
6080
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
6081
|
+
}
|
|
6082
|
+
|
|
4240
6083
|
// src/trace/trace-engine.ts
|
|
4241
|
-
function
|
|
6084
|
+
function normalizeOperation2(value) {
|
|
4242
6085
|
if (!value) return void 0;
|
|
4243
6086
|
return value.startsWith("/") ? value.slice(1) : value;
|
|
4244
6087
|
}
|
|
4245
6088
|
function positiveDepth(value) {
|
|
4246
6089
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
|
|
4247
6090
|
}
|
|
4248
|
-
function operationStartScope(db, repoId, start, hintOptions) {
|
|
4249
|
-
const requested =
|
|
6091
|
+
function operationStartScope(db, repoId, start, hintOptions, workspaceId) {
|
|
6092
|
+
const requested = normalizeOperation2(start.operationPath ?? start.operation);
|
|
4250
6093
|
if (!requested) return void 0;
|
|
4251
6094
|
const rows2 = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.service_path servicePath,r.id repoId,r.name repoName
|
|
4252
6095
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
4253
|
-
WHERE (? IS NULL OR r.
|
|
4254
|
-
|
|
6096
|
+
WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR r.id=?)
|
|
6097
|
+
AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
|
|
6098
|
+
ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(workspaceId, workspaceId, repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith("/") ? requested : `/${requested}`);
|
|
4255
6099
|
if (rows2.length === 0) return void 0;
|
|
4256
6100
|
const repoCount = new Set(rows2.map((row) => String(row.repoName))).size;
|
|
4257
6101
|
const serviceCount = new Set(rows2.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
|
|
@@ -4282,54 +6126,34 @@ function implementationRejectionReasons(evidence) {
|
|
|
4282
6126
|
const reasons = candidates.flatMap((candidate) => Array.isArray(candidate.rejectedReasons) ? candidate.rejectedReasons.filter((reason) => typeof reason === "string") : []);
|
|
4283
6127
|
return [...new Set(reasons)].sort();
|
|
4284
6128
|
}
|
|
4285
|
-
function sourceFilesForStart(db, repoId, start) {
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
const
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
operation,
|
|
4310
|
-
operation
|
|
6129
|
+
function sourceFilesForStart(db, repoId, start, workspaceId) {
|
|
6130
|
+
return sourceScopeForSelector(db, repoId, start, workspaceId);
|
|
6131
|
+
}
|
|
6132
|
+
function startScope(db, start, hintOptions, workspaceId) {
|
|
6133
|
+
const repos = start.repo ? reposByName(db, start.repo, workspaceId).map((row) => ({
|
|
6134
|
+
id: row.id,
|
|
6135
|
+
name: row.name,
|
|
6136
|
+
packageName: row.package_name ?? void 0
|
|
6137
|
+
})) : [];
|
|
6138
|
+
if (start.repo && repos.length === 0) return {
|
|
6139
|
+
selectorMatched: false,
|
|
6140
|
+
startDiagnostics: [selectorRepoNotFoundDiagnostic(start.repo)]
|
|
6141
|
+
};
|
|
6142
|
+
if (start.repo && repos.length > 1) return {
|
|
6143
|
+
selectorMatched: false,
|
|
6144
|
+
startDiagnostics: [selectorRepoAmbiguousDiagnostic(start.repo, repos)]
|
|
6145
|
+
};
|
|
6146
|
+
const repo = repos[0];
|
|
6147
|
+
const operationScope = operationStartScope(
|
|
6148
|
+
db,
|
|
6149
|
+
repo?.id,
|
|
6150
|
+
start,
|
|
6151
|
+
hintOptions,
|
|
6152
|
+
workspaceId
|
|
4311
6153
|
);
|
|
4312
|
-
if (rows2.length > 0) return { files: new Set(rows2.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(rows2.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
|
|
4313
|
-
if (start.servicePath && operation) {
|
|
4314
|
-
const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId
|
|
4315
|
-
FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
|
|
4316
|
-
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)
|
|
4317
|
-
JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
4318
|
-
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4319
|
-
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
|
|
4320
|
-
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);
|
|
4321
|
-
if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
|
|
4322
|
-
}
|
|
4323
|
-
return void 0;
|
|
4324
|
-
}
|
|
4325
|
-
function startScope(db, start, hintOptions) {
|
|
4326
|
-
const repo = start.repo ? db.prepare(
|
|
4327
|
-
"SELECT id,name FROM repositories WHERE name=? OR package_name=?"
|
|
4328
|
-
).get(start.repo, start.repo) : void 0;
|
|
4329
|
-
if (start.repo && !repo) return { repo, selectorMatched: false };
|
|
4330
|
-
const operationScope = operationStartScope(db, repo?.id, start, hintOptions);
|
|
4331
6154
|
const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === "operation" || d.resolutionStage === "implementation");
|
|
4332
|
-
const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
|
|
6155
|
+
const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start, workspaceId);
|
|
6156
|
+
const terminalSelectorScope = Boolean(sourceScope?.diagnostics?.length && !sourceScope.files);
|
|
4333
6157
|
const sourceFiles = sourceScope?.files;
|
|
4334
6158
|
const hasSelector = Boolean(
|
|
4335
6159
|
start.handler ?? start.operation ?? start.operationPath ?? start.servicePath
|
|
@@ -4341,9 +6165,9 @@ function startScope(db, start, hintOptions) {
|
|
|
4341
6165
|
executionRepoId: sourceScope?.repoId ?? repo?.id,
|
|
4342
6166
|
sourceFiles,
|
|
4343
6167
|
symbolIds: sourceScope?.symbols,
|
|
4344
|
-
selectorMatched: !terminalOperationScope && (!hasSelector || sourceFiles !== void 0),
|
|
6168
|
+
selectorMatched: !terminalOperationScope && !terminalSelectorScope && (!hasSelector || sourceFiles !== void 0),
|
|
4345
6169
|
startOperationId: operationScope?.operationId,
|
|
4346
|
-
startDiagnostics: operationScope?.diagnostics
|
|
6170
|
+
startDiagnostics: operationScope?.diagnostics?.length ? operationScope.diagnostics : sourceScope?.diagnostics
|
|
4347
6171
|
};
|
|
4348
6172
|
}
|
|
4349
6173
|
function handlerFilesForOperation(db, operationId) {
|
|
@@ -4352,12 +6176,23 @@ function handlerFilesForOperation(db, operationId) {
|
|
|
4352
6176
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?`
|
|
4353
6177
|
).get(operationId);
|
|
4354
6178
|
if (!op) return /* @__PURE__ */ new Set();
|
|
4355
|
-
const operation =
|
|
6179
|
+
const operation = normalizeOperation2(op.operationPath ?? op.operationName);
|
|
4356
6180
|
const rows2 = db.prepare(
|
|
4357
6181
|
`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId FROM handler_classes hc
|
|
4358
6182
|
JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
4359
|
-
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
4360
|
-
|
|
6183
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
6184
|
+
AND sym.source_file=hc.source_file
|
|
6185
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
6186
|
+
AND sym.start_line=hm.source_line
|
|
6187
|
+
WHERE hc.repo_id=?
|
|
6188
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
6189
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
6190
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
6191
|
+
ELSE 'unsupported' END)='operation'
|
|
6192
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
6193
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
6194
|
+
THEN 1 ELSE 0 END)=1
|
|
6195
|
+
AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`
|
|
4361
6196
|
).all(op.repoId, operation, operation, op.operationName);
|
|
4362
6197
|
return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
4363
6198
|
}
|
|
@@ -4372,7 +6207,9 @@ function handlerMethodNode(db, methodId) {
|
|
|
4372
6207
|
function implementationScope(db, operationId) {
|
|
4373
6208
|
const edge = implementationEdge(db, operationId);
|
|
4374
6209
|
if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
|
|
4375
|
-
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.
|
|
6210
|
+
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.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(edge.to_id);
|
|
6211
|
+
if (!row || typeof row.symbolId !== "number")
|
|
6212
|
+
return { repoId: row?.repoId, files: /* @__PURE__ */ new Set(), edge };
|
|
4376
6213
|
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
4377
6214
|
}
|
|
4378
6215
|
function implementationMethodIdFromHint(edge, options) {
|
|
@@ -4408,8 +6245,8 @@ function contextImplementationMethodId(edge, callerRepoId, remoteEvidence, hintO
|
|
|
4408
6245
|
return { blocksAutomatic: false, evidence: hinted.evidence.reason === "no_scoped_hint_matched_edge" ? hinted.evidence : { status: "tied", tieReason: scores.length > 1 ? "duplicate_helper_implementation_candidates" : "no_unique_materially_stronger_candidate", candidateScores: scores } };
|
|
4409
6246
|
}
|
|
4410
6247
|
function handlerScope(db, methodId) {
|
|
4411
|
-
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.
|
|
4412
|
-
if (!row) return void 0;
|
|
6248
|
+
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.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(methodId);
|
|
6249
|
+
if (!row || typeof row.symbolId !== "number") return void 0;
|
|
4413
6250
|
return { repoId: row.repoId, files: new Set(row.sourceFile ? [row.sourceFile] : []), symbolId: row.symbolId };
|
|
4414
6251
|
}
|
|
4415
6252
|
function traceEdgeType(call, row) {
|
|
@@ -4616,20 +6453,22 @@ function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRo
|
|
|
4616
6453
|
}
|
|
4617
6454
|
function trace(db, start, options) {
|
|
4618
6455
|
const hintOptions = { implementationRepo: options.implementationRepo, implementationHints: options.implementationHints };
|
|
4619
|
-
const scope = startScope(db, start, hintOptions);
|
|
4620
|
-
const
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
6456
|
+
const scope = startScope(db, start, hintOptions, options.workspaceId);
|
|
6457
|
+
const hasSelector = Boolean(start.repo || start.handler || start.operation || start.operationPath || start.servicePath);
|
|
6458
|
+
const diagnosticRepoId = scope.executionRepoId ?? scope.repo?.id;
|
|
6459
|
+
const diagnostics = loadTraceDiagnostics(
|
|
6460
|
+
db,
|
|
6461
|
+
diagnosticRepoId,
|
|
6462
|
+
!hasSelector,
|
|
6463
|
+
options.workspaceId
|
|
6464
|
+
);
|
|
6465
|
+
const stale = diagnosticRepoId !== void 0 || !hasSelector ? db.prepare("SELECT name,graph_stale_reason reason FROM repositories WHERE graph_stale_reason IS NOT NULL AND (? IS NULL OR id=?) AND (? IS NULL OR workspace_id=?) ORDER BY name,id").all(diagnosticRepoId, diagnosticRepoId, options.workspaceId, options.workspaceId) : [];
|
|
4624
6466
|
for (const row of stale)
|
|
4625
|
-
diagnostics
|
|
4626
|
-
for (const diagnostic of scope.startDiagnostics ?? [])
|
|
6467
|
+
prependTraceDiagnostic(diagnostics, { severity: "warning", code: "graph_stale", message: `Graph is stale for ${row.name ?? "repository"}: ${row.reason ?? "facts_changed"}. Run service-flow link.` });
|
|
6468
|
+
for (const diagnostic of scope.startDiagnostics ?? [])
|
|
6469
|
+
prependTraceDiagnostic(diagnostics, diagnostic);
|
|
4627
6470
|
if (!scope.selectorMatched && !scope.startDiagnostics?.length)
|
|
4628
|
-
diagnostics
|
|
4629
|
-
severity: "warning",
|
|
4630
|
-
code: "trace_start_not_found",
|
|
4631
|
-
message: start.servicePath && !start.operation && !start.operationPath && !start.handler ? "Service-only trace requires --operation or --path and will not broaden to the whole workspace" : "No handler source matched the requested trace start selector"
|
|
4632
|
-
});
|
|
6471
|
+
prependTraceDiagnostic(diagnostics, selectorNotFoundDiagnostic(start));
|
|
4633
6472
|
const maxDepth = positiveDepth(options.depth);
|
|
4634
6473
|
const edges = [];
|
|
4635
6474
|
const nodes = /* @__PURE__ */ new Map();
|
|
@@ -4658,8 +6497,8 @@ function trace(db, start, options) {
|
|
|
4658
6497
|
if (seenScopes.has(key)) continue;
|
|
4659
6498
|
seenScopes.add(key);
|
|
4660
6499
|
const calls = db.prepare(
|
|
4661
|
-
`SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) ORDER BY c.source_file,c.source_line`
|
|
4662
|
-
).all(current.repoId, current.repoId);
|
|
6500
|
+
`SELECT c.*,r.name repoName FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE (? IS NULL OR c.repo_id=?) AND (? IS NULL OR r.workspace_id=?) ORDER BY c.source_file,c.source_line`
|
|
6501
|
+
).all(current.repoId, current.repoId, options.workspaceId, options.workspaceId);
|
|
4663
6502
|
const filtered = calls.filter(
|
|
4664
6503
|
(c) => (!current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
|
|
4665
6504
|
);
|
|
@@ -4738,7 +6577,7 @@ function trace(db, start, options) {
|
|
|
4738
6577
|
if (implementation.edge) {
|
|
4739
6578
|
const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
|
|
4740
6579
|
const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence.implementationHintSuggestions);
|
|
4741
|
-
if (hintDiagnostic) diagnostics
|
|
6580
|
+
if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
|
|
4742
6581
|
const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
|
|
4743
6582
|
const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
4744
6583
|
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
@@ -4784,16 +6623,35 @@ function trace(db, start, options) {
|
|
|
4784
6623
|
}
|
|
4785
6624
|
}
|
|
4786
6625
|
const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
|
|
4787
|
-
if (runtimeDiagnostic) diagnostics
|
|
6626
|
+
if (runtimeDiagnostic) prependTraceDiagnostic(diagnostics, runtimeDiagnostic);
|
|
6627
|
+
for (const diagnostic of runtimeNoCandidateDiagnostics(edges))
|
|
6628
|
+
prependTraceDiagnostic(diagnostics, diagnostic);
|
|
4788
6629
|
return { start, nodes: [...nodes.values()], edges, diagnostics };
|
|
4789
6630
|
}
|
|
4790
6631
|
|
|
4791
6632
|
export {
|
|
6633
|
+
upsertWorkspace,
|
|
6634
|
+
getWorkspace,
|
|
6635
|
+
upsertRepository,
|
|
6636
|
+
listRepositories,
|
|
6637
|
+
reposByName,
|
|
6638
|
+
clearRepoFacts,
|
|
6639
|
+
insertRequires,
|
|
6640
|
+
insertService,
|
|
6641
|
+
insertHandler,
|
|
6642
|
+
handlerMethodIsExecutable,
|
|
6643
|
+
insertRegistrations,
|
|
6644
|
+
insertBindings,
|
|
6645
|
+
insertExecutableSymbols,
|
|
6646
|
+
insertSymbolCalls,
|
|
6647
|
+
insertCalls,
|
|
4792
6648
|
normalizePath,
|
|
4793
6649
|
stripQuotes,
|
|
4794
6650
|
discoverRepositories,
|
|
4795
6651
|
parsePackageJson,
|
|
6652
|
+
loadPackageJsonSnapshot,
|
|
4796
6653
|
parseCdsFile,
|
|
6654
|
+
loadRepositorySourceContext,
|
|
4797
6655
|
parseDecorators,
|
|
4798
6656
|
parseHandlerRegistrations,
|
|
4799
6657
|
redactText,
|
|
@@ -4810,6 +6668,7 @@ export {
|
|
|
4810
6668
|
implementationHintSuggestions,
|
|
4811
6669
|
linkWorkspace,
|
|
4812
6670
|
parseVars,
|
|
6671
|
+
selectorRepoAmbiguousDiagnostic,
|
|
4813
6672
|
trace
|
|
4814
6673
|
};
|
|
4815
|
-
//# sourceMappingURL=chunk-
|
|
6674
|
+
//# sourceMappingURL=chunk-PTLDSHRC.js.map
|