@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,17 @@
|
|
|
1
|
+
import type { CatalogueSnapshot, RelationshipRecord } from "../catalogue/schema.js";
|
|
2
|
+
export interface DependencyNeighborhoodOptions {
|
|
3
|
+
path: string;
|
|
4
|
+
depth?: number;
|
|
5
|
+
direction?: "imports" | "imported_by" | "both";
|
|
6
|
+
}
|
|
7
|
+
export interface DependencyNeighborhoodNode {
|
|
8
|
+
kind: "file" | "externalPackage";
|
|
9
|
+
id: string;
|
|
10
|
+
packageId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface DependencyNeighborhoodResult {
|
|
13
|
+
root: string;
|
|
14
|
+
nodes: DependencyNeighborhoodNode[];
|
|
15
|
+
relationships: RelationshipRecord[];
|
|
16
|
+
}
|
|
17
|
+
export declare function getDependencyNeighborhood(snapshot: CatalogueSnapshot, options: DependencyNeighborhoodOptions): DependencyNeighborhoodResult;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export function getDependencyNeighborhood(snapshot, options) {
|
|
2
|
+
const root = options.path.replaceAll("\\", "/");
|
|
3
|
+
const depth = Math.max(0, options.depth ?? 1);
|
|
4
|
+
const direction = options.direction ?? "both";
|
|
5
|
+
const rootNodeId = root;
|
|
6
|
+
const packageByFilePath = new Map();
|
|
7
|
+
for (const packageRecord of snapshot.packages) {
|
|
8
|
+
for (const modulePath of packageRecord.modules ?? []) {
|
|
9
|
+
packageByFilePath.set(modulePath, packageRecord.id);
|
|
10
|
+
}
|
|
11
|
+
for (const publicPath of packageRecord.publicPaths ?? []) {
|
|
12
|
+
packageByFilePath.set(publicPath, packageRecord.id);
|
|
13
|
+
}
|
|
14
|
+
if (packageRecord.path && packageRecord.path !== ".") {
|
|
15
|
+
for (const file of snapshot.files) {
|
|
16
|
+
if (file.path === packageRecord.path || file.path.startsWith(`${packageRecord.path}/`)) {
|
|
17
|
+
packageByFilePath.set(file.path, packageRecord.id);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
for (const file of snapshot.files) {
|
|
23
|
+
if (!packageByFilePath.has(file.path)) {
|
|
24
|
+
packageByFilePath.set(file.path, packageRecord.id);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const adjacency = new Map();
|
|
30
|
+
for (const relationship of snapshot.relationships) {
|
|
31
|
+
if (relationship.type !== "imports") {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (relationship.from.kind === "file") {
|
|
35
|
+
const fromId = relationship.from.id;
|
|
36
|
+
const toId = relationship.to.id;
|
|
37
|
+
const list = adjacency.get(fromId) ?? [];
|
|
38
|
+
list.push({ relationship, nextId: toId });
|
|
39
|
+
adjacency.set(fromId, list);
|
|
40
|
+
}
|
|
41
|
+
if (relationship.to.kind === "file") {
|
|
42
|
+
const fromId = relationship.from.id;
|
|
43
|
+
const toId = relationship.to.id;
|
|
44
|
+
const reverseList = adjacency.get(toId) ?? [];
|
|
45
|
+
reverseList.push({ relationship, nextId: fromId });
|
|
46
|
+
adjacency.set(toId, reverseList);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const visited = new Set([rootNodeId]);
|
|
50
|
+
const frontier = [{ id: rootNodeId, remainingDepth: depth }];
|
|
51
|
+
const selectedRelationships = [];
|
|
52
|
+
const selectedRelationshipIds = new Set();
|
|
53
|
+
while (frontier.length > 0) {
|
|
54
|
+
const current = frontier.shift();
|
|
55
|
+
if (!current || current.remainingDepth <= 0) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const edges = adjacency.get(current.id) ?? [];
|
|
59
|
+
for (const edge of edges) {
|
|
60
|
+
const relationship = edge.relationship;
|
|
61
|
+
const isForward = relationship.from.id === current.id;
|
|
62
|
+
const allowsForward = direction === "imports" || direction === "both";
|
|
63
|
+
const allowsReverse = direction === "imported_by" || direction === "both";
|
|
64
|
+
if ((isForward && !allowsForward) || (!isForward && !allowsReverse)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!selectedRelationshipIds.has(relationship.id)) {
|
|
68
|
+
selectedRelationshipIds.add(relationship.id);
|
|
69
|
+
selectedRelationships.push(relationship);
|
|
70
|
+
}
|
|
71
|
+
if (!visited.has(edge.nextId)) {
|
|
72
|
+
visited.add(edge.nextId);
|
|
73
|
+
frontier.push({ id: edge.nextId, remainingDepth: current.remainingDepth - 1 });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const nodes = [];
|
|
78
|
+
for (const nodeId of visited) {
|
|
79
|
+
if (nodeId === rootNodeId || snapshot.files.some((file) => file.path === nodeId)) {
|
|
80
|
+
nodes.push({ kind: "file", id: nodeId, packageId: packageByFilePath.get(nodeId) });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (nodeId.startsWith("external:")) {
|
|
84
|
+
nodes.push({ kind: "externalPackage", id: nodeId });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
nodes.sort((a, b) => a.id.localeCompare(b.id));
|
|
88
|
+
selectedRelationships.sort((a, b) => a.id.localeCompare(b.id));
|
|
89
|
+
return {
|
|
90
|
+
root,
|
|
91
|
+
nodes,
|
|
92
|
+
relationships: selectedRelationships
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CatalogueSnapshot, DiagnosticRecord } from "../catalogue/schema.js";
|
|
2
|
+
import { type AdoptionAudit, type AdoptionGap } from "./adoption.js";
|
|
3
|
+
export interface DogfoodReportOptions {
|
|
4
|
+
workspace?: string;
|
|
5
|
+
maxDiagnostics?: number;
|
|
6
|
+
maxGaps?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface DogfoodReport {
|
|
9
|
+
workspace?: string;
|
|
10
|
+
catalogueId: string;
|
|
11
|
+
packageCount: number;
|
|
12
|
+
stats: CatalogueSnapshot["stats"];
|
|
13
|
+
diagnostics: {
|
|
14
|
+
total: number;
|
|
15
|
+
bySeverity: {
|
|
16
|
+
info: number;
|
|
17
|
+
warning: number;
|
|
18
|
+
error: number;
|
|
19
|
+
};
|
|
20
|
+
topCodes: Array<{
|
|
21
|
+
code: string;
|
|
22
|
+
count: number;
|
|
23
|
+
}>;
|
|
24
|
+
topDiagnostics: Array<{
|
|
25
|
+
id: string;
|
|
26
|
+
severity: DiagnosticRecord["severity"];
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
filePath?: string;
|
|
30
|
+
target?: DiagnosticRecord["target"];
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
adoption: {
|
|
34
|
+
summary: AdoptionAudit["summary"];
|
|
35
|
+
topGapKinds: Array<{
|
|
36
|
+
kind: AdoptionGap["kind"];
|
|
37
|
+
count: number;
|
|
38
|
+
}>;
|
|
39
|
+
topGaps: AdoptionGap[];
|
|
40
|
+
};
|
|
41
|
+
limits: {
|
|
42
|
+
diagnosticsShown: number;
|
|
43
|
+
gapsShown: number;
|
|
44
|
+
};
|
|
45
|
+
estimatedBytes: number;
|
|
46
|
+
provenance: {
|
|
47
|
+
source: "mapwright";
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export declare function getDogfoodReport(snapshot: CatalogueSnapshot, options?: DogfoodReportOptions): DogfoodReport;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { getAdoptionAudit } from "./adoption.js";
|
|
2
|
+
export function getDogfoodReport(snapshot, options = {}) {
|
|
3
|
+
const maxDiagnostics = Math.max(0, options.maxDiagnostics ?? 20);
|
|
4
|
+
const maxGaps = Math.max(0, options.maxGaps ?? 20);
|
|
5
|
+
const diagnostics = [...snapshot.diagnostics].sort(sortDiagnostics);
|
|
6
|
+
const audit = getAdoptionAudit(snapshot, {
|
|
7
|
+
includeInfo: true,
|
|
8
|
+
includeGenerated: false,
|
|
9
|
+
maxGaps: Number.MAX_SAFE_INTEGER
|
|
10
|
+
});
|
|
11
|
+
const diagnosticTotals = countBySeverity(diagnostics);
|
|
12
|
+
const topDiagnosticCodes = buildTopDiagnosticCodes(diagnostics);
|
|
13
|
+
const topDiagnostics = diagnostics.slice(0, maxDiagnostics).map((diagnostic) => ({
|
|
14
|
+
id: diagnostic.id,
|
|
15
|
+
severity: diagnostic.severity,
|
|
16
|
+
code: diagnostic.code,
|
|
17
|
+
message: diagnostic.message,
|
|
18
|
+
filePath: diagnostic.filePath,
|
|
19
|
+
target: diagnostic.target
|
|
20
|
+
}));
|
|
21
|
+
const topGapKinds = buildTopGapKinds(audit.gaps);
|
|
22
|
+
const topGaps = audit.gaps.slice(0, maxGaps);
|
|
23
|
+
const report = {
|
|
24
|
+
workspace: options.workspace,
|
|
25
|
+
catalogueId: snapshot.catalogueId,
|
|
26
|
+
packageCount: snapshot.packages.length,
|
|
27
|
+
stats: snapshot.stats,
|
|
28
|
+
diagnostics: {
|
|
29
|
+
total: diagnostics.length,
|
|
30
|
+
bySeverity: diagnosticTotals,
|
|
31
|
+
topCodes: topDiagnosticCodes,
|
|
32
|
+
topDiagnostics
|
|
33
|
+
},
|
|
34
|
+
adoption: {
|
|
35
|
+
summary: audit.summary,
|
|
36
|
+
topGapKinds,
|
|
37
|
+
topGaps
|
|
38
|
+
},
|
|
39
|
+
limits: {
|
|
40
|
+
diagnosticsShown: maxDiagnostics,
|
|
41
|
+
gapsShown: maxGaps
|
|
42
|
+
},
|
|
43
|
+
estimatedBytes: estimateReportSize(snapshot, diagnostics.length, audit.gaps.length, maxDiagnostics, maxGaps),
|
|
44
|
+
provenance: {
|
|
45
|
+
source: "mapwright"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return report;
|
|
49
|
+
}
|
|
50
|
+
function sortDiagnostics(a, b) {
|
|
51
|
+
const severityDiff = severityRank(a.severity) - severityRank(b.severity);
|
|
52
|
+
if (severityDiff !== 0) {
|
|
53
|
+
return severityDiff;
|
|
54
|
+
}
|
|
55
|
+
const codeDiff = a.code.localeCompare(b.code);
|
|
56
|
+
if (codeDiff !== 0) {
|
|
57
|
+
return codeDiff;
|
|
58
|
+
}
|
|
59
|
+
return a.id.localeCompare(b.id);
|
|
60
|
+
}
|
|
61
|
+
function severityRank(severity) {
|
|
62
|
+
if (severity === "error") {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
if (severity === "warning") {
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
return 2;
|
|
69
|
+
}
|
|
70
|
+
function countBySeverity(diagnostics) {
|
|
71
|
+
const counts = { info: 0, warning: 0, error: 0 };
|
|
72
|
+
for (const diagnostic of diagnostics) {
|
|
73
|
+
counts[diagnostic.severity] += 1;
|
|
74
|
+
}
|
|
75
|
+
return counts;
|
|
76
|
+
}
|
|
77
|
+
function buildTopDiagnosticCodes(diagnostics) {
|
|
78
|
+
const counts = new Map();
|
|
79
|
+
for (const diagnostic of diagnostics) {
|
|
80
|
+
counts.set(diagnostic.code, (counts.get(diagnostic.code) ?? 0) + 1);
|
|
81
|
+
}
|
|
82
|
+
return [...counts.entries()]
|
|
83
|
+
.map(([code, count]) => ({ code, count }))
|
|
84
|
+
.sort((a, b) => {
|
|
85
|
+
const countDiff = b.count - a.count;
|
|
86
|
+
if (countDiff !== 0) {
|
|
87
|
+
return countDiff;
|
|
88
|
+
}
|
|
89
|
+
return a.code.localeCompare(b.code);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function buildTopGapKinds(gaps) {
|
|
93
|
+
const counts = new Map();
|
|
94
|
+
for (const gap of gaps) {
|
|
95
|
+
counts.set(gap.kind, (counts.get(gap.kind) ?? 0) + 1);
|
|
96
|
+
}
|
|
97
|
+
return [...counts.entries()]
|
|
98
|
+
.map(([kind, count]) => ({ kind, count }))
|
|
99
|
+
.sort((a, b) => {
|
|
100
|
+
const countDiff = b.count - a.count;
|
|
101
|
+
if (countDiff !== 0) {
|
|
102
|
+
return countDiff;
|
|
103
|
+
}
|
|
104
|
+
return a.kind.localeCompare(b.kind);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function estimateReportSize(snapshot, diagnosticsTotal, gapsTotal, diagnosticsShown, gapsShown) {
|
|
108
|
+
return JSON.stringify({
|
|
109
|
+
catalogueId: snapshot.catalogueId,
|
|
110
|
+
diagnosticsTotal,
|
|
111
|
+
gapsTotal,
|
|
112
|
+
diagnosticsShown,
|
|
113
|
+
gapsShown,
|
|
114
|
+
stats: snapshot.stats
|
|
115
|
+
}).length;
|
|
116
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded, opt-in editing context (Gate 4).
|
|
3
|
+
*
|
|
4
|
+
* Assembles a compact, advisory "what should I know before editing this?"
|
|
5
|
+
* bundle for a public API or symbol. Every list-like section carries its own
|
|
6
|
+
* `{ total, returned, truncated, records }` budget so callers always know which
|
|
7
|
+
* section was cut, and no section can emit an unbounded graph.
|
|
8
|
+
*
|
|
9
|
+
* Edges are resolved to canonical symbol endpoints where possible so that
|
|
10
|
+
* dependency/dependant references point at authored declarations rather than
|
|
11
|
+
* generated duplicates. This helper is read-only and never mutates the
|
|
12
|
+
* snapshot; its output is advisory context, not authoritative coverage.
|
|
13
|
+
*/
|
|
14
|
+
import type { CatalogueSnapshot, PublicApiRecord, RelationshipType, SymbolRecord } from "../catalogue/schema.js";
|
|
15
|
+
/** Small per-section default budget (kept intentionally tight). */
|
|
16
|
+
export declare const DEFAULT_EDITING_CONTEXT_LIMIT = 6;
|
|
17
|
+
/** Maximum per-section budget a caller may request. */
|
|
18
|
+
export declare const MAX_EDITING_CONTEXT_LIMIT = 25;
|
|
19
|
+
/** Human-readable advisory attached to every editing-context payload. */
|
|
20
|
+
export declare const EDITING_CONTEXT_ADVISORY: string;
|
|
21
|
+
export interface EditingContextSection<T> {
|
|
22
|
+
total: number;
|
|
23
|
+
returned: number;
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
records: T[];
|
|
26
|
+
}
|
|
27
|
+
export interface EditingContextRef {
|
|
28
|
+
kind: "symbol" | "publicApi" | "file" | "package" | "capability" | "test" | "example" | "externalPackage";
|
|
29
|
+
id: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
/** Present when the referenced endpoint resolves to a canonical declaration. */
|
|
32
|
+
canonicalSymbolId?: string;
|
|
33
|
+
/** Import path for public entrypoints. */
|
|
34
|
+
importPath?: string;
|
|
35
|
+
/** Export kind for public entrypoints. */
|
|
36
|
+
exportKind?: PublicApiRecord["exportKind"];
|
|
37
|
+
/** Relationship type when the record represents an edge. */
|
|
38
|
+
relationshipType?: RelationshipType;
|
|
39
|
+
}
|
|
40
|
+
export interface EditingContextDefinition {
|
|
41
|
+
symbolId?: string;
|
|
42
|
+
publicApiId?: string;
|
|
43
|
+
name: string;
|
|
44
|
+
kind?: SymbolRecord["kind"];
|
|
45
|
+
qualifiedName?: string;
|
|
46
|
+
filePath?: string;
|
|
47
|
+
language?: string;
|
|
48
|
+
visibility?: SymbolRecord["visibility"];
|
|
49
|
+
exported?: boolean;
|
|
50
|
+
canonicalSymbolId?: string;
|
|
51
|
+
range?: SymbolRecord["range"];
|
|
52
|
+
}
|
|
53
|
+
export interface EditingContextPackage {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
path: string;
|
|
57
|
+
ecosystem?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface EditingContext {
|
|
60
|
+
advisory: string;
|
|
61
|
+
perSectionLimit: number;
|
|
62
|
+
definition: EditingContextDefinition | null;
|
|
63
|
+
owningPackage: EditingContextPackage | null;
|
|
64
|
+
publicEntrypoints: EditingContextSection<EditingContextRef>;
|
|
65
|
+
reExportChain: EditingContextSection<EditingContextRef>;
|
|
66
|
+
dependencies: EditingContextSection<EditingContextRef>;
|
|
67
|
+
dependants: EditingContextSection<EditingContextRef>;
|
|
68
|
+
tests: EditingContextSection<EditingContextRef>;
|
|
69
|
+
examples: EditingContextSection<EditingContextRef>;
|
|
70
|
+
capabilities: EditingContextSection<EditingContextRef>;
|
|
71
|
+
contracts: EditingContextSection<string>;
|
|
72
|
+
limitations: EditingContextSection<string>;
|
|
73
|
+
diagnostics: EditingContextSection<EditingContextRef>;
|
|
74
|
+
}
|
|
75
|
+
export interface EditingContextFocus {
|
|
76
|
+
symbol?: SymbolRecord;
|
|
77
|
+
publicApi?: PublicApiRecord;
|
|
78
|
+
}
|
|
79
|
+
export interface BuildEditingContextOptions {
|
|
80
|
+
/** Per-section budget; clamped to [1, MAX_EDITING_CONTEXT_LIMIT]. */
|
|
81
|
+
perSectionLimit?: number;
|
|
82
|
+
}
|
|
83
|
+
export declare function normalizeEditingContextLimit(value: number | undefined): number;
|
|
84
|
+
export declare function buildEditingContext(snapshot: CatalogueSnapshot, focus: EditingContextFocus, options?: BuildEditingContextOptions): EditingContext;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/** Small per-section default budget (kept intentionally tight). */
|
|
2
|
+
export const DEFAULT_EDITING_CONTEXT_LIMIT = 6;
|
|
3
|
+
/** Maximum per-section budget a caller may request. */
|
|
4
|
+
export const MAX_EDITING_CONTEXT_LIMIT = 25;
|
|
5
|
+
/** Human-readable advisory attached to every editing-context payload. */
|
|
6
|
+
export const EDITING_CONTEXT_ADVISORY = "Advisory editing context. Sections are bounded per-section and may be truncated; "
|
|
7
|
+
+ "inferred edges are not authoritative coverage. Use the canonical list/search tools for complete data.";
|
|
8
|
+
const EDGE_TYPES = ["imports", "resolved_direct_import", "resolved_direct_usage"];
|
|
9
|
+
export function normalizeEditingContextLimit(value) {
|
|
10
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
11
|
+
return DEFAULT_EDITING_CONTEXT_LIMIT;
|
|
12
|
+
}
|
|
13
|
+
const floored = Math.floor(value);
|
|
14
|
+
if (floored <= 0) {
|
|
15
|
+
return DEFAULT_EDITING_CONTEXT_LIMIT;
|
|
16
|
+
}
|
|
17
|
+
return Math.min(floored, MAX_EDITING_CONTEXT_LIMIT);
|
|
18
|
+
}
|
|
19
|
+
export function buildEditingContext(snapshot, focus, options = {}) {
|
|
20
|
+
const perSectionLimit = normalizeEditingContextLimit(options.perSectionLimit);
|
|
21
|
+
const symbolsById = new Map(snapshot.symbols.map((symbol) => [symbol.id, symbol]));
|
|
22
|
+
const publicApi = focus.publicApi
|
|
23
|
+
?? (focus.symbol?.publicApi ? snapshot.publicApis.find((api) => api.id === focus.symbol.publicApi) : undefined);
|
|
24
|
+
const symbol = focus.symbol
|
|
25
|
+
?? (publicApi ? symbolsById.get(publicApi.symbolId) : undefined);
|
|
26
|
+
const filePath = symbol?.filePath;
|
|
27
|
+
const focusSymbolIds = new Set();
|
|
28
|
+
if (symbol) {
|
|
29
|
+
focusSymbolIds.add(symbol.id);
|
|
30
|
+
if (symbol.canonicalSymbolId) {
|
|
31
|
+
focusSymbolIds.add(symbol.canonicalSymbolId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const focusPublicApiId = publicApi?.id;
|
|
35
|
+
const canonicalize = (symbolId) => {
|
|
36
|
+
const record = symbolsById.get(symbolId);
|
|
37
|
+
return record?.canonicalSymbolId ?? symbolId;
|
|
38
|
+
};
|
|
39
|
+
const refFromEndpoint = (endpoint, relationshipType) => {
|
|
40
|
+
if (endpoint.kind === "symbol") {
|
|
41
|
+
const canonicalSymbolId = canonicalize(endpoint.id);
|
|
42
|
+
const record = symbolsById.get(canonicalSymbolId) ?? symbolsById.get(endpoint.id);
|
|
43
|
+
return {
|
|
44
|
+
kind: "symbol",
|
|
45
|
+
id: canonicalSymbolId,
|
|
46
|
+
name: record?.name,
|
|
47
|
+
canonicalSymbolId,
|
|
48
|
+
relationshipType
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (endpoint.kind === "file"
|
|
52
|
+
|| endpoint.kind === "externalPackage"
|
|
53
|
+
|| endpoint.kind === "package"
|
|
54
|
+
|| endpoint.kind === "publicApi"
|
|
55
|
+
|| endpoint.kind === "capability"
|
|
56
|
+
|| endpoint.kind === "test") {
|
|
57
|
+
return { kind: endpoint.kind, id: endpoint.id, relationshipType };
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
};
|
|
61
|
+
const endpointMatchesFocus = (endpoint) => {
|
|
62
|
+
if (endpoint.kind === "symbol") {
|
|
63
|
+
return focusSymbolIds.has(endpoint.id) || focusSymbolIds.has(canonicalize(endpoint.id));
|
|
64
|
+
}
|
|
65
|
+
if (endpoint.kind === "publicApi") {
|
|
66
|
+
return focusPublicApiId !== undefined && endpoint.id === focusPublicApiId;
|
|
67
|
+
}
|
|
68
|
+
if (endpoint.kind === "file") {
|
|
69
|
+
return filePath !== undefined && endpoint.id === filePath;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
73
|
+
const dependencyRefs = [];
|
|
74
|
+
const dependantRefs = [];
|
|
75
|
+
const reExportRefs = [];
|
|
76
|
+
for (const relationship of snapshot.relationships) {
|
|
77
|
+
if (relationship.type === "exports") {
|
|
78
|
+
if (endpointMatchesFocus(relationship.from) || endpointMatchesFocus(relationship.to)) {
|
|
79
|
+
const other = endpointMatchesFocus(relationship.from) ? relationship.to : relationship.from;
|
|
80
|
+
const ref = refFromEndpoint(other, relationship.type);
|
|
81
|
+
if (ref) {
|
|
82
|
+
reExportRefs.push(ref);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!EDGE_TYPES.includes(relationship.type)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (endpointMatchesFocus(relationship.from)) {
|
|
91
|
+
const ref = refFromEndpoint(relationship.to, relationship.type);
|
|
92
|
+
if (ref) {
|
|
93
|
+
dependencyRefs.push(ref);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (endpointMatchesFocus(relationship.to)) {
|
|
97
|
+
const ref = refFromEndpoint(relationship.from, relationship.type);
|
|
98
|
+
if (ref) {
|
|
99
|
+
dependantRefs.push(ref);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const owningPackage = resolveOwningPackage(snapshot, symbol, publicApi);
|
|
104
|
+
const publicEntrypointRefs = owningPackage
|
|
105
|
+
? snapshot.publicApis
|
|
106
|
+
.filter((api) => api.packageId === owningPackage.id)
|
|
107
|
+
.map((api) => ({
|
|
108
|
+
kind: "publicApi",
|
|
109
|
+
id: api.id,
|
|
110
|
+
name: api.name,
|
|
111
|
+
importPath: api.importPath,
|
|
112
|
+
exportKind: api.exportKind
|
|
113
|
+
}))
|
|
114
|
+
: [];
|
|
115
|
+
const testRefs = snapshot.tests
|
|
116
|
+
.filter((test) => {
|
|
117
|
+
if (publicApi && test.coversPublicApis.includes(publicApi.id))
|
|
118
|
+
return true;
|
|
119
|
+
if (publicApi && (publicApi.tests.includes(test.id) || publicApi.tests.includes(test.filePath)))
|
|
120
|
+
return true;
|
|
121
|
+
if (symbol && test.coversSymbols.some((id) => focusSymbolIds.has(id) || focusSymbolIds.has(canonicalize(id))))
|
|
122
|
+
return true;
|
|
123
|
+
return false;
|
|
124
|
+
})
|
|
125
|
+
.map((test) => ({ kind: "test", id: test.id, name: test.name }));
|
|
126
|
+
const exampleRefs = snapshot.examples
|
|
127
|
+
.filter((example) => {
|
|
128
|
+
if (publicApi && example.publicApiIds.includes(publicApi.id))
|
|
129
|
+
return true;
|
|
130
|
+
if (symbol && example.symbolIds.some((id) => focusSymbolIds.has(id) || focusSymbolIds.has(canonicalize(id))))
|
|
131
|
+
return true;
|
|
132
|
+
return false;
|
|
133
|
+
})
|
|
134
|
+
.map((example) => ({ kind: "example", id: example.id, name: example.title }));
|
|
135
|
+
const capabilityIds = new Set([
|
|
136
|
+
...(publicApi?.capabilityIds ?? []),
|
|
137
|
+
...(symbol?.capabilities ?? [])
|
|
138
|
+
]);
|
|
139
|
+
const capabilityRefs = snapshot.capabilities
|
|
140
|
+
.filter((capability) => capabilityIds.has(capability.id))
|
|
141
|
+
.map((capability) => ({ kind: "capability", id: capability.id, name: capability.name }));
|
|
142
|
+
const linkedCapabilities = snapshot.capabilities.filter((capability) => capabilityIds.has(capability.id));
|
|
143
|
+
const contracts = [
|
|
144
|
+
...new Set([
|
|
145
|
+
...(publicApi?.contracts ?? []),
|
|
146
|
+
...linkedCapabilities.flatMap((capability) => capability.contracts)
|
|
147
|
+
])
|
|
148
|
+
].filter((value) => value.trim().length > 0);
|
|
149
|
+
const limitations = [
|
|
150
|
+
...new Set(linkedCapabilities.flatMap((capability) => capability.limitations))
|
|
151
|
+
].filter((value) => value.trim().length > 0);
|
|
152
|
+
const diagnosticRefs = snapshot.diagnostics
|
|
153
|
+
.filter((diagnostic) => {
|
|
154
|
+
if (publicApi && diagnostic.target?.kind === "publicApi" && diagnostic.target.id === publicApi.id)
|
|
155
|
+
return true;
|
|
156
|
+
if (symbol && diagnostic.target?.kind === "symbol"
|
|
157
|
+
&& (focusSymbolIds.has(diagnostic.target.id) || focusSymbolIds.has(canonicalize(diagnostic.target.id))))
|
|
158
|
+
return true;
|
|
159
|
+
if (filePath && diagnostic.filePath === filePath)
|
|
160
|
+
return true;
|
|
161
|
+
return false;
|
|
162
|
+
})
|
|
163
|
+
.map((diagnostic) => ({ kind: "file", id: diagnostic.id, name: diagnostic.code }));
|
|
164
|
+
return {
|
|
165
|
+
advisory: EDITING_CONTEXT_ADVISORY,
|
|
166
|
+
perSectionLimit,
|
|
167
|
+
definition: buildDefinition(symbol, publicApi),
|
|
168
|
+
owningPackage,
|
|
169
|
+
publicEntrypoints: toSection(dedupeRefs(publicEntrypointRefs), perSectionLimit),
|
|
170
|
+
reExportChain: toSection(dedupeRefs(reExportRefs), perSectionLimit),
|
|
171
|
+
dependencies: toSection(dedupeRefs(dependencyRefs), perSectionLimit),
|
|
172
|
+
dependants: toSection(dedupeRefs(dependantRefs), perSectionLimit),
|
|
173
|
+
tests: toSection(dedupeRefs(testRefs), perSectionLimit),
|
|
174
|
+
examples: toSection(dedupeRefs(exampleRefs), perSectionLimit),
|
|
175
|
+
capabilities: toSection(dedupeRefs(capabilityRefs), perSectionLimit),
|
|
176
|
+
contracts: toStringSection(contracts.sort((a, b) => a.localeCompare(b)), perSectionLimit),
|
|
177
|
+
limitations: toStringSection(limitations.sort((a, b) => a.localeCompare(b)), perSectionLimit),
|
|
178
|
+
diagnostics: toSection(dedupeRefs(diagnosticRefs), perSectionLimit)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function buildDefinition(symbol, publicApi) {
|
|
182
|
+
if (!symbol && !publicApi) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
symbolId: symbol?.id,
|
|
187
|
+
publicApiId: publicApi?.id,
|
|
188
|
+
name: symbol?.name ?? publicApi?.name ?? "",
|
|
189
|
+
kind: symbol?.kind,
|
|
190
|
+
qualifiedName: symbol?.qualifiedName,
|
|
191
|
+
filePath: symbol?.filePath,
|
|
192
|
+
language: symbol?.language,
|
|
193
|
+
visibility: symbol?.visibility,
|
|
194
|
+
exported: symbol?.exported,
|
|
195
|
+
canonicalSymbolId: symbol?.canonicalSymbolId,
|
|
196
|
+
range: symbol?.range
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function resolveOwningPackage(snapshot, symbol, publicApi) {
|
|
200
|
+
const packageId = publicApi?.packageId ?? symbol?.packageId;
|
|
201
|
+
let pkg = packageId ? snapshot.packages.find((candidate) => candidate.id === packageId) : undefined;
|
|
202
|
+
if (!pkg && symbol?.filePath) {
|
|
203
|
+
const candidates = snapshot.packages.filter((candidate) => {
|
|
204
|
+
if (candidate.path === ".")
|
|
205
|
+
return true;
|
|
206
|
+
return symbol.filePath === candidate.path || symbol.filePath.startsWith(`${candidate.path}/`);
|
|
207
|
+
});
|
|
208
|
+
candidates.sort((a, b) => b.path.length - a.path.length || a.id.localeCompare(b.id));
|
|
209
|
+
pkg = candidates[0];
|
|
210
|
+
}
|
|
211
|
+
if (!pkg) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return { id: pkg.id, name: pkg.name, path: pkg.path, ecosystem: pkg.ecosystem };
|
|
215
|
+
}
|
|
216
|
+
function refSortKey(ref) {
|
|
217
|
+
return [ref.kind, ref.relationshipType ?? "", ref.id].join("\u0000");
|
|
218
|
+
}
|
|
219
|
+
function dedupeRefs(refs) {
|
|
220
|
+
const byKey = new Map();
|
|
221
|
+
for (const ref of refs) {
|
|
222
|
+
const key = refSortKey(ref);
|
|
223
|
+
if (!byKey.has(key)) {
|
|
224
|
+
byKey.set(key, ref);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return [...byKey.values()].sort((a, b) => refSortKey(a).localeCompare(refSortKey(b)));
|
|
228
|
+
}
|
|
229
|
+
function toSection(records, limit) {
|
|
230
|
+
const sliced = records.slice(0, limit);
|
|
231
|
+
return {
|
|
232
|
+
total: records.length,
|
|
233
|
+
returned: sliced.length,
|
|
234
|
+
truncated: sliced.length < records.length,
|
|
235
|
+
records: sliced
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function toStringSection(records, limit) {
|
|
239
|
+
return toSection(records, limit);
|
|
240
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CatalogueSnapshot, ExampleRecord } from "../catalogue/schema.js";
|
|
2
|
+
export interface ListExamplesOptions {
|
|
3
|
+
capabilityId?: string;
|
|
4
|
+
publicApiId?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
offset?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function listExamples(snapshot: CatalogueSnapshot, options?: ListExamplesOptions): ExampleRecord[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function listExamples(snapshot, options = {}) {
|
|
2
|
+
const filtered = snapshot.examples
|
|
3
|
+
.filter((example) => {
|
|
4
|
+
if (options.capabilityId && !example.capabilityIds.includes(options.capabilityId)) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (options.publicApiId && !example.publicApiIds.includes(options.publicApiId)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return true;
|
|
11
|
+
})
|
|
12
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
13
|
+
const offset = Math.max(0, Math.floor(options.offset ?? 0));
|
|
14
|
+
const limit = Math.max(1, Math.floor(options.limit ?? 50));
|
|
15
|
+
return filtered.slice(offset, offset + limit);
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CatalogueSnapshot, FileRecord } from "../catalogue/schema.js";
|
|
2
|
+
export interface ListFilesOptions {
|
|
3
|
+
language?: string;
|
|
4
|
+
kind?: FileRecord["kind"];
|
|
5
|
+
limit?: number;
|
|
6
|
+
offset?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function listFiles(snapshot: CatalogueSnapshot, options?: ListFilesOptions): FileRecord[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function listFiles(snapshot, options = {}) {
|
|
2
|
+
const filtered = snapshot.files.filter((file) => {
|
|
3
|
+
if (options.language && file.language !== options.language) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
if (options.kind && file.kind !== options.kind) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return true;
|
|
10
|
+
});
|
|
11
|
+
const offset = Math.max(0, options.offset ?? 0);
|
|
12
|
+
const limit = Math.max(1, options.limit ?? 100);
|
|
13
|
+
return filtered.slice(offset, offset + limit);
|
|
14
|
+
}
|