@ulpi/cli 0.1.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/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/auth-PN7TMQHV-2W4ICG64.js +15 -0
- package/dist/chunk-247GVVKK.js +2259 -0
- package/dist/chunk-2CLNOKPA.js +793 -0
- package/dist/chunk-2HEE5OKX.js +79 -0
- package/dist/chunk-2MZER6ND.js +415 -0
- package/dist/chunk-3SBPZRB5.js +772 -0
- package/dist/chunk-4VNS5WPM.js +42 -0
- package/dist/chunk-6JCMYYBT.js +1546 -0
- package/dist/chunk-6OCEY7JY.js +422 -0
- package/dist/chunk-74WVVWJ4.js +375 -0
- package/dist/chunk-7AL4DOEJ.js +131 -0
- package/dist/chunk-7LXY5UVC.js +330 -0
- package/dist/chunk-DBMUNBNB.js +3048 -0
- package/dist/chunk-JWUUVXIV.js +13694 -0
- package/dist/chunk-KIKPIH6N.js +4048 -0
- package/dist/chunk-KLEASXUR.js +70 -0
- package/dist/chunk-MIAQVCFW.js +39 -0
- package/dist/chunk-NNUWU6CV.js +1610 -0
- package/dist/chunk-PKD4ASEM.js +115 -0
- package/dist/chunk-Q4HIY43N.js +4230 -0
- package/dist/chunk-QJ5GSMEC.js +146 -0
- package/dist/chunk-SIAQVRKG.js +2163 -0
- package/dist/chunk-SPOI23SB.js +197 -0
- package/dist/chunk-YM2HV4IA.js +505 -0
- package/dist/codemap-RRJIDBQ5.js +636 -0
- package/dist/config-EGAXXCGL.js +127 -0
- package/dist/dist-6G7JC2RA.js +90 -0
- package/dist/dist-7LHZ65GC.js +418 -0
- package/dist/dist-LZKZFPVX.js +140 -0
- package/dist/dist-R5F4MX3I.js +107 -0
- package/dist/dist-R5ZJ4LX5.js +56 -0
- package/dist/dist-RJGCUS3L.js +87 -0
- package/dist/dist-RKOGLK7R.js +151 -0
- package/dist/dist-W7K4WPAF.js +597 -0
- package/dist/export-import-4A5MWLIA.js +53 -0
- package/dist/history-ATTUKOHO.js +934 -0
- package/dist/index.js +2120 -0
- package/dist/init-AY5C2ZAS.js +393 -0
- package/dist/launchd-LF2QMSKZ.js +148 -0
- package/dist/log-TVTUXAYD.js +75 -0
- package/dist/mcp-installer-NQCGKQ23.js +124 -0
- package/dist/memory-J3G24QHS.js +406 -0
- package/dist/ollama-3XCUZMZT-FYKHW4TZ.js +7 -0
- package/dist/openai-E7G2YAHU-UYY4ZWON.js +8 -0
- package/dist/projects-ATHDD3D6.js +271 -0
- package/dist/review-ADUPV3PN.js +152 -0
- package/dist/rules-E427DKYJ.js +134 -0
- package/dist/server-MOYPE4SM-N7SE2AN7.js +18 -0
- package/dist/server-X5P6WH2M-7K2RY34N.js +11 -0
- package/dist/skills/ulpi-generate-guardian/SKILL.md +511 -0
- package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +692 -0
- package/dist/skills/ulpi-generate-guardian/references/language-rules.md +596 -0
- package/dist/skills-CX73O3IV.js +76 -0
- package/dist/status-4DFHDJMN.js +66 -0
- package/dist/templates/biome.yml +24 -0
- package/dist/templates/conventional-commits.yml +18 -0
- package/dist/templates/django.yml +30 -0
- package/dist/templates/docker.yml +30 -0
- package/dist/templates/eslint.yml +13 -0
- package/dist/templates/express.yml +20 -0
- package/dist/templates/fastapi.yml +23 -0
- package/dist/templates/git-flow.yml +26 -0
- package/dist/templates/github-flow.yml +27 -0
- package/dist/templates/go.yml +33 -0
- package/dist/templates/jest.yml +24 -0
- package/dist/templates/laravel.yml +30 -0
- package/dist/templates/monorepo.yml +26 -0
- package/dist/templates/nestjs.yml +21 -0
- package/dist/templates/nextjs.yml +31 -0
- package/dist/templates/nodejs.yml +33 -0
- package/dist/templates/npm.yml +15 -0
- package/dist/templates/php.yml +25 -0
- package/dist/templates/pnpm.yml +15 -0
- package/dist/templates/prettier.yml +23 -0
- package/dist/templates/prisma.yml +21 -0
- package/dist/templates/python.yml +33 -0
- package/dist/templates/quality-of-life.yml +111 -0
- package/dist/templates/ruby.yml +25 -0
- package/dist/templates/rust.yml +34 -0
- package/dist/templates/typescript.yml +14 -0
- package/dist/templates/vitest.yml +24 -0
- package/dist/templates/yarn.yml +15 -0
- package/dist/templates-U7T6MARD.js +156 -0
- package/dist/ui-L7UAWXDY.js +167 -0
- package/dist/ui.html +698 -0
- package/dist/ulpi-RMMCUAGP-JCJ273T6.js +161 -0
- package/dist/uninstall-6SW35IK4.js +25 -0
- package/dist/update-M2B4RLGH.js +61 -0
- package/dist/version-checker-ANCS3IHR.js +10 -0
- package/package.json +92 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallToolRequestSchema,
|
|
3
|
+
ListToolsRequestSchema,
|
|
4
|
+
Server,
|
|
5
|
+
StdioServerTransport
|
|
6
|
+
} from "./chunk-JWUUVXIV.js";
|
|
7
|
+
import {
|
|
8
|
+
getIncomingEdges,
|
|
9
|
+
getOutgoingEdges,
|
|
10
|
+
getTransitiveDeps,
|
|
11
|
+
getTransitiveRdeps,
|
|
12
|
+
loadGraph,
|
|
13
|
+
loadMetrics,
|
|
14
|
+
loadPageRank
|
|
15
|
+
} from "./chunk-247GVVKK.js";
|
|
16
|
+
import {
|
|
17
|
+
getCodemapStatus,
|
|
18
|
+
loadManifest,
|
|
19
|
+
loadSymbolIndex,
|
|
20
|
+
runInitPipeline,
|
|
21
|
+
searchCode,
|
|
22
|
+
searchSymbols
|
|
23
|
+
} from "./chunk-DBMUNBNB.js";
|
|
24
|
+
import "./chunk-NNUWU6CV.js";
|
|
25
|
+
import "./chunk-YM2HV4IA.js";
|
|
26
|
+
import "./chunk-74WVVWJ4.js";
|
|
27
|
+
import "./chunk-KIKPIH6N.js";
|
|
28
|
+
import "./chunk-2HEE5OKX.js";
|
|
29
|
+
import "./chunk-KLEASXUR.js";
|
|
30
|
+
import {
|
|
31
|
+
getCurrentBranch
|
|
32
|
+
} from "./chunk-7LXY5UVC.js";
|
|
33
|
+
import "./chunk-4VNS5WPM.js";
|
|
34
|
+
|
|
35
|
+
// ../../packages/codemap-mcp/dist/index.js
|
|
36
|
+
var searchCodeTool = {
|
|
37
|
+
name: "search_code",
|
|
38
|
+
description: "Search code by natural language query. Returns relevant code chunks with file paths, line ranges, and relevance scores.",
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
query: { type: "string", description: "Natural language search query" },
|
|
43
|
+
limit: { type: "number", description: "Maximum results (default 10, max 50)" },
|
|
44
|
+
pathPrefix: { type: "string", description: "Filter results to files under this path" },
|
|
45
|
+
includeTests: { type: "boolean", description: "Include test files (default true)" },
|
|
46
|
+
includeDocs: { type: "boolean", description: "Include markdown/docs files (default true)" }
|
|
47
|
+
},
|
|
48
|
+
required: ["query"]
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
async function handleSearchCode(projectDir, args, branch) {
|
|
52
|
+
const query = args.query;
|
|
53
|
+
const limit = typeof args.limit === "number" ? Math.min(Math.max(1, args.limit), 50) : 10;
|
|
54
|
+
const pathPrefix = typeof args.pathPrefix === "string" ? args.pathPrefix : void 0;
|
|
55
|
+
const includeTests = typeof args.includeTests === "boolean" ? args.includeTests : true;
|
|
56
|
+
const includeDocs = typeof args.includeDocs === "boolean" ? args.includeDocs : true;
|
|
57
|
+
const result = await searchCode(projectDir, query, {
|
|
58
|
+
limit,
|
|
59
|
+
pathPrefix,
|
|
60
|
+
includeTests,
|
|
61
|
+
includeDocs,
|
|
62
|
+
branch
|
|
63
|
+
});
|
|
64
|
+
const payload = {
|
|
65
|
+
query: result.query,
|
|
66
|
+
durationMs: result.durationMs,
|
|
67
|
+
resultCount: result.results.length,
|
|
68
|
+
results: result.results.map((r) => ({
|
|
69
|
+
filePath: r.filePath,
|
|
70
|
+
startLine: r.startLine,
|
|
71
|
+
endLine: r.endLine,
|
|
72
|
+
score: Math.round(r.score * 1e3) / 1e3,
|
|
73
|
+
snippet: r.snippet
|
|
74
|
+
})),
|
|
75
|
+
summary: result.results.length > 0 ? `Found ${result.results.length} results for "${query}" in ${result.durationMs}ms` : `No results found for "${query}"`
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
var searchSymbolsTool = {
|
|
82
|
+
name: "search_symbols",
|
|
83
|
+
description: "Search for code symbols (functions, classes, interfaces, types) by name. Returns exact, prefix, and substring matches ranked by relevance.",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
query: { type: "string", description: "Symbol name to search for" },
|
|
88
|
+
symbolType: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Filter by symbol type",
|
|
91
|
+
enum: ["function", "class", "method", "interface", "type", "const", "variable", "enum"]
|
|
92
|
+
},
|
|
93
|
+
pathPrefix: { type: "string", description: "Filter results to files under this path" },
|
|
94
|
+
limit: { type: "number", description: "Maximum results (default 20)" }
|
|
95
|
+
},
|
|
96
|
+
required: ["query"]
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
async function handleSearchSymbols(projectDir, args, branch) {
|
|
100
|
+
const query = args.query;
|
|
101
|
+
const symbolType = args.symbolType;
|
|
102
|
+
const pathPrefix = typeof args.pathPrefix === "string" ? args.pathPrefix : void 0;
|
|
103
|
+
const limit = typeof args.limit === "number" ? Math.min(Math.max(1, args.limit), 100) : 20;
|
|
104
|
+
const results = searchSymbols(projectDir, query, {
|
|
105
|
+
symbolType,
|
|
106
|
+
pathPrefix,
|
|
107
|
+
limit
|
|
108
|
+
}, branch);
|
|
109
|
+
const payload = {
|
|
110
|
+
query,
|
|
111
|
+
resultCount: results.length,
|
|
112
|
+
results: results.map((r) => ({
|
|
113
|
+
name: r.name,
|
|
114
|
+
symbolType: r.symbolType,
|
|
115
|
+
filePath: r.filePath,
|
|
116
|
+
startLine: r.startLine,
|
|
117
|
+
endLine: r.endLine,
|
|
118
|
+
score: r.score
|
|
119
|
+
})),
|
|
120
|
+
summary: results.length > 0 ? `Found ${results.length} symbols matching "${query}"` : `No symbols found matching "${query}"`
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
var getFileSummaryTool = {
|
|
127
|
+
name: "get_file_summary",
|
|
128
|
+
description: "Get a summary of a specific file in the codemap index: chunks, symbols, and size information.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
filePath: { type: "string", description: "Repository-relative file path" }
|
|
133
|
+
},
|
|
134
|
+
required: ["filePath"]
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
async function handleGetFileSummary(projectDir, args, branch) {
|
|
138
|
+
const filePath = args.filePath;
|
|
139
|
+
const manifest = loadManifest(projectDir, branch);
|
|
140
|
+
if (!manifest) {
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Index not initialized", summary: "No codemap index found" }) }]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const fileEntry = manifest.files[filePath];
|
|
146
|
+
if (!fileEntry) {
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text", text: JSON.stringify({ error: `File not found in index: ${filePath}`, summary: `"${filePath}" is not in the codemap index` }) }]
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const symbols = loadSymbolIndex(projectDir, branch);
|
|
152
|
+
const fileSymbols = symbols.filter((s) => s.filePath === filePath);
|
|
153
|
+
const payload = {
|
|
154
|
+
filePath,
|
|
155
|
+
chunks: fileEntry.chunkIds.length,
|
|
156
|
+
sizeBytes: fileEntry.sizeBytes,
|
|
157
|
+
contentHash: fileEntry.contentHash,
|
|
158
|
+
symbols: fileSymbols.map((s) => ({
|
|
159
|
+
name: s.name,
|
|
160
|
+
type: s.symbolType,
|
|
161
|
+
line: s.startLine
|
|
162
|
+
})),
|
|
163
|
+
summary: `${filePath}: ${fileEntry.chunkIds.length} chunks, ${fileSymbols.length} symbols, ${(fileEntry.sizeBytes / 1024).toFixed(1)} KB`
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
var getIndexStatsTool = {
|
|
170
|
+
name: "get_index_stats",
|
|
171
|
+
description: "Get codemap index statistics: file count, chunk count, embedding provider, index size, and status.",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
async function handleGetIndexStats(projectDir, _args, branch) {
|
|
178
|
+
const status = getCodemapStatus(projectDir, branch);
|
|
179
|
+
const payload = {
|
|
180
|
+
...status,
|
|
181
|
+
summary: status.initialized ? `CodeMap index: ${status.totalFiles} files, ${status.totalChunks} chunks, ${status.embeddingProvider}/${status.embeddingModel}, mode: ${status.mode}` : "CodeMap index not initialized. Run 'ulpi codemap init' to create the index."
|
|
182
|
+
};
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
var reindexTool = {
|
|
188
|
+
name: "reindex",
|
|
189
|
+
description: "Re-index the entire codebase. Creates fresh embeddings for all files. This may take several minutes for large projects.",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
async function handleReindex(projectDir, _args, branch) {
|
|
196
|
+
const result = await runInitPipeline(projectDir, void 0, branch);
|
|
197
|
+
const payload = {
|
|
198
|
+
totalFiles: result.totalFiles,
|
|
199
|
+
totalChunks: result.totalChunks,
|
|
200
|
+
durationMs: result.durationMs,
|
|
201
|
+
embeddingProvider: result.embeddingProvider,
|
|
202
|
+
embeddingModel: result.embeddingModel,
|
|
203
|
+
summary: `Re-indexed ${result.totalFiles} files (${result.totalChunks} chunks) in ${(result.durationMs / 1e3).toFixed(1)}s using ${result.embeddingProvider}/${result.embeddingModel}`
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
var getDependenciesTool = {
|
|
210
|
+
name: "get_dependencies",
|
|
211
|
+
description: "Get files that a given file depends on (references their definitions). Shows direct or transitive dependencies with shared symbols.",
|
|
212
|
+
inputSchema: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
filePath: { type: "string", description: "Repository-relative file path to get dependencies for" },
|
|
216
|
+
maxDepth: { type: "number", description: "Maximum traversal depth for transitive dependencies (default 5, max 10)" },
|
|
217
|
+
includeTransitive: { type: "boolean", description: "Include transitive (indirect) dependencies (default false)" }
|
|
218
|
+
},
|
|
219
|
+
required: ["filePath"]
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
async function handleGetDependencies(projectDir, args, branch) {
|
|
223
|
+
const filePath = args.filePath;
|
|
224
|
+
const maxDepth = typeof args.maxDepth === "number" ? Math.min(Math.max(1, args.maxDepth), 10) : 5;
|
|
225
|
+
const includeTransitive = typeof args.includeTransitive === "boolean" ? args.includeTransitive : false;
|
|
226
|
+
const resolvedBranch = branch ?? getCurrentBranch(projectDir);
|
|
227
|
+
const graph = loadGraph(projectDir, resolvedBranch);
|
|
228
|
+
if (!graph) {
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
231
|
+
error: "Dependency graph not built yet. Run `ulpi codemap init` first."
|
|
232
|
+
}) }]
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (!graph.nodes[filePath]) {
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
238
|
+
error: `File not found in dependency graph: ${filePath}`,
|
|
239
|
+
summary: `"${filePath}" is not in the dependency graph`
|
|
240
|
+
}) }]
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (includeTransitive) {
|
|
244
|
+
const transitive = getTransitiveDeps(graph, filePath, maxDepth);
|
|
245
|
+
const payload2 = {
|
|
246
|
+
filePath,
|
|
247
|
+
mode: "transitive",
|
|
248
|
+
maxDepth,
|
|
249
|
+
dependencyCount: transitive.length,
|
|
250
|
+
dependencies: transitive.sort(),
|
|
251
|
+
summary: `${filePath} transitively depends on ${transitive.length} files (depth ${maxDepth})`
|
|
252
|
+
};
|
|
253
|
+
return {
|
|
254
|
+
content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }]
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const edges = getOutgoingEdges(graph, filePath);
|
|
258
|
+
const dependencies = edges.map((e) => ({
|
|
259
|
+
filePath: e.target,
|
|
260
|
+
symbols: e.symbols
|
|
261
|
+
}));
|
|
262
|
+
const payload = {
|
|
263
|
+
filePath,
|
|
264
|
+
mode: "direct",
|
|
265
|
+
dependencyCount: dependencies.length,
|
|
266
|
+
dependencies,
|
|
267
|
+
summary: `${filePath} directly depends on ${dependencies.length} files`
|
|
268
|
+
};
|
|
269
|
+
return {
|
|
270
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
var getDependentsTool = {
|
|
274
|
+
name: "get_dependents",
|
|
275
|
+
description: "Get files that depend on a given file (reference it). Shows direct or transitive reverse dependencies with shared symbols.",
|
|
276
|
+
inputSchema: {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: {
|
|
279
|
+
filePath: { type: "string", description: "Repository-relative file path to get dependents for" },
|
|
280
|
+
maxDepth: { type: "number", description: "Maximum traversal depth for transitive dependents (default 5, max 10)" },
|
|
281
|
+
includeTransitive: { type: "boolean", description: "Include transitive (indirect) dependents (default false)" }
|
|
282
|
+
},
|
|
283
|
+
required: ["filePath"]
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
async function handleGetDependents(projectDir, args, branch) {
|
|
287
|
+
const filePath = args.filePath;
|
|
288
|
+
const maxDepth = typeof args.maxDepth === "number" ? Math.min(Math.max(1, args.maxDepth), 10) : 5;
|
|
289
|
+
const includeTransitive = typeof args.includeTransitive === "boolean" ? args.includeTransitive : false;
|
|
290
|
+
const resolvedBranch = branch ?? getCurrentBranch(projectDir);
|
|
291
|
+
const graph = loadGraph(projectDir, resolvedBranch);
|
|
292
|
+
if (!graph) {
|
|
293
|
+
return {
|
|
294
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
295
|
+
error: "Dependency graph not built yet. Run `ulpi codemap init` first."
|
|
296
|
+
}) }]
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (!graph.nodes[filePath]) {
|
|
300
|
+
return {
|
|
301
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
302
|
+
error: `File not found in dependency graph: ${filePath}`,
|
|
303
|
+
summary: `"${filePath}" is not in the dependency graph`
|
|
304
|
+
}) }]
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (includeTransitive) {
|
|
308
|
+
const transitive = getTransitiveRdeps(graph, filePath, maxDepth);
|
|
309
|
+
const payload2 = {
|
|
310
|
+
filePath,
|
|
311
|
+
mode: "transitive",
|
|
312
|
+
maxDepth,
|
|
313
|
+
dependentCount: transitive.length,
|
|
314
|
+
dependents: transitive.sort(),
|
|
315
|
+
summary: `${transitive.length} files transitively depend on ${filePath} (depth ${maxDepth})`
|
|
316
|
+
};
|
|
317
|
+
return {
|
|
318
|
+
content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }]
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const edges = getIncomingEdges(graph, filePath);
|
|
322
|
+
const dependents = edges.map((e) => ({
|
|
323
|
+
filePath: e.source,
|
|
324
|
+
symbols: e.symbols
|
|
325
|
+
}));
|
|
326
|
+
const payload = {
|
|
327
|
+
filePath,
|
|
328
|
+
mode: "direct",
|
|
329
|
+
dependentCount: dependents.length,
|
|
330
|
+
dependents,
|
|
331
|
+
summary: `${dependents.length} files directly depend on ${filePath}`
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
var getFileRankTool = {
|
|
338
|
+
name: "get_file_rank",
|
|
339
|
+
description: "Get PageRank importance scores for files. Without filePath, returns top-N ranked files. With filePath, returns that file's rank and position.",
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: {
|
|
343
|
+
filePath: { type: "string", description: "Optional file path to get rank for. Omit to get top-N ranked files." },
|
|
344
|
+
limit: { type: "number", description: "Maximum number of top files to return (default 20, max 100)" }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
async function handleGetFileRank(projectDir, args, branch) {
|
|
349
|
+
const filePath = typeof args.filePath === "string" ? args.filePath : void 0;
|
|
350
|
+
const limit = typeof args.limit === "number" ? Math.min(Math.max(1, args.limit), 100) : 20;
|
|
351
|
+
const resolvedBranch = branch ?? getCurrentBranch(projectDir);
|
|
352
|
+
const pageRank = loadPageRank(projectDir, resolvedBranch);
|
|
353
|
+
if (!pageRank) {
|
|
354
|
+
return {
|
|
355
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
356
|
+
error: "Dependency graph not built yet. Run `ulpi codemap init` first."
|
|
357
|
+
}) }]
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const sorted = Object.entries(pageRank.ranks).map(([file, rank]) => ({ filePath: file, rank })).sort((a, b) => b.rank - a.rank);
|
|
361
|
+
if (filePath) {
|
|
362
|
+
const rank = pageRank.ranks[filePath];
|
|
363
|
+
if (rank === void 0) {
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
366
|
+
error: `File not found in PageRank data: ${filePath}`,
|
|
367
|
+
summary: `"${filePath}" is not in the PageRank index`
|
|
368
|
+
}) }]
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const position = sorted.findIndex((e) => e.filePath === filePath) + 1;
|
|
372
|
+
const payload2 = {
|
|
373
|
+
filePath,
|
|
374
|
+
rank: Math.round(rank * 1e6) / 1e6,
|
|
375
|
+
position,
|
|
376
|
+
totalFiles: sorted.length,
|
|
377
|
+
converged: pageRank.converged,
|
|
378
|
+
iterations: pageRank.iterations,
|
|
379
|
+
summary: `${filePath} ranked #${position} of ${sorted.length} files (rank: ${rank.toFixed(6)})`
|
|
380
|
+
};
|
|
381
|
+
return {
|
|
382
|
+
content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }]
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const topFiles = sorted.slice(0, limit).map((e, i) => ({
|
|
386
|
+
position: i + 1,
|
|
387
|
+
filePath: e.filePath,
|
|
388
|
+
rank: Math.round(e.rank * 1e6) / 1e6
|
|
389
|
+
}));
|
|
390
|
+
const payload = {
|
|
391
|
+
totalFiles: sorted.length,
|
|
392
|
+
showing: topFiles.length,
|
|
393
|
+
converged: pageRank.converged,
|
|
394
|
+
iterations: pageRank.iterations,
|
|
395
|
+
files: topFiles,
|
|
396
|
+
summary: `Top ${topFiles.length} of ${sorted.length} files by PageRank`
|
|
397
|
+
};
|
|
398
|
+
return {
|
|
399
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
var findCyclesTool = {
|
|
403
|
+
name: "find_cycles",
|
|
404
|
+
description: "Detect circular dependency cycles in the codebase. Returns groups of files that form dependency cycles.",
|
|
405
|
+
inputSchema: {
|
|
406
|
+
type: "object",
|
|
407
|
+
properties: {
|
|
408
|
+
limit: { type: "number", description: "Maximum number of cycles to return (default 20, max 100)" }
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
async function handleFindCycles(projectDir, args, branch) {
|
|
413
|
+
const limit = typeof args.limit === "number" ? Math.min(Math.max(1, args.limit), 100) : 20;
|
|
414
|
+
const resolvedBranch = branch ?? getCurrentBranch(projectDir);
|
|
415
|
+
const metrics = loadMetrics(projectDir, resolvedBranch);
|
|
416
|
+
if (!metrics) {
|
|
417
|
+
return {
|
|
418
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
419
|
+
error: "Dependency graph not built yet. Run `ulpi codemap init` first."
|
|
420
|
+
}) }]
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const allCycles = [...metrics.cycles].sort((a, b) => b.files.length - a.files.length);
|
|
424
|
+
const cycles = allCycles.slice(0, limit).map((c) => ({
|
|
425
|
+
files: c.files,
|
|
426
|
+
edgeCount: c.edgeCount,
|
|
427
|
+
fileCount: c.files.length
|
|
428
|
+
}));
|
|
429
|
+
const payload = {
|
|
430
|
+
totalCycles: allCycles.length,
|
|
431
|
+
showing: cycles.length,
|
|
432
|
+
cycles,
|
|
433
|
+
summary: allCycles.length > 0 ? `Found ${allCycles.length} dependency cycles (showing ${cycles.length})` : "No circular dependency cycles detected"
|
|
434
|
+
};
|
|
435
|
+
return {
|
|
436
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
var getCouplingMetricsTool = {
|
|
440
|
+
name: "get_coupling_metrics",
|
|
441
|
+
description: "Get module coupling metrics. Shows afferent coupling (Ca), efferent coupling (Ce), and instability index (I) per file. Filter by path prefix.",
|
|
442
|
+
inputSchema: {
|
|
443
|
+
type: "object",
|
|
444
|
+
properties: {
|
|
445
|
+
modulePath: { type: "string", description: "Path prefix to filter files (e.g. 'src/', 'packages/core/'). Omit to show all files." }
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
async function handleGetCouplingMetrics(projectDir, args, branch) {
|
|
450
|
+
const modulePath = typeof args.modulePath === "string" ? args.modulePath : void 0;
|
|
451
|
+
const resolvedBranch = branch ?? getCurrentBranch(projectDir);
|
|
452
|
+
const metrics = loadMetrics(projectDir, resolvedBranch);
|
|
453
|
+
if (!metrics) {
|
|
454
|
+
return {
|
|
455
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
456
|
+
error: "Dependency graph not built yet. Run `ulpi codemap init` first."
|
|
457
|
+
}) }]
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
const filtered = modulePath ? metrics.coupling.filter((c) => c.filePath.startsWith(modulePath)) : metrics.coupling;
|
|
461
|
+
const sorted = [...filtered].sort((a, b) => b.instability - a.instability);
|
|
462
|
+
const couplingData = sorted.map((c) => ({
|
|
463
|
+
filePath: c.filePath,
|
|
464
|
+
afferentCoupling: c.afferentCoupling,
|
|
465
|
+
efferentCoupling: c.efferentCoupling,
|
|
466
|
+
instability: Math.round(c.instability * 1e3) / 1e3
|
|
467
|
+
}));
|
|
468
|
+
const avgInstability = sorted.length > 0 ? sorted.reduce((sum, c) => sum + c.instability, 0) / sorted.length : 0;
|
|
469
|
+
const label = modulePath ? `under "${modulePath}"` : "in the project";
|
|
470
|
+
const payload = {
|
|
471
|
+
modulePath: modulePath ?? null,
|
|
472
|
+
fileCount: couplingData.length,
|
|
473
|
+
avgInstability: Math.round(avgInstability * 1e3) / 1e3,
|
|
474
|
+
metrics: couplingData,
|
|
475
|
+
summary: couplingData.length > 0 ? `${couplingData.length} files ${label} \u2014 avg instability: ${avgInstability.toFixed(3)}` : `No files found ${label} in the dependency graph`
|
|
476
|
+
};
|
|
477
|
+
return {
|
|
478
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
var getDepgraphStatsTool = {
|
|
482
|
+
name: "get_depgraph_stats",
|
|
483
|
+
description: "Get dependency graph statistics: file count, edge count, definition count, reference count, cycle count, and top-ranked files.",
|
|
484
|
+
inputSchema: {
|
|
485
|
+
type: "object",
|
|
486
|
+
properties: {}
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
async function handleGetDepgraphStats(projectDir, _args, branch) {
|
|
490
|
+
const resolvedBranch = branch ?? getCurrentBranch(projectDir);
|
|
491
|
+
const graph = loadGraph(projectDir, resolvedBranch);
|
|
492
|
+
if (!graph) {
|
|
493
|
+
return {
|
|
494
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
495
|
+
error: "Dependency graph not built yet. Run `ulpi codemap init` first."
|
|
496
|
+
}) }]
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const pageRank = loadPageRank(projectDir, resolvedBranch);
|
|
500
|
+
const metrics = loadMetrics(projectDir, resolvedBranch);
|
|
501
|
+
const fileCount = Object.keys(graph.nodes).length;
|
|
502
|
+
const edgeCount = graph.edges.length;
|
|
503
|
+
let totalDefinitions = 0;
|
|
504
|
+
let totalReferences = 0;
|
|
505
|
+
for (const node of Object.values(graph.nodes)) {
|
|
506
|
+
totalDefinitions += node.definitionCount;
|
|
507
|
+
totalReferences += node.referenceCount;
|
|
508
|
+
}
|
|
509
|
+
const topFiles = pageRank ? Object.entries(pageRank.ranks).sort(([, a], [, b]) => b - a).slice(0, 5).map(([file, rank], i) => ({
|
|
510
|
+
position: i + 1,
|
|
511
|
+
filePath: file,
|
|
512
|
+
rank: Math.round(rank * 1e6) / 1e6
|
|
513
|
+
})) : [];
|
|
514
|
+
const payload = {
|
|
515
|
+
generatedAt: graph.generatedAt,
|
|
516
|
+
fileCount,
|
|
517
|
+
edgeCount,
|
|
518
|
+
totalDefinitions,
|
|
519
|
+
totalReferences,
|
|
520
|
+
cycleCount: metrics?.cycles.length ?? null,
|
|
521
|
+
avgFanIn: metrics?.avgFanIn != null ? Math.round(metrics.avgFanIn * 100) / 100 : null,
|
|
522
|
+
avgFanOut: metrics?.avgFanOut != null ? Math.round(metrics.avgFanOut * 100) / 100 : null,
|
|
523
|
+
maxFanIn: metrics?.maxFanIn ?? null,
|
|
524
|
+
maxFanOut: metrics?.maxFanOut ?? null,
|
|
525
|
+
pageRankConverged: pageRank?.converged ?? null,
|
|
526
|
+
pageRankIterations: pageRank?.iterations ?? null,
|
|
527
|
+
topFiles,
|
|
528
|
+
summary: `Dependency graph: ${fileCount} files, ${edgeCount} edges, ${totalDefinitions} definitions, ${totalReferences} references` + (metrics ? `, ${metrics.cycles.length} cycles` : "")
|
|
529
|
+
};
|
|
530
|
+
return {
|
|
531
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
var ALL_TOOLS = [
|
|
535
|
+
searchCodeTool,
|
|
536
|
+
searchSymbolsTool,
|
|
537
|
+
getFileSummaryTool,
|
|
538
|
+
getIndexStatsTool,
|
|
539
|
+
reindexTool,
|
|
540
|
+
getDependenciesTool,
|
|
541
|
+
getDependentsTool,
|
|
542
|
+
getFileRankTool,
|
|
543
|
+
findCyclesTool,
|
|
544
|
+
getCouplingMetricsTool,
|
|
545
|
+
getDepgraphStatsTool
|
|
546
|
+
];
|
|
547
|
+
var HANDLERS = {
|
|
548
|
+
search_code: handleSearchCode,
|
|
549
|
+
search_symbols: handleSearchSymbols,
|
|
550
|
+
get_file_summary: handleGetFileSummary,
|
|
551
|
+
get_index_stats: handleGetIndexStats,
|
|
552
|
+
reindex: handleReindex,
|
|
553
|
+
get_dependencies: handleGetDependencies,
|
|
554
|
+
get_dependents: handleGetDependents,
|
|
555
|
+
get_file_rank: handleGetFileRank,
|
|
556
|
+
find_cycles: handleFindCycles,
|
|
557
|
+
get_coupling_metrics: handleGetCouplingMetrics,
|
|
558
|
+
get_depgraph_stats: handleGetDepgraphStats
|
|
559
|
+
};
|
|
560
|
+
function createMcpServer(options) {
|
|
561
|
+
const server = new Server(
|
|
562
|
+
{ name: "codemap", version: "0.1.0" },
|
|
563
|
+
{ capabilities: { tools: {} } }
|
|
564
|
+
);
|
|
565
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
566
|
+
tools: ALL_TOOLS
|
|
567
|
+
}));
|
|
568
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
569
|
+
const { name, arguments: args } = request.params;
|
|
570
|
+
const handler = HANDLERS[name];
|
|
571
|
+
if (!handler) {
|
|
572
|
+
return {
|
|
573
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
|
|
574
|
+
isError: true
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
return await handler(options.projectDir, args ?? {}, options.branch);
|
|
579
|
+
} catch (err) {
|
|
580
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
581
|
+
return {
|
|
582
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
583
|
+
isError: true
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
return server;
|
|
588
|
+
}
|
|
589
|
+
async function startMcpServer(options) {
|
|
590
|
+
const server = createMcpServer(options);
|
|
591
|
+
const transport = new StdioServerTransport();
|
|
592
|
+
await server.connect(transport);
|
|
593
|
+
}
|
|
594
|
+
export {
|
|
595
|
+
createMcpServer,
|
|
596
|
+
startMcpServer
|
|
597
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseRules
|
|
3
|
+
} from "./chunk-SIAQVRKG.js";
|
|
4
|
+
import "./chunk-KIKPIH6N.js";
|
|
5
|
+
import "./chunk-4VNS5WPM.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/export-import.ts
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
async function runExport(args, projectDir) {
|
|
12
|
+
const outputPath = args[0] ?? "ulpi-rules-export.yml";
|
|
13
|
+
const rulesPath = path.join(projectDir, ".ulpi", "guards.yml");
|
|
14
|
+
if (!fs.existsSync(rulesPath)) {
|
|
15
|
+
console.log(chalk.red("No guards.yml found. Run 'ulpi init' first."));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const content = fs.readFileSync(rulesPath, "utf-8");
|
|
19
|
+
fs.writeFileSync(outputPath, content, "utf-8");
|
|
20
|
+
console.log(chalk.green(`\u2713 Rules exported to ${outputPath}`));
|
|
21
|
+
}
|
|
22
|
+
async function runImport(args, projectDir) {
|
|
23
|
+
const inputPath = args[0];
|
|
24
|
+
if (!inputPath) {
|
|
25
|
+
console.log(chalk.red("Usage: ulpi import <file-path>"));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!fs.existsSync(inputPath)) {
|
|
29
|
+
console.log(chalk.red(`File not found: ${inputPath}`));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const rulesDir = path.join(projectDir, ".ulpi");
|
|
33
|
+
const rulesPath = path.join(rulesDir, "guards.yml");
|
|
34
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
35
|
+
const content = fs.readFileSync(inputPath, "utf-8");
|
|
36
|
+
try {
|
|
37
|
+
const parsed = parseRules(content);
|
|
38
|
+
if (!parsed || !parsed.project) {
|
|
39
|
+
console.log(chalk.red("Error: Invalid rules file \u2014 missing 'project' section."));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44
|
+
console.log(chalk.red(`Error: Invalid rules file \u2014 ${message}`));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
fs.writeFileSync(rulesPath, content, "utf-8");
|
|
48
|
+
console.log(chalk.green(`\u2713 Rules imported from ${inputPath}`));
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
runExport,
|
|
52
|
+
runImport
|
|
53
|
+
};
|