@saptools/service-flow 0.1.13 → 0.1.15
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 +17 -0
- package/README.md +5 -0
- package/TECHNICAL-NOTE.md +11 -4
- package/dist/{chunk-Q7W7TE3W.js → chunk-ZSUL2X77.js} +42 -14
- package/dist/chunk-ZSUL2X77.js.map +1 -0
- package/dist/cli.js +125 -26
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-Q7W7TE3W.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
parsePackageJson,
|
|
11
11
|
parseServiceBindings,
|
|
12
12
|
trace
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ZSUL2X77.js";
|
|
14
14
|
|
|
15
15
|
// src/cli.ts
|
|
16
16
|
import { Command } from "commander";
|
|
@@ -185,7 +185,7 @@ function migrate(db) {
|
|
|
185
185
|
// package.json
|
|
186
186
|
var package_default = {
|
|
187
187
|
name: "@saptools/service-flow",
|
|
188
|
-
version: "0.1.
|
|
188
|
+
version: "0.1.15",
|
|
189
189
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
190
190
|
type: "module",
|
|
191
191
|
publishConfig: {
|
|
@@ -543,9 +543,25 @@ function insertExecutableSymbols(db, repoId, rows) {
|
|
|
543
543
|
for (const r of rows) 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);
|
|
544
544
|
}
|
|
545
545
|
function insertSymbolCalls(db, repoId, rows) {
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
546
|
+
const findTarget = `(SELECT id FROM symbols WHERE repo_id=? AND ((source_file=? AND (name=? OR qualified_name=?)) OR (source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?))) ORDER BY CASE WHEN source_file=? THEN 0 ELSE 1 END,id LIMIT 1)`;
|
|
547
|
+
const stmt = 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(?,(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),${findTarget},?,?,?,?,CASE WHEN ${findTarget} IS NULL THEN 'unresolved' ELSE 'resolved' END,0.8,?,CASE WHEN ${findTarget} IS NULL THEN 'No local symbol target matched exactly' ELSE NULL END)`);
|
|
548
|
+
for (const r of rows) {
|
|
549
|
+
const targetArgs = [repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName, r.sourceFile];
|
|
550
|
+
stmt.run(
|
|
551
|
+
repoId,
|
|
552
|
+
repoId,
|
|
553
|
+
r.sourceFile,
|
|
554
|
+
r.callerQualifiedName,
|
|
555
|
+
...targetArgs,
|
|
556
|
+
r.calleeExpression,
|
|
557
|
+
r.importSource,
|
|
558
|
+
r.sourceFile,
|
|
559
|
+
r.sourceLine,
|
|
560
|
+
...targetArgs,
|
|
561
|
+
JSON.stringify(r.evidence),
|
|
562
|
+
...targetArgs
|
|
563
|
+
);
|
|
564
|
+
}
|
|
549
565
|
}
|
|
550
566
|
function insertCalls(db, repoId, rows) {
|
|
551
567
|
const stmt = db.prepare(
|
|
@@ -643,6 +659,10 @@ function isFunctionLike(node) {
|
|
|
643
659
|
function exported(node) {
|
|
644
660
|
return Boolean(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export);
|
|
645
661
|
}
|
|
662
|
+
function isPublicStaticMethod(node) {
|
|
663
|
+
const flags = ts.getCombinedModifierFlags(node);
|
|
664
|
+
return (flags & ts.ModifierFlags.Static) !== 0 && (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
|
|
665
|
+
}
|
|
646
666
|
function exportDeclarations(source) {
|
|
647
667
|
const exports = /* @__PURE__ */ new Map();
|
|
648
668
|
const visit = (node) => {
|
|
@@ -663,14 +683,32 @@ function isObjectFunction(node) {
|
|
|
663
683
|
var commonTerminalMembers = /* @__PURE__ */ new Set(["push", "includes", "find", "findIndex", "map", "filter", "reduce", "forEach", "some", "every", "toUpperCase", "toLowerCase", "trim", "split", "join", "get", "set", "has"]);
|
|
664
684
|
var loggerMembers = /* @__PURE__ */ new Set(["trace", "debug", "info", "warn", "error", "fatal", "log"]);
|
|
665
685
|
var globalObjects = /* @__PURE__ */ new Set(["JSON", "Object", "Array", "String", "Number", "Boolean", "Math", "Date", "Promise", "Reflect"]);
|
|
686
|
+
var capDslRoots = /* @__PURE__ */ new Set(["SELECT", "INSERT", "UPSERT", "UPDATE", "DELETE"]);
|
|
687
|
+
var requestHelpers = /* @__PURE__ */ new Set(["reject", "error", "info", "warn", "notify"]);
|
|
688
|
+
var transportMembers = /* @__PURE__ */ new Set(["emit", "publish", "send", "on"]);
|
|
666
689
|
function callName(expr) {
|
|
667
690
|
if (ts.isIdentifier(expr)) return { expression: expr.text, local: expr.text };
|
|
668
691
|
if (ts.isPropertyAccessExpression(expr)) {
|
|
669
692
|
const left = expr.expression.getText();
|
|
670
|
-
|
|
693
|
+
const root = left.split(".")[0];
|
|
694
|
+
return { expression: expr.getText(), local: left === "this" ? void 0 : root, member: expr.name.text, receiver: left };
|
|
671
695
|
}
|
|
672
696
|
return { expression: expr.getText() };
|
|
673
697
|
}
|
|
698
|
+
function requireSource(expr) {
|
|
699
|
+
if (!ts.isCallExpression(expr) || !ts.isIdentifier(expr.expression) || expr.expression.text !== "require") return void 0;
|
|
700
|
+
const first = expr.arguments[0];
|
|
701
|
+
return first && ts.isStringLiteral(first) ? first.text : void 0;
|
|
702
|
+
}
|
|
703
|
+
function ignoredFrameworkCall(callee) {
|
|
704
|
+
if (callee.local && capDslRoots.has(callee.local)) return true;
|
|
705
|
+
if (callee.expression === "cds.run" || callee.expression.startsWith("cds.connect.") || callee.expression.startsWith("cds.services.") || callee.expression.startsWith("cds.parse.")) return true;
|
|
706
|
+
if (callee.local === "req" && callee.member && requestHelpers.has(callee.member)) return true;
|
|
707
|
+
if (callee.member && transportMembers.has(callee.member)) return true;
|
|
708
|
+
if (callee.local && globalObjects.has(callee.local)) return true;
|
|
709
|
+
if (callee.expression.startsWith("new Date().")) return true;
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
674
712
|
function nearest(symbols, line) {
|
|
675
713
|
return symbols.filter((s) => s.startLine <= line && s.endLine >= line).sort((a, b) => a.endLine - a.startLine - (b.endLine - b.startLine))[0];
|
|
676
714
|
}
|
|
@@ -683,11 +721,20 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
683
721
|
const imports = /* @__PURE__ */ new Map();
|
|
684
722
|
const exportNames = exportDeclarations(source);
|
|
685
723
|
const objectExports = /* @__PURE__ */ new Set();
|
|
686
|
-
const
|
|
687
|
-
|
|
724
|
+
const exportedClasses = /* @__PURE__ */ new Set();
|
|
725
|
+
const proxyVariables = /* @__PURE__ */ new Map();
|
|
726
|
+
const addSymbol = (kind, localName, node, parentName, exportedName, evidence) => {
|
|
727
|
+
const parentRoot = parentName?.split(".")[0] ?? "";
|
|
728
|
+
const declaredExportName = exportedName ?? exportNames.get(parentName ? parentRoot : localName);
|
|
688
729
|
const qualifiedName = parentName ? `${parentName}.${localName}` : localName;
|
|
689
|
-
const objectExported = parentName ? objectExports.has(
|
|
690
|
-
|
|
730
|
+
const objectExported = parentName ? objectExports.has(parentRoot) : false;
|
|
731
|
+
const classMemberExported = kind === "method" && parentName ? exportedClasses.has(parentRoot) && ts.isMethodDeclaration(node) && isPublicStaticMethod(node) : false;
|
|
732
|
+
const effectiveExportedName = classMemberExported || objectExported ? qualifiedName : declaredExportName;
|
|
733
|
+
const sourceEvidence = evidence ?? (classMemberExported ? { source: "exported_class_member", exportedClass: parentRoot, memberKind: "static_method" } : declaredExportName ? { exportedName: declaredExportName, source: "export_declaration" } : objectExported ? { exportedName: qualifiedName, source: "exported_object_literal" } : void 0);
|
|
734
|
+
symbols.push({ kind, localName: kind === "object_method" ? qualifiedName : localName, exportedName: effectiveExportedName, qualifiedName, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: exported(node) || Boolean(effectiveExportedName), importExportEvidence: sourceEvidence });
|
|
735
|
+
};
|
|
736
|
+
const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
|
|
737
|
+
symbols.push({ kind: "object_alias", localName: propertyName, exportedName: propertyName, qualifiedName: `${objectName}.${propertyName}`, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: true, importExportEvidence: { source: "exported_object_shorthand", objectName, propertyName, targetImportSource } });
|
|
691
738
|
};
|
|
692
739
|
const visitImports = (node) => {
|
|
693
740
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
@@ -698,11 +745,21 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
698
745
|
if (named && ts.isNamedImports(named)) for (const el of named.elements) imports.set(el.name.text, sourceText);
|
|
699
746
|
if (named && ts.isNamespaceImport(named)) imports.set(named.name.text, sourceText);
|
|
700
747
|
}
|
|
748
|
+
if (ts.isVariableStatement(node)) {
|
|
749
|
+
for (const declaration of node.declarationList.declarations) {
|
|
750
|
+
const requiredSource = declaration.initializer ? requireSource(declaration.initializer) : void 0;
|
|
751
|
+
if (ts.isIdentifier(declaration.name) && requiredSource) imports.set(declaration.name.text, requiredSource);
|
|
752
|
+
if (ts.isObjectBindingPattern(declaration.name) && requiredSource) {
|
|
753
|
+
for (const element of declaration.name.elements) if (ts.isIdentifier(element.name)) imports.set(element.name.text, requiredSource);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
701
757
|
ts.forEachChild(node, visitImports);
|
|
702
758
|
};
|
|
703
759
|
visitImports(source);
|
|
704
760
|
const visitSymbols = (node, parentClass) => {
|
|
705
761
|
if (ts.isClassDeclaration(node) && node.name) {
|
|
762
|
+
if (exported(node) || exportNames.has(node.name.text)) exportedClasses.add(node.name.text);
|
|
706
763
|
for (const member of node.members) visitSymbols(member, node.name.text);
|
|
707
764
|
return;
|
|
708
765
|
}
|
|
@@ -716,8 +773,10 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
716
773
|
if (!localName || !d.initializer) continue;
|
|
717
774
|
if (isFunctionLike(d.initializer)) addSymbol("function", localName, d.initializer, void 0, exported(node) ? localName : exportNames.get(localName));
|
|
718
775
|
if (ts.isObjectLiteralExpression(d.initializer)) {
|
|
719
|
-
|
|
776
|
+
const objectIsExported = exported(node) || exportNames.has(localName);
|
|
777
|
+
if (objectIsExported) objectExports.add(localName);
|
|
720
778
|
for (const prop of d.initializer.properties) {
|
|
779
|
+
if (objectIsExported && ts.isShorthandPropertyAssignment(prop)) addAliasSymbol(localName, prop.name.text, prop.name, imports.get(prop.name.text));
|
|
721
780
|
if (ts.isPropertyAssignment(prop) && isObjectFunction(prop.initializer)) {
|
|
722
781
|
const propName = nameOf(prop.name);
|
|
723
782
|
if (propName) addSymbol("object_method", propName, prop.initializer, localName);
|
|
@@ -731,6 +790,15 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
731
790
|
} else ts.forEachChild(node, (child) => visitSymbols(child, parentClass));
|
|
732
791
|
};
|
|
733
792
|
visitSymbols(source);
|
|
793
|
+
const visitProxyVariables = (node) => {
|
|
794
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isPropertyAccessExpression(node.initializer.expression)) {
|
|
795
|
+
const callee = callName(node.initializer.expression);
|
|
796
|
+
const importSource = callee.local ? imports.get(callee.local) : void 0;
|
|
797
|
+
if (callee.member && importSource && isRelativeImport(importSource)) proxyVariables.set(node.name.text, { importSource, factory: callee.expression });
|
|
798
|
+
}
|
|
799
|
+
ts.forEachChild(node, visitProxyVariables);
|
|
800
|
+
};
|
|
801
|
+
visitProxyVariables(source);
|
|
734
802
|
const localCallables = new Set(symbols.flatMap((sym) => [sym.localName, sym.qualifiedName]));
|
|
735
803
|
const visitCalls = (node) => {
|
|
736
804
|
if (ts.isCallExpression(node)) {
|
|
@@ -738,19 +806,21 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
738
806
|
const caller = nearest(symbols, line);
|
|
739
807
|
if (caller) {
|
|
740
808
|
const callee = callName(node.expression);
|
|
741
|
-
const
|
|
742
|
-
const
|
|
809
|
+
const proxy = callee.local ? proxyVariables.get(callee.local) : void 0;
|
|
810
|
+
const importSource = proxy?.importSource ?? (callee.local ? imports.get(callee.local) : void 0) ?? (callee.member && callee.local ? imports.get(callee.local) : void 0);
|
|
811
|
+
const targetName = proxy && callee.member ? callee.member : callee.expression.startsWith("this.") ? callee.member : callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
|
|
743
812
|
const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
|
|
744
813
|
const thisTarget = className && callee.member ? `${className}.${callee.member}` : void 0;
|
|
745
814
|
const loggerLike = callee.receiver?.endsWith(".logger") || callee.local === "logger" || (callee.expression.startsWith("this.logger.") && callee.member ? loggerMembers.has(callee.member) : false);
|
|
746
815
|
const terminalMember = callee.member ? commonTerminalMembers.has(callee.member) || loggerMembers.has(callee.member) : false;
|
|
747
816
|
const provenLocal = Boolean(targetName) && localCallables.has(String(targetName));
|
|
748
817
|
const provenThisMethod = Boolean(thisTarget && localCallables.has(thisTarget));
|
|
749
|
-
const provenRelativeImport = Boolean(isRelativeImport(importSource));
|
|
750
|
-
const globalLike = callee.local ? globalObjects.has(callee.local) : false;
|
|
818
|
+
const provenRelativeImport = Boolean(isRelativeImport(importSource) && targetName);
|
|
751
819
|
const importedFromPackage = Boolean(importSource && !isRelativeImport(importSource));
|
|
752
|
-
const
|
|
753
|
-
|
|
820
|
+
const ignored = loggerLike || terminalMember || importedFromPackage || ignoredFrameworkCall(callee);
|
|
821
|
+
const resolvedTarget = provenThisMethod ? thisTarget : targetName;
|
|
822
|
+
const keep = Boolean(resolvedTarget) && !ignored && (provenLocal || provenThisMethod || provenRelativeImport);
|
|
823
|
+
if (keep) calls.push({ callerQualifiedName: caller.qualifiedName, calleeExpression: callee.expression, calleeLocalName: resolvedTarget, receiverLocalName: callee.member ? callee.local : void 0, importSource, sourceFile, sourceLine: line, evidence: { relation: proxy ? "relative_import_proxy_member" : importSource ? "relative_import" : provenThisMethod ? "indexed_this_method" : "indexed_local_symbol", caller: caller.qualifiedName, targetName: resolvedTarget, factory: proxy?.factory } });
|
|
754
824
|
}
|
|
755
825
|
}
|
|
756
826
|
ts.forEachChild(node, visitCalls);
|
|
@@ -905,14 +975,10 @@ function location(evidence) {
|
|
|
905
975
|
}
|
|
906
976
|
return ":";
|
|
907
977
|
}
|
|
908
|
-
function displayTarget(value, evidence) {
|
|
909
|
-
if (/^\d+$/.test(value) && evidence.parserWarning) return "Entity: unknown";
|
|
910
|
-
return value;
|
|
911
|
-
}
|
|
912
978
|
function renderTraceTable(result) {
|
|
913
979
|
const lines = ["Step Type From To Evidence"];
|
|
914
980
|
for (const e of result.edges) {
|
|
915
|
-
lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${
|
|
981
|
+
lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${e.to.slice(0, 35).padEnd(36)} ${location(e.evidence)}`);
|
|
916
982
|
}
|
|
917
983
|
if (result.diagnostics.length > 0) lines.push("", "Diagnostics:", ...result.diagnostics.map((d) => `${String(d.severity ?? "info")} ${String(d.code ?? "diagnostic")} ${String(d.message ?? "")}`));
|
|
918
984
|
return `${lines.join("\n")}
|
|
@@ -933,8 +999,6 @@ function safe(value) {
|
|
|
933
999
|
return value.replace(/[^\w-]/g, "_").slice(0, 60);
|
|
934
1000
|
}
|
|
935
1001
|
function label(trace2, idOrLabel) {
|
|
936
|
-
const edge = trace2.edges.find((item) => item.to === idOrLabel);
|
|
937
|
-
if (/^\d+$/.test(idOrLabel) && edge?.evidence.parserWarning) return "Entity: unknown";
|
|
938
1002
|
const node = trace2.nodes.find((item) => item.id === idOrLabel || item.label === idOrLabel);
|
|
939
1003
|
return String(node?.label ?? idOrLabel);
|
|
940
1004
|
}
|
|
@@ -1007,7 +1071,15 @@ function localServiceDiagnostics(db, strict) {
|
|
|
1007
1071
|
const implementationContext = rows.filter((row) => row.status === "resolved" && String(row.evidenceJson ?? "").includes("implementation_context_caller_ownership")).length;
|
|
1008
1072
|
const withoutOwnership = rows.filter((row) => row.reason === "local_service_candidate_without_caller_ownership" || String(row.evidenceJson ?? "").includes("local_service_candidate_without_caller_ownership")).length;
|
|
1009
1073
|
const unresolved = rows.filter((row) => row.status === "unresolved").length;
|
|
1010
|
-
const outsideScope = rows.filter((row) =>
|
|
1074
|
+
const outsideScope = rows.filter((row) => {
|
|
1075
|
+
if (row.status !== "unresolved") return false;
|
|
1076
|
+
try {
|
|
1077
|
+
const evidence = JSON.parse(String(row.evidenceJson ?? "{}"));
|
|
1078
|
+
return Number(evidence.candidateCount ?? 0) > 0;
|
|
1079
|
+
} catch {
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
}).length;
|
|
1011
1083
|
const out = [];
|
|
1012
1084
|
if (withoutOwnership > 0) out.push({ severity: "warning", code: "local_service_candidate_without_caller_ownership", message: `Local service calls have operation candidates but no caller ownership evidence: ${withoutOwnership}` });
|
|
1013
1085
|
if (outsideScope > 0) out.push({ severity: "warning", code: "local_service_candidates_outside_local_scope", message: `Local service calls found candidates outside same-repository scope: ${outsideScope}` });
|
|
@@ -1015,6 +1087,32 @@ function localServiceDiagnostics(db, strict) {
|
|
|
1015
1087
|
if (strict && implementationContext > 0) out.push({ severity: "info", code: "local_service_calls_resolved_by_implementation_context", message: `Local service calls resolved by implementation-context ownership: ${implementationContext}` });
|
|
1016
1088
|
return out;
|
|
1017
1089
|
}
|
|
1090
|
+
function parserQualityDiagnostics(db, strict) {
|
|
1091
|
+
if (!strict) return [];
|
|
1092
|
+
const symbolUnresolvedThreshold = 0.05;
|
|
1093
|
+
const dbUnknownThreshold = 0.25;
|
|
1094
|
+
const outboundUnownedThreshold = 0.01;
|
|
1095
|
+
const symbol = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN status='resolved' THEN 1 ELSE 0 END) resolved, SUM(CASE WHEN status='unresolved' THEN 1 ELSE 0 END) unresolved FROM symbol_calls").get();
|
|
1096
|
+
const top = db.prepare("SELECT callee_expression calleeExpression,COUNT(*) count FROM symbol_calls WHERE status='unresolved' GROUP BY callee_expression ORDER BY count DESC,callee_expression LIMIT 5").all();
|
|
1097
|
+
const evidence = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN json_valid(evidence_json)=0 OR json_type(evidence_json) <> 'object' THEN 1 ELSE 0 END) nonObject FROM symbol_calls").get();
|
|
1098
|
+
const dbq = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN query_entity IS NOT NULL THEN 1 ELSE 0 END) known, SUM(CASE WHEN query_entity IS NULL THEN 1 ELSE 0 END) unknown FROM outbound_calls WHERE call_type='local_db_query'").get();
|
|
1099
|
+
const outbound = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN source_symbol_id IS NULL THEN 1 ELSE 0 END) withoutOwnership FROM outbound_calls").get();
|
|
1100
|
+
const symbolTotal = Number(symbol.total ?? 0);
|
|
1101
|
+
const symbolUnresolved = Number(symbol.unresolved ?? 0);
|
|
1102
|
+
const symbolUnresolvedRatio = symbolTotal === 0 ? 0 : Number((symbolUnresolved / symbolTotal).toFixed(4));
|
|
1103
|
+
const queryTotal = Number(dbq.total ?? 0);
|
|
1104
|
+
const queryUnknown = Number(dbq.unknown ?? 0);
|
|
1105
|
+
const queryUnknownRatio = queryTotal === 0 ? 0 : Number((queryUnknown / queryTotal).toFixed(4));
|
|
1106
|
+
const outboundTotal = Number(outbound.total ?? 0);
|
|
1107
|
+
const outboundWithoutOwnership = Number(outbound.withoutOwnership ?? 0);
|
|
1108
|
+
const outboundWithoutOwnershipRatio = outboundTotal === 0 ? 0 : Number((outboundWithoutOwnership / outboundTotal).toFixed(4));
|
|
1109
|
+
return [
|
|
1110
|
+
{ severity: Number(evidence.nonObject ?? 0) > 0 ? "warning" : "info", code: "strict_symbol_call_evidence_quality", message: "Symbol-call evidence JSON object aggregate", total: Number(evidence.total ?? 0), nonObject: Number(evidence.nonObject ?? 0) },
|
|
1111
|
+
{ severity: symbolUnresolvedRatio > symbolUnresolvedThreshold ? "warning" : "info", code: "strict_symbol_call_quality", message: "Symbol-call quality aggregate", total: symbolTotal, resolved: Number(symbol.resolved ?? 0), unresolved: symbolUnresolved, unresolvedRatio: symbolUnresolvedRatio, unresolvedRatioThreshold: symbolUnresolvedThreshold, topUnresolvedCallees: top },
|
|
1112
|
+
{ severity: queryUnknownRatio > dbUnknownThreshold ? "warning" : "info", code: "strict_db_query_quality", message: "Local DB query quality aggregate", total: queryTotal, known: Number(dbq.known ?? 0), unknown: queryUnknown, unknownRatio: queryUnknownRatio, unknownRatioThreshold: dbUnknownThreshold },
|
|
1113
|
+
{ severity: outboundWithoutOwnershipRatio > outboundUnownedThreshold ? "warning" : "info", code: "strict_outbound_source_ownership_quality", message: "Outbound call source-symbol ownership aggregate", total: outboundTotal, withoutOwnership: outboundWithoutOwnership, withoutOwnershipRatio: outboundWithoutOwnershipRatio, withoutOwnershipRatioThreshold: outboundUnownedThreshold }
|
|
1114
|
+
];
|
|
1115
|
+
}
|
|
1018
1116
|
function createProgram() {
|
|
1019
1117
|
const program = new Command();
|
|
1020
1118
|
program.name("service-flow").description(
|
|
@@ -1225,7 +1323,8 @@ function createProgram() {
|
|
|
1225
1323
|
FROM index_runs WHERE status='running' AND datetime(started_at) < datetime('now','-60 minutes')`
|
|
1226
1324
|
).all(Boolean(opts.strict), Boolean(opts.strict), Boolean(opts.strict), Boolean(opts.strict), Boolean(opts.strict), Boolean(opts.strict), Boolean(opts.strict), Boolean(opts.strict));
|
|
1227
1325
|
const localServiceHealth = localServiceDiagnostics(db, Boolean(opts.strict));
|
|
1228
|
-
const
|
|
1326
|
+
const parserQualityHealth = parserQualityDiagnostics(db, Boolean(opts.strict));
|
|
1327
|
+
const allDiagnostics = [...diagnostics, ...health, ...localServiceHealth, ...parserQualityHealth];
|
|
1229
1328
|
process.stdout.write(
|
|
1230
1329
|
allDiagnostics.length ? renderJson(allDiagnostics) : `${pc.green("No diagnostics recorded")}
|
|
1231
1330
|
`
|