@mortice/mapwright 0.8.0
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 +161 -0
- package/LICENSE +21 -0
- package/README.md +262 -0
- package/dist/catalogue/canonicalisation.d.ts +36 -0
- package/dist/catalogue/canonicalisation.js +262 -0
- package/dist/catalogue/config.d.ts +13 -0
- package/dist/catalogue/config.js +77 -0
- package/dist/catalogue/deterministic.d.ts +3 -0
- package/dist/catalogue/deterministic.js +25 -0
- package/dist/catalogue/diagnostics.d.ts +2 -0
- package/dist/catalogue/diagnostics.js +34 -0
- package/dist/catalogue/evidence.d.ts +1 -0
- package/dist/catalogue/evidence.js +10 -0
- package/dist/catalogue/examples.d.ts +3 -0
- package/dist/catalogue/examples.js +20 -0
- package/dist/catalogue/identity.d.ts +61 -0
- package/dist/catalogue/identity.js +114 -0
- package/dist/catalogue/ignore.d.ts +4 -0
- package/dist/catalogue/ignore.js +29 -0
- package/dist/catalogue/indexer.d.ts +20 -0
- package/dist/catalogue/indexer.js +1393 -0
- package/dist/catalogue/linkage.d.ts +16 -0
- package/dist/catalogue/linkage.js +419 -0
- package/dist/catalogue/moduleResolution.d.ts +4 -0
- package/dist/catalogue/moduleResolution.js +67 -0
- package/dist/catalogue/ordering.d.ts +13 -0
- package/dist/catalogue/ordering.js +86 -0
- package/dist/catalogue/packages.d.ts +12 -0
- package/dist/catalogue/packages.js +475 -0
- package/dist/catalogue/pythonReExports.d.ts +7 -0
- package/dist/catalogue/pythonReExports.js +113 -0
- package/dist/catalogue/resolveRefs.d.ts +40 -0
- package/dist/catalogue/resolveRefs.js +284 -0
- package/dist/catalogue/scanner.d.ts +8 -0
- package/dist/catalogue/scanner.js +99 -0
- package/dist/catalogue/schema.d.ts +289 -0
- package/dist/catalogue/schema.js +1 -0
- package/dist/catalogue/store.d.ts +41 -0
- package/dist/catalogue/store.js +209 -0
- package/dist/catalogue/templates.d.ts +18 -0
- package/dist/catalogue/templates.js +158 -0
- package/dist/catalogue/version.d.ts +12 -0
- package/dist/catalogue/version.js +30 -0
- package/dist/cli/commands/adoptionPlan.d.ts +8 -0
- package/dist/cli/commands/adoptionPlan.js +24 -0
- package/dist/cli/commands/auditDocs.d.ts +8 -0
- package/dist/cli/commands/auditDocs.js +24 -0
- package/dist/cli/commands/changeImpact.d.ts +10 -0
- package/dist/cli/commands/changeImpact.js +36 -0
- package/dist/cli/commands/changes.d.ts +12 -0
- package/dist/cli/commands/changes.js +31 -0
- package/dist/cli/commands/check.d.ts +9 -0
- package/dist/cli/commands/check.js +4 -0
- package/dist/cli/commands/compatibility.d.ts +10 -0
- package/dist/cli/commands/compatibility.js +43 -0
- package/dist/cli/commands/diff.d.ts +31 -0
- package/dist/cli/commands/diff.js +118 -0
- package/dist/cli/commands/dogfood.d.ts +8 -0
- package/dist/cli/commands/dogfood.js +24 -0
- package/dist/cli/commands/drift.d.ts +8 -0
- package/dist/cli/commands/drift.js +13 -0
- package/dist/cli/commands/index.d.ts +9 -0
- package/dist/cli/commands/index.js +9 -0
- package/dist/cli/commands/indexWorkspace.d.ts +8 -0
- package/dist/cli/commands/indexWorkspace.js +23 -0
- package/dist/cli/commands/recommendConfig.d.ts +7 -0
- package/dist/cli/commands/recommendConfig.js +20 -0
- package/dist/cli/commands/schema.d.ts +1 -0
- package/dist/cli/commands/schema.js +37 -0
- package/dist/cli/commands/template.d.ts +5 -0
- package/dist/cli/commands/template.js +44 -0
- package/dist/cli/commands/validate.d.ts +9 -0
- package/dist/cli/commands/validate.js +85 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +211 -0
- package/dist/comparison/artefactValidate.d.ts +19 -0
- package/dist/comparison/artefactValidate.js +334 -0
- package/dist/comparison/candidates.d.ts +12 -0
- package/dist/comparison/candidates.js +391 -0
- package/dist/comparison/compare.d.ts +15 -0
- package/dist/comparison/compare.js +240 -0
- package/dist/comparison/compatibility.d.ts +26 -0
- package/dist/comparison/compatibility.js +316 -0
- package/dist/comparison/drift.d.ts +34 -0
- package/dist/comparison/drift.js +71 -0
- package/dist/comparison/identity.d.ts +30 -0
- package/dist/comparison/identity.js +73 -0
- package/dist/comparison/impact.d.ts +14 -0
- package/dist/comparison/impact.js +291 -0
- package/dist/comparison/index.d.ts +6 -0
- package/dist/comparison/index.js +6 -0
- package/dist/comparison/integrity.d.ts +12 -0
- package/dist/comparison/integrity.js +199 -0
- package/dist/comparison/ordering.d.ts +4 -0
- package/dist/comparison/ordering.js +69 -0
- package/dist/comparison/projections.d.ts +19 -0
- package/dist/comparison/projections.js +185 -0
- package/dist/comparison/relationshipPolicy.d.ts +19 -0
- package/dist/comparison/relationshipPolicy.js +98 -0
- package/dist/comparison/schema.d.ts +178 -0
- package/dist/comparison/schema.js +16 -0
- package/dist/comparison/store.d.ts +58 -0
- package/dist/comparison/store.js +462 -0
- package/dist/comparison/validate.d.ts +23 -0
- package/dist/comparison/validate.js +137 -0
- package/dist/comparison/version.d.ts +9 -0
- package/dist/comparison/version.js +11 -0
- package/dist/extractors/docTags.d.ts +9 -0
- package/dist/extractors/docTags.js +106 -0
- package/dist/extractors/generic.d.ts +5 -0
- package/dist/extractors/generic.js +3 -0
- package/dist/extractors/javascript.d.ts +3 -0
- package/dist/extractors/javascript.js +4 -0
- package/dist/extractors/markdown.d.ts +7 -0
- package/dist/extractors/markdown.js +269 -0
- package/dist/extractors/python.d.ts +7 -0
- package/dist/extractors/python.js +314 -0
- package/dist/extractors/typescript.d.ts +7 -0
- package/dist/extractors/typescript.js +387 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp/resources.d.ts +18 -0
- package/dist/mcp/resources.js +57 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +95 -0
- package/dist/mcp/tools.d.ts +29 -0
- package/dist/mcp/tools.js +902 -0
- package/dist/queries/adoption.d.ts +62 -0
- package/dist/queries/adoption.js +366 -0
- package/dist/queries/capabilities.d.ts +45 -0
- package/dist/queries/capabilities.js +124 -0
- package/dist/queries/changes.d.ts +33 -0
- package/dist/queries/changes.js +53 -0
- package/dist/queries/dependencies.d.ts +17 -0
- package/dist/queries/dependencies.js +94 -0
- package/dist/queries/dogfood.d.ts +50 -0
- package/dist/queries/dogfood.js +116 -0
- package/dist/queries/editingContext.d.ts +84 -0
- package/dist/queries/editingContext.js +240 -0
- package/dist/queries/examples.d.ts +8 -0
- package/dist/queries/examples.js +16 -0
- package/dist/queries/files.d.ts +8 -0
- package/dist/queries/files.js +14 -0
- package/dist/queries/lists.d.ts +94 -0
- package/dist/queries/lists.js +184 -0
- package/dist/queries/overview.d.ts +2 -0
- package/dist/queries/overview.js +8 -0
- package/dist/queries/publicApis.d.ts +60 -0
- package/dist/queries/publicApis.js +120 -0
- package/dist/queries/ranking.d.ts +67 -0
- package/dist/queries/ranking.js +387 -0
- package/dist/queries/recommendConfig.d.ts +47 -0
- package/dist/queries/recommendConfig.js +123 -0
- package/dist/queries/relationships.d.ts +5 -0
- package/dist/queries/relationships.js +15 -0
- package/dist/queries/reuseCandidates.d.ts +34 -0
- package/dist/queries/reuseCandidates.js +181 -0
- package/dist/queries/symbols.d.ts +40 -0
- package/dist/queries/symbols.js +41 -0
- package/dist/security/pathPolicy.d.ts +12 -0
- package/dist/security/pathPolicy.js +46 -0
- package/dist/security/secretPatterns.d.ts +1 -0
- package/dist/security/secretPatterns.js +4 -0
- package/dist/security/sourceAccess.d.ts +12 -0
- package/dist/security/sourceAccess.js +35 -0
- package/package.json +60 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { CatalogueSnapshot } from "../catalogue/schema.js";
|
|
2
|
+
export type AdoptionGapKind = "missing_public_api_docs" | "missing_capability_summary" | "missing_capability_examples" | "missing_capability_tests" | "missing_package_catalogue" | "unlinked_example" | "unlinked_test" | "unknown_reference";
|
|
3
|
+
export interface AdoptionGap {
|
|
4
|
+
id: string;
|
|
5
|
+
severity: "info" | "warning" | "error";
|
|
6
|
+
kind: AdoptionGapKind;
|
|
7
|
+
target: {
|
|
8
|
+
kind: "package" | "capability" | "publicApi" | "symbol" | "file";
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
filePath?: string;
|
|
12
|
+
message: string;
|
|
13
|
+
suggestedAction: string;
|
|
14
|
+
templateId?: DocumentationTemplateId;
|
|
15
|
+
provenance: {
|
|
16
|
+
source: "adoption-audit";
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface AdoptionRecommendation {
|
|
20
|
+
id: string;
|
|
21
|
+
priority: "high" | "medium" | "low";
|
|
22
|
+
kind: AdoptionGapKind;
|
|
23
|
+
title: string;
|
|
24
|
+
templateId?: DocumentationTemplateId;
|
|
25
|
+
gapIds: string[];
|
|
26
|
+
targetIds: string[];
|
|
27
|
+
suggestedAction: string;
|
|
28
|
+
provenance: {
|
|
29
|
+
source: "adoption-audit";
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface AdoptionAudit {
|
|
33
|
+
summary: {
|
|
34
|
+
publicApisTotal: number;
|
|
35
|
+
publicApisMissingDocs: number;
|
|
36
|
+
capabilitiesTotal: number;
|
|
37
|
+
capabilitiesMissingExamples: number;
|
|
38
|
+
capabilitiesMissingTests: number;
|
|
39
|
+
packagesTotal: number;
|
|
40
|
+
packagesMissingCatalogueDocs: number;
|
|
41
|
+
gapsTotal: number;
|
|
42
|
+
gapsReturned: number;
|
|
43
|
+
gapsTruncated: boolean;
|
|
44
|
+
};
|
|
45
|
+
gaps: AdoptionGap[];
|
|
46
|
+
recommendations: AdoptionRecommendation[];
|
|
47
|
+
}
|
|
48
|
+
export interface AdoptionPlan {
|
|
49
|
+
summary: AdoptionAudit["summary"];
|
|
50
|
+
groupedRecommendations: Array<{
|
|
51
|
+
priority: "high" | "medium" | "low";
|
|
52
|
+
recommendations: AdoptionRecommendation[];
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
export interface GetAdoptionAuditOptions {
|
|
56
|
+
includeInfo?: boolean;
|
|
57
|
+
includeGenerated?: boolean;
|
|
58
|
+
maxGaps?: number;
|
|
59
|
+
}
|
|
60
|
+
export type DocumentationTemplateId = "package-catalogue" | "capability-section" | "typescript-public-api-jsdoc" | "python-public-api-docstring" | "test-coverage-doc" | "example-reference";
|
|
61
|
+
export declare function getAdoptionAudit(snapshot: CatalogueSnapshot, options?: GetAdoptionAuditOptions): AdoptionAudit;
|
|
62
|
+
export declare function getAdoptionPlan(snapshot: CatalogueSnapshot, options?: GetAdoptionAuditOptions): AdoptionPlan;
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
const UNKNOWN_REFERENCE_CODES = new Set([
|
|
2
|
+
"example_references_unknown_capability",
|
|
3
|
+
"example_references_unknown_public_api",
|
|
4
|
+
"example_references_ambiguous_public_api",
|
|
5
|
+
"public_api_references_unknown_capability",
|
|
6
|
+
"test_references_unknown_capability",
|
|
7
|
+
"test_references_unknown_symbol",
|
|
8
|
+
"test_references_unknown_public_api",
|
|
9
|
+
"capability_references_unknown_symbol",
|
|
10
|
+
"capability_references_unknown_public_api"
|
|
11
|
+
]);
|
|
12
|
+
const DEFAULT_MAX_GAPS = 500;
|
|
13
|
+
const GENERATED_PROVENANCE_SOURCE_FRAGMENTS = [
|
|
14
|
+
"extractor",
|
|
15
|
+
"scanner",
|
|
16
|
+
"manifest",
|
|
17
|
+
"directory",
|
|
18
|
+
"fallback",
|
|
19
|
+
"generated",
|
|
20
|
+
"inferred"
|
|
21
|
+
];
|
|
22
|
+
export function getAdoptionAudit(snapshot, options = {}) {
|
|
23
|
+
const gaps = [];
|
|
24
|
+
const includeGenerated = options.includeGenerated ?? false;
|
|
25
|
+
const maxGaps = options.maxGaps ?? DEFAULT_MAX_GAPS;
|
|
26
|
+
const symbolsById = new Map(snapshot.symbols.map((symbol) => [symbol.id, symbol]));
|
|
27
|
+
const filesByPath = new Map(snapshot.files.map((file) => [file.path, file]));
|
|
28
|
+
for (const api of snapshot.publicApis) {
|
|
29
|
+
const symbol = symbolsById.get(api.symbolId);
|
|
30
|
+
if (!symbol || symbol.visibility === "private" || symbol.visibility === "test_only" || symbol.visibility === "supported_internal") {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const hasCataloguePublicApiTag = Boolean(symbol.doc?.tags?.some((tag) => tag.tag === "catalogue" && tag.value === "public-api"));
|
|
34
|
+
if (!hasCataloguePublicApiTag) {
|
|
35
|
+
const hasCapabilityLink = symbol.capabilities.length > 0 || api.capabilityIds.length > 0;
|
|
36
|
+
const hasDocBody = Boolean(symbol.doc?.present && symbol.doc.text?.trim());
|
|
37
|
+
gaps.push(createGap({
|
|
38
|
+
severity: hasCapabilityLink || hasDocBody ? "warning" : "info",
|
|
39
|
+
kind: "missing_public_api_docs",
|
|
40
|
+
targetKind: "publicApi",
|
|
41
|
+
targetId: api.id,
|
|
42
|
+
filePath: symbol.filePath,
|
|
43
|
+
message: `Public API ${api.id} is missing @catalogue public-api documentation tag.`,
|
|
44
|
+
suggestedAction: "Add a conservative public API JSDoc/docstring using TODO placeholders.",
|
|
45
|
+
templateId: symbol.language === "python" ? "python-public-api-docstring" : "typescript-public-api-jsdoc"
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const capability of snapshot.capabilities) {
|
|
50
|
+
if (!includeGenerated && isCapabilityGenerated(capability)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (capability.stability === "internal" || capability.stability === "test-only") {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!capability.summary?.trim()) {
|
|
57
|
+
gaps.push(createGap({
|
|
58
|
+
severity: "warning",
|
|
59
|
+
kind: "missing_capability_summary",
|
|
60
|
+
targetKind: "capability",
|
|
61
|
+
targetId: capability.id,
|
|
62
|
+
filePath: capability.files[0],
|
|
63
|
+
message: `Capability ${capability.id} is missing a summary.`,
|
|
64
|
+
suggestedAction: "Add a concise factual summary to the capability section.",
|
|
65
|
+
templateId: "capability-section"
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
if (capability.examples.length === 0) {
|
|
69
|
+
gaps.push(createGap({
|
|
70
|
+
severity: "info",
|
|
71
|
+
kind: "missing_capability_examples",
|
|
72
|
+
targetKind: "capability",
|
|
73
|
+
targetId: capability.id,
|
|
74
|
+
filePath: capability.files[0],
|
|
75
|
+
message: `Capability ${capability.id} has no linked examples.`,
|
|
76
|
+
suggestedAction: "Add metadata-only example references to the capability section.",
|
|
77
|
+
templateId: "example-reference"
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
const hasTests = capability.tests.length > 0
|
|
81
|
+
|| snapshot.tests.some((test) => test.coversCapabilities.includes(capability.id));
|
|
82
|
+
if (!hasTests) {
|
|
83
|
+
gaps.push(createGap({
|
|
84
|
+
severity: "warning",
|
|
85
|
+
kind: "missing_capability_tests",
|
|
86
|
+
targetKind: "capability",
|
|
87
|
+
targetId: capability.id,
|
|
88
|
+
filePath: capability.files[0],
|
|
89
|
+
message: `Capability ${capability.id} has no linked tests.`,
|
|
90
|
+
suggestedAction: "Link at least one test using @coversCapability or capability Tests section metadata.",
|
|
91
|
+
templateId: "test-coverage-doc"
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const pkg of snapshot.packages) {
|
|
96
|
+
if (!includeGenerated && isPackageGenerated(pkg)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (isPackageCatalogueAuthored(pkg)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
gaps.push(createGap({
|
|
103
|
+
severity: "warning",
|
|
104
|
+
kind: "missing_package_catalogue",
|
|
105
|
+
targetKind: "package",
|
|
106
|
+
targetId: pkg.id,
|
|
107
|
+
filePath: packageCataloguePath(pkg),
|
|
108
|
+
message: `Package ${pkg.id} is missing authored package catalogue metadata.`,
|
|
109
|
+
suggestedAction: "Create a CATALOGUE.md package section for this package with conservative TODO placeholders.",
|
|
110
|
+
templateId: "package-catalogue"
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
for (const example of snapshot.examples) {
|
|
114
|
+
if (!includeGenerated && example.filePath && isFileGenerated(example.filePath, filesByPath)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (example.capabilityIds.length === 0 && example.publicApiIds.length === 0) {
|
|
118
|
+
gaps.push(createGap({
|
|
119
|
+
severity: "info",
|
|
120
|
+
kind: "unlinked_example",
|
|
121
|
+
targetKind: "file",
|
|
122
|
+
targetId: example.filePath ?? example.id,
|
|
123
|
+
filePath: example.filePath,
|
|
124
|
+
message: `Example ${example.id} is not linked to a capability or public API.`,
|
|
125
|
+
suggestedAction: "Link the example to a capability and/or public API metadata reference.",
|
|
126
|
+
templateId: "example-reference"
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const test of snapshot.tests) {
|
|
131
|
+
if (!includeGenerated && isFileGenerated(test.filePath, filesByPath)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (test.coversCapabilities.length === 0 && test.coversSymbols.length === 0 && test.coversPublicApis.length === 0) {
|
|
135
|
+
gaps.push(createGap({
|
|
136
|
+
severity: "info",
|
|
137
|
+
kind: "unlinked_test",
|
|
138
|
+
targetKind: "file",
|
|
139
|
+
targetId: test.filePath,
|
|
140
|
+
filePath: test.filePath,
|
|
141
|
+
message: `Test ${test.id} has no @coversCapability, @coversSymbol, or @coversPublicApi links.`,
|
|
142
|
+
suggestedAction: "Add conservative coverage tags to link this test to symbols/capabilities/public APIs.",
|
|
143
|
+
templateId: "test-coverage-doc"
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const diagnostic of snapshot.diagnostics) {
|
|
148
|
+
if (!UNKNOWN_REFERENCE_CODES.has(diagnostic.code)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
gaps.push(gapFromUnknownReferenceDiagnostic(diagnostic));
|
|
152
|
+
}
|
|
153
|
+
const fullSortedGaps = gaps
|
|
154
|
+
.filter((gap) => (options.includeInfo ?? true) || gap.severity !== "info")
|
|
155
|
+
.sort(sortGaps);
|
|
156
|
+
const sortedGaps = fullSortedGaps.slice(0, Math.max(0, maxGaps));
|
|
157
|
+
const recommendations = buildRecommendations(sortedGaps);
|
|
158
|
+
return {
|
|
159
|
+
summary: {
|
|
160
|
+
publicApisTotal: snapshot.publicApis.length,
|
|
161
|
+
publicApisMissingDocs: fullSortedGaps.filter((gap) => gap.kind === "missing_public_api_docs").length,
|
|
162
|
+
capabilitiesTotal: snapshot.capabilities.length,
|
|
163
|
+
capabilitiesMissingExamples: fullSortedGaps.filter((gap) => gap.kind === "missing_capability_examples").length,
|
|
164
|
+
capabilitiesMissingTests: fullSortedGaps.filter((gap) => gap.kind === "missing_capability_tests").length,
|
|
165
|
+
packagesTotal: snapshot.packages.length,
|
|
166
|
+
packagesMissingCatalogueDocs: fullSortedGaps.filter((gap) => gap.kind === "missing_package_catalogue").length,
|
|
167
|
+
gapsTotal: fullSortedGaps.length,
|
|
168
|
+
gapsReturned: sortedGaps.length,
|
|
169
|
+
gapsTruncated: sortedGaps.length < fullSortedGaps.length
|
|
170
|
+
},
|
|
171
|
+
gaps: sortedGaps,
|
|
172
|
+
recommendations
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export function getAdoptionPlan(snapshot, options = {}) {
|
|
176
|
+
const audit = getAdoptionAudit(snapshot, options);
|
|
177
|
+
const priorities = ["high", "medium", "low"];
|
|
178
|
+
return {
|
|
179
|
+
summary: audit.summary,
|
|
180
|
+
groupedRecommendations: priorities.map((priority) => ({
|
|
181
|
+
priority,
|
|
182
|
+
recommendations: audit.recommendations
|
|
183
|
+
.filter((recommendation) => recommendation.priority === priority)
|
|
184
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
185
|
+
}))
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function buildRecommendations(gaps) {
|
|
189
|
+
const groups = new Map();
|
|
190
|
+
for (const gap of gaps) {
|
|
191
|
+
const key = `${gap.kind}:${gap.templateId ?? "none"}`;
|
|
192
|
+
const current = groups.get(key) ?? [];
|
|
193
|
+
current.push(gap);
|
|
194
|
+
groups.set(key, current);
|
|
195
|
+
}
|
|
196
|
+
const recommendations = [];
|
|
197
|
+
for (const [key, groupedGaps] of groups.entries()) {
|
|
198
|
+
const [kind, templateIdRaw] = key.split(":", 2);
|
|
199
|
+
const templateId = templateIdRaw === "none" ? undefined : templateIdRaw;
|
|
200
|
+
const priority = groupedGaps.some((gap) => gap.severity === "error")
|
|
201
|
+
? "high"
|
|
202
|
+
: groupedGaps.some((gap) => gap.severity === "warning")
|
|
203
|
+
? "medium"
|
|
204
|
+
: "low";
|
|
205
|
+
const gapIds = groupedGaps.map((gap) => gap.id).sort();
|
|
206
|
+
const targetIds = [...new Set(groupedGaps.map((gap) => gap.target.id))].sort();
|
|
207
|
+
recommendations.push({
|
|
208
|
+
id: `recommendation:${kind}:${templateId ?? "none"}`,
|
|
209
|
+
priority,
|
|
210
|
+
kind: kind,
|
|
211
|
+
title: recommendationTitle(kind),
|
|
212
|
+
templateId,
|
|
213
|
+
gapIds,
|
|
214
|
+
targetIds,
|
|
215
|
+
suggestedAction: groupedGaps[0]?.suggestedAction ?? "Review adoption gaps and fill missing documentation conservatively.",
|
|
216
|
+
provenance: {
|
|
217
|
+
source: "adoption-audit"
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return recommendations.sort((a, b) => {
|
|
222
|
+
const priorityDiff = priorityRank(a.priority) - priorityRank(b.priority);
|
|
223
|
+
if (priorityDiff !== 0) {
|
|
224
|
+
return priorityDiff;
|
|
225
|
+
}
|
|
226
|
+
return a.id.localeCompare(b.id);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function recommendationTitle(kind) {
|
|
230
|
+
switch (kind) {
|
|
231
|
+
case "missing_public_api_docs":
|
|
232
|
+
return "Add missing public API catalogue docs";
|
|
233
|
+
case "missing_capability_summary":
|
|
234
|
+
return "Complete capability summaries";
|
|
235
|
+
case "missing_capability_examples":
|
|
236
|
+
return "Link capability examples";
|
|
237
|
+
case "missing_capability_tests":
|
|
238
|
+
return "Link capability tests";
|
|
239
|
+
case "missing_package_catalogue":
|
|
240
|
+
return "Create package CATALOGUE metadata";
|
|
241
|
+
case "unlinked_example":
|
|
242
|
+
return "Link unlinked examples";
|
|
243
|
+
case "unlinked_test":
|
|
244
|
+
return "Link unlinked tests";
|
|
245
|
+
case "unknown_reference":
|
|
246
|
+
return "Resolve unknown documentation references";
|
|
247
|
+
default:
|
|
248
|
+
return "Address adoption documentation gaps";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function gapFromUnknownReferenceDiagnostic(diagnostic) {
|
|
252
|
+
const targetKind = diagnostic.target?.kind === "package"
|
|
253
|
+
? "package"
|
|
254
|
+
: diagnostic.target?.kind === "capability"
|
|
255
|
+
? "capability"
|
|
256
|
+
: diagnostic.target?.kind === "publicApi"
|
|
257
|
+
? "publicApi"
|
|
258
|
+
: diagnostic.target?.kind === "symbol"
|
|
259
|
+
? "symbol"
|
|
260
|
+
: "file";
|
|
261
|
+
const targetId = diagnostic.target?.id ?? diagnostic.filePath ?? diagnostic.id;
|
|
262
|
+
return createGap({
|
|
263
|
+
severity: diagnostic.severity,
|
|
264
|
+
kind: "unknown_reference",
|
|
265
|
+
targetKind,
|
|
266
|
+
targetId,
|
|
267
|
+
filePath: diagnostic.filePath,
|
|
268
|
+
message: diagnostic.message,
|
|
269
|
+
suggestedAction: "Update documentation metadata to reference existing capabilities, symbols, and public APIs."
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function createGap(input) {
|
|
273
|
+
return {
|
|
274
|
+
id: `adoption-gap:${input.kind}:${input.targetKind}:${input.targetId}`,
|
|
275
|
+
severity: input.severity,
|
|
276
|
+
kind: input.kind,
|
|
277
|
+
target: {
|
|
278
|
+
kind: input.targetKind,
|
|
279
|
+
id: input.targetId
|
|
280
|
+
},
|
|
281
|
+
filePath: input.filePath,
|
|
282
|
+
message: input.message,
|
|
283
|
+
suggestedAction: input.suggestedAction,
|
|
284
|
+
templateId: input.templateId,
|
|
285
|
+
provenance: {
|
|
286
|
+
source: "adoption-audit"
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function isPackageCatalogueAuthored(pkg) {
|
|
291
|
+
const source = pkg.provenance.source.toLowerCase();
|
|
292
|
+
if (source === "catalogue-md") {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return (pkg.provenance.locations ?? []).some((location) => /(^|\/)CATALOGUE\.md$/i.test(location));
|
|
296
|
+
}
|
|
297
|
+
function isPackageGenerated(pkg) {
|
|
298
|
+
if (pkg.path === ".") {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
return isGeneratedByProvenance(pkg.provenance.source, pkg.provenance.locations);
|
|
302
|
+
}
|
|
303
|
+
function isCapabilityGenerated(capability) {
|
|
304
|
+
return isGeneratedByProvenance(capability.provenance.source, capability.provenance.locations);
|
|
305
|
+
}
|
|
306
|
+
function isFileGenerated(filePath, filesByPath) {
|
|
307
|
+
const file = filesByPath.get(filePath);
|
|
308
|
+
if (!file) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
return file.isGenerated || file.kind === "generated";
|
|
312
|
+
}
|
|
313
|
+
function isGeneratedByProvenance(source, locations) {
|
|
314
|
+
const normalized = (source ?? "").toLowerCase();
|
|
315
|
+
if (normalized.includes("catalogue")) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
const locationList = locations ?? [];
|
|
319
|
+
if (locationList.some((location) => /(^|\/)CATALOGUE\.md$/i.test(location))) {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
return GENERATED_PROVENANCE_SOURCE_FRAGMENTS.some((fragment) => normalized.includes(fragment));
|
|
323
|
+
}
|
|
324
|
+
function packageCataloguePath(pkg) {
|
|
325
|
+
if (pkg.path === ".") {
|
|
326
|
+
return "CATALOGUE.md";
|
|
327
|
+
}
|
|
328
|
+
return `${pkg.path}/CATALOGUE.md`;
|
|
329
|
+
}
|
|
330
|
+
function sortGaps(a, b) {
|
|
331
|
+
const severityDiff = severityRank(a.severity) - severityRank(b.severity);
|
|
332
|
+
if (severityDiff !== 0) {
|
|
333
|
+
return severityDiff;
|
|
334
|
+
}
|
|
335
|
+
const kindDiff = a.kind.localeCompare(b.kind);
|
|
336
|
+
if (kindDiff !== 0) {
|
|
337
|
+
return kindDiff;
|
|
338
|
+
}
|
|
339
|
+
const targetKindDiff = a.target.kind.localeCompare(b.target.kind);
|
|
340
|
+
if (targetKindDiff !== 0) {
|
|
341
|
+
return targetKindDiff;
|
|
342
|
+
}
|
|
343
|
+
const targetIdDiff = a.target.id.localeCompare(b.target.id);
|
|
344
|
+
if (targetIdDiff !== 0) {
|
|
345
|
+
return targetIdDiff;
|
|
346
|
+
}
|
|
347
|
+
return a.id.localeCompare(b.id);
|
|
348
|
+
}
|
|
349
|
+
function severityRank(severity) {
|
|
350
|
+
if (severity === "error") {
|
|
351
|
+
return 0;
|
|
352
|
+
}
|
|
353
|
+
if (severity === "warning") {
|
|
354
|
+
return 1;
|
|
355
|
+
}
|
|
356
|
+
return 2;
|
|
357
|
+
}
|
|
358
|
+
function priorityRank(priority) {
|
|
359
|
+
if (priority === "high") {
|
|
360
|
+
return 0;
|
|
361
|
+
}
|
|
362
|
+
if (priority === "medium") {
|
|
363
|
+
return 1;
|
|
364
|
+
}
|
|
365
|
+
return 2;
|
|
366
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CapabilityRecord, CatalogueSnapshot, DiagnosticRecord, ExampleRecord, PackageRecord, PublicApiRecord, SymbolRecord, TestRecord } from "../catalogue/schema.js";
|
|
2
|
+
export interface ListCapabilitiesOptions {
|
|
3
|
+
stability?: CapabilityRecord["stability"];
|
|
4
|
+
limit?: number;
|
|
5
|
+
offset?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function listCapabilities(snapshot: CatalogueSnapshot, options?: ListCapabilitiesOptions): CapabilityRecord[];
|
|
8
|
+
export interface CapabilityContextResult {
|
|
9
|
+
capability: CapabilityRecord;
|
|
10
|
+
packages: PackageRecord[];
|
|
11
|
+
publicApis: PublicApiRecord[];
|
|
12
|
+
symbols: SymbolRecord[];
|
|
13
|
+
examples: ExampleRecord[];
|
|
14
|
+
tests: TestRecord[];
|
|
15
|
+
contracts: string[];
|
|
16
|
+
limitations: string[];
|
|
17
|
+
relatedFiles: string[];
|
|
18
|
+
dependencyNeighborhoodSummary: {
|
|
19
|
+
relatedImportRelationships: number;
|
|
20
|
+
relatedExportRelationships: number;
|
|
21
|
+
neighboringFiles: string[];
|
|
22
|
+
neighboringExternalPackages: string[];
|
|
23
|
+
};
|
|
24
|
+
diagnostics: DiagnosticRecord[];
|
|
25
|
+
truncated: {
|
|
26
|
+
publicApis: boolean;
|
|
27
|
+
publicApisTotal: number;
|
|
28
|
+
symbols: boolean;
|
|
29
|
+
symbolsTotal: number;
|
|
30
|
+
examples: boolean;
|
|
31
|
+
examplesTotal: number;
|
|
32
|
+
tests: boolean;
|
|
33
|
+
testsTotal: number;
|
|
34
|
+
diagnostics: boolean;
|
|
35
|
+
diagnosticsTotal: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export interface CapabilityContextOptions {
|
|
39
|
+
maxPublicApis?: number;
|
|
40
|
+
maxSymbols?: number;
|
|
41
|
+
maxExamples?: number;
|
|
42
|
+
maxTests?: number;
|
|
43
|
+
maxDiagnostics?: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function getCapabilityContext(snapshot: CatalogueSnapshot, capabilityId: string, options?: CapabilityContextOptions): CapabilityContextResult | undefined;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export function listCapabilities(snapshot, options = {}) {
|
|
2
|
+
const filtered = snapshot.capabilities.filter((capability) => {
|
|
3
|
+
if (options.stability && capability.stability !== options.stability) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
return true;
|
|
7
|
+
});
|
|
8
|
+
const offset = Math.max(0, options.offset ?? 0);
|
|
9
|
+
const limit = Math.max(1, options.limit ?? 50);
|
|
10
|
+
return filtered.slice(offset, offset + limit);
|
|
11
|
+
}
|
|
12
|
+
const DEFAULT_CAPABILITY_CONTEXT_LIMITS = {
|
|
13
|
+
maxPublicApis: 100,
|
|
14
|
+
maxSymbols: 200,
|
|
15
|
+
maxExamples: 100,
|
|
16
|
+
maxTests: 100,
|
|
17
|
+
maxDiagnostics: 200
|
|
18
|
+
};
|
|
19
|
+
export function getCapabilityContext(snapshot, capabilityId, options = {}) {
|
|
20
|
+
const limits = {
|
|
21
|
+
maxPublicApis: normalizeLimit(options.maxPublicApis, DEFAULT_CAPABILITY_CONTEXT_LIMITS.maxPublicApis),
|
|
22
|
+
maxSymbols: normalizeLimit(options.maxSymbols, DEFAULT_CAPABILITY_CONTEXT_LIMITS.maxSymbols),
|
|
23
|
+
maxExamples: normalizeLimit(options.maxExamples, DEFAULT_CAPABILITY_CONTEXT_LIMITS.maxExamples),
|
|
24
|
+
maxTests: normalizeLimit(options.maxTests, DEFAULT_CAPABILITY_CONTEXT_LIMITS.maxTests),
|
|
25
|
+
maxDiagnostics: normalizeLimit(options.maxDiagnostics, DEFAULT_CAPABILITY_CONTEXT_LIMITS.maxDiagnostics)
|
|
26
|
+
};
|
|
27
|
+
const capability = snapshot.capabilities.find((item) => item.id === capabilityId);
|
|
28
|
+
if (!capability) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const allPublicApis = snapshot.publicApis.filter((api) => capability.publicApis.includes(api.id)).sort((a, b) => a.id.localeCompare(b.id));
|
|
32
|
+
const publicApis = allPublicApis.slice(0, limits.maxPublicApis);
|
|
33
|
+
const allSymbols = snapshot.symbols.filter((symbol) => capability.symbols.includes(symbol.id)).sort((a, b) => a.id.localeCompare(b.id));
|
|
34
|
+
const symbols = allSymbols.slice(0, limits.maxSymbols);
|
|
35
|
+
const packages = snapshot.packages.filter((pkg) => capability.packages.includes(pkg.id)).sort((a, b) => a.id.localeCompare(b.id));
|
|
36
|
+
const allExamples = snapshot.examples
|
|
37
|
+
.filter((example) => example.capabilityIds.includes(capability.id) || example.publicApiIds.some((apiId) => capability.publicApis.includes(apiId)))
|
|
38
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
39
|
+
const examples = allExamples.slice(0, limits.maxExamples);
|
|
40
|
+
const testsByCoverage = snapshot.tests.filter((test) => test.coversCapabilities.includes(capability.id));
|
|
41
|
+
const testsByReference = snapshot.tests.filter((test) => capability.tests.includes(test.filePath) || capability.tests.includes(test.id));
|
|
42
|
+
const allTests = [...new Map([...testsByCoverage, ...testsByReference].map((test) => [test.id, test])).values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
43
|
+
const tests = allTests.slice(0, limits.maxTests);
|
|
44
|
+
const relatedFiles = [...new Set([
|
|
45
|
+
...capability.files,
|
|
46
|
+
...symbols.map((symbol) => symbol.filePath),
|
|
47
|
+
...tests.map((test) => test.filePath),
|
|
48
|
+
...examples.map((example) => example.filePath).filter((filePath) => Boolean(filePath))
|
|
49
|
+
])].sort();
|
|
50
|
+
const relatedFileSet = new Set(relatedFiles);
|
|
51
|
+
const importRels = snapshot.relationships.filter((relationship) => {
|
|
52
|
+
if (relationship.type !== "imports") {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return (relationship.from.kind === "file" && relatedFileSet.has(relationship.from.id))
|
|
56
|
+
|| (relationship.to.kind === "file" && relatedFileSet.has(relationship.to.id));
|
|
57
|
+
});
|
|
58
|
+
const exportRels = snapshot.relationships.filter((relationship) => {
|
|
59
|
+
if (relationship.type !== "exports") {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return (relationship.from.kind === "file" && relatedFileSet.has(relationship.from.id))
|
|
63
|
+
|| (relationship.to.kind === "file" && relatedFileSet.has(relationship.to.id));
|
|
64
|
+
});
|
|
65
|
+
const neighboringFiles = [...new Set(importRels
|
|
66
|
+
.flatMap((relationship) => [relationship.from, relationship.to])
|
|
67
|
+
.filter((endpoint) => endpoint.kind === "file")
|
|
68
|
+
.map((endpoint) => endpoint.id)
|
|
69
|
+
.filter((filePath) => !relatedFileSet.has(filePath)))].sort();
|
|
70
|
+
const neighboringExternalPackages = [...new Set(importRels
|
|
71
|
+
.flatMap((relationship) => [relationship.from, relationship.to])
|
|
72
|
+
.filter((endpoint) => endpoint.kind === "externalPackage")
|
|
73
|
+
.map((endpoint) => endpoint.id))].sort();
|
|
74
|
+
const allDiagnostics = snapshot.diagnostics.filter((diagnostic) => {
|
|
75
|
+
if (diagnostic.target?.kind === "capability" && diagnostic.target.id === capability.id) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (diagnostic.filePath && relatedFileSet.has(diagnostic.filePath)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
83
|
+
const diagnostics = allDiagnostics.slice(0, limits.maxDiagnostics);
|
|
84
|
+
return {
|
|
85
|
+
capability,
|
|
86
|
+
packages,
|
|
87
|
+
publicApis,
|
|
88
|
+
symbols,
|
|
89
|
+
examples,
|
|
90
|
+
tests,
|
|
91
|
+
contracts: [...new Set(capability.contracts)].sort(),
|
|
92
|
+
limitations: [...new Set(capability.limitations)].sort(),
|
|
93
|
+
relatedFiles,
|
|
94
|
+
dependencyNeighborhoodSummary: {
|
|
95
|
+
relatedImportRelationships: importRels.length,
|
|
96
|
+
relatedExportRelationships: exportRels.length,
|
|
97
|
+
neighboringFiles,
|
|
98
|
+
neighboringExternalPackages
|
|
99
|
+
},
|
|
100
|
+
diagnostics,
|
|
101
|
+
truncated: {
|
|
102
|
+
publicApis: publicApis.length < allPublicApis.length,
|
|
103
|
+
publicApisTotal: allPublicApis.length,
|
|
104
|
+
symbols: symbols.length < allSymbols.length,
|
|
105
|
+
symbolsTotal: allSymbols.length,
|
|
106
|
+
examples: examples.length < allExamples.length,
|
|
107
|
+
examplesTotal: allExamples.length,
|
|
108
|
+
tests: tests.length < allTests.length,
|
|
109
|
+
testsTotal: allTests.length,
|
|
110
|
+
diagnostics: diagnostics.length < allDiagnostics.length,
|
|
111
|
+
diagnosticsTotal: allDiagnostics.length
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function normalizeLimit(value, fallback) {
|
|
116
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
117
|
+
return fallback;
|
|
118
|
+
}
|
|
119
|
+
const normalized = Math.floor(value);
|
|
120
|
+
if (normalized <= 0) {
|
|
121
|
+
return fallback;
|
|
122
|
+
}
|
|
123
|
+
return normalized;
|
|
124
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChangeRecord, ComparisonSnapshot, CompatibilityFinding, RecordDomain } from "../comparison/schema.js";
|
|
2
|
+
export declare function getChangeSummary(comparison: ComparisonSnapshot): Record<string, unknown>;
|
|
3
|
+
export declare function listChanges(comparison: ComparisonSnapshot, options?: {
|
|
4
|
+
domain?: RecordDomain;
|
|
5
|
+
status?: ChangeRecord["status"];
|
|
6
|
+
offset?: number;
|
|
7
|
+
limit?: number;
|
|
8
|
+
}): {
|
|
9
|
+
total: number;
|
|
10
|
+
returned: number;
|
|
11
|
+
offset: number;
|
|
12
|
+
limit: number;
|
|
13
|
+
truncated: boolean;
|
|
14
|
+
records: ChangeRecord[];
|
|
15
|
+
};
|
|
16
|
+
export declare function getChange(comparison: ComparisonSnapshot, changeId: string): ChangeRecord | null;
|
|
17
|
+
export declare function getCompatibilityReport(comparison: ComparisonSnapshot, options?: {
|
|
18
|
+
offset?: number;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}): {
|
|
21
|
+
aggregate: ComparisonSnapshot["manifest"]["aggregateCompatibility"];
|
|
22
|
+
total: number;
|
|
23
|
+
returned: number;
|
|
24
|
+
offset: number;
|
|
25
|
+
limit: number;
|
|
26
|
+
truncated: boolean;
|
|
27
|
+
findings: CompatibilityFinding[];
|
|
28
|
+
};
|
|
29
|
+
export declare function getChangeImpact(comparison: ComparisonSnapshot, changeId: string, options?: {
|
|
30
|
+
maxDepth?: number;
|
|
31
|
+
limit?: number;
|
|
32
|
+
offset?: number;
|
|
33
|
+
}): import("../comparison/schema.js").ChangeImpactResult | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { orderChangeRecords, orderCompatibilityFindings } from "../comparison/ordering.js";
|
|
2
|
+
import { getChangeImpact as impactGetChangeImpact } from "../comparison/impact.js";
|
|
3
|
+
export function getChangeSummary(comparison) {
|
|
4
|
+
return {
|
|
5
|
+
comparisonId: comparison.manifest.comparisonId,
|
|
6
|
+
counts: comparison.manifest.counts,
|
|
7
|
+
aggregateCompatibility: comparison.manifest.aggregateCompatibility,
|
|
8
|
+
baseCatalogueId: comparison.manifest.baseCatalogueId,
|
|
9
|
+
headCatalogueId: comparison.manifest.headCatalogueId,
|
|
10
|
+
baseGeneratorVersion: comparison.manifest.baseGeneratorVersion,
|
|
11
|
+
headGeneratorVersion: comparison.manifest.headGeneratorVersion,
|
|
12
|
+
comparisonGeneratorVersion: comparison.manifest.comparisonGeneratorVersion
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function listChanges(comparison, options) {
|
|
16
|
+
const offset = options?.offset ?? 0;
|
|
17
|
+
const limit = Math.min(options?.limit ?? 50, 200);
|
|
18
|
+
let records = orderChangeRecords(comparison.changes).filter((c) => c.status !== "unchanged");
|
|
19
|
+
if (options?.domain)
|
|
20
|
+
records = records.filter((c) => c.domain === options.domain);
|
|
21
|
+
if (options?.status)
|
|
22
|
+
records = records.filter((c) => c.status === options.status);
|
|
23
|
+
const slice = records.slice(offset, offset + limit);
|
|
24
|
+
return {
|
|
25
|
+
total: records.length,
|
|
26
|
+
returned: slice.length,
|
|
27
|
+
offset,
|
|
28
|
+
limit,
|
|
29
|
+
truncated: offset + slice.length < records.length,
|
|
30
|
+
records: slice
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function getChange(comparison, changeId) {
|
|
34
|
+
return comparison.changes.find((c) => c.id === changeId) ?? null;
|
|
35
|
+
}
|
|
36
|
+
export function getCompatibilityReport(comparison, options) {
|
|
37
|
+
const offset = options?.offset ?? 0;
|
|
38
|
+
const limit = Math.min(options?.limit ?? 50, 200);
|
|
39
|
+
const findings = orderCompatibilityFindings(comparison.compatibilityFindings);
|
|
40
|
+
const slice = findings.slice(offset, offset + limit);
|
|
41
|
+
return {
|
|
42
|
+
aggregate: comparison.manifest.aggregateCompatibility,
|
|
43
|
+
total: findings.length,
|
|
44
|
+
returned: slice.length,
|
|
45
|
+
offset,
|
|
46
|
+
limit,
|
|
47
|
+
truncated: offset + slice.length < findings.length,
|
|
48
|
+
findings: slice
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function getChangeImpact(comparison, changeId, options) {
|
|
52
|
+
return impactGetChangeImpact(comparison, changeId, options);
|
|
53
|
+
}
|