@saptools/service-flow 0.1.49 → 0.1.51
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 +14 -0
- package/README.md +10 -2
- package/TECHNICAL-NOTE.md +6 -1
- package/dist/{chunk-XOROZHW4.js → chunk-YZJKE5UX.js} +757 -122
- package/dist/chunk-YZJKE5UX.js.map +1 -0
- package/dist/cli.js +107 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +20 -10
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +59 -1
- package/src/cli.ts +22 -0
- package/src/db/migrations.ts +4 -1
- package/src/db/repositories.ts +2 -1
- package/src/db/schema.ts +1 -1
- package/src/index.ts +1 -1
- package/src/linker/cross-repo-linker.ts +111 -6
- package/src/linker/operation-decorator-normalizer.ts +3 -3
- package/src/linker/service-resolver.ts +15 -4
- package/src/output/table-output.ts +29 -2
- package/src/parsers/decorator-parser.ts +128 -22
- package/src/trace/dynamic-branches.ts +45 -0
- package/src/trace/dynamic-targets.ts +424 -0
- package/src/trace/evidence.ts +117 -7
- package/src/trace/selectors.ts +36 -0
- package/src/trace/trace-engine.ts +12 -31
- package/src/types.ts +24 -0
- package/dist/chunk-XOROZHW4.js.map +0 -1
|
@@ -315,6 +315,46 @@ import fs4 from "fs/promises";
|
|
|
315
315
|
import path5 from "path";
|
|
316
316
|
import ts2 from "typescript";
|
|
317
317
|
|
|
318
|
+
// src/linker/operation-decorator-normalizer.ts
|
|
319
|
+
function lowerFirst(value) {
|
|
320
|
+
return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
|
|
321
|
+
}
|
|
322
|
+
function normalizedOperationName(value) {
|
|
323
|
+
return value.replace(/^\//, "");
|
|
324
|
+
}
|
|
325
|
+
function clean(value) {
|
|
326
|
+
return value.replace(/^[`'"]|[`'"]$/g, "");
|
|
327
|
+
}
|
|
328
|
+
function generatedOperationNameFromConstant(value) {
|
|
329
|
+
for (const prefix of ["Action", "Func"]) {
|
|
330
|
+
if (value.startsWith(prefix) && value.length > prefix.length && /^[A-Z]/.test(value.slice(prefix.length))) return lowerFirst(value.slice(prefix.length));
|
|
331
|
+
}
|
|
332
|
+
return void 0;
|
|
333
|
+
}
|
|
334
|
+
function resolved(value, raw) {
|
|
335
|
+
const literal2 = clean(value);
|
|
336
|
+
const generated = generatedOperationNameFromConstant(literal2);
|
|
337
|
+
return { status: "resolved", operationName: generated ?? normalizedOperationName(literal2), raw };
|
|
338
|
+
}
|
|
339
|
+
function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
|
|
340
|
+
if (value) return resolved(value, raw);
|
|
341
|
+
if (!raw || raw.trim().length === 0) return { status: "none", raw };
|
|
342
|
+
const expression = raw.trim();
|
|
343
|
+
const nameMatch = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(expression);
|
|
344
|
+
if (nameMatch?.[1]) return resolved(nameMatch[1], expression);
|
|
345
|
+
const stringMatch = /^String\(([A-Za-z_$][\w$]*)\)$/.exec(expression);
|
|
346
|
+
if (stringMatch?.[1]) {
|
|
347
|
+
const identifier = stringMatch[1];
|
|
348
|
+
const generated = generatedOperationNameFromConstant(identifier);
|
|
349
|
+
const normalizedCandidate2 = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
|
|
350
|
+
if (generated) return { status: "resolved", operationName: generated, raw: expression };
|
|
351
|
+
if (normalizedCandidate2 && identifier === normalizedCandidate2) return { status: "resolved", operationName: identifier, raw: expression };
|
|
352
|
+
return { status: "unsupported", raw: expression, reason: "string_wrapper_identifier_not_resolved" };
|
|
353
|
+
}
|
|
354
|
+
if (/^[`'"]/.test(expression) && !/[`'"]$/.test(expression)) return { status: "malformed", raw: expression, reason: "unterminated_literal" };
|
|
355
|
+
return { status: "unsupported", raw: expression, reason: "unsupported_decorator_expression" };
|
|
356
|
+
}
|
|
357
|
+
|
|
318
358
|
// src/parsers/ts-project.ts
|
|
319
359
|
import ts from "typescript";
|
|
320
360
|
function createSourceFile(filePath, text) {
|
|
@@ -340,16 +380,102 @@ function callName(d) {
|
|
|
340
380
|
}
|
|
341
381
|
function firstArg(d) {
|
|
342
382
|
const e = d.expression;
|
|
343
|
-
return ts2.isCallExpression(e)
|
|
383
|
+
return ts2.isCallExpression(e) ? e.arguments[0] : void 0;
|
|
384
|
+
}
|
|
385
|
+
function unwrapExpression(expression) {
|
|
386
|
+
let current = expression;
|
|
387
|
+
while (ts2.isParenthesizedExpression(current) || ts2.isAsExpression(current) || ts2.isTypeAssertionExpression(current) || ts2.isSatisfiesExpression(current)) current = current.expression;
|
|
388
|
+
return current;
|
|
389
|
+
}
|
|
390
|
+
function stringValue(expression) {
|
|
391
|
+
if (!expression) return void 0;
|
|
392
|
+
const unwrapped = unwrapExpression(expression);
|
|
393
|
+
return ts2.isStringLiteralLike(unwrapped) ? unwrapped.text : void 0;
|
|
394
|
+
}
|
|
395
|
+
function propertyName(node) {
|
|
396
|
+
if (ts2.isIdentifier(node) || ts2.isStringLiteralLike(node)) return node.text;
|
|
397
|
+
return void 0;
|
|
398
|
+
}
|
|
399
|
+
function collectEnumMembers(statement, lookups) {
|
|
400
|
+
for (const member of statement.members) {
|
|
401
|
+
const name = propertyName(member.name);
|
|
402
|
+
const value = stringValue(member.initializer);
|
|
403
|
+
if (name && value !== void 0)
|
|
404
|
+
lookups.enumMembers.set(`${statement.name.text}.${name}`, value);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function collectObjectProperties(name, initializer, lookups) {
|
|
408
|
+
const object = unwrapExpression(initializer);
|
|
409
|
+
if (!ts2.isObjectLiteralExpression(object)) return;
|
|
410
|
+
for (const property of object.properties) {
|
|
411
|
+
if (!ts2.isPropertyAssignment(property)) continue;
|
|
412
|
+
const key = propertyName(property.name);
|
|
413
|
+
const value = stringValue(property.initializer);
|
|
414
|
+
if (key && value !== void 0)
|
|
415
|
+
lookups.objectProperties.set(`${name}.${key}`, value);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function collectStringLookups(source) {
|
|
419
|
+
const lookups = {
|
|
420
|
+
identifiers: /* @__PURE__ */ new Map(),
|
|
421
|
+
enumMembers: /* @__PURE__ */ new Map(),
|
|
422
|
+
objectProperties: /* @__PURE__ */ new Map()
|
|
423
|
+
};
|
|
424
|
+
for (const statement of source.statements) {
|
|
425
|
+
if (ts2.isEnumDeclaration(statement)) collectEnumMembers(statement, lookups);
|
|
426
|
+
if (!ts2.isVariableStatement(statement) || !(statement.declarationList.flags & ts2.NodeFlags.Const)) continue;
|
|
427
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
428
|
+
if (!ts2.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
429
|
+
const value = stringValue(declaration.initializer);
|
|
430
|
+
if (value !== void 0) lookups.identifiers.set(declaration.name.text, value);
|
|
431
|
+
collectObjectProperties(declaration.name.text, declaration.initializer, lookups);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return lookups;
|
|
435
|
+
}
|
|
436
|
+
function unresolved(rawExpression, reason) {
|
|
437
|
+
return { rawExpression, resolutionKind: "unresolved", unresolvedReason: reason };
|
|
438
|
+
}
|
|
439
|
+
function generatedConstant(rawExpression) {
|
|
440
|
+
const match = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(rawExpression.trim());
|
|
441
|
+
return match?.[1] ? generatedOperationNameFromConstant(match[1]) : void 0;
|
|
442
|
+
}
|
|
443
|
+
function resolveDecoratorArgument(argument, lookups) {
|
|
444
|
+
if (!argument) return unresolved("", "decorator_argument_missing");
|
|
445
|
+
const rawExpression = argument.getText();
|
|
446
|
+
const expression = unwrapExpression(argument);
|
|
447
|
+
if (ts2.isStringLiteralLike(expression))
|
|
448
|
+
return { rawExpression, resolvedValue: expression.text, resolutionKind: "literal" };
|
|
449
|
+
if (ts2.isIdentifier(expression)) {
|
|
450
|
+
const value = lookups.identifiers.get(expression.text);
|
|
451
|
+
return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string") : { rawExpression, resolvedValue: value, resolutionKind: "const_identifier" };
|
|
452
|
+
}
|
|
453
|
+
if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression)) {
|
|
454
|
+
const key = `${expression.expression.text}.${expression.name.text}`;
|
|
455
|
+
const enumValue = lookups.enumMembers.get(key);
|
|
456
|
+
if (enumValue !== void 0)
|
|
457
|
+
return { rawExpression, resolvedValue: enumValue, resolutionKind: "enum_member" };
|
|
458
|
+
const objectValue2 = lookups.objectProperties.get(key);
|
|
459
|
+
if (objectValue2 !== void 0)
|
|
460
|
+
return { rawExpression, resolvedValue: objectValue2, resolutionKind: "const_object_property" };
|
|
461
|
+
}
|
|
462
|
+
const generatedValue = generatedConstant(rawExpression);
|
|
463
|
+
if (generatedValue !== void 0)
|
|
464
|
+
return {
|
|
465
|
+
rawExpression,
|
|
466
|
+
resolvedValue: generatedValue,
|
|
467
|
+
resolutionKind: "generated_constant_name"
|
|
468
|
+
};
|
|
469
|
+
if (ts2.isPropertyAccessExpression(expression))
|
|
470
|
+
return unresolved(rawExpression, "property_access_not_resolved_to_local_string");
|
|
471
|
+
return unresolved(rawExpression, "unsupported_decorator_expression");
|
|
344
472
|
}
|
|
345
473
|
async function parseDecorators(repoPath, filePath) {
|
|
346
474
|
const text = await fs4.readFile(path5.join(repoPath, filePath), "utf8");
|
|
347
475
|
const sf = createSourceFile(filePath, text);
|
|
348
|
-
const
|
|
476
|
+
const lookups = collectStringLookups(sf);
|
|
349
477
|
const handlers = [];
|
|
350
478
|
function visit(node) {
|
|
351
|
-
if (ts2.isVariableDeclaration(node) && ts2.isIdentifier(node.name) && node.initializer && ts2.isStringLiteralLike(node.initializer))
|
|
352
|
-
constants.set(node.name.text, node.initializer.text);
|
|
353
479
|
if (ts2.isClassDeclaration(node)) {
|
|
354
480
|
const className = node.name?.text ?? "AnonymousHandler";
|
|
355
481
|
const hasHandler = decs(node).some((d) => callName(d) === "Handler");
|
|
@@ -357,13 +483,13 @@ async function parseDecorators(repoPath, filePath) {
|
|
|
357
483
|
(m) => decs(m).filter(
|
|
358
484
|
(d) => ["Func", "Action", "On", "Event"].includes(callName(d))
|
|
359
485
|
).map((d) => {
|
|
360
|
-
const
|
|
361
|
-
const value = raw.startsWith('"') || raw.startsWith("'") || raw.startsWith("`") ? stripQuotes(raw) : constants.get(raw) ?? (raw.endsWith(".name") ? raw.split(".").at(-2) : void 0);
|
|
486
|
+
const decoratorResolution = resolveDecoratorArgument(firstArg(d), lookups);
|
|
362
487
|
return {
|
|
363
488
|
methodName: m.name.getText(),
|
|
364
489
|
decoratorKind: callName(d),
|
|
365
|
-
decoratorValue:
|
|
366
|
-
decoratorRawExpression:
|
|
490
|
+
decoratorValue: decoratorResolution.resolvedValue,
|
|
491
|
+
decoratorRawExpression: decoratorResolution.rawExpression,
|
|
492
|
+
decoratorResolution,
|
|
367
493
|
sourceFile: normalizePath(filePath),
|
|
368
494
|
sourceLine: line(sf, m.getStart())
|
|
369
495
|
};
|
|
@@ -588,7 +714,7 @@ import ts4 from "typescript";
|
|
|
588
714
|
function lineOf3(sf, node) {
|
|
589
715
|
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
590
716
|
}
|
|
591
|
-
function
|
|
717
|
+
function stringValue2(node) {
|
|
592
718
|
if (!node) return void 0;
|
|
593
719
|
if (ts4.isStringLiteralLike(node) || ts4.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
594
720
|
if (ts4.isTemplateExpression(node)) return node.getText().replace(/^`|`$/g, "");
|
|
@@ -609,7 +735,7 @@ function connectFactFromCall(call) {
|
|
|
609
735
|
let alias;
|
|
610
736
|
let aliasExpr;
|
|
611
737
|
if (ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) alias = first.text;
|
|
612
|
-
else if (!ts4.isObjectLiteralExpression(first)) aliasExpr =
|
|
738
|
+
else if (!ts4.isObjectLiteralExpression(first)) aliasExpr = stringValue2(first);
|
|
613
739
|
if ((ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) && !objectArg) return { alias: first.text, isDynamic: false, placeholders: [] };
|
|
614
740
|
if (!objectArg && aliasExpr) return { aliasExpr, isDynamic: true, placeholders: placeholders(aliasExpr) };
|
|
615
741
|
const expressions = objectArg ? objectExpressions(objectArg) : {};
|
|
@@ -622,8 +748,8 @@ function objectExpressions(objectArg) {
|
|
|
622
748
|
for (const prop of obj.properties) {
|
|
623
749
|
if (!ts4.isPropertyAssignment(prop)) continue;
|
|
624
750
|
const name = ts4.isIdentifier(prop.name) || ts4.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
625
|
-
if (name === "destination") out.destinationExpr =
|
|
626
|
-
if (name === "path" || name === "servicePath") out.servicePathExpr =
|
|
751
|
+
if (name === "destination") out.destinationExpr = stringValue2(prop.initializer);
|
|
752
|
+
if (name === "path" || name === "servicePath") out.servicePathExpr = stringValue2(prop.initializer);
|
|
627
753
|
if (ts4.isObjectLiteralExpression(prop.initializer)) visitObject(prop.initializer);
|
|
628
754
|
}
|
|
629
755
|
}
|
|
@@ -733,8 +859,8 @@ function collectReturnedObjectBindings(fn) {
|
|
|
733
859
|
for (const prop of node.expression.properties) {
|
|
734
860
|
if (ts5.isShorthandPropertyAssignment(prop)) returns.set(prop.name.text, prop.name.text);
|
|
735
861
|
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
|
|
736
|
-
const
|
|
737
|
-
if (
|
|
862
|
+
const propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
863
|
+
if (propertyName3) returns.set(propertyName3, prop.initializer.text);
|
|
738
864
|
}
|
|
739
865
|
}
|
|
740
866
|
}
|
|
@@ -742,9 +868,9 @@ function collectReturnedObjectBindings(fn) {
|
|
|
742
868
|
}
|
|
743
869
|
visit(fn);
|
|
744
870
|
const out = /* @__PURE__ */ new Map();
|
|
745
|
-
for (const [
|
|
871
|
+
for (const [propertyName3, variableName] of returns) {
|
|
746
872
|
const fact = bindings.get(variableName);
|
|
747
|
-
if (fact) out.set(
|
|
873
|
+
if (fact) out.set(propertyName3, fact);
|
|
748
874
|
}
|
|
749
875
|
return out;
|
|
750
876
|
}
|
|
@@ -1028,8 +1154,8 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1028
1154
|
if (helpers.length === 0) return;
|
|
1029
1155
|
for (const el of decl.name.elements) {
|
|
1030
1156
|
if (!ts5.isIdentifier(el.name)) continue;
|
|
1031
|
-
const
|
|
1032
|
-
const matches2 = helpers.filter((row) => row.helper.returnedProperty ===
|
|
1157
|
+
const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
1158
|
+
const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
|
|
1033
1159
|
if (matches2.length !== 1) continue;
|
|
1034
1160
|
const resolved2 = matches2[0];
|
|
1035
1161
|
out.push({
|
|
@@ -1042,7 +1168,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1042
1168
|
placeholders: resolved2.helper.placeholders,
|
|
1043
1169
|
sourceFile: normalizePath(filePath),
|
|
1044
1170
|
sourceLine: lineOf3(sourceFileAst, decl),
|
|
1045
|
-
helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty:
|
|
1171
|
+
helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName3, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
|
|
1046
1172
|
});
|
|
1047
1173
|
}
|
|
1048
1174
|
}
|
|
@@ -1052,17 +1178,17 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1052
1178
|
const helpers = await helpersForCall(call);
|
|
1053
1179
|
if (helpers.length === 0) return;
|
|
1054
1180
|
for (const prop of pattern.properties) {
|
|
1055
|
-
let
|
|
1181
|
+
let propertyName3;
|
|
1056
1182
|
let targetName;
|
|
1057
1183
|
if (ts5.isShorthandPropertyAssignment(prop)) {
|
|
1058
|
-
|
|
1184
|
+
propertyName3 = prop.name.text;
|
|
1059
1185
|
targetName = prop.name.text;
|
|
1060
1186
|
} else if (ts5.isPropertyAssignment(prop)) {
|
|
1061
|
-
|
|
1187
|
+
propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
1062
1188
|
targetName = ts5.isIdentifier(prop.initializer) ? prop.initializer.text : void 0;
|
|
1063
1189
|
}
|
|
1064
|
-
if (!
|
|
1065
|
-
const matches2 = helpers.filter((row) => row.helper.returnedProperty ===
|
|
1190
|
+
if (!propertyName3 || !targetName) continue;
|
|
1191
|
+
const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
|
|
1066
1192
|
if (matches2.length !== 1) continue;
|
|
1067
1193
|
const resolved2 = matches2[0];
|
|
1068
1194
|
out.push({
|
|
@@ -1075,7 +1201,7 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1075
1201
|
placeholders: resolved2.helper.placeholders,
|
|
1076
1202
|
sourceFile: normalizePath(filePath),
|
|
1077
1203
|
sourceLine: lineOf3(sourceFileAst, node),
|
|
1078
|
-
helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty:
|
|
1204
|
+
helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName3, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
|
|
1079
1205
|
});
|
|
1080
1206
|
}
|
|
1081
1207
|
}
|
|
@@ -1087,9 +1213,9 @@ async function parseServiceBindings(repoPath, filePath) {
|
|
|
1087
1213
|
if (target.expression.kind !== ts5.SyntaxKind.ThisKeyword) return;
|
|
1088
1214
|
for (const el of decl.name.elements) {
|
|
1089
1215
|
if (!ts5.isIdentifier(el.name)) continue;
|
|
1090
|
-
const
|
|
1216
|
+
const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
1091
1217
|
const helper = classHelpers.find(
|
|
1092
|
-
(h) => h.helperName === target.name.text && h.propertyName ===
|
|
1218
|
+
(h) => h.helperName === target.name.text && h.propertyName === propertyName3
|
|
1093
1219
|
);
|
|
1094
1220
|
if (!helper) continue;
|
|
1095
1221
|
out.push({
|
|
@@ -1242,13 +1368,13 @@ function collectClassHelpers(sf) {
|
|
|
1242
1368
|
});
|
|
1243
1369
|
}
|
|
1244
1370
|
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
|
|
1245
|
-
const
|
|
1246
|
-
const fact =
|
|
1247
|
-
if (
|
|
1371
|
+
const propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
|
|
1372
|
+
const fact = propertyName3 ? bindings.get(prop.initializer.text) : void 0;
|
|
1373
|
+
if (propertyName3 && fact)
|
|
1248
1374
|
helpers.push({
|
|
1249
1375
|
className,
|
|
1250
1376
|
helperName,
|
|
1251
|
-
propertyName:
|
|
1377
|
+
propertyName: propertyName3,
|
|
1252
1378
|
variableName: prop.initializer.text,
|
|
1253
1379
|
fact,
|
|
1254
1380
|
sourceLine: lineOf3(sf, prop)
|
|
@@ -1944,12 +2070,12 @@ function mappedParameterIndex(expr, parameters) {
|
|
|
1944
2070
|
}
|
|
1945
2071
|
function propertyExpression(object, key) {
|
|
1946
2072
|
for (const property of object.properties) {
|
|
1947
|
-
if (ts7.isPropertyAssignment(property) &&
|
|
2073
|
+
if (ts7.isPropertyAssignment(property) && propertyName2(property.name) === key) return property.initializer;
|
|
1948
2074
|
if (ts7.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
|
|
1949
2075
|
}
|
|
1950
2076
|
return void 0;
|
|
1951
2077
|
}
|
|
1952
|
-
function
|
|
2078
|
+
function propertyName2(name) {
|
|
1953
2079
|
return ts7.isIdentifier(name) || ts7.isStringLiteralLike(name) ? name.text : void 0;
|
|
1954
2080
|
}
|
|
1955
2081
|
function literal(expr) {
|
|
@@ -2716,8 +2842,8 @@ function buildRemoteQueryTarget(input) {
|
|
|
2716
2842
|
// src/linker/service-resolver.ts
|
|
2717
2843
|
function rows(db, operationPath, workspaceId) {
|
|
2718
2844
|
const names = operationLookupNames(operationPath);
|
|
2719
|
-
|
|
2720
|
-
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score
|
|
2845
|
+
const result = db.prepare(
|
|
2846
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score
|
|
2721
2847
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
2722
2848
|
WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path IN (?,?) OR o.operation_name IN (?,?)) ORDER BY r.name,s.service_path,o.operation_name`
|
|
2723
2849
|
).all(
|
|
@@ -2728,6 +2854,11 @@ function rows(db, operationPath, workspaceId) {
|
|
|
2728
2854
|
names.name,
|
|
2729
2855
|
names.simpleName
|
|
2730
2856
|
);
|
|
2857
|
+
return result.map((row) => ({
|
|
2858
|
+
...row,
|
|
2859
|
+
score: Number(row.score ?? 0),
|
|
2860
|
+
reasons: []
|
|
2861
|
+
}));
|
|
2731
2862
|
}
|
|
2732
2863
|
function operationLookupNames(operationPath) {
|
|
2733
2864
|
const name = operationPath.replace(/^\//, "");
|
|
@@ -2744,7 +2875,11 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
2744
2875
|
if (missing.length > 0)
|
|
2745
2876
|
return {
|
|
2746
2877
|
status: "dynamic",
|
|
2747
|
-
candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId)
|
|
2878
|
+
candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId).map((candidate) => ({
|
|
2879
|
+
...candidate,
|
|
2880
|
+
score: 0.2,
|
|
2881
|
+
reasons: ["operation_path_match"]
|
|
2882
|
+
})) : [],
|
|
2748
2883
|
reasons: [...new Set(missing)].map((name) => `missing_variable:${name}`)
|
|
2749
2884
|
};
|
|
2750
2885
|
if (!signals.operationPath)
|
|
@@ -2918,46 +3053,6 @@ function linkHelperPackages(db, workspaceId, generation) {
|
|
|
2918
3053
|
return summary;
|
|
2919
3054
|
}
|
|
2920
3055
|
|
|
2921
|
-
// src/linker/operation-decorator-normalizer.ts
|
|
2922
|
-
function lowerFirst(value) {
|
|
2923
|
-
return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
|
|
2924
|
-
}
|
|
2925
|
-
function normalizedOperationName(value) {
|
|
2926
|
-
return value.replace(/^\//, "");
|
|
2927
|
-
}
|
|
2928
|
-
function clean(value) {
|
|
2929
|
-
return value.replace(/^[`'"]|[`'"]$/g, "");
|
|
2930
|
-
}
|
|
2931
|
-
function generatedFromConstantName(value) {
|
|
2932
|
-
for (const prefix of ["Action", "Func"]) {
|
|
2933
|
-
if (value.startsWith(prefix) && value.length > prefix.length && /^[A-Z]/.test(value.slice(prefix.length))) return lowerFirst(value.slice(prefix.length));
|
|
2934
|
-
}
|
|
2935
|
-
return void 0;
|
|
2936
|
-
}
|
|
2937
|
-
function resolved(value, raw) {
|
|
2938
|
-
const literal2 = clean(value);
|
|
2939
|
-
const generated = generatedFromConstantName(literal2);
|
|
2940
|
-
return { status: "resolved", operationName: generated ?? normalizedOperationName(literal2), raw };
|
|
2941
|
-
}
|
|
2942
|
-
function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
|
|
2943
|
-
if (value) return resolved(value, raw);
|
|
2944
|
-
if (!raw || raw.trim().length === 0) return { status: "none", raw };
|
|
2945
|
-
const expression = raw.trim();
|
|
2946
|
-
const nameMatch = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(expression);
|
|
2947
|
-
if (nameMatch?.[1]) return resolved(nameMatch[1], expression);
|
|
2948
|
-
const stringMatch = /^String\(([A-Za-z_$][\w$]*)\)$/.exec(expression);
|
|
2949
|
-
if (stringMatch?.[1]) {
|
|
2950
|
-
const identifier = stringMatch[1];
|
|
2951
|
-
const generated = generatedFromConstantName(identifier);
|
|
2952
|
-
const normalizedCandidate2 = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
|
|
2953
|
-
if (generated) return { status: "resolved", operationName: generated, raw: expression };
|
|
2954
|
-
if (normalizedCandidate2 && identifier === normalizedCandidate2) return { status: "resolved", operationName: identifier, raw: expression };
|
|
2955
|
-
return { status: "unsupported", raw: expression, reason: "string_wrapper_identifier_not_resolved" };
|
|
2956
|
-
}
|
|
2957
|
-
if (/^[`'"]/.test(expression) && !/[`'"]$/.test(expression)) return { status: "malformed", raw: expression, reason: "unterminated_literal" };
|
|
2958
|
-
return { status: "unsupported", raw: expression, reason: "unsupported_decorator_expression" };
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
3056
|
// src/linker/cross-repo-linker.ts
|
|
2962
3057
|
function linkWorkspace(db, workspaceId, vars = {}) {
|
|
2963
3058
|
return db.transaction(() => {
|
|
@@ -3140,14 +3235,20 @@ function objectJson(value) {
|
|
|
3140
3235
|
}
|
|
3141
3236
|
function callEvidence(call, resolution, servicePath, op, destination, normalized, odataPathIntent) {
|
|
3142
3237
|
const bindingHasDynamicExpression = Boolean(Number(call.isDynamic ?? 0));
|
|
3143
|
-
const routingPlaceholderKeys = placeholderKeys([servicePath, destination,
|
|
3238
|
+
const routingPlaceholderKeys = placeholderKeys([servicePath, destination, stringValue3(call.aliasExpr), stringValue3(call.alias)]);
|
|
3144
3239
|
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, callId: call.id, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : odataPathIntent?.rawPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArguments: normalized?.wasInvocation ? normalized.invocationArguments : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, routingPlaceholderKeys: routingPlaceholderKeys.length ? routingPlaceholderKeys : void 0, odataOperationNormalizationReason: normalized?.normalizationReason, odataOperationNormalizationRejectedReason: normalized?.normalizationRejectedReason, localServiceName: call.local_service_name, localServiceLookup: call.local_service_lookup, aliasChain: parseJson(call.alias_chain_json), transport: call.call_type === "local_service_call" ? "local" : void 0, targetRepo: resolution.target?.repoName, targetServicePath: resolution.target?.servicePath, targetOperationPath: resolution.target?.operationPath, targetOperation: resolution.target?.operationName, helperChain: parseJson(call.helperChainJson), candidates: resolution.candidates, candidateScores: compactCandidateScores(resolution.candidates), candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, odataPathIntent, queryStringPresent: odataPathIntent?.hasQueryString || void 0, queryPlaceholderKeys: odataPathIntent?.placeholderKeys.length ? odataPathIntent.placeholderKeys : void 0, bindingHasDynamicExpression: bindingHasDynamicExpression || void 0, outboundEvidence: objectJson(call.evidence_json), analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
3145
3240
|
}
|
|
3146
3241
|
function compactCandidateScores(candidates) {
|
|
3147
3242
|
return candidates.flatMap((candidate) => {
|
|
3148
3243
|
const row = objectValue(candidate);
|
|
3149
3244
|
if (!row) return [];
|
|
3150
|
-
return [{
|
|
3245
|
+
return [{
|
|
3246
|
+
repo: row.repoName,
|
|
3247
|
+
servicePath: row.servicePath,
|
|
3248
|
+
operationPath: row.operationPath,
|
|
3249
|
+
score: row.score,
|
|
3250
|
+
reasons: Array.isArray(row.reasons) ? row.reasons.filter((reason) => typeof reason === "string") : ["operation_path_match"]
|
|
3251
|
+
}];
|
|
3151
3252
|
});
|
|
3152
3253
|
}
|
|
3153
3254
|
function placeholderKeys(values) {
|
|
@@ -3157,7 +3258,7 @@ function placeholderKeys(values) {
|
|
|
3157
3258
|
function objectValue(value) {
|
|
3158
3259
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3159
3260
|
}
|
|
3160
|
-
function
|
|
3261
|
+
function stringValue3(value) {
|
|
3161
3262
|
return typeof value === "string" ? value : void 0;
|
|
3162
3263
|
}
|
|
3163
3264
|
function linkImplementations(db, workspaceId, generation) {
|
|
@@ -3212,13 +3313,54 @@ function implementationContextForOperation(db, operation) {
|
|
|
3212
3313
|
}
|
|
3213
3314
|
function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
3214
3315
|
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
3215
|
-
|
|
3316
|
+
const invalid = rows2.filter((row) => !validRegistrationPair(row));
|
|
3317
|
+
recordRegistrationInvariantDiagnostics(db, invalid);
|
|
3318
|
+
return deduplicateCandidates(
|
|
3319
|
+
rows2.filter(validRegistrationPair).map((row) => scoreImplementationCandidate(row, operation))
|
|
3320
|
+
).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
|
|
3321
|
+
}
|
|
3322
|
+
function validRegistrationPair(row) {
|
|
3323
|
+
if (row.registrationHandlerClassId === null || row.registrationHandlerClassId === void 0)
|
|
3324
|
+
return registrationPairingStrategy(row) !== "unproven";
|
|
3325
|
+
return Number(row.registrationHandlerClassId) === Number(row.classId);
|
|
3326
|
+
}
|
|
3327
|
+
function registrationPairingStrategy(row) {
|
|
3328
|
+
if (row.registrationHandlerClassId !== null && row.registrationHandlerClassId !== void 0)
|
|
3329
|
+
return "exact_handler_class_id";
|
|
3330
|
+
if (Number(row.applicationRepoId) === Number(row.handlerRepoId))
|
|
3331
|
+
return "same_repository_class_name_fallback";
|
|
3332
|
+
const source = stringValue3(row.importSource);
|
|
3333
|
+
const separator = source?.lastIndexOf("#") ?? -1;
|
|
3334
|
+
if (!source || separator <= 0) return "unproven";
|
|
3335
|
+
const moduleName = source.slice(0, separator);
|
|
3336
|
+
const importedName = source.slice(separator + 1);
|
|
3337
|
+
const matchesClass = importedName === row.className || importedName === "default" && row.registrationClassName === row.className;
|
|
3338
|
+
return moduleName === row.handlerPackage && matchesClass ? "explicit_package_import" : "unproven";
|
|
3339
|
+
}
|
|
3340
|
+
function recordRegistrationInvariantDiagnostics(db, rows2) {
|
|
3341
|
+
const insert = db.prepare(`INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line)
|
|
3342
|
+
SELECT ?,'error','handler_registration_class_mismatch',
|
|
3343
|
+
'Implementation candidate registration did not match its persisted handler class id',?,?
|
|
3344
|
+
WHERE NOT EXISTS (
|
|
3345
|
+
SELECT 1 FROM diagnostics
|
|
3346
|
+
WHERE repo_id=? AND code='handler_registration_class_mismatch'
|
|
3347
|
+
AND source_file=? AND source_line=?
|
|
3348
|
+
)`);
|
|
3349
|
+
for (const row of rows2)
|
|
3350
|
+
insert.run(
|
|
3351
|
+
row.applicationRepoId,
|
|
3352
|
+
row.registrationFile,
|
|
3353
|
+
row.registrationLine,
|
|
3354
|
+
row.applicationRepoId,
|
|
3355
|
+
row.registrationFile,
|
|
3356
|
+
row.registrationLine
|
|
3357
|
+
);
|
|
3216
3358
|
}
|
|
3217
3359
|
function deduplicateCandidates(rows2) {
|
|
3218
3360
|
const merged = /* @__PURE__ */ new Map();
|
|
3219
3361
|
for (const row of rows2) {
|
|
3220
3362
|
const key = [row.methodId, row.classId, row.handlerRepoId].join(":");
|
|
3221
|
-
const registration =
|
|
3363
|
+
const registration = registrationEvidence(row);
|
|
3222
3364
|
const existing = merged.get(key);
|
|
3223
3365
|
if (!existing) {
|
|
3224
3366
|
merged.set(key, { ...row, registrations: [registration] });
|
|
@@ -3232,6 +3374,17 @@ function deduplicateCandidates(rows2) {
|
|
|
3232
3374
|
}
|
|
3233
3375
|
return [...merged.values()];
|
|
3234
3376
|
}
|
|
3377
|
+
function registrationEvidence(row) {
|
|
3378
|
+
return {
|
|
3379
|
+
id: row.registrationId,
|
|
3380
|
+
handlerClassId: row.registrationHandlerClassId,
|
|
3381
|
+
file: row.registrationFile,
|
|
3382
|
+
line: row.registrationLine,
|
|
3383
|
+
kind: row.registrationKind,
|
|
3384
|
+
importSource: row.importSource,
|
|
3385
|
+
pairingStrategy: registrationPairingStrategy(row)
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3235
3388
|
function uniqueRegistrations(rows2) {
|
|
3236
3389
|
const seen = /* @__PURE__ */ new Set();
|
|
3237
3390
|
return rows2.filter((row) => {
|
|
@@ -3260,10 +3413,14 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
3260
3413
|
hm.method_name methodName,
|
|
3261
3414
|
hm.decorator_value decoratorValue,
|
|
3262
3415
|
hm.decorator_raw_expression decoratorRawExpression,
|
|
3416
|
+
hm.decorator_resolution_json decoratorResolutionJson,
|
|
3263
3417
|
hc.id classId,
|
|
3264
3418
|
hc.class_name className,
|
|
3265
3419
|
hc.source_file sourceFile,
|
|
3266
3420
|
hc.source_line sourceLine,
|
|
3421
|
+
hr.id registrationId,
|
|
3422
|
+
hr.handler_class_id registrationHandlerClassId,
|
|
3423
|
+
hr.class_name registrationClassName,
|
|
3267
3424
|
hr.repo_id applicationRepoId,
|
|
3268
3425
|
hr.registration_file registrationFile,
|
|
3269
3426
|
hr.registration_line registrationLine,
|
|
@@ -3286,14 +3443,36 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
3286
3443
|
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
3287
3444
|
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,
|
|
3288
3445
|
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
3289
|
-
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON (localClass.id=localReg.handler_class_id OR localClass.class_name=localReg.class_name) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
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,
|
|
3290
3447
|
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,
|
|
3291
3448
|
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,
|
|
3292
3449
|
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
|
|
3293
3450
|
FROM handler_methods hm
|
|
3294
3451
|
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
3295
3452
|
JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
|
|
3296
|
-
JOIN handler_registrations hr ON (
|
|
3453
|
+
JOIN handler_registrations hr ON (
|
|
3454
|
+
(hr.handler_class_id IS NOT NULL AND hr.handler_class_id=hc.id)
|
|
3455
|
+
OR (
|
|
3456
|
+
hr.handler_class_id IS NULL
|
|
3457
|
+
AND (
|
|
3458
|
+
(hr.class_name=hc.class_name AND hr.repo_id=hc.repo_id)
|
|
3459
|
+
OR (
|
|
3460
|
+
instr(hr.import_source,'#')>1
|
|
3461
|
+
AND substr(hr.import_source,1,instr(hr.import_source,'#')-1)
|
|
3462
|
+
=handlerRepo.package_name
|
|
3463
|
+
AND (
|
|
3464
|
+
substr(hr.import_source,instr(hr.import_source,'#')+1)
|
|
3465
|
+
=hc.class_name
|
|
3466
|
+
OR (
|
|
3467
|
+
substr(hr.import_source,instr(hr.import_source,'#')+1)
|
|
3468
|
+
='default'
|
|
3469
|
+
AND hr.class_name=hc.class_name
|
|
3470
|
+
)
|
|
3471
|
+
)
|
|
3472
|
+
)
|
|
3473
|
+
)
|
|
3474
|
+
)
|
|
3475
|
+
)
|
|
3297
3476
|
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
3298
3477
|
WHERE appRepo.workspace_id=?
|
|
3299
3478
|
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
|
|
@@ -3395,8 +3574,16 @@ function candidateEvidence(candidate, rank) {
|
|
|
3395
3574
|
className: candidate.className,
|
|
3396
3575
|
sourceFile: candidate.sourceFile,
|
|
3397
3576
|
sourceLine: candidate.sourceLine,
|
|
3398
|
-
|
|
3577
|
+
decoratorResolution: objectJson(candidate.decoratorResolutionJson),
|
|
3578
|
+
registration: registrationEvidence(candidate),
|
|
3399
3579
|
registrations: candidate.registrations ?? [],
|
|
3580
|
+
registrationPairing: {
|
|
3581
|
+
strategy: registrationPairingStrategy(candidate),
|
|
3582
|
+
registrationId: candidate.registrationId,
|
|
3583
|
+
registrationHandlerClassId: candidate.registrationHandlerClassId,
|
|
3584
|
+
candidateHandlerClassId: candidate.classId,
|
|
3585
|
+
invariantStatus: validRegistrationPair(candidate) ? "valid" : "invalid"
|
|
3586
|
+
},
|
|
3400
3587
|
applicationPackage: { id: candidate.applicationRepoId, name: candidate.applicationRepo, packageName: candidate.applicationPackage },
|
|
3401
3588
|
handlerPackage: { id: candidate.handlerRepoId, name: candidate.handlerRepo, packageName: candidate.handlerPackage },
|
|
3402
3589
|
modelPackage: { id: candidate.modelRepoId, name: candidate.modelRepo, packageName: candidate.modelPackage },
|
|
@@ -3436,6 +3623,337 @@ function normalizedOperation(value) {
|
|
|
3436
3623
|
return value.startsWith("/") ? value.slice(1) : value;
|
|
3437
3624
|
}
|
|
3438
3625
|
|
|
3626
|
+
// src/trace/selectors.ts
|
|
3627
|
+
function parseVars(values) {
|
|
3628
|
+
const out = {};
|
|
3629
|
+
for (const value of values ?? []) {
|
|
3630
|
+
const [key, ...rest] = value.split("=");
|
|
3631
|
+
if (key && rest.length > 0) out[key] = rest.join("=");
|
|
3632
|
+
}
|
|
3633
|
+
return out;
|
|
3634
|
+
}
|
|
3635
|
+
function ambiguousStartDiagnostic(requested, candidates, message) {
|
|
3636
|
+
const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
|
|
3637
|
+
return {
|
|
3638
|
+
severity: "warning",
|
|
3639
|
+
code: "trace_start_ambiguous",
|
|
3640
|
+
message,
|
|
3641
|
+
normalizedSelectorValue: requested,
|
|
3642
|
+
resolutionStage: "operation",
|
|
3643
|
+
resolutionStatus: "ambiguous_operation",
|
|
3644
|
+
candidates,
|
|
3645
|
+
serviceSuggestions,
|
|
3646
|
+
selectorSuggestions: fullSelectorSuggestions(candidates)
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
function fullSelectorSuggestions(candidates) {
|
|
3650
|
+
const includeRepo = new Set(candidates.map((row) => row.repoName)).size > 1;
|
|
3651
|
+
return [...new Set(candidates.flatMap((row) => {
|
|
3652
|
+
if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
|
|
3653
|
+
const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
|
|
3654
|
+
return [
|
|
3655
|
+
`${repoSelector}--service ${row.servicePath} --path ${row.operationPath}`
|
|
3656
|
+
];
|
|
3657
|
+
}))].sort();
|
|
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);
|
|
3675
|
+
return {
|
|
3676
|
+
mode,
|
|
3677
|
+
candidateCount: candidates.length,
|
|
3678
|
+
shownCandidateCount: shownCandidates.length,
|
|
3679
|
+
omittedCandidateCount: Math.max(0, candidates.length - shownCandidates.length),
|
|
3680
|
+
missingVariables,
|
|
3681
|
+
candidates,
|
|
3682
|
+
shownCandidates,
|
|
3683
|
+
suggestedVarSets: suggestedVarSets(candidates, missingVariables, order),
|
|
3684
|
+
inference
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
function candidateTargets(db, evidence, workspaceId) {
|
|
3688
|
+
const embedded = rowsFromEvidence(evidence.candidates);
|
|
3689
|
+
if (embedded.length > 0) return embedded;
|
|
3690
|
+
const operationPath = stringValue4(evidence.operationPath);
|
|
3691
|
+
if (!operationPath || extractPlaceholders(operationPath).length > 0) return [];
|
|
3692
|
+
return queryOperationTargets(db, operationPath, workspaceId);
|
|
3693
|
+
}
|
|
3694
|
+
function rowsFromEvidence(value) {
|
|
3695
|
+
if (!Array.isArray(value)) return [];
|
|
3696
|
+
return value.flatMap((item) => {
|
|
3697
|
+
const row = record(item);
|
|
3698
|
+
const operationId = numberValue(row.operationId);
|
|
3699
|
+
const repoName = stringValue4(row.repoName);
|
|
3700
|
+
const servicePath = stringValue4(row.servicePath);
|
|
3701
|
+
const operationPath = stringValue4(row.operationPath);
|
|
3702
|
+
const operationName = stringValue4(row.operationName) ?? operationPath?.replace(/^\//, "");
|
|
3703
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
|
|
3704
|
+
return [{
|
|
3705
|
+
operationId,
|
|
3706
|
+
repoName,
|
|
3707
|
+
serviceName: stringValue4(row.serviceName) ?? "",
|
|
3708
|
+
qualifiedName: stringValue4(row.qualifiedName) ?? "",
|
|
3709
|
+
servicePath,
|
|
3710
|
+
operationPath,
|
|
3711
|
+
operationName,
|
|
3712
|
+
sourceFile: stringValue4(row.sourceFile) ?? "",
|
|
3713
|
+
sourceLine: numberValue(row.sourceLine) ?? 0,
|
|
3714
|
+
repoId: numberValue(row.repoId),
|
|
3715
|
+
packageName: stringValue4(row.packageName),
|
|
3716
|
+
score: numberValue(row.score) ?? 0,
|
|
3717
|
+
reasons: stringArray(row.reasons)
|
|
3718
|
+
}];
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
function queryOperationTargets(db, operationPath, workspaceId) {
|
|
3722
|
+
const simple = operationPath.replace(/^\//, "").split(".").at(-1) ?? operationPath;
|
|
3723
|
+
const rows2 = db.prepare(
|
|
3724
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
|
|
3725
|
+
s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
|
|
3726
|
+
o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
|
|
3727
|
+
o.source_line sourceLine
|
|
3728
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
3729
|
+
JOIN repositories r ON r.id=s.repo_id
|
|
3730
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
3731
|
+
AND (o.operation_path IN (?,?) OR o.operation_name=?)
|
|
3732
|
+
ORDER BY r.name,s.service_path,o.operation_name`
|
|
3733
|
+
).all(workspaceId, workspaceId, operationPath, `/${simple}`, simple);
|
|
3734
|
+
return rows2.map((row) => ({
|
|
3735
|
+
operationId: Number(row.operationId),
|
|
3736
|
+
repoId: numberValue(row.repoId),
|
|
3737
|
+
repoName: String(row.repoName),
|
|
3738
|
+
packageName: stringValue4(row.packageName),
|
|
3739
|
+
serviceName: String(row.serviceName),
|
|
3740
|
+
qualifiedName: String(row.qualifiedName),
|
|
3741
|
+
servicePath: String(row.servicePath),
|
|
3742
|
+
operationPath: String(row.operationPath),
|
|
3743
|
+
operationName: String(row.operationName),
|
|
3744
|
+
sourceFile: String(row.sourceFile),
|
|
3745
|
+
sourceLine: Number(row.sourceLine),
|
|
3746
|
+
score: 0.2,
|
|
3747
|
+
reasons: ["operation_path_match"]
|
|
3748
|
+
}));
|
|
3749
|
+
}
|
|
3750
|
+
function rankedCandidates(db, candidates, references, templates, order) {
|
|
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) {
|
|
3770
|
+
return {
|
|
3771
|
+
candidateOperationId: candidate.operationId,
|
|
3772
|
+
repoName: candidate.repoName,
|
|
3773
|
+
packageName: candidate.packageName ?? void 0,
|
|
3774
|
+
servicePath: candidate.servicePath,
|
|
3775
|
+
operationPath: candidate.operationPath,
|
|
3776
|
+
operationName: candidate.operationName,
|
|
3777
|
+
derivedVariables: {},
|
|
3778
|
+
derivedVariableSources: {},
|
|
3779
|
+
missingVariables: [],
|
|
3780
|
+
score: Math.max(0.2, Number(candidate.score ?? 0)),
|
|
3781
|
+
reasons: nonEmptyStrings(candidate.reasons, ["operation_path_match"]),
|
|
3782
|
+
rejectedReasons: []
|
|
3783
|
+
};
|
|
3784
|
+
}
|
|
3785
|
+
function applyDirectTemplate(state, template, concrete, kind) {
|
|
3786
|
+
const matched = matchTemplate(template, concrete);
|
|
3787
|
+
if (!template) return;
|
|
3788
|
+
if (!matched) {
|
|
3789
|
+
state.rejectedReasons.push(`${kind}_template_mismatch`);
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3792
|
+
mergeDerived(state, matched, `${kind}_template`);
|
|
3793
|
+
addScore(state, kind === "service_path" ? 0.35 : 0.25, `${kind}_template_match`);
|
|
3794
|
+
}
|
|
3795
|
+
function applyReferenceTemplate(state, template, refs, kind) {
|
|
3796
|
+
if (!template || extractPlaceholders(template).length === 0) return;
|
|
3797
|
+
for (const ref of refs) {
|
|
3798
|
+
const concrete = kind === "alias" ? ref.alias : ref.destination;
|
|
3799
|
+
const matched = isConcrete(concrete) ? matchTemplate(template, concrete) : void 0;
|
|
3800
|
+
if (!matched) continue;
|
|
3801
|
+
mergeDerived(state, matched, `${ref.sourceKind}.${kind}`);
|
|
3802
|
+
addScore(state, 0.2, `${kind}_template_match`);
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3805
|
+
if (refs.some((ref) => isConcrete(kind === "alias" ? ref.alias : ref.destination)))
|
|
3806
|
+
state.rejectedReasons.push(`${kind}_template_mismatch`);
|
|
3807
|
+
}
|
|
3808
|
+
function inferenceDecision(candidates) {
|
|
3809
|
+
const complete = candidates.filter((candidate) => candidate.missingVariables.length === 0 && candidate.rejectedReasons.length === 0);
|
|
3810
|
+
const first = complete[0];
|
|
3811
|
+
const second = complete[1];
|
|
3812
|
+
if (!first) return { status: "unresolved", reason: "missing_required_runtime_variable" };
|
|
3813
|
+
if (first.score < 0.85) return { status: "unresolved", reason: "candidate_score_below_inference_threshold" };
|
|
3814
|
+
if (second && first.score - second.score <= 0.05) {
|
|
3815
|
+
for (const candidate of complete.filter((item) => first.score - item.score <= 0.05))
|
|
3816
|
+
candidate.rejectedReasons.push("candidate_tied_with_equal_score");
|
|
3817
|
+
return { status: "ambiguous", reason: "candidate_tied_with_equal_score" };
|
|
3818
|
+
}
|
|
3819
|
+
return {
|
|
3820
|
+
status: "resolved",
|
|
3821
|
+
candidateOperationId: first.candidateOperationId,
|
|
3822
|
+
inferredVariables: first.derivedVariables,
|
|
3823
|
+
score: first.score,
|
|
3824
|
+
reasons: first.reasons
|
|
3825
|
+
};
|
|
3826
|
+
}
|
|
3827
|
+
function suggestedVarSets(candidates, required, order) {
|
|
3828
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3829
|
+
return candidates.flatMap((candidate) => {
|
|
3830
|
+
if (required.some((key) => candidate.derivedVariables[key] === void 0)) return [];
|
|
3831
|
+
const cli = cliFor(candidate.derivedVariables, order);
|
|
3832
|
+
if (seen.has(cli)) return [];
|
|
3833
|
+
seen.add(cli);
|
|
3834
|
+
return [{ variables: candidate.derivedVariables, cli }];
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
function referenceRows(db, workspaceId) {
|
|
3838
|
+
const rows2 = db.prepare(
|
|
3839
|
+
`SELECT req.alias alias,req.destination destination,req.service_path servicePath,
|
|
3840
|
+
'cds_require' sourceKind,r.name repoName
|
|
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
|
+
}));
|
|
3856
|
+
}
|
|
3857
|
+
function allMissingVariables(evidence, templates) {
|
|
3858
|
+
const fromSubstitutions = Object.values(record(evidence.runtimeSubstitutions)).flatMap((value) => stringArray(record(value).missing));
|
|
3859
|
+
const fromTemplates = [
|
|
3860
|
+
templates.servicePath,
|
|
3861
|
+
templates.operationPath,
|
|
3862
|
+
templates.alias,
|
|
3863
|
+
templates.destination
|
|
3864
|
+
].flatMap((value) => extractPlaceholders(value));
|
|
3865
|
+
return [.../* @__PURE__ */ new Set([...fromSubstitutions, ...fromTemplates])].sort();
|
|
3866
|
+
}
|
|
3867
|
+
function variableOrder(templates, missingVariables) {
|
|
3868
|
+
const ordered = [
|
|
3869
|
+
templates.servicePath,
|
|
3870
|
+
templates.operationPath,
|
|
3871
|
+
templates.alias,
|
|
3872
|
+
templates.destination
|
|
3873
|
+
].flatMap((value) => extractPlaceholders(value));
|
|
3874
|
+
return [.../* @__PURE__ */ new Set([...ordered, ...missingVariables])];
|
|
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
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
function matchTemplate(template, concrete) {
|
|
3885
|
+
if (!template || !concrete) return void 0;
|
|
3886
|
+
const keys = extractPlaceholders(template);
|
|
3887
|
+
if (keys.length === 0) return template === concrete ? {} : void 0;
|
|
3888
|
+
const regex = new RegExp(`^${templateToPattern(template)}$`);
|
|
3889
|
+
const match = regex.exec(concrete);
|
|
3890
|
+
if (!match) return void 0;
|
|
3891
|
+
const values = {};
|
|
3892
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
3893
|
+
const key = keys[index];
|
|
3894
|
+
const value = match[index + 1];
|
|
3895
|
+
if (!key || value === void 0) return void 0;
|
|
3896
|
+
if (values[key] !== void 0 && values[key] !== value) return void 0;
|
|
3897
|
+
values[key] = value;
|
|
3898
|
+
}
|
|
3899
|
+
return values;
|
|
3900
|
+
}
|
|
3901
|
+
function templateToPattern(template) {
|
|
3902
|
+
let pattern = "";
|
|
3903
|
+
let lastIndex = 0;
|
|
3904
|
+
for (const match of template.matchAll(/\$\{([^}]*)\}/g)) {
|
|
3905
|
+
pattern += escapeRegex(template.slice(lastIndex, match.index));
|
|
3906
|
+
pattern += "([^/]+?)";
|
|
3907
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
3908
|
+
}
|
|
3909
|
+
return `${pattern}${escapeRegex(template.slice(lastIndex))}`;
|
|
3910
|
+
}
|
|
3911
|
+
function mergeDerived(state, values, sourceKind) {
|
|
3912
|
+
for (const [key, value] of Object.entries(values)) {
|
|
3913
|
+
if (state.derivedVariables[key] !== void 0 && state.derivedVariables[key] !== value) {
|
|
3914
|
+
state.rejectedReasons.push("template_variable_conflict");
|
|
3915
|
+
continue;
|
|
3916
|
+
}
|
|
3917
|
+
state.derivedVariables[key] = value;
|
|
3918
|
+
state.derivedVariableSources[key] = { sourceKind, value };
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
function addScore(state, amount, reason) {
|
|
3922
|
+
state.score += amount;
|
|
3923
|
+
if (!state.reasons.includes(reason)) state.reasons.push(reason);
|
|
3924
|
+
}
|
|
3925
|
+
function hasResolvedImplementation(db, operationId) {
|
|
3926
|
+
const row = db.prepare(
|
|
3927
|
+
"SELECT 1 FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status='resolved' AND from_kind='operation' AND from_id=? LIMIT 1"
|
|
3928
|
+
).get(String(operationId));
|
|
3929
|
+
return Boolean(row);
|
|
3930
|
+
}
|
|
3931
|
+
function cliFor(variables, order) {
|
|
3932
|
+
return order.filter((key) => variables[key] !== void 0).map((key) => `--var ${key}=${variables[key]}`).join(" ");
|
|
3933
|
+
}
|
|
3934
|
+
function record(value) {
|
|
3935
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3936
|
+
}
|
|
3937
|
+
function stringValue4(value) {
|
|
3938
|
+
return typeof value === "string" ? value : void 0;
|
|
3939
|
+
}
|
|
3940
|
+
function numberValue(value) {
|
|
3941
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3942
|
+
}
|
|
3943
|
+
function stringArray(value) {
|
|
3944
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
3945
|
+
}
|
|
3946
|
+
function nonEmptyStrings(value, fallback) {
|
|
3947
|
+
const values = stringArray(value).filter((item) => item.length > 0);
|
|
3948
|
+
return values.length > 0 ? values : fallback;
|
|
3949
|
+
}
|
|
3950
|
+
function isConcrete(value) {
|
|
3951
|
+
return typeof value === "string" && value.length > 0 && extractPlaceholders(value).length === 0;
|
|
3952
|
+
}
|
|
3953
|
+
function escapeRegex(value) {
|
|
3954
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3439
3957
|
// src/trace/evidence.ts
|
|
3440
3958
|
function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
3441
3959
|
const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
|
|
@@ -3454,23 +3972,45 @@ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
|
3454
3972
|
persistedResolution: persistedResolution(row)
|
|
3455
3973
|
};
|
|
3456
3974
|
}
|
|
3457
|
-
function runtimeResolution(db, row, evidence,
|
|
3458
|
-
const substituted = evidenceWithRuntimeVariables(evidence, vars);
|
|
3459
|
-
|
|
3975
|
+
function runtimeResolution(db, row, evidence, options, workspaceId, contextualUnresolvedReason) {
|
|
3976
|
+
const substituted = evidenceWithRuntimeVariables(evidence, options.vars);
|
|
3977
|
+
const dynamicMode = options.dynamicMode ?? "strict";
|
|
3978
|
+
const analysis = analyzeDynamicTargetCandidates(
|
|
3979
|
+
db,
|
|
3980
|
+
substituted,
|
|
3981
|
+
workspaceId,
|
|
3982
|
+
dynamicMode,
|
|
3983
|
+
positiveCandidateCap(options.maxDynamicCandidates)
|
|
3984
|
+
);
|
|
3985
|
+
const enriched = analysis ? evidenceWithDynamicAnalysis(substituted, analysis) : substituted;
|
|
3986
|
+
if (dynamicMode === "infer") {
|
|
3987
|
+
const inferred = inferredTarget(analysis);
|
|
3988
|
+
if (inferred) {
|
|
3989
|
+
const resolvedRow = { ...row, status: "resolved", to_kind: "operation", to_id: String(inferred.operationId), unresolved_reason: void 0, confidence: inferred.score };
|
|
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
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
if (!isRemoteRuntimeCandidate(row, evidence, options.vars)) {
|
|
3460
3995
|
const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason;
|
|
3461
|
-
const withSections = withEffectiveResolution(
|
|
3996
|
+
const withSections = withEffectiveResolution(enriched, row, unresolvedReason2);
|
|
3462
3997
|
return { row, evidence: withSections, unresolvedReason: unresolvedReason2 };
|
|
3463
3998
|
}
|
|
3464
|
-
const resolution = resolveRuntimeOperation(db,
|
|
3999
|
+
const resolution = resolveRuntimeOperation(db, enriched, workspaceId);
|
|
3465
4000
|
if (resolution.target) {
|
|
3466
4001
|
const resolvedRow = { ...row, status: "resolved", to_kind: "operation", to_id: String(resolution.target.operationId), unresolved_reason: void 0, confidence: Math.max(0, Math.min(1, resolution.target.score)) };
|
|
3467
|
-
return { row: resolvedRow, evidence: withEffectiveResolution(
|
|
4002
|
+
return { row: resolvedRow, evidence: withEffectiveResolution(enriched, resolvedRow, void 0, resolution), target: resolution.target };
|
|
3468
4003
|
}
|
|
3469
4004
|
const unresolvedReason = runtimeUnresolvedReason(resolution);
|
|
3470
|
-
return { row, evidence: withEffectiveResolution(
|
|
4005
|
+
return { row, evidence: withEffectiveResolution(enriched, row, unresolvedReason, resolution), unresolvedReason };
|
|
3471
4006
|
}
|
|
3472
4007
|
function runtimeVariableDiagnostic(edges) {
|
|
3473
4008
|
const missing = /* @__PURE__ */ new Set();
|
|
4009
|
+
let candidateCount = 0;
|
|
4010
|
+
let shownCandidateCount = 0;
|
|
4011
|
+
let omittedCandidateCount = 0;
|
|
4012
|
+
const candidateSuggestions = [];
|
|
4013
|
+
const suggestedVarSets2 = [];
|
|
3474
4014
|
for (const edge of edges) {
|
|
3475
4015
|
const effective = parseObject(edge.evidence.effectiveResolution);
|
|
3476
4016
|
if (!["dynamic", "unresolved", "ambiguous"].includes(String(effective.status ?? "")))
|
|
@@ -3479,6 +4019,12 @@ function runtimeVariableDiagnostic(edges) {
|
|
|
3479
4019
|
if (!substitutions || typeof substitutions !== "object" || Array.isArray(substitutions)) continue;
|
|
3480
4020
|
for (const value of Object.values(substitutions))
|
|
3481
4021
|
for (const key of value.missing ?? []) missing.add(key);
|
|
4022
|
+
const exploration = parseObject(edge.evidence.dynamicTargetExploration);
|
|
4023
|
+
candidateCount += numeric(exploration.candidateCount);
|
|
4024
|
+
shownCandidateCount += numeric(exploration.shownCandidateCount);
|
|
4025
|
+
omittedCandidateCount += numeric(exploration.omittedCandidateCount);
|
|
4026
|
+
candidateSuggestions.push(...recordArray(edge.evidence.dynamicTargetCandidateSuggestions));
|
|
4027
|
+
suggestedVarSets2.push(...recordArray(exploration.suggestedVarSets));
|
|
3482
4028
|
}
|
|
3483
4029
|
const missingVariables = [...missing].sort();
|
|
3484
4030
|
if (missingVariables.length === 0) return void 0;
|
|
@@ -3487,25 +4033,34 @@ function runtimeVariableDiagnostic(edges) {
|
|
|
3487
4033
|
code: "trace_runtime_variables_missing",
|
|
3488
4034
|
message: `Runtime variables are required to resolve dynamic trace targets: ${missingVariables.join(", ")}`,
|
|
3489
4035
|
missingVariables,
|
|
3490
|
-
suggestions: missingVariables.map((key) => `--var ${key}=<value>`)
|
|
4036
|
+
suggestions: missingVariables.map((key) => `--var ${key}=<value>`),
|
|
4037
|
+
candidateCount,
|
|
4038
|
+
shownCandidateCount,
|
|
4039
|
+
omittedCandidateCount,
|
|
4040
|
+
candidateSuggestions: candidateSuggestions.slice(0, 5),
|
|
4041
|
+
suggestedVarSets: uniqueCliRows(suggestedVarSets2).slice(0, 5),
|
|
4042
|
+
copyableExamples: [
|
|
4043
|
+
...uniqueCliRows(suggestedVarSets2).slice(0, 3).flatMap((row) => typeof row.cli === "string" ? [row.cli] : []),
|
|
4044
|
+
...candidateCount > 0 ? ["--dynamic-mode candidates --max-dynamic-candidates 20"] : []
|
|
4045
|
+
]
|
|
3491
4046
|
};
|
|
3492
4047
|
}
|
|
3493
4048
|
function edgeTarget(row, evidence) {
|
|
3494
4049
|
const effective = parseObject(evidence.effectiveResolution);
|
|
3495
|
-
const targetServicePath =
|
|
3496
|
-
const targetOperationPath =
|
|
4050
|
+
const targetServicePath = stringValue5(effective.targetServicePath ?? evidence.targetServicePath);
|
|
4051
|
+
const targetOperationPath = stringValue5(effective.targetOperationPath ?? evidence.targetOperationPath);
|
|
3497
4052
|
if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
|
|
3498
4053
|
const runtimeCandidate = evidence.runtimeResolvedCandidate;
|
|
3499
4054
|
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
|
|
3500
|
-
const servicePath =
|
|
3501
|
-
const operationPath =
|
|
3502
|
-
const targetOperation =
|
|
3503
|
-
const targetRepo =
|
|
4055
|
+
const servicePath = stringValue5(evidence.servicePath);
|
|
4056
|
+
const operationPath = stringValue5(evidence.operationPath);
|
|
4057
|
+
const targetOperation = stringValue5(evidence.targetOperation);
|
|
4058
|
+
const targetRepo = stringValue5(evidence.targetRepo) ?? "";
|
|
3504
4059
|
if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
|
|
3505
|
-
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return
|
|
4060
|
+
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue5(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
|
|
3506
4061
|
if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
|
|
3507
4062
|
const target = parseObject(evidence.externalTarget);
|
|
3508
|
-
return
|
|
4063
|
+
return stringValue5(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
|
|
3509
4064
|
}
|
|
3510
4065
|
if (servicePath && operationPath) return `${servicePath}${operationPath}`;
|
|
3511
4066
|
return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
|
|
@@ -3544,12 +4099,12 @@ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
|
|
|
3544
4099
|
return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
|
|
3545
4100
|
}
|
|
3546
4101
|
function resolveRuntimeOperation(db, evidence, workspaceId) {
|
|
3547
|
-
const servicePath =
|
|
3548
|
-
const rawOperationPath =
|
|
4102
|
+
const servicePath = stringValue5(evidence.servicePath);
|
|
4103
|
+
const rawOperationPath = stringValue5(evidence.operationPath);
|
|
3549
4104
|
const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
|
|
3550
|
-
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath :
|
|
3551
|
-
const alias =
|
|
3552
|
-
const destination =
|
|
4105
|
+
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue5(evidence.normalizedOperationPath) ?? rawOperationPath;
|
|
4106
|
+
const alias = stringValue5(evidence.serviceAliasExpr ?? evidence.serviceAlias);
|
|
4107
|
+
const destination = stringValue5(evidence.destination);
|
|
3553
4108
|
return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
|
|
3554
4109
|
}
|
|
3555
4110
|
function evidenceWithRuntimeVariables(evidence, vars) {
|
|
@@ -3561,6 +4116,22 @@ function evidenceWithRuntimeVariables(evidence, vars) {
|
|
|
3561
4116
|
if (Object.keys(vars ?? {}).length > 0) next.runtimeVariablesApplied = true;
|
|
3562
4117
|
return next;
|
|
3563
4118
|
}
|
|
4119
|
+
function evidenceWithDynamicAnalysis(evidence, analysis) {
|
|
4120
|
+
return {
|
|
4121
|
+
...evidence,
|
|
4122
|
+
dynamicTargetExploration: {
|
|
4123
|
+
mode: analysis.mode,
|
|
4124
|
+
missingVariables: analysis.missingVariables,
|
|
4125
|
+
candidateCount: analysis.candidateCount,
|
|
4126
|
+
shownCandidateCount: analysis.shownCandidateCount,
|
|
4127
|
+
omittedCandidateCount: analysis.omittedCandidateCount,
|
|
4128
|
+
suggestedVarSets: analysis.suggestedVarSets
|
|
4129
|
+
},
|
|
4130
|
+
dynamicTargetCandidates: analysis.candidates,
|
|
4131
|
+
dynamicTargetCandidateSuggestions: analysis.shownCandidates,
|
|
4132
|
+
dynamicTargetInference: analysis.inference
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
3564
4135
|
function runtimeSubstitutions(evidence, vars) {
|
|
3565
4136
|
const substitutions = {};
|
|
3566
4137
|
for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
|
|
@@ -3570,7 +4141,7 @@ function runtimeSubstitutions(evidence, vars) {
|
|
|
3570
4141
|
return substitutions;
|
|
3571
4142
|
}
|
|
3572
4143
|
function substitutionValue(evidence, key) {
|
|
3573
|
-
const value =
|
|
4144
|
+
const value = stringValue5(evidence[key]);
|
|
3574
4145
|
if (key !== "operationPath") return value;
|
|
3575
4146
|
const normalized = normalizeODataOperationInvocationPath(value);
|
|
3576
4147
|
return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
|
|
@@ -3581,6 +4152,29 @@ function isRemoteRuntimeCandidate(row, evidence, vars) {
|
|
|
3581
4152
|
if (!["DYNAMIC_EDGE_CANDIDATE", "UNRESOLVED_EDGE", "REMOTE_CALL_RESOLVES_TO_OPERATION"].includes(row.edge_type)) return false;
|
|
3582
4153
|
return ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"].some((key) => hasRuntimeVariable(evidence[key], vars));
|
|
3583
4154
|
}
|
|
4155
|
+
function inferredTarget(analysis) {
|
|
4156
|
+
if (analysis?.inference.status !== "resolved") return void 0;
|
|
4157
|
+
const id = Number(analysis.inference.candidateOperationId);
|
|
4158
|
+
const candidate = analysis.candidates.find((item) => item.candidateOperationId === id);
|
|
4159
|
+
if (!candidate) return void 0;
|
|
4160
|
+
return targetFromCandidate(candidate);
|
|
4161
|
+
}
|
|
4162
|
+
function targetFromCandidate(candidate) {
|
|
4163
|
+
return {
|
|
4164
|
+
operationId: candidate.candidateOperationId,
|
|
4165
|
+
repoName: candidate.repoName,
|
|
4166
|
+
serviceName: "",
|
|
4167
|
+
qualifiedName: "",
|
|
4168
|
+
servicePath: candidate.servicePath,
|
|
4169
|
+
operationPath: candidate.operationPath,
|
|
4170
|
+
operationName: candidate.operationName,
|
|
4171
|
+
packageName: candidate.packageName,
|
|
4172
|
+
score: candidate.score,
|
|
4173
|
+
reasons: candidate.reasons,
|
|
4174
|
+
sourceFile: "",
|
|
4175
|
+
sourceLine: 0
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
3584
4178
|
function hasRuntimeVariable(value, vars) {
|
|
3585
4179
|
return typeof value === "string" && extractPlaceholders(value).some((key) => Object.hasOwn(vars, key));
|
|
3586
4180
|
}
|
|
@@ -3592,9 +4186,56 @@ function runtimeUnresolvedReason(resolution) {
|
|
|
3592
4186
|
function parseObject(value) {
|
|
3593
4187
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3594
4188
|
}
|
|
3595
|
-
function
|
|
4189
|
+
function stringValue5(value) {
|
|
3596
4190
|
return typeof value === "string" ? value : void 0;
|
|
3597
4191
|
}
|
|
4192
|
+
function numeric(value) {
|
|
4193
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
4194
|
+
}
|
|
4195
|
+
function recordArray(value) {
|
|
4196
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
4197
|
+
}
|
|
4198
|
+
function uniqueCliRows(rows2) {
|
|
4199
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4200
|
+
return rows2.filter((row) => {
|
|
4201
|
+
const cli = typeof row.cli === "string" ? row.cli : JSON.stringify(row);
|
|
4202
|
+
if (seen.has(cli)) return false;
|
|
4203
|
+
seen.add(cli);
|
|
4204
|
+
return true;
|
|
4205
|
+
});
|
|
4206
|
+
}
|
|
4207
|
+
function positiveCandidateCap(value) {
|
|
4208
|
+
return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : 5;
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
// src/trace/dynamic-branches.ts
|
|
4212
|
+
function dynamicCandidateBranches(depth, call, evidence) {
|
|
4213
|
+
const exploration = objectRecord(evidence.dynamicTargetExploration);
|
|
4214
|
+
return recordArray2(evidence.dynamicTargetCandidateSuggestions).map((candidate) => ({
|
|
4215
|
+
step: depth,
|
|
4216
|
+
type: "dynamic_candidate_branch",
|
|
4217
|
+
from: `${call.repoName}:${call.source_file}:${call.source_line}`,
|
|
4218
|
+
to: `${String(candidate.servicePath ?? "")}${String(candidate.operationPath ?? "")}`,
|
|
4219
|
+
evidence: {
|
|
4220
|
+
...candidate,
|
|
4221
|
+
exploratory: true,
|
|
4222
|
+
dynamicMode: String(exploration.mode ?? "candidates"),
|
|
4223
|
+
selected: false,
|
|
4224
|
+
omittedCandidateCount: numericValue(exploration.omittedCandidateCount)
|
|
4225
|
+
},
|
|
4226
|
+
confidence: numericValue(candidate.score),
|
|
4227
|
+
unresolvedReason: "Exploratory dynamic target candidate; provide runtime variables to select it"
|
|
4228
|
+
}));
|
|
4229
|
+
}
|
|
4230
|
+
function objectRecord(value) {
|
|
4231
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
4232
|
+
}
|
|
4233
|
+
function recordArray2(value) {
|
|
4234
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
4235
|
+
}
|
|
4236
|
+
function numericValue(value) {
|
|
4237
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
4238
|
+
}
|
|
3598
4239
|
|
|
3599
4240
|
// src/trace/trace-engine.ts
|
|
3600
4241
|
function normalizeOperation(value) {
|
|
@@ -3641,19 +4282,6 @@ function implementationRejectionReasons(evidence) {
|
|
|
3641
4282
|
const reasons = candidates.flatMap((candidate) => Array.isArray(candidate.rejectedReasons) ? candidate.rejectedReasons.filter((reason) => typeof reason === "string") : []);
|
|
3642
4283
|
return [...new Set(reasons)].sort();
|
|
3643
4284
|
}
|
|
3644
|
-
function ambiguousStartDiagnostic(requested, candidates, message) {
|
|
3645
|
-
const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
|
|
3646
|
-
return {
|
|
3647
|
-
severity: "warning",
|
|
3648
|
-
code: "trace_start_ambiguous",
|
|
3649
|
-
message,
|
|
3650
|
-
normalizedSelectorValue: requested,
|
|
3651
|
-
resolutionStage: "operation",
|
|
3652
|
-
resolutionStatus: "ambiguous_operation",
|
|
3653
|
-
candidates,
|
|
3654
|
-
serviceSuggestions
|
|
3655
|
-
};
|
|
3656
|
-
}
|
|
3657
4285
|
function sourceFilesForStart(db, repoId, start) {
|
|
3658
4286
|
const handler = start.handler;
|
|
3659
4287
|
const operation = normalizeOperation(start.operation ?? start.operationPath);
|
|
@@ -4076,7 +4704,11 @@ function trace(db, start, options) {
|
|
|
4076
4704
|
String(row.evidence_json || "{}")
|
|
4077
4705
|
);
|
|
4078
4706
|
const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
|
|
4079
|
-
const effective = runtimeResolution(db, row, rawEvidence,
|
|
4707
|
+
const effective = runtimeResolution(db, row, rawEvidence, {
|
|
4708
|
+
vars: options.vars,
|
|
4709
|
+
dynamicMode: options.dynamicMode ?? "strict",
|
|
4710
|
+
maxDynamicCandidates: options.maxDynamicCandidates
|
|
4711
|
+
}, workspaceIdForCall(db, String(call.id)), contextual.unresolvedReason);
|
|
4080
4712
|
const evidence = effective.evidence;
|
|
4081
4713
|
const effectiveRow = effective.row;
|
|
4082
4714
|
const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
|
|
@@ -4096,6 +4728,8 @@ function trace(db, start, options) {
|
|
|
4096
4728
|
confidence: Number(effectiveRow.confidence ?? call.confidence),
|
|
4097
4729
|
unresolvedReason: effective.unresolvedReason
|
|
4098
4730
|
});
|
|
4731
|
+
if ((options.dynamicMode ?? "strict") === "candidates")
|
|
4732
|
+
edges.push(...dynamicCandidateBranches(current.depth, call, evidence));
|
|
4099
4733
|
if (effectiveRow.to_kind === "operation") {
|
|
4100
4734
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
4101
4735
|
const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence, hintOptions);
|
|
@@ -4175,6 +4809,7 @@ export {
|
|
|
4175
4809
|
parseImplementationHint,
|
|
4176
4810
|
implementationHintSuggestions,
|
|
4177
4811
|
linkWorkspace,
|
|
4812
|
+
parseVars,
|
|
4178
4813
|
trace
|
|
4179
4814
|
};
|
|
4180
|
-
//# sourceMappingURL=chunk-
|
|
4815
|
+
//# sourceMappingURL=chunk-YZJKE5UX.js.map
|