@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,291 @@
|
|
|
1
|
+
import { getRelationshipImpactPolicy, normalizeEndpointKind } from "./relationshipPolicy.js";
|
|
2
|
+
export const IMPACT_DEFAULT_DEPTH = 3;
|
|
3
|
+
export const IMPACT_MAX_DEPTH = 8;
|
|
4
|
+
export const IMPACT_DEFAULT_LIMIT = 50;
|
|
5
|
+
export const IMPACT_MAX_LIMIT = 200;
|
|
6
|
+
export const IMPACT_MAX_VISITED_NODES = 10_000;
|
|
7
|
+
export const IMPACT_MAX_TRAVERSED_EDGES = 50_000;
|
|
8
|
+
export const IMPACT_MAX_PATHS_PER_ENDPOINT = 500;
|
|
9
|
+
function emptySection(offset = 0, limit = IMPACT_DEFAULT_LIMIT) {
|
|
10
|
+
return { total: 0, returned: 0, offset, limit, truncated: false, records: [] };
|
|
11
|
+
}
|
|
12
|
+
function nodeKey(kind, id) {
|
|
13
|
+
return `${normalizeEndpointKind(kind)}:${id}`;
|
|
14
|
+
}
|
|
15
|
+
function edgeSortKey(edge) {
|
|
16
|
+
return `${edge.type}|${edge.fromKind}:${edge.fromId}|${edge.toKind}:${edge.toId}|${edge.provenance ?? ""}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build adjacency for dependant-impact: from a dependency node, walk to dependants.
|
|
20
|
+
* For reverseTraversal relationships, an edge A→B (A imports B) yields adjacency B→A.
|
|
21
|
+
*/
|
|
22
|
+
function buildDependantAdjacency(edges) {
|
|
23
|
+
const adj = new Map();
|
|
24
|
+
for (const edge of edges) {
|
|
25
|
+
const policy = getRelationshipImpactPolicy(edge.type);
|
|
26
|
+
if (!policy.participatesInImpact)
|
|
27
|
+
continue;
|
|
28
|
+
const fromKind = normalizeEndpointKind(edge.fromKind);
|
|
29
|
+
const toKind = normalizeEndpointKind(edge.toKind);
|
|
30
|
+
const normalized = {
|
|
31
|
+
...edge,
|
|
32
|
+
fromKind,
|
|
33
|
+
toKind
|
|
34
|
+
};
|
|
35
|
+
let dependantKey;
|
|
36
|
+
let dependencyKey;
|
|
37
|
+
if (policy.reverseTraversalForImpact) {
|
|
38
|
+
// dependency is "to"; dependant is "from" → walk from dependency toward dependant
|
|
39
|
+
dependencyKey = nodeKey(toKind, edge.toId);
|
|
40
|
+
dependantKey = nodeKey(fromKind, edge.fromId);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
dependencyKey = nodeKey(fromKind, edge.fromId);
|
|
44
|
+
dependantKey = nodeKey(toKind, edge.toId);
|
|
45
|
+
}
|
|
46
|
+
const list = adj.get(dependencyKey) ?? [];
|
|
47
|
+
list.push({
|
|
48
|
+
...normalized,
|
|
49
|
+
// store traversal hop metadata: from=dependency side, to=dependant side for path clarity
|
|
50
|
+
fromKind: dependencyKey.split(":")[0],
|
|
51
|
+
fromId: dependencyKey.slice(dependencyKey.indexOf(":") + 1),
|
|
52
|
+
toKind: dependantKey.split(":")[0],
|
|
53
|
+
toId: dependantKey.slice(dependantKey.indexOf(":") + 1)
|
|
54
|
+
});
|
|
55
|
+
adj.set(dependencyKey, list);
|
|
56
|
+
}
|
|
57
|
+
for (const [key, list] of adj) {
|
|
58
|
+
adj.set(key, list.sort((a, b) => edgeSortKey(a).localeCompare(edgeSortKey(b))));
|
|
59
|
+
}
|
|
60
|
+
return adj;
|
|
61
|
+
}
|
|
62
|
+
function orderImpactRecords(records) {
|
|
63
|
+
const levelOrder = {
|
|
64
|
+
direct_evidence: 0,
|
|
65
|
+
transitive_inference: 1,
|
|
66
|
+
possible_impact: 2
|
|
67
|
+
};
|
|
68
|
+
return [...records].sort((a, b) => {
|
|
69
|
+
const l = levelOrder[a.evidenceLevel] - levelOrder[b.evidenceLevel];
|
|
70
|
+
if (l !== 0)
|
|
71
|
+
return l;
|
|
72
|
+
if (a.pathLength !== b.pathLength)
|
|
73
|
+
return a.pathLength - b.pathLength;
|
|
74
|
+
const p = (a.packageId ?? "").localeCompare(b.packageId ?? "");
|
|
75
|
+
if (p !== 0)
|
|
76
|
+
return p;
|
|
77
|
+
const k = a.kind.localeCompare(b.kind);
|
|
78
|
+
if (k !== 0)
|
|
79
|
+
return k;
|
|
80
|
+
const q = (a.qualifiedName ?? "").localeCompare(b.qualifiedName ?? "");
|
|
81
|
+
if (q !== 0)
|
|
82
|
+
return q;
|
|
83
|
+
return a.id.localeCompare(b.id);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function paginate(records, offset, limit, traversalIncomplete) {
|
|
87
|
+
const ordered = orderImpactRecords(records);
|
|
88
|
+
const slice = ordered.slice(offset, offset + limit);
|
|
89
|
+
return {
|
|
90
|
+
total: ordered.length,
|
|
91
|
+
returned: slice.length,
|
|
92
|
+
offset,
|
|
93
|
+
limit,
|
|
94
|
+
truncated: offset + slice.length < ordered.length || traversalIncomplete,
|
|
95
|
+
records: slice,
|
|
96
|
+
...(traversalIncomplete ? { totalIsLowerBound: true, traversalIncomplete: true } : {})
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function traverseDependants(startKind, startId, edges, snapshot, maxDepth) {
|
|
100
|
+
const adj = buildDependantAdjacency(edges);
|
|
101
|
+
const records = [];
|
|
102
|
+
const visited = new Set();
|
|
103
|
+
const warnings = [];
|
|
104
|
+
let traversedEdges = 0;
|
|
105
|
+
let incomplete = false;
|
|
106
|
+
const queue = [
|
|
107
|
+
{ kind: normalizeEndpointKind(startKind), id: startId, depth: 0, path: [] }
|
|
108
|
+
];
|
|
109
|
+
while (queue.length > 0) {
|
|
110
|
+
if (visited.size >= IMPACT_MAX_VISITED_NODES || traversedEdges >= IMPACT_MAX_TRAVERSED_EDGES) {
|
|
111
|
+
incomplete = true;
|
|
112
|
+
warnings.push("impact_work_budget_exhausted");
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
if (records.length >= IMPACT_MAX_PATHS_PER_ENDPOINT) {
|
|
116
|
+
incomplete = true;
|
|
117
|
+
warnings.push("impact_max_paths_per_endpoint");
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
const current = queue.shift();
|
|
121
|
+
const currentKey = nodeKey(current.kind, current.id);
|
|
122
|
+
if (current.depth > 0) {
|
|
123
|
+
if (visited.has(currentKey))
|
|
124
|
+
continue;
|
|
125
|
+
visited.add(currentKey);
|
|
126
|
+
const evidenceLevel = current.depth === 1 ? "direct_evidence" : "transitive_inference";
|
|
127
|
+
records.push({
|
|
128
|
+
id: current.id,
|
|
129
|
+
kind: current.kind,
|
|
130
|
+
pathLength: current.depth,
|
|
131
|
+
evidenceLevel,
|
|
132
|
+
path: current.path.map((e) => ({ ...e, snapshot }))
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (current.depth >= maxDepth)
|
|
136
|
+
continue;
|
|
137
|
+
const next = adj.get(currentKey) ?? [];
|
|
138
|
+
for (const edge of next) {
|
|
139
|
+
traversedEdges += 1;
|
|
140
|
+
if (traversedEdges > IMPACT_MAX_TRAVERSED_EDGES) {
|
|
141
|
+
incomplete = true;
|
|
142
|
+
warnings.push("impact_work_budget_exhausted");
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
const hopKey = `${edge.type}|${edge.fromKind}:${edge.fromId}|${edge.toKind}:${edge.toId}`;
|
|
146
|
+
if (current.path.some((p) => `${p.relationshipType}|${p.fromKind}:${p.fromId}|${p.toKind}:${p.toId}` === hopKey)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const evidenceLevel = current.depth + 1 === 1 ? "direct_evidence" : "transitive_inference";
|
|
150
|
+
queue.push({
|
|
151
|
+
kind: edge.toKind,
|
|
152
|
+
id: edge.toId,
|
|
153
|
+
depth: current.depth + 1,
|
|
154
|
+
path: [
|
|
155
|
+
...current.path,
|
|
156
|
+
{
|
|
157
|
+
snapshot,
|
|
158
|
+
relationshipType: edge.type,
|
|
159
|
+
fromKind: edge.fromKind,
|
|
160
|
+
fromId: edge.fromId,
|
|
161
|
+
toKind: edge.toKind,
|
|
162
|
+
toId: edge.toId,
|
|
163
|
+
provenance: edge.provenance,
|
|
164
|
+
evidenceLevel
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { records, incomplete, warnings };
|
|
171
|
+
}
|
|
172
|
+
function endpointForChange(change) {
|
|
173
|
+
const id = change.status === "added" ? change.headRecordId : change.baseRecordId;
|
|
174
|
+
if (!id)
|
|
175
|
+
return null;
|
|
176
|
+
const kindMap = {
|
|
177
|
+
packages: "package",
|
|
178
|
+
files: "file",
|
|
179
|
+
symbols: "symbol",
|
|
180
|
+
publicApis: "public-api",
|
|
181
|
+
tests: "test",
|
|
182
|
+
examples: "example",
|
|
183
|
+
capabilities: "capability",
|
|
184
|
+
relationships: "relationship",
|
|
185
|
+
diagnostics: "diagnostic"
|
|
186
|
+
};
|
|
187
|
+
return { kind: kindMap[change.domain] ?? change.domain, id };
|
|
188
|
+
}
|
|
189
|
+
function endpointsForCandidate(candidate) {
|
|
190
|
+
const kindMap = {
|
|
191
|
+
packages: "package",
|
|
192
|
+
files: "file",
|
|
193
|
+
symbols: "symbol",
|
|
194
|
+
publicApis: "public-api",
|
|
195
|
+
tests: "test",
|
|
196
|
+
examples: "example",
|
|
197
|
+
capabilities: "capability",
|
|
198
|
+
relationships: "relationship",
|
|
199
|
+
diagnostics: "diagnostic"
|
|
200
|
+
};
|
|
201
|
+
const kind = kindMap[candidate.domain] ?? candidate.domain;
|
|
202
|
+
return {
|
|
203
|
+
base: { kind, id: candidate.baseRecordId },
|
|
204
|
+
head: { kind, id: candidate.headRecordId }
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
export function getChangeImpact(comparison, changeId, options) {
|
|
208
|
+
const change = comparison.changes.find((c) => c.id === changeId);
|
|
209
|
+
const candidate = comparison.candidates.find((c) => c.id === changeId);
|
|
210
|
+
if (!change && !candidate)
|
|
211
|
+
return null;
|
|
212
|
+
const maxDepth = Math.min(Math.max(options?.maxDepth ?? IMPACT_DEFAULT_DEPTH, 1), IMPACT_MAX_DEPTH);
|
|
213
|
+
const limit = Math.min(Math.max(options?.limit ?? IMPACT_DEFAULT_LIMIT, 1), IMPACT_MAX_LIMIT);
|
|
214
|
+
const offset = Math.max(options?.offset ?? 0, 0);
|
|
215
|
+
let baseRaw = [];
|
|
216
|
+
let headRaw = [];
|
|
217
|
+
let incomplete = false;
|
|
218
|
+
if (candidate) {
|
|
219
|
+
const ends = endpointsForCandidate(candidate);
|
|
220
|
+
const baseT = traverseDependants(ends.base.kind, ends.base.id, comparison.graphIndex.base, "base", maxDepth);
|
|
221
|
+
const headT = traverseDependants(ends.head.kind, ends.head.id, comparison.graphIndex.head, "head", maxDepth);
|
|
222
|
+
baseRaw = baseT.records;
|
|
223
|
+
headRaw = headT.records;
|
|
224
|
+
incomplete = baseT.incomplete || headT.incomplete;
|
|
225
|
+
}
|
|
226
|
+
else if (change) {
|
|
227
|
+
if (change.domain === "relationships") {
|
|
228
|
+
// Relationship add/remove: affected path is the edge itself via endpoints
|
|
229
|
+
const edgeId = change.status === "added" ? change.headRecordId : change.baseRecordId;
|
|
230
|
+
const edges = change.status === "added" ? comparison.graphIndex.head : comparison.graphIndex.base;
|
|
231
|
+
const snap = change.status === "added" ? "head" : "base";
|
|
232
|
+
const match = edges.find((e) => e.id === edgeId);
|
|
233
|
+
if (match) {
|
|
234
|
+
const rec = {
|
|
235
|
+
id: match.toId,
|
|
236
|
+
kind: normalizeEndpointKind(match.toKind),
|
|
237
|
+
pathLength: 1,
|
|
238
|
+
evidenceLevel: "direct_evidence",
|
|
239
|
+
path: [
|
|
240
|
+
{
|
|
241
|
+
snapshot: snap,
|
|
242
|
+
relationshipType: match.type,
|
|
243
|
+
fromKind: normalizeEndpointKind(match.fromKind),
|
|
244
|
+
fromId: match.fromId,
|
|
245
|
+
toKind: normalizeEndpointKind(match.toKind),
|
|
246
|
+
toId: match.toId,
|
|
247
|
+
provenance: match.provenance,
|
|
248
|
+
evidenceLevel: "direct_evidence"
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
};
|
|
252
|
+
if (change.status === "removed")
|
|
253
|
+
baseRaw = [rec];
|
|
254
|
+
else
|
|
255
|
+
headRaw = [rec];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
const endpoint = endpointForChange(change);
|
|
260
|
+
if (endpoint) {
|
|
261
|
+
if (change.status !== "added") {
|
|
262
|
+
const t = traverseDependants(endpoint.kind, endpoint.id, comparison.graphIndex.base, "base", maxDepth);
|
|
263
|
+
baseRaw = t.records;
|
|
264
|
+
incomplete = incomplete || t.incomplete;
|
|
265
|
+
}
|
|
266
|
+
if (change.status !== "removed") {
|
|
267
|
+
const headId = change.headRecordId ?? endpoint.id;
|
|
268
|
+
const t = traverseDependants(endpoint.kind, headId, comparison.graphIndex.head, "head", maxDepth);
|
|
269
|
+
headRaw = t.records;
|
|
270
|
+
incomplete = incomplete || t.incomplete;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const baseImpact = paginate(baseRaw, offset, limit, incomplete);
|
|
276
|
+
const headImpact = paginate(headRaw, offset, limit, incomplete);
|
|
277
|
+
const baseIds = new Set(baseRaw.map((r) => `${r.kind}:${r.id}`));
|
|
278
|
+
const headIds = new Set(headRaw.map((r) => `${r.kind}:${r.id}`));
|
|
279
|
+
const retained = baseRaw.filter((r) => headIds.has(`${r.kind}:${r.id}`));
|
|
280
|
+
const removed = baseRaw.filter((r) => !headIds.has(`${r.kind}:${r.id}`));
|
|
281
|
+
const added = headRaw.filter((r) => !baseIds.has(`${r.kind}:${r.id}`));
|
|
282
|
+
return {
|
|
283
|
+
changeId,
|
|
284
|
+
baseImpact,
|
|
285
|
+
headImpact,
|
|
286
|
+
retainedImpact: paginate(retained, offset, limit, incomplete),
|
|
287
|
+
removedImpact: paginate(removed, offset, limit, incomplete),
|
|
288
|
+
addedImpact: paginate(added, offset, limit, incomplete),
|
|
289
|
+
possibleImpact: emptySection(offset, limit)
|
|
290
|
+
};
|
|
291
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { compareCatalogues } from "./compare.js";
|
|
2
|
+
export { loadCataloguePair, validateCataloguePair } from "./validate.js";
|
|
3
|
+
export { writeComparisonArtefact, loadComparisonArtefact } from "./store.js";
|
|
4
|
+
export { getChangeImpact } from "./impact.js";
|
|
5
|
+
export { comparisonIdentityId, changeIdentityId } from "./identity.js";
|
|
6
|
+
export { DEFAULT_COMPARISON_OPTIONS, type ComparisonSnapshot, type ComparisonOptions, type ChangeRecord, type ComparisonError } from "./schema.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { compareCatalogues } from "./compare.js";
|
|
2
|
+
export { loadCataloguePair, validateCataloguePair } from "./validate.js";
|
|
3
|
+
export { writeComparisonArtefact, loadComparisonArtefact } from "./store.js";
|
|
4
|
+
export { getChangeImpact } from "./impact.js";
|
|
5
|
+
export { comparisonIdentityId, changeIdentityId } from "./identity.js";
|
|
6
|
+
export { DEFAULT_COMPARISON_OPTIONS } from "./schema.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CatalogueSnapshot } from "../catalogue/schema.js";
|
|
2
|
+
import type { ComparisonError } from "./schema.js";
|
|
3
|
+
export type IntegrityIssue = {
|
|
4
|
+
ref: string;
|
|
5
|
+
problem: string;
|
|
6
|
+
kind: "authoritative" | "derived";
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Validate Schema 2 cross-references before comparison.
|
|
10
|
+
* Authoritative forward refs fail closed; derived backlinks may warn.
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateReferentialIntegrity(snapshot: CatalogueSnapshot, side: "base" | "head"): ComparisonError | null;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { normalizeEndpointKind } from "./relationshipPolicy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Validate Schema 2 cross-references before comparison.
|
|
4
|
+
* Authoritative forward refs fail closed; derived backlinks may warn.
|
|
5
|
+
*/
|
|
6
|
+
export function validateReferentialIntegrity(snapshot, side) {
|
|
7
|
+
const details = [];
|
|
8
|
+
const warnings = [];
|
|
9
|
+
const symbolIds = new Set(snapshot.symbols.map((s) => s.id));
|
|
10
|
+
const packageIds = new Set(snapshot.packages.map((p) => p.id));
|
|
11
|
+
const publicApiIds = new Set(snapshot.publicApis.map((p) => p.id));
|
|
12
|
+
const capabilityIds = new Set(snapshot.capabilities.map((c) => c.id));
|
|
13
|
+
const testIds = new Set(snapshot.tests.map((t) => t.id));
|
|
14
|
+
const exampleIds = new Set(snapshot.examples.map((e) => e.id));
|
|
15
|
+
const filePaths = new Set(snapshot.files.map((f) => f.path));
|
|
16
|
+
const auth = (ref, problem) => {
|
|
17
|
+
if (details.length < 50)
|
|
18
|
+
details.push({ ref, problem, kind: "authoritative" });
|
|
19
|
+
};
|
|
20
|
+
const derived = (ref, problem) => {
|
|
21
|
+
if (warnings.length < 50)
|
|
22
|
+
warnings.push({ ref, problem, kind: "derived" });
|
|
23
|
+
};
|
|
24
|
+
for (const api of snapshot.publicApis) {
|
|
25
|
+
if (api.symbolId && !symbolIds.has(api.symbolId)) {
|
|
26
|
+
auth(`publicApi:${api.id}.symbolId`, `missing symbol ${api.symbolId}`);
|
|
27
|
+
}
|
|
28
|
+
if (api.packageId && !packageIds.has(api.packageId)) {
|
|
29
|
+
auth(`publicApi:${api.id}.packageId`, `missing package ${api.packageId}`);
|
|
30
|
+
}
|
|
31
|
+
for (const cap of api.capabilityIds ?? []) {
|
|
32
|
+
if (!capabilityIds.has(cap))
|
|
33
|
+
auth(`publicApi:${api.id}.capabilityIds`, `missing capability ${cap}`);
|
|
34
|
+
}
|
|
35
|
+
for (const ex of api.examples ?? []) {
|
|
36
|
+
if (!exampleIds.has(ex))
|
|
37
|
+
auth(`publicApi:${api.id}.examples`, `missing example ${ex}`);
|
|
38
|
+
}
|
|
39
|
+
for (const test of api.tests ?? []) {
|
|
40
|
+
if (!testIds.has(test))
|
|
41
|
+
auth(`publicApi:${api.id}.tests`, `missing test ${test}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const symbol of snapshot.symbols) {
|
|
45
|
+
if (symbol.canonicalSymbolId && !symbolIds.has(symbol.canonicalSymbolId)) {
|
|
46
|
+
auth(`symbol:${symbol.id}.canonicalSymbolId`, `missing ${symbol.canonicalSymbolId}`);
|
|
47
|
+
}
|
|
48
|
+
if (symbol.duplicateOf && !symbolIds.has(symbol.duplicateOf)) {
|
|
49
|
+
auth(`symbol:${symbol.id}.duplicateOf`, `missing ${symbol.duplicateOf}`);
|
|
50
|
+
}
|
|
51
|
+
if (symbol.packageId && !packageIds.has(symbol.packageId)) {
|
|
52
|
+
auth(`symbol:${symbol.id}.packageId`, `missing package ${symbol.packageId}`);
|
|
53
|
+
}
|
|
54
|
+
for (const cap of symbol.capabilities ?? []) {
|
|
55
|
+
if (!capabilityIds.has(cap))
|
|
56
|
+
auth(`symbol:${symbol.id}.capabilities`, `missing capability ${cap}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const cap of snapshot.capabilities) {
|
|
60
|
+
for (const api of cap.publicApis ?? []) {
|
|
61
|
+
if (!publicApiIds.has(api))
|
|
62
|
+
auth(`capability:${cap.id}.publicApis`, `missing publicApi ${api}`);
|
|
63
|
+
}
|
|
64
|
+
for (const sym of cap.symbols ?? []) {
|
|
65
|
+
if (!symbolIds.has(sym))
|
|
66
|
+
auth(`capability:${cap.id}.symbols`, `missing symbol ${sym}`);
|
|
67
|
+
}
|
|
68
|
+
for (const pkg of cap.packages ?? []) {
|
|
69
|
+
if (!packageIds.has(pkg))
|
|
70
|
+
auth(`capability:${cap.id}.packages`, `missing package ${pkg}`);
|
|
71
|
+
}
|
|
72
|
+
for (const ex of cap.examples ?? []) {
|
|
73
|
+
if (!exampleIds.has(ex))
|
|
74
|
+
auth(`capability:${cap.id}.examples`, `missing example ${ex}`);
|
|
75
|
+
}
|
|
76
|
+
for (const test of cap.tests ?? []) {
|
|
77
|
+
if (!testIds.has(test))
|
|
78
|
+
auth(`capability:${cap.id}.tests`, `missing test ${test}`);
|
|
79
|
+
}
|
|
80
|
+
for (const file of cap.files ?? []) {
|
|
81
|
+
if (snapshot.files.length > 0 && !filePaths.has(file)) {
|
|
82
|
+
auth(`capability:${cap.id}.files`, `missing file ${file}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const test of snapshot.tests) {
|
|
87
|
+
if (test.symbolId && !symbolIds.has(test.symbolId)) {
|
|
88
|
+
auth(`test:${test.id}.symbolId`, `missing symbol ${test.symbolId}`);
|
|
89
|
+
}
|
|
90
|
+
for (const api of test.coversPublicApis ?? []) {
|
|
91
|
+
if (!publicApiIds.has(api))
|
|
92
|
+
auth(`test:${test.id}.coversPublicApis`, `missing publicApi ${api}`);
|
|
93
|
+
}
|
|
94
|
+
for (const cap of test.coversCapabilities ?? []) {
|
|
95
|
+
if (!capabilityIds.has(cap))
|
|
96
|
+
auth(`test:${test.id}.coversCapabilities`, `missing capability ${cap}`);
|
|
97
|
+
}
|
|
98
|
+
for (const sym of test.coversSymbols ?? []) {
|
|
99
|
+
if (!symbolIds.has(sym))
|
|
100
|
+
auth(`test:${test.id}.coversSymbols`, `missing symbol ${sym}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const example of snapshot.examples) {
|
|
104
|
+
for (const api of example.publicApiIds ?? []) {
|
|
105
|
+
if (!publicApiIds.has(api))
|
|
106
|
+
auth(`example:${example.id}.publicApiIds`, `missing publicApi ${api}`);
|
|
107
|
+
}
|
|
108
|
+
for (const cap of example.capabilityIds ?? []) {
|
|
109
|
+
if (!capabilityIds.has(cap))
|
|
110
|
+
auth(`example:${example.id}.capabilityIds`, `missing capability ${cap}`);
|
|
111
|
+
}
|
|
112
|
+
for (const sym of example.symbolIds ?? []) {
|
|
113
|
+
if (!symbolIds.has(sym))
|
|
114
|
+
auth(`example:${example.id}.symbolIds`, `missing symbol ${sym}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Package membership arrays are treated as derived backlinks (may lag).
|
|
118
|
+
for (const pkg of snapshot.packages) {
|
|
119
|
+
for (const api of pkg.publicApis ?? []) {
|
|
120
|
+
if (!publicApiIds.has(api))
|
|
121
|
+
derived(`package:${pkg.id}.publicApis`, `missing publicApi ${api}`);
|
|
122
|
+
}
|
|
123
|
+
for (const test of pkg.tests ?? []) {
|
|
124
|
+
if (!testIds.has(test))
|
|
125
|
+
derived(`package:${pkg.id}.tests`, `missing test ${test}`);
|
|
126
|
+
}
|
|
127
|
+
for (const cap of pkg.capabilities ?? []) {
|
|
128
|
+
if (!capabilityIds.has(cap))
|
|
129
|
+
derived(`package:${pkg.id}.capabilities`, `missing capability ${cap}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const file of snapshot.files) {
|
|
133
|
+
for (const sym of file.symbols ?? []) {
|
|
134
|
+
if (!symbolIds.has(sym))
|
|
135
|
+
derived(`file:${file.path}.symbols`, `missing symbol ${sym}`);
|
|
136
|
+
}
|
|
137
|
+
for (const api of file.publicApis ?? []) {
|
|
138
|
+
if (!publicApiIds.has(api))
|
|
139
|
+
derived(`file:${file.path}.publicApis`, `missing publicApi ${api}`);
|
|
140
|
+
}
|
|
141
|
+
for (const cap of file.capabilities ?? []) {
|
|
142
|
+
if (!capabilityIds.has(cap))
|
|
143
|
+
derived(`file:${file.path}.capabilities`, `missing capability ${cap}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const rel of snapshot.relationships) {
|
|
147
|
+
for (const end of [rel.from, rel.to]) {
|
|
148
|
+
const kind = normalizeEndpointKind(end.kind);
|
|
149
|
+
if (kind === "external-package")
|
|
150
|
+
continue;
|
|
151
|
+
if (kind === "file")
|
|
152
|
+
continue;
|
|
153
|
+
if (kind === "symbol" && !symbolIds.has(end.id)) {
|
|
154
|
+
auth(`relationship:${rel.id}`, `missing symbol endpoint ${end.id}`);
|
|
155
|
+
}
|
|
156
|
+
else if (kind === "public-api" && !publicApiIds.has(end.id)) {
|
|
157
|
+
auth(`relationship:${rel.id}`, `missing publicApi endpoint ${end.id}`);
|
|
158
|
+
}
|
|
159
|
+
else if (kind === "package" && !packageIds.has(end.id)) {
|
|
160
|
+
auth(`relationship:${rel.id}`, `missing package endpoint ${end.id}`);
|
|
161
|
+
}
|
|
162
|
+
else if (kind === "capability" && !capabilityIds.has(end.id)) {
|
|
163
|
+
auth(`relationship:${rel.id}`, `missing capability endpoint ${end.id}`);
|
|
164
|
+
}
|
|
165
|
+
else if (kind === "test" && !testIds.has(end.id)) {
|
|
166
|
+
auth(`relationship:${rel.id}`, `missing test endpoint ${end.id}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const diag of snapshot.diagnostics) {
|
|
171
|
+
if (!diag.target)
|
|
172
|
+
continue;
|
|
173
|
+
const t = diag.target;
|
|
174
|
+
if (t.kind === "symbol" && !symbolIds.has(t.id))
|
|
175
|
+
auth(`diagnostic:${diag.id}`, `missing symbol ${t.id}`);
|
|
176
|
+
if ((t.kind === "publicApi" || t.kind === "public-api") && !publicApiIds.has(t.id)) {
|
|
177
|
+
auth(`diagnostic:${diag.id}`, `missing publicApi ${t.id}`);
|
|
178
|
+
}
|
|
179
|
+
if (t.kind === "package" && !packageIds.has(t.id))
|
|
180
|
+
auth(`diagnostic:${diag.id}`, `missing package ${t.id}`);
|
|
181
|
+
if (t.kind === "capability" && !capabilityIds.has(t.id)) {
|
|
182
|
+
auth(`diagnostic:${diag.id}`, `missing capability ${t.id}`);
|
|
183
|
+
}
|
|
184
|
+
if (t.kind === "test" && !testIds.has(t.id))
|
|
185
|
+
auth(`diagnostic:${diag.id}`, `missing test ${t.id}`);
|
|
186
|
+
if (t.kind === "file" && snapshot.files.length > 0 && !filePaths.has(t.id)) {
|
|
187
|
+
auth(`diagnostic:${diag.id}`, `missing file ${t.id}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (details.length === 0) {
|
|
191
|
+
// Derived warnings alone do not fail comparison.
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
code: "comparison_referential_integrity_failed",
|
|
196
|
+
message: `Referential integrity failed for ${side} catalogue (${details.length} authoritative issue(s)).`,
|
|
197
|
+
details: { side, issues: details, derivedWarnings: warnings.slice(0, 20) }
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ChangeRecord, CompatibilityFinding, CompatibilityClassification } from "./schema.js";
|
|
2
|
+
export declare function orderChangeRecords(records: ChangeRecord[]): ChangeRecord[];
|
|
3
|
+
export declare function orderCompatibilityFindings(findings: CompatibilityFinding[]): CompatibilityFinding[];
|
|
4
|
+
export declare function aggregateCompatibility(findings: CompatibilityFinding[]): CompatibilityClassification | null;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const STATUS_ORDER = {
|
|
2
|
+
removed: 0,
|
|
3
|
+
modified: 1,
|
|
4
|
+
added: 2,
|
|
5
|
+
unchanged: 3
|
|
6
|
+
};
|
|
7
|
+
const SEVERITY_ORDER = {
|
|
8
|
+
breaking: 0,
|
|
9
|
+
potentially_breaking: 1,
|
|
10
|
+
unknown: 2,
|
|
11
|
+
non_breaking: 3,
|
|
12
|
+
documentation_only: 4,
|
|
13
|
+
not_applicable: 5
|
|
14
|
+
};
|
|
15
|
+
function cmp(a, b) {
|
|
16
|
+
const left = (a ?? "").normalize("NFC");
|
|
17
|
+
const right = (b ?? "").normalize("NFC");
|
|
18
|
+
if (left < right)
|
|
19
|
+
return -1;
|
|
20
|
+
if (left > right)
|
|
21
|
+
return 1;
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
export function orderChangeRecords(records) {
|
|
25
|
+
return [...records].sort((a, b) => {
|
|
26
|
+
const d = cmp(a.domain, b.domain);
|
|
27
|
+
if (d !== 0)
|
|
28
|
+
return d;
|
|
29
|
+
const s = (STATUS_ORDER[a.status] ?? 99) - (STATUS_ORDER[b.status] ?? 99);
|
|
30
|
+
if (s !== 0)
|
|
31
|
+
return s;
|
|
32
|
+
const p = cmp(a.packageId, b.packageId);
|
|
33
|
+
if (p !== 0)
|
|
34
|
+
return p;
|
|
35
|
+
const i = cmp(a.importPath ?? a.modulePath, b.importPath ?? b.modulePath);
|
|
36
|
+
if (i !== 0)
|
|
37
|
+
return i;
|
|
38
|
+
const q = cmp(a.qualifiedName ?? a.name, b.qualifiedName ?? b.name);
|
|
39
|
+
if (q !== 0)
|
|
40
|
+
return q;
|
|
41
|
+
return cmp(a.id, b.id);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function orderCompatibilityFindings(findings) {
|
|
45
|
+
return [...findings].sort((a, b) => {
|
|
46
|
+
const s = SEVERITY_ORDER[a.classification] - SEVERITY_ORDER[b.classification];
|
|
47
|
+
if (s !== 0)
|
|
48
|
+
return s;
|
|
49
|
+
const p = cmp(a.publicApiId, b.publicApiId);
|
|
50
|
+
if (p !== 0)
|
|
51
|
+
return p;
|
|
52
|
+
const r = cmp(a.ruleId, b.ruleId);
|
|
53
|
+
if (r !== 0)
|
|
54
|
+
return r;
|
|
55
|
+
return cmp(a.id, b.id);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function aggregateCompatibility(findings) {
|
|
59
|
+
if (findings.length === 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
let best = "not_applicable";
|
|
63
|
+
for (const finding of findings) {
|
|
64
|
+
if (SEVERITY_ORDER[finding.classification] < SEVERITY_ORDER[best]) {
|
|
65
|
+
best = finding.classification;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return best;
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CatalogueSnapshot } from "../catalogue/schema.js";
|
|
2
|
+
import type { FacetDiff, RecordDomain } from "./schema.js";
|
|
3
|
+
/** Strip line/column ranges from relationship evidence for semantic comparison. */
|
|
4
|
+
export declare function semanticRelationshipEvidence(evidence: unknown): unknown;
|
|
5
|
+
export declare function recordKey(domain: RecordDomain, record: Record<string, unknown>): string;
|
|
6
|
+
export declare function indexDomain(domain: RecordDomain, snapshot: CatalogueSnapshot): Map<string, Record<string, unknown>>;
|
|
7
|
+
/** Same-ID facet projection for a modified pair. Identity fields are not compared here. */
|
|
8
|
+
export declare function projectFacets(domain: RecordDomain, base: Record<string, unknown>, head: Record<string, unknown>): FacetDiff[];
|
|
9
|
+
export declare function normalizeConsoleScripts(value: unknown): Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
target: string;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function displayFields(domain: RecordDomain, record: Record<string, unknown>): {
|
|
14
|
+
packageId?: string | null;
|
|
15
|
+
importPath?: string | null;
|
|
16
|
+
modulePath?: string | null;
|
|
17
|
+
qualifiedName?: string | null;
|
|
18
|
+
name?: string | null;
|
|
19
|
+
};
|