@saptools/service-flow 0.1.48 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +10 -0
- package/dist/{chunk-EGY2A4AT.js → chunk-XOROZHW4.js} +593 -242
- package/dist/chunk-XOROZHW4.js.map +1 -0
- package/dist/cli.js +265 -24
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +128 -2
- package/src/db/repositories.ts +186 -10
- package/src/linker/cross-repo-linker.ts +70 -1
- package/src/output/table-output.ts +18 -8
- package/src/parsers/imported-wrapper-parser.ts +20 -46
- package/src/parsers/operation-path-analysis.ts +350 -0
- package/src/parsers/outbound-call-parser.ts +63 -53
- package/src/trace/evidence.ts +18 -3
- package/src/trace/trace-engine.ts +118 -25
- package/dist/chunk-EGY2A4AT.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
parsePackageJson,
|
|
16
16
|
parseServiceBindings,
|
|
17
17
|
trace
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-XOROZHW4.js";
|
|
19
19
|
|
|
20
20
|
// src/cli.ts
|
|
21
21
|
import { Command } from "commander";
|
|
@@ -210,7 +210,7 @@ function migrate(db) {
|
|
|
210
210
|
// package.json
|
|
211
211
|
var package_default = {
|
|
212
212
|
name: "@saptools/service-flow",
|
|
213
|
-
version: "0.1.
|
|
213
|
+
version: "0.1.49",
|
|
214
214
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
215
215
|
type: "module",
|
|
216
216
|
publishConfig: {
|
|
@@ -554,11 +554,15 @@ function insertRegistrations(db, repoId, rows) {
|
|
|
554
554
|
}
|
|
555
555
|
function insertBindings(db, repoId, rows) {
|
|
556
556
|
const stmt = db.prepare(
|
|
557
|
-
"INSERT INTO service_bindings(repo_id,variable_name,alias,alias_expr,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,helper_chain_json) VALUES(
|
|
557
|
+
"INSERT INTO service_bindings(repo_id,symbol_id,variable_name,alias,alias_expr,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,helper_chain_json) VALUES(?,(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1),?,?,?,?,?,?,?,?,?,?)"
|
|
558
558
|
);
|
|
559
559
|
for (const r of rows)
|
|
560
560
|
stmt.run(
|
|
561
561
|
repoId,
|
|
562
|
+
repoId,
|
|
563
|
+
r.sourceFile,
|
|
564
|
+
r.sourceLine,
|
|
565
|
+
r.sourceLine,
|
|
562
566
|
r.variableName,
|
|
563
567
|
r.alias,
|
|
564
568
|
r.aliasExpr,
|
|
@@ -612,9 +616,14 @@ function resolveSymbolCallTarget(db, repoId, r) {
|
|
|
612
616
|
}
|
|
613
617
|
function insertCalls(db, repoId, rows) {
|
|
614
618
|
const stmt = db.prepare(
|
|
615
|
-
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,evidence_json,external_target_kind,external_target_id,external_target_label,external_target_dynamic,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1))
|
|
619
|
+
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,evidence_json,external_target_kind,external_target_id,external_target_label,external_target_dynamic,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
|
|
616
620
|
);
|
|
617
|
-
for (const r of rows)
|
|
621
|
+
for (const r of rows) {
|
|
622
|
+
const binding = resolvePersistedBinding(db, repoId, r);
|
|
623
|
+
const evidence = {
|
|
624
|
+
...r.evidence ?? {},
|
|
625
|
+
serviceBindingResolution: binding.evidence
|
|
626
|
+
};
|
|
618
627
|
stmt.run(
|
|
619
628
|
repoId,
|
|
620
629
|
repoId,
|
|
@@ -633,21 +642,146 @@ function insertCalls(db, repoId, rows) {
|
|
|
633
642
|
r.sourceFile,
|
|
634
643
|
r.sourceLine,
|
|
635
644
|
r.confidence,
|
|
636
|
-
r.unresolvedReason,
|
|
645
|
+
r.unresolvedReason ?? binding.unresolvedReason,
|
|
637
646
|
r.localServiceName,
|
|
638
647
|
r.localServiceLookup,
|
|
639
648
|
r.aliasChain ? JSON.stringify(r.aliasChain) : null,
|
|
640
|
-
|
|
649
|
+
JSON.stringify(evidence),
|
|
641
650
|
r.externalTarget?.kind ?? null,
|
|
642
651
|
r.externalTarget?.stableId ?? null,
|
|
643
652
|
r.externalTarget?.label ?? null,
|
|
644
653
|
r.externalTarget?.dynamic ? 1 : 0,
|
|
645
|
-
|
|
646
|
-
r.serviceVariableName,
|
|
647
|
-
r.sourceFile,
|
|
648
|
-
r.sourceLine,
|
|
649
|
-
r.sourceLine
|
|
654
|
+
binding.bindingId
|
|
650
655
|
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function resolvePersistedBinding(db, repoId, call) {
|
|
659
|
+
if (!call.serviceVariableName)
|
|
660
|
+
return { bindingId: null, evidence: { status: "not_applicable", candidateCount: 0 } };
|
|
661
|
+
const candidates = bindingCandidates(db, repoId, call);
|
|
662
|
+
const prior = candidates.filter((candidate) => candidate.sourceLine <= call.sourceLine);
|
|
663
|
+
const families = new Set(prior.map(bindingSignature));
|
|
664
|
+
if (prior.length > 0 && families.size === 1) {
|
|
665
|
+
const selected = prior.at(-1);
|
|
666
|
+
return {
|
|
667
|
+
bindingId: selected?.id ?? null,
|
|
668
|
+
evidence: bindingEvidence("selected", prior, selected)
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
if (prior.length > 1) {
|
|
672
|
+
return {
|
|
673
|
+
bindingId: null,
|
|
674
|
+
unresolvedReason: "ambiguous_service_binding_candidates",
|
|
675
|
+
evidence: bindingEvidence("ambiguous", prior)
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (candidates.length > 0) {
|
|
679
|
+
return {
|
|
680
|
+
bindingId: null,
|
|
681
|
+
unresolvedReason: "service_binding_declared_after_call",
|
|
682
|
+
evidence: bindingEvidence("rejected_future_binding", candidates)
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
bindingId: null,
|
|
687
|
+
evidence: bindingEvidence("unrecoverable", [])
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function bindingCandidates(db, repoId, call) {
|
|
691
|
+
const ownerId = callSymbolId(db, repoId, call);
|
|
692
|
+
const rows = db.prepare(`
|
|
693
|
+
SELECT id,symbol_id symbolId,variable_name variableName,alias,alias_expr aliasExpr,
|
|
694
|
+
destination_expr destinationExpr,service_path_expr servicePathExpr,
|
|
695
|
+
source_file sourceFile,source_line sourceLine,helper_chain_json helperChainJson
|
|
696
|
+
FROM service_bindings
|
|
697
|
+
WHERE repo_id=? AND variable_name=? AND source_file=?
|
|
698
|
+
AND (? IS NULL OR symbol_id IS NULL OR symbol_id=?)
|
|
699
|
+
ORDER BY source_line,id
|
|
700
|
+
`).all(
|
|
701
|
+
repoId,
|
|
702
|
+
call.serviceVariableName,
|
|
703
|
+
call.sourceFile,
|
|
704
|
+
ownerId,
|
|
705
|
+
ownerId
|
|
706
|
+
);
|
|
707
|
+
return rows.flatMap((row) => {
|
|
708
|
+
if (typeof row.id !== "number" || typeof row.variableName !== "string" || typeof row.sourceFile !== "string" || typeof row.sourceLine !== "number")
|
|
709
|
+
return [];
|
|
710
|
+
return [{
|
|
711
|
+
id: row.id,
|
|
712
|
+
symbolId: nullableNumber(row.symbolId),
|
|
713
|
+
variableName: row.variableName,
|
|
714
|
+
alias: nullableString(row.alias),
|
|
715
|
+
aliasExpr: nullableString(row.aliasExpr),
|
|
716
|
+
destinationExpr: nullableString(row.destinationExpr),
|
|
717
|
+
servicePathExpr: nullableString(row.servicePathExpr),
|
|
718
|
+
sourceFile: row.sourceFile,
|
|
719
|
+
sourceLine: row.sourceLine,
|
|
720
|
+
helperChainJson: nullableString(row.helperChainJson)
|
|
721
|
+
}];
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
function nullableString(value) {
|
|
725
|
+
return value === null || typeof value === "string" ? value : void 0;
|
|
726
|
+
}
|
|
727
|
+
function nullableNumber(value) {
|
|
728
|
+
return value === null || typeof value === "number" ? value : void 0;
|
|
729
|
+
}
|
|
730
|
+
function callSymbolId(db, repoId, call) {
|
|
731
|
+
const row = db.prepare(`
|
|
732
|
+
SELECT id FROM symbols
|
|
733
|
+
WHERE repo_id=? AND source_file=?
|
|
734
|
+
AND ((? IS NOT NULL AND qualified_name=?)
|
|
735
|
+
OR (start_line<=? AND end_line>=?))
|
|
736
|
+
ORDER BY CASE WHEN qualified_name=? THEN 0 ELSE 1 END,
|
|
737
|
+
(end_line-start_line),id
|
|
738
|
+
LIMIT 1
|
|
739
|
+
`).get(
|
|
740
|
+
repoId,
|
|
741
|
+
call.sourceFile,
|
|
742
|
+
call.sourceSymbolQualifiedName,
|
|
743
|
+
call.sourceSymbolQualifiedName,
|
|
744
|
+
call.sourceLine,
|
|
745
|
+
call.sourceLine,
|
|
746
|
+
call.sourceSymbolQualifiedName
|
|
747
|
+
);
|
|
748
|
+
return typeof row?.id === "number" ? row.id : void 0;
|
|
749
|
+
}
|
|
750
|
+
function bindingEvidence(status, candidates, selected) {
|
|
751
|
+
return {
|
|
752
|
+
status,
|
|
753
|
+
candidateCount: candidates.length,
|
|
754
|
+
selectedBindingId: selected?.id,
|
|
755
|
+
sourceOrderRule: "binding_source_line_must_not_follow_call",
|
|
756
|
+
candidates: candidates.map((candidate) => ({
|
|
757
|
+
bindingId: candidate.id,
|
|
758
|
+
symbolId: candidate.symbolId,
|
|
759
|
+
variableName: candidate.variableName,
|
|
760
|
+
alias: candidate.alias,
|
|
761
|
+
aliasExpr: candidate.aliasExpr,
|
|
762
|
+
destinationExpr: candidate.destinationExpr,
|
|
763
|
+
servicePathExpr: candidate.servicePathExpr,
|
|
764
|
+
sourceFile: candidate.sourceFile,
|
|
765
|
+
sourceLine: candidate.sourceLine,
|
|
766
|
+
helperChain: parseBindingChain(candidate.helperChainJson)
|
|
767
|
+
}))
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function bindingSignature(candidate) {
|
|
771
|
+
return JSON.stringify([
|
|
772
|
+
candidate.alias,
|
|
773
|
+
candidate.aliasExpr,
|
|
774
|
+
candidate.destinationExpr,
|
|
775
|
+
candidate.servicePathExpr
|
|
776
|
+
]);
|
|
777
|
+
}
|
|
778
|
+
function parseBindingChain(value) {
|
|
779
|
+
if (!value) return void 0;
|
|
780
|
+
try {
|
|
781
|
+
return JSON.parse(value);
|
|
782
|
+
} catch {
|
|
783
|
+
return void 0;
|
|
784
|
+
}
|
|
651
785
|
}
|
|
652
786
|
|
|
653
787
|
// src/discovery/classify-repository.ts
|
|
@@ -1434,6 +1568,7 @@ function parserQualityDiagnostics(db, strict, options) {
|
|
|
1434
1568
|
implementationCandidateQuality(db, Boolean(options.detail)),
|
|
1435
1569
|
classInstanceNoiseQuality(db),
|
|
1436
1570
|
contextualBindingPropagationQuality(db),
|
|
1571
|
+
serviceBindingQuality(db, Boolean(options.detail)),
|
|
1437
1572
|
nestedThisReceiverQuality(db),
|
|
1438
1573
|
wrapperPathPropagationQuality(db),
|
|
1439
1574
|
remoteQueryTargetQuality(db),
|
|
@@ -1628,6 +1763,83 @@ function contextualBindingPropagationQuality(db) {
|
|
|
1628
1763
|
const opportunities = db.prepare("SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath FROM outbound_calls c WHERE c.call_type='remote_action' AND json_extract(c.evidence_json,'$.receiver') IS NOT NULL AND c.service_binding_id IS NULL ORDER BY c.source_file,c.source_line LIMIT 8").all();
|
|
1629
1764
|
return { severity: missing.count + opportunities.length > 0 ? "warning" : "info", code: "strict_contextual_binding_propagation_quality", message: "Contextual service-client propagation opportunities for trace-time helper resolution", calleeSymbolsMissingParameterMetadata: missing.count, traceTimeContextualOpportunities: opportunities.length, examples: opportunities };
|
|
1630
1765
|
}
|
|
1766
|
+
function serviceBindingQuality(db, detail) {
|
|
1767
|
+
const rows = db.prepare(`
|
|
1768
|
+
SELECT c.source_file sourceFile,c.source_line sourceLine,
|
|
1769
|
+
c.unresolved_reason unresolvedReason,c.evidence_json evidenceJson,
|
|
1770
|
+
s.evidence_json symbolEvidenceJson
|
|
1771
|
+
FROM outbound_calls c
|
|
1772
|
+
LEFT JOIN symbols s ON s.id=c.source_symbol_id
|
|
1773
|
+
WHERE c.call_type='remote_action'
|
|
1774
|
+
AND c.operation_path_expr IS NOT NULL
|
|
1775
|
+
AND c.service_binding_id IS NULL
|
|
1776
|
+
ORDER BY c.source_file,c.source_line
|
|
1777
|
+
`).all();
|
|
1778
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1779
|
+
for (const row of rows) {
|
|
1780
|
+
const category = bindingCategory(row);
|
|
1781
|
+
groups.set(category, [...groups.get(category) ?? [], bindingExample(row)]);
|
|
1782
|
+
}
|
|
1783
|
+
const categories = [...groups.entries()].map(([category, examples]) => ({
|
|
1784
|
+
category,
|
|
1785
|
+
count: examples.length,
|
|
1786
|
+
severity: "warning",
|
|
1787
|
+
suggestedAction: bindingCategoryAction(category),
|
|
1788
|
+
examples: examples.slice(0, 3),
|
|
1789
|
+
expandedExamples: detail ? examples : void 0
|
|
1790
|
+
}));
|
|
1791
|
+
return {
|
|
1792
|
+
severity: rows.length > 0 ? "warning" : "info",
|
|
1793
|
+
code: "strict_service_binding_quality",
|
|
1794
|
+
message: "Remote service-client binding quality aggregate",
|
|
1795
|
+
total: rows.length,
|
|
1796
|
+
categories
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
function bindingCategory(row) {
|
|
1800
|
+
const evidence = parseObject(row.evidenceJson);
|
|
1801
|
+
const resolution = parseObject(evidence.serviceBindingResolution);
|
|
1802
|
+
if (resolution.status === "rejected_future_binding") return "direct_binding_missing";
|
|
1803
|
+
if (resolution.status === "ambiguous") return "ambiguous_binding_candidates";
|
|
1804
|
+
const receiver = evidence.receiver;
|
|
1805
|
+
const symbolEvidence = parseObject(row.symbolEvidenceJson);
|
|
1806
|
+
if (symbolHasReceiverParameter(symbolEvidence, receiver))
|
|
1807
|
+
return "contextual_binding_recoverable";
|
|
1808
|
+
if (!Array.isArray(symbolEvidence.parameterBindings))
|
|
1809
|
+
return "missing_symbol_parameter_metadata";
|
|
1810
|
+
return "unrecoverable_binding";
|
|
1811
|
+
}
|
|
1812
|
+
function symbolHasReceiverParameter(evidence, receiver) {
|
|
1813
|
+
if (typeof receiver !== "string" || !Array.isArray(evidence.parameterBindings))
|
|
1814
|
+
return false;
|
|
1815
|
+
return asRecords(evidence.parameterBindings).some((binding) => {
|
|
1816
|
+
if (binding.kind === "identifier") return binding.name === receiver;
|
|
1817
|
+
if (binding.kind === "object_pattern")
|
|
1818
|
+
return asRecords(binding.properties).some((property) => property.local === receiver);
|
|
1819
|
+
return asRecords(binding.elements).some((element) => element.local === receiver);
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
function bindingExample(row) {
|
|
1823
|
+
const evidence = parseObject(row.evidenceJson);
|
|
1824
|
+
return {
|
|
1825
|
+
sourceFile: row.sourceFile,
|
|
1826
|
+
sourceLine: row.sourceLine,
|
|
1827
|
+
receiver: evidence.receiver,
|
|
1828
|
+
unresolvedReason: row.unresolvedReason,
|
|
1829
|
+
bindingResolution: evidence.serviceBindingResolution
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
function bindingCategoryAction(category) {
|
|
1833
|
+
if (category === "direct_binding_missing")
|
|
1834
|
+
return "Move the binding before the call or bind the call to an earlier immutable client.";
|
|
1835
|
+
if (category === "contextual_binding_recoverable")
|
|
1836
|
+
return "Trace from the caller so parameter binding evidence can be applied.";
|
|
1837
|
+
if (category === "ambiguous_binding_candidates")
|
|
1838
|
+
return "Split mutable client alternatives or add a statically unique client assignment.";
|
|
1839
|
+
if (category === "missing_symbol_parameter_metadata")
|
|
1840
|
+
return "Use named or destructured parameters on an indexed helper symbol.";
|
|
1841
|
+
return "Add a direct CAP client binding or statically provable helper-return binding.";
|
|
1842
|
+
}
|
|
1631
1843
|
function wrapperPathPropagationQuality(db) {
|
|
1632
1844
|
const examples = db.prepare("SELECT source_file sourceFile,source_line sourceLine,json_extract(evidence_json,'$.receiver') receiverName,json_extract(evidence_json,'$.operationPathExpression') pathIdentifier FROM outbound_calls WHERE call_type='remote_action' AND unresolved_reason='dynamic_operation_path_identifier' ORDER BY source_file,source_line LIMIT 5").all();
|
|
1633
1845
|
return { severity: examples.length > 0 ? "warning" : "info", code: "strict_wrapper_path_propagation_quality", message: "Dynamic path sends where send({ path }) used a path identifier", dynamicPathIdentifierCalls: examples.length, examples };
|
|
@@ -1699,10 +1911,33 @@ function externalHttpTargetQuality(db) {
|
|
|
1699
1911
|
return { severity: bad > 0 ? "warning" : "info", code: "strict_external_http_target_quality", message: "External HTTP semantic target aggregate", totalExternalHttpCalls: Number(row.total ?? 0), numericTargetCount: Number(row.numericTargets ?? 0), invalidOrMissingExternalTargetEvidence: Number(row.invalidEvidence ?? 0) };
|
|
1700
1912
|
}
|
|
1701
1913
|
function odataInvocationResolutionQuality(db) {
|
|
1702
|
-
const rows = db.prepare(
|
|
1914
|
+
const rows = db.prepare(`SELECT c.operation_path_expr operationPathExpr,
|
|
1915
|
+
c.source_file sourceFile,c.source_line sourceLine,e.id graphEdgeId,
|
|
1916
|
+
e.status status,e.evidence_json evidenceJson
|
|
1917
|
+
FROM outbound_calls c JOIN graph_edges e
|
|
1918
|
+
ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1919
|
+
WHERE c.call_type='remote_action' AND c.operation_path_expr LIKE '%(%'
|
|
1920
|
+
ORDER BY c.source_file,c.source_line`).all();
|
|
1703
1921
|
const unresolved = rows.filter((row) => row.status === "unresolved" && normalizeODataOperationInvocationPath(row.operationPathExpr)?.wasInvocation).length;
|
|
1704
1922
|
const ambiguous = rows.filter((row) => row.status === "ambiguous" && normalizeODataOperationInvocationPath(row.operationPathExpr)?.wasInvocation).length;
|
|
1705
|
-
|
|
1923
|
+
const examples = rows.filter((row) => row.status === "ambiguous" || row.status === "unresolved").map(odataInvocationExample).slice(0, 5);
|
|
1924
|
+
return { severity: unresolved + ambiguous > 0 ? "warning" : "info", code: "strict_odata_invocation_resolution_quality", message: "OData invocation-path resolution quality aggregate", totalInvocationRemoteActions: rows.length, resolvedInvocationCalls: rows.filter((row) => row.status === "resolved").length, dynamicInvocationCalls: rows.filter((row) => row.status === "dynamic").length, ambiguousInvocationCalls: rows.filter((row) => row.status === "ambiguous").length, unresolvedInvocationCalls: rows.filter((row) => row.status === "unresolved").length, ambiguousNormalizedCalls: ambiguous, unresolvedNormalizedCallsWithIndexedCandidates: unresolved, examples };
|
|
1925
|
+
}
|
|
1926
|
+
function odataInvocationExample(row) {
|
|
1927
|
+
const evidence = parseObject(row.evidenceJson);
|
|
1928
|
+
const normalized = normalizeODataOperationInvocationPath(row.operationPathExpr);
|
|
1929
|
+
return {
|
|
1930
|
+
sourceFile: row.sourceFile,
|
|
1931
|
+
sourceLine: row.sourceLine,
|
|
1932
|
+
graphEdgeId: row.graphEdgeId,
|
|
1933
|
+
status: row.status,
|
|
1934
|
+
rawPath: row.operationPathExpr,
|
|
1935
|
+
normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0,
|
|
1936
|
+
indexedOperationCandidateCount: evidence.indexedOperationCandidateCount,
|
|
1937
|
+
candidateScores: evidence.candidateScores,
|
|
1938
|
+
entityOperationPrecedence: evidence.entityOperationPrecedence,
|
|
1939
|
+
resolutionReasons: evidence.resolutionReasons
|
|
1940
|
+
};
|
|
1706
1941
|
}
|
|
1707
1942
|
function identityAliasBindingQuality(db) {
|
|
1708
1943
|
const examples = db.prepare("SELECT c.source_file sourceFile,c.source_line sourceLine FROM outbound_calls c WHERE c.call_type='remote_action' AND c.service_binding_id IS NULL AND json_extract(c.evidence_json,'$.receiver') IS NOT NULL ORDER BY c.source_file,c.source_line LIMIT 5").all();
|
|
@@ -1755,8 +1990,8 @@ function renderTraceTable(result) {
|
|
|
1755
1990
|
const lines = ["Step Type From To Evidence"];
|
|
1756
1991
|
for (const e of result.edges) {
|
|
1757
1992
|
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)}`);
|
|
1758
|
-
|
|
1759
|
-
|
|
1993
|
+
if (e.unresolvedReason)
|
|
1994
|
+
lines.push(...hintLines(e.evidence).map((hint) => ` ${hint}`));
|
|
1760
1995
|
}
|
|
1761
1996
|
if (result.diagnostics.length > 0) lines.push("", "Diagnostics:", ...result.diagnostics.flatMap(diagnosticLines));
|
|
1762
1997
|
return `${lines.join("\n")}
|
|
@@ -1764,14 +1999,20 @@ function renderTraceTable(result) {
|
|
|
1764
1999
|
}
|
|
1765
2000
|
function diagnosticLines(diagnostic) {
|
|
1766
2001
|
const first = `${String(diagnostic.severity ?? "info")} ${String(diagnostic.code ?? "diagnostic")} ${String(diagnostic.message ?? "")}`;
|
|
1767
|
-
|
|
1768
|
-
return hint ? [first, ` try ${hint}`] : [first];
|
|
2002
|
+
return [first, ...hintLines(diagnostic).map((hint) => ` ${hint}`)];
|
|
1769
2003
|
}
|
|
1770
|
-
function
|
|
2004
|
+
function hintLines(evidence) {
|
|
1771
2005
|
const suggestions = evidence.implementationHintSuggestions;
|
|
1772
|
-
if (!Array.isArray(suggestions)) return
|
|
1773
|
-
const
|
|
1774
|
-
|
|
2006
|
+
if (!Array.isArray(suggestions)) return [];
|
|
2007
|
+
const hints = suggestions.flatMap((item) => isRecord(item) && typeof item.cli === "string" ? [item.cli] : []);
|
|
2008
|
+
const unique = [...new Set(hints)];
|
|
2009
|
+
const shown = unique.slice(0, 3).map((hint) => `try ${hint}`);
|
|
2010
|
+
if (unique.length > shown.length)
|
|
2011
|
+
shown.push(`... ${unique.length - shown.length} more hint(s) available in JSON`);
|
|
2012
|
+
return shown;
|
|
2013
|
+
}
|
|
2014
|
+
function isRecord(value) {
|
|
2015
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1775
2016
|
}
|
|
1776
2017
|
|
|
1777
2018
|
// src/output/json-output.ts
|
|
@@ -1843,11 +2084,11 @@ function cliHints(value) {
|
|
|
1843
2084
|
function cliHintsFromSuggestions(value) {
|
|
1844
2085
|
if (!Array.isArray(value)) return [];
|
|
1845
2086
|
return value.flatMap((item) => {
|
|
1846
|
-
if (!
|
|
2087
|
+
if (!isRecord2(item)) return [];
|
|
1847
2088
|
return typeof item.cli === "string" ? [item.cli] : [];
|
|
1848
2089
|
});
|
|
1849
2090
|
}
|
|
1850
|
-
function
|
|
2091
|
+
function isRecord2(value) {
|
|
1851
2092
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1852
2093
|
}
|
|
1853
2094
|
function cappedHints(hints) {
|