@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,181 @@
|
|
|
1
|
+
import { nameMatchReasons } from "./ranking.js";
|
|
2
|
+
export function findReuseCandidates(snapshot, options) {
|
|
3
|
+
const query = options.query.trim();
|
|
4
|
+
const normalizedQuery = query.toLowerCase();
|
|
5
|
+
const tokens = tokenize(normalizedQuery);
|
|
6
|
+
const limit = Math.max(1, options.limit ?? 10);
|
|
7
|
+
const capabilityCandidates = snapshot.capabilities
|
|
8
|
+
.filter((capability) => {
|
|
9
|
+
if (options.capabilityId && capability.id !== options.capabilityId) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (options.packageId && !capability.packages.includes(options.packageId)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
})
|
|
17
|
+
.map((capability) => scoreCapability(snapshot, capability, normalizedQuery, tokens));
|
|
18
|
+
const publicApiCandidates = snapshot.publicApis
|
|
19
|
+
.filter((api) => {
|
|
20
|
+
if (options.capabilityId && !api.capabilityIds.includes(options.capabilityId)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (!options.packageId) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const symbol = snapshot.symbols.find((item) => item.id === api.symbolId);
|
|
27
|
+
const ownerPackage = symbol ? findOwningPackageId(snapshot, symbol.filePath) : undefined;
|
|
28
|
+
return ownerPackage === options.packageId;
|
|
29
|
+
})
|
|
30
|
+
.map((api) => scorePublicApi(snapshot, api, normalizedQuery, tokens));
|
|
31
|
+
const candidates = [...capabilityCandidates, ...publicApiCandidates]
|
|
32
|
+
.filter((candidate) => candidate.score > 0)
|
|
33
|
+
.sort((a, b) => b.score - a.score || a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id))
|
|
34
|
+
.slice(0, limit);
|
|
35
|
+
return { query, tokens, candidates };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Thin wrapper over {@link findReuseCandidates} that annotates each candidate
|
|
39
|
+
* with closed-vocabulary match reasons derived from its name. Ordering and
|
|
40
|
+
* scoring are unchanged from the underlying reuse scorer.
|
|
41
|
+
*/
|
|
42
|
+
export function rankReuseCandidates(snapshot, options) {
|
|
43
|
+
const result = findReuseCandidates(snapshot, options);
|
|
44
|
+
const candidates = result.candidates.map((candidate) => {
|
|
45
|
+
const name = candidate.kind === "capability"
|
|
46
|
+
? snapshot.capabilities.find((item) => item.id === candidate.id)?.name
|
|
47
|
+
: snapshot.publicApis.find((item) => item.id === candidate.id)?.name;
|
|
48
|
+
const reasons = name ? nameMatchReasons(name, result.query, "exact_declared_name") : [];
|
|
49
|
+
return { ...candidate, matchReasons: reasons };
|
|
50
|
+
});
|
|
51
|
+
return { query: result.query, tokens: result.tokens, candidates };
|
|
52
|
+
}
|
|
53
|
+
function scoreCapability(snapshot, capability, query, tokens) {
|
|
54
|
+
let score = 0;
|
|
55
|
+
const reasons = [];
|
|
56
|
+
if (query && capability.id.toLowerCase() === query) {
|
|
57
|
+
score += 100;
|
|
58
|
+
reasons.push("matched capability id");
|
|
59
|
+
}
|
|
60
|
+
if (query && capability.name.toLowerCase().includes(query)) {
|
|
61
|
+
score += 35;
|
|
62
|
+
reasons.push("matched capability name");
|
|
63
|
+
}
|
|
64
|
+
if (tokens.some((token) => capability.keywords.map((keyword) => keyword.toLowerCase()).includes(token))) {
|
|
65
|
+
score += 20;
|
|
66
|
+
reasons.push("matched capability keywords");
|
|
67
|
+
}
|
|
68
|
+
const linkedApis = snapshot.publicApis.filter((api) => capability.publicApis.includes(api.id));
|
|
69
|
+
if (query && linkedApis.some((api) => api.name.toLowerCase().includes(query))) {
|
|
70
|
+
score += 25;
|
|
71
|
+
reasons.push("matched public API name");
|
|
72
|
+
}
|
|
73
|
+
const linkedSymbols = snapshot.symbols.filter((symbol) => capability.symbols.includes(symbol.id));
|
|
74
|
+
if (query && linkedSymbols.some((symbol) => symbol.name.toLowerCase().includes(query))) {
|
|
75
|
+
score += 20;
|
|
76
|
+
reasons.push("matched symbol name");
|
|
77
|
+
}
|
|
78
|
+
const linkedPackages = snapshot.packages.filter((pkg) => capability.packages.includes(pkg.id));
|
|
79
|
+
if (query && linkedPackages.some((pkg) => pkg.name.toLowerCase().includes(query))) {
|
|
80
|
+
score += 15;
|
|
81
|
+
reasons.push("matched package name");
|
|
82
|
+
}
|
|
83
|
+
const searchableText = [
|
|
84
|
+
capability.summary ?? "",
|
|
85
|
+
capability.intendedUse ?? "",
|
|
86
|
+
...capability.contracts,
|
|
87
|
+
...capability.limitations,
|
|
88
|
+
...capability.tests,
|
|
89
|
+
...capability.examples
|
|
90
|
+
].join(" ").toLowerCase();
|
|
91
|
+
if (query && searchableText.includes(query)) {
|
|
92
|
+
score += 12;
|
|
93
|
+
reasons.push("matched capability documentation");
|
|
94
|
+
}
|
|
95
|
+
if (score > 0
|
|
96
|
+
&& (capability.tests.length > 0 || linkedApis.some((api) => api.tests.length > 0))
|
|
97
|
+
&& (capability.examples.length > 0 || linkedApis.some((api) => api.examples.length > 0))) {
|
|
98
|
+
score += 6;
|
|
99
|
+
reasons.push("has tests and examples");
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
kind: "capability",
|
|
103
|
+
id: capability.id,
|
|
104
|
+
score: normalizeScore(score, 200),
|
|
105
|
+
reasons: dedupe(reasons)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function scorePublicApi(snapshot, api, query, tokens) {
|
|
109
|
+
let score = 0;
|
|
110
|
+
const reasons = [];
|
|
111
|
+
if (query && api.id.toLowerCase() === query) {
|
|
112
|
+
score += 100;
|
|
113
|
+
reasons.push("matched public API id");
|
|
114
|
+
}
|
|
115
|
+
if (query && api.name.toLowerCase().includes(query)) {
|
|
116
|
+
score += 45;
|
|
117
|
+
reasons.push("matched public API name");
|
|
118
|
+
}
|
|
119
|
+
if (tokens.some((token) => api.capabilityIds.some((capabilityId) => capabilityId.toLowerCase().includes(token)))) {
|
|
120
|
+
score += 20;
|
|
121
|
+
reasons.push("matched linked capability");
|
|
122
|
+
}
|
|
123
|
+
const symbol = snapshot.symbols.find((item) => item.id === api.symbolId);
|
|
124
|
+
if (query && symbol?.name.toLowerCase().includes(query)) {
|
|
125
|
+
score += 20;
|
|
126
|
+
reasons.push("matched implementation symbol");
|
|
127
|
+
}
|
|
128
|
+
if (query) {
|
|
129
|
+
const owningPackageId = symbol ? findOwningPackageId(snapshot, symbol.filePath) : undefined;
|
|
130
|
+
const owningPackage = owningPackageId ? snapshot.packages.find((pkg) => pkg.id === owningPackageId) : undefined;
|
|
131
|
+
if (owningPackage?.name.toLowerCase().includes(query)) {
|
|
132
|
+
score += 15;
|
|
133
|
+
reasons.push("matched owning package");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const searchableText = [
|
|
137
|
+
api.intendedUse ?? "",
|
|
138
|
+
api.inputs ?? "",
|
|
139
|
+
api.returns ?? "",
|
|
140
|
+
...api.contracts,
|
|
141
|
+
...api.tests,
|
|
142
|
+
...api.examples
|
|
143
|
+
].join(" ").toLowerCase();
|
|
144
|
+
if (query && searchableText.includes(query)) {
|
|
145
|
+
score += 12;
|
|
146
|
+
reasons.push("matched API documentation");
|
|
147
|
+
}
|
|
148
|
+
if (score > 0 && api.tests.length > 0 && api.examples.length > 0) {
|
|
149
|
+
score += 6;
|
|
150
|
+
reasons.push("has tests and examples");
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
kind: "publicApi",
|
|
154
|
+
id: api.id,
|
|
155
|
+
score: normalizeScore(score, 200),
|
|
156
|
+
reasons: dedupe(reasons)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function findOwningPackageId(snapshot, filePath) {
|
|
160
|
+
const candidates = snapshot.packages
|
|
161
|
+
.filter((pkg) => pkg.path === "." || filePath === pkg.path || filePath.startsWith(`${pkg.path}/`))
|
|
162
|
+
.sort((a, b) => b.path.length - a.path.length || a.id.localeCompare(b.id));
|
|
163
|
+
return candidates[0]?.id;
|
|
164
|
+
}
|
|
165
|
+
function tokenize(input) {
|
|
166
|
+
return input
|
|
167
|
+
.split(/\s+/)
|
|
168
|
+
.map((token) => token.trim())
|
|
169
|
+
.filter(Boolean)
|
|
170
|
+
.sort();
|
|
171
|
+
}
|
|
172
|
+
function normalizeScore(score, max) {
|
|
173
|
+
if (score <= 0) {
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
const normalized = Math.min(1, score / max);
|
|
177
|
+
return Number(normalized.toFixed(4));
|
|
178
|
+
}
|
|
179
|
+
function dedupe(values) {
|
|
180
|
+
return [...new Set(values)].sort();
|
|
181
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CatalogueSnapshot, SymbolRecord } from "../catalogue/schema.js";
|
|
2
|
+
import { type RankedEnvelope } from "./ranking.js";
|
|
3
|
+
import { type EditingContext } from "./editingContext.js";
|
|
4
|
+
export interface SearchSymbolsOptions {
|
|
5
|
+
query: string;
|
|
6
|
+
kind?: SymbolRecord["kind"];
|
|
7
|
+
language?: string;
|
|
8
|
+
packageId?: string;
|
|
9
|
+
publicOnly?: boolean;
|
|
10
|
+
capabilityId?: string;
|
|
11
|
+
hasExamples?: boolean;
|
|
12
|
+
hasTests?: boolean;
|
|
13
|
+
limit?: number;
|
|
14
|
+
offset?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SymbolCompact {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
qualifiedName: string;
|
|
20
|
+
kind: SymbolRecord["kind"];
|
|
21
|
+
language: string;
|
|
22
|
+
filePath: string;
|
|
23
|
+
packageId?: string;
|
|
24
|
+
visibility: SymbolRecord["visibility"];
|
|
25
|
+
exported: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function toSymbolCompact(symbol: SymbolRecord): SymbolCompact;
|
|
28
|
+
export declare function searchSymbols(snapshot: CatalogueSnapshot, options: SearchSymbolsOptions): RankedEnvelope<SymbolCompact>;
|
|
29
|
+
export interface GetSymbolContextOptions {
|
|
30
|
+
/** Opt-in bounded editing context (Gate 4). Default false. */
|
|
31
|
+
includeEditingContext?: boolean;
|
|
32
|
+
/** Per-section budget for editing context when included. */
|
|
33
|
+
editingContextLimit?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface SymbolContextResult {
|
|
36
|
+
symbol: SymbolRecord;
|
|
37
|
+
/** Bounded, advisory editing context; present only when opted in. */
|
|
38
|
+
editingContext?: EditingContext;
|
|
39
|
+
}
|
|
40
|
+
export declare function getSymbolContext(snapshot: CatalogueSnapshot, symbolId: string, options?: GetSymbolContextOptions): SymbolContextResult | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildRankedEnvelope, rankSymbols } from "./ranking.js";
|
|
2
|
+
import { buildEditingContext } from "./editingContext.js";
|
|
3
|
+
export function toSymbolCompact(symbol) {
|
|
4
|
+
return {
|
|
5
|
+
id: symbol.id,
|
|
6
|
+
name: symbol.name,
|
|
7
|
+
qualifiedName: symbol.qualifiedName,
|
|
8
|
+
kind: symbol.kind,
|
|
9
|
+
language: symbol.language,
|
|
10
|
+
filePath: symbol.filePath,
|
|
11
|
+
packageId: symbol.packageId,
|
|
12
|
+
visibility: symbol.visibility,
|
|
13
|
+
exported: symbol.exported
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function searchSymbols(snapshot, options) {
|
|
17
|
+
const filters = {
|
|
18
|
+
kind: options.kind,
|
|
19
|
+
language: options.language,
|
|
20
|
+
packageId: options.packageId,
|
|
21
|
+
publicOnly: options.publicOnly,
|
|
22
|
+
capabilityId: options.capabilityId,
|
|
23
|
+
hasExamples: options.hasExamples,
|
|
24
|
+
hasTests: options.hasTests
|
|
25
|
+
};
|
|
26
|
+
const ranked = rankSymbols(options.query, snapshot.symbols, filters, snapshot);
|
|
27
|
+
return buildRankedEnvelope(options.query, ranked, toSymbolCompact, filters, {
|
|
28
|
+
offset: options.offset,
|
|
29
|
+
limit: options.limit
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function getSymbolContext(snapshot, symbolId, options = {}) {
|
|
33
|
+
const symbol = snapshot.symbols.find((item) => item.id === symbolId);
|
|
34
|
+
if (!symbol) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const editingContext = options.includeEditingContext
|
|
38
|
+
? buildEditingContext(snapshot, { symbol }, { perSectionLimit: options.editingContextLimit })
|
|
39
|
+
: undefined;
|
|
40
|
+
return { symbol, editingContext };
|
|
41
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function toWorkspaceRelative(workspaceRoot: string, targetPath: string): string;
|
|
2
|
+
export declare function isWithinWorkspace(workspaceRoot: string, targetPath: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a filesystem path to its canonical target.
|
|
5
|
+
* Returns undefined on missing paths, broken symlinks, or inaccessible targets.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveCanonicalPath(input: string): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* True when candidatePath's canonical target is inside workspaceRoot's canonical root.
|
|
10
|
+
* False when either path cannot be canonicalised or the target escapes the root.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isCanonicalWithinWorkspace(workspaceRoot: string, candidatePath: string): boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
export function toWorkspaceRelative(workspaceRoot, targetPath) {
|
|
4
|
+
const relative = path.relative(path.resolve(workspaceRoot), path.resolve(targetPath));
|
|
5
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
return relative.replaceAll("\\", "/");
|
|
9
|
+
}
|
|
10
|
+
export function isWithinWorkspace(workspaceRoot, targetPath) {
|
|
11
|
+
const resolvedRoot = path.resolve(workspaceRoot);
|
|
12
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
13
|
+
return isRelativeWithinRoot(resolvedRoot, resolvedTarget);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a filesystem path to its canonical target.
|
|
17
|
+
* Returns undefined on missing paths, broken symlinks, or inaccessible targets.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveCanonicalPath(input) {
|
|
20
|
+
try {
|
|
21
|
+
const realpathNative = realpathSync.native;
|
|
22
|
+
if (typeof realpathNative === "function") {
|
|
23
|
+
return realpathNative(input);
|
|
24
|
+
}
|
|
25
|
+
return realpathSync(input);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* True when candidatePath's canonical target is inside workspaceRoot's canonical root.
|
|
33
|
+
* False when either path cannot be canonicalised or the target escapes the root.
|
|
34
|
+
*/
|
|
35
|
+
export function isCanonicalWithinWorkspace(workspaceRoot, candidatePath) {
|
|
36
|
+
const canonicalRoot = resolveCanonicalPath(workspaceRoot);
|
|
37
|
+
const canonicalTarget = resolveCanonicalPath(candidatePath);
|
|
38
|
+
if (!canonicalRoot || !canonicalTarget) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return isRelativeWithinRoot(canonicalRoot, canonicalTarget);
|
|
42
|
+
}
|
|
43
|
+
function isRelativeWithinRoot(root, target) {
|
|
44
|
+
const relative = path.relative(root, target);
|
|
45
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isSecretLikeFile(filePath: string): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface SourceAccessOptions {
|
|
2
|
+
workspaceRoot: string;
|
|
3
|
+
relativePath: string;
|
|
4
|
+
includeSource?: boolean;
|
|
5
|
+
maxSourceLines: number;
|
|
6
|
+
maxFileSizeBytes: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function getBoundedSourcePreview(options: SourceAccessOptions): {
|
|
9
|
+
allowed: boolean;
|
|
10
|
+
reason?: string;
|
|
11
|
+
source?: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { isSecretLikeFile } from "./secretPatterns.js";
|
|
4
|
+
import { isCanonicalWithinWorkspace, isWithinWorkspace, resolveCanonicalPath } from "./pathPolicy.js";
|
|
5
|
+
export function getBoundedSourcePreview(options) {
|
|
6
|
+
if (!options.includeSource) {
|
|
7
|
+
return { allowed: false, reason: "source_disabled" };
|
|
8
|
+
}
|
|
9
|
+
if (isSecretLikeFile(path.basename(options.relativePath))) {
|
|
10
|
+
return { allowed: false, reason: "raw_source_excluded" };
|
|
11
|
+
}
|
|
12
|
+
const absolute = path.resolve(options.workspaceRoot, options.relativePath);
|
|
13
|
+
if (!isWithinWorkspace(options.workspaceRoot, absolute)) {
|
|
14
|
+
return { allowed: false, reason: "path_outside_workspace" };
|
|
15
|
+
}
|
|
16
|
+
const canonicalRoot = resolveCanonicalPath(options.workspaceRoot);
|
|
17
|
+
const canonicalTarget = resolveCanonicalPath(absolute);
|
|
18
|
+
if (!canonicalRoot || !canonicalTarget) {
|
|
19
|
+
// Missing path, broken symlink, or inaccessible target — fail closed without throwing.
|
|
20
|
+
return { allowed: false, reason: "source_missing" };
|
|
21
|
+
}
|
|
22
|
+
if (!isCanonicalWithinWorkspace(options.workspaceRoot, absolute)) {
|
|
23
|
+
return { allowed: false, reason: "path_outside_workspace" };
|
|
24
|
+
}
|
|
25
|
+
const buffer = readFileSync(canonicalTarget);
|
|
26
|
+
if (buffer.includes(0)) {
|
|
27
|
+
return { allowed: false, reason: "binary_source_excluded" };
|
|
28
|
+
}
|
|
29
|
+
const content = buffer.toString("utf8");
|
|
30
|
+
if (Buffer.byteLength(content, "utf8") > options.maxFileSizeBytes) {
|
|
31
|
+
return { allowed: false, reason: "raw_source_excluded" };
|
|
32
|
+
}
|
|
33
|
+
const lines = content.split(/\r?\n/).slice(0, options.maxSourceLines);
|
|
34
|
+
return { allowed: true, source: lines.join("\n") };
|
|
35
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mortice/mapwright",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Mortice Mapwright — a deterministic map of what a repository contains, what changed, and what it affects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mapwright": "dist/cli/index.js",
|
|
10
|
+
"mapwright-mcp": "dist/mcp/server.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"CHANGELOG.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"clean": "node -e \"import('node:fs').then(fs=>fs.rmSync('dist',{recursive:true,force:true}))\"",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
24
|
+
"cli": "tsx src/cli/index.ts",
|
|
25
|
+
"mcp": "tsx src/mcp/server.ts"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mcp",
|
|
29
|
+
"mapwright",
|
|
30
|
+
"mortice",
|
|
31
|
+
"catalogue",
|
|
32
|
+
"change-intelligence"
|
|
33
|
+
],
|
|
34
|
+
"author": "",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/andrewmuir/Mapwright.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/andrewmuir/Mapwright#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/andrewmuir/Mapwright/issues"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
|
+
"fast-glob": "^3.3.3",
|
|
50
|
+
"ignore": "^7.0.5",
|
|
51
|
+
"minimatch": "^10.2.5",
|
|
52
|
+
"typescript": "^6.0.3",
|
|
53
|
+
"zod": "^4.4.3"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^26.1.0",
|
|
57
|
+
"tsx": "^4.23.0",
|
|
58
|
+
"vitest": "^4.1.9"
|
|
59
|
+
}
|
|
60
|
+
}
|