codebase-context 1.0.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 +74 -0
- package/dist/analyzers/angular/index.d.ts +44 -0
- package/dist/analyzers/angular/index.d.ts.map +1 -0
- package/dist/analyzers/angular/index.js +922 -0
- package/dist/analyzers/angular/index.js.map +1 -0
- package/dist/analyzers/generic/index.d.ts +23 -0
- package/dist/analyzers/generic/index.d.ts.map +1 -0
- package/dist/analyzers/generic/index.js +354 -0
- package/dist/analyzers/generic/index.js.map +1 -0
- package/dist/core/analyzer-registry.d.ts +36 -0
- package/dist/core/analyzer-registry.d.ts.map +1 -0
- package/dist/core/analyzer-registry.js +78 -0
- package/dist/core/analyzer-registry.js.map +1 -0
- package/dist/core/file-watcher.d.ts +63 -0
- package/dist/core/file-watcher.d.ts.map +1 -0
- package/dist/core/file-watcher.js +210 -0
- package/dist/core/file-watcher.js.map +1 -0
- package/dist/core/indexer.d.ts +29 -0
- package/dist/core/indexer.d.ts.map +1 -0
- package/dist/core/indexer.js +507 -0
- package/dist/core/indexer.js.map +1 -0
- package/dist/core/search.d.ts +31 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +307 -0
- package/dist/core/search.js.map +1 -0
- package/dist/embeddings/index.d.ts +5 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +33 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/openai.d.ts +19 -0
- package/dist/embeddings/openai.d.ts.map +1 -0
- package/dist/embeddings/openai.js +59 -0
- package/dist/embeddings/openai.js.map +1 -0
- package/dist/embeddings/transformers.d.ts +17 -0
- package/dist/embeddings/transformers.d.ts.map +1 -0
- package/dist/embeddings/transformers.js +83 -0
- package/dist/embeddings/transformers.js.map +1 -0
- package/dist/embeddings/types.d.ts +20 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +9 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +790 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +58 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +81 -0
- package/dist/lib.js.map +1 -0
- package/dist/storage/index.d.ts +12 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +18 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/lancedb.d.ts +24 -0
- package/dist/storage/lancedb.d.ts.map +1 -0
- package/dist/storage/lancedb.js +197 -0
- package/dist/storage/lancedb.js.map +1 -0
- package/dist/storage/types.d.ts +45 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +8 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/types/index.d.ts +367 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/chunking.d.ts +23 -0
- package/dist/utils/chunking.d.ts.map +1 -0
- package/dist/utils/chunking.js +226 -0
- package/dist/utils/chunking.js.map +1 -0
- package/dist/utils/language-detection.d.ts +29 -0
- package/dist/utils/language-detection.d.ts.map +1 -0
- package/dist/utils/language-detection.js +127 -0
- package/dist/utils/language-detection.js.map +1 -0
- package/dist/utils/pattern-detector.d.ts +41 -0
- package/dist/utils/pattern-detector.d.ts.map +1 -0
- package/dist/utils/pattern-detector.js +101 -0
- package/dist/utils/pattern-detector.js.map +1 -0
- package/dist/utils/usage-tracker.d.ts +120 -0
- package/dist/utils/usage-tracker.d.ts.map +1 -0
- package/dist/utils/usage-tracker.js +336 -0
- package/dist/utils/usage-tracker.js.map +1 -0
- package/package.json +98 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server for Codebase Context
|
|
4
|
+
* Provides codebase indexing and semantic search capabilities
|
|
5
|
+
*/
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { glob } from "glob";
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { CodebaseIndexer } from "./core/indexer.js";
|
|
13
|
+
import { CodebaseSearcher } from "./core/search.js";
|
|
14
|
+
import { analyzerRegistry } from "./core/analyzer-registry.js";
|
|
15
|
+
import { AngularAnalyzer } from "./analyzers/angular/index.js";
|
|
16
|
+
import { GenericAnalyzer } from "./analyzers/generic/index.js";
|
|
17
|
+
analyzerRegistry.register(new AngularAnalyzer());
|
|
18
|
+
analyzerRegistry.register(new GenericAnalyzer());
|
|
19
|
+
// Resolve root path with validation
|
|
20
|
+
function resolveRootPath() {
|
|
21
|
+
const arg = process.argv[2];
|
|
22
|
+
const envPath = process.env.CODEBASE_ROOT;
|
|
23
|
+
// Priority: CLI arg > env var > cwd
|
|
24
|
+
let rootPath = arg || envPath || process.cwd();
|
|
25
|
+
rootPath = path.resolve(rootPath);
|
|
26
|
+
// Warn if using cwd as fallback
|
|
27
|
+
if (!arg && !envPath) {
|
|
28
|
+
console.error(`WARNING: No project path specified. Using current directory: ${rootPath}`);
|
|
29
|
+
console.error(`Hint: Specify path as CLI argument or set CODEBASE_ROOT env var`);
|
|
30
|
+
}
|
|
31
|
+
return rootPath;
|
|
32
|
+
}
|
|
33
|
+
const ROOT_PATH = resolveRootPath();
|
|
34
|
+
const indexState = {
|
|
35
|
+
status: "idle",
|
|
36
|
+
};
|
|
37
|
+
const server = new Server({
|
|
38
|
+
name: "codebase-context-mcp",
|
|
39
|
+
version: "1.0.0",
|
|
40
|
+
}, {
|
|
41
|
+
capabilities: {
|
|
42
|
+
tools: {},
|
|
43
|
+
resources: {},
|
|
44
|
+
logging: {}, // Enable structured logging for clients that support it
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const TOOLS = [
|
|
48
|
+
{
|
|
49
|
+
name: "search_codebase",
|
|
50
|
+
description: "Search the indexed codebase using natural language queries. Returns code summaries with file locations. " +
|
|
51
|
+
"Supports framework-specific queries and architectural layer filtering. " +
|
|
52
|
+
"Use the returned filePath with other tools to read complete file contents.",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
query: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "Natural language search query",
|
|
59
|
+
},
|
|
60
|
+
limit: {
|
|
61
|
+
type: "number",
|
|
62
|
+
description: "Maximum number of results to return (default: 5)",
|
|
63
|
+
default: 5,
|
|
64
|
+
},
|
|
65
|
+
filters: {
|
|
66
|
+
type: "object",
|
|
67
|
+
description: "Optional filters",
|
|
68
|
+
properties: {
|
|
69
|
+
framework: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Filter by framework (angular, react, vue)",
|
|
72
|
+
},
|
|
73
|
+
language: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Filter by programming language",
|
|
76
|
+
},
|
|
77
|
+
componentType: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Filter by component type (component, service, directive, etc.)",
|
|
80
|
+
},
|
|
81
|
+
layer: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Filter by architectural layer (presentation, business, data, state, core, shared)",
|
|
84
|
+
},
|
|
85
|
+
tags: {
|
|
86
|
+
type: "array",
|
|
87
|
+
items: { type: "string" },
|
|
88
|
+
description: "Filter by tags",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ["query"],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "get_codebase_metadata",
|
|
98
|
+
description: "Get codebase metadata including framework information, dependencies, architecture patterns, " +
|
|
99
|
+
"and project statistics.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "get_indexing_status",
|
|
107
|
+
description: "Get current indexing status: state, statistics, and progress. " +
|
|
108
|
+
"Use refresh_index to manually trigger re-indexing when needed.",
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "refresh_index",
|
|
116
|
+
description: "Re-index the codebase. Supports full re-index or incremental mode. " +
|
|
117
|
+
"Use incrementalOnly=true to only process files changed since last index.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
reason: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "Reason for refreshing the index (for logging)",
|
|
124
|
+
},
|
|
125
|
+
incrementalOnly: {
|
|
126
|
+
type: "boolean",
|
|
127
|
+
description: "If true, only re-index files changed since last full index (faster). Default: false (full re-index)",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "get_style_guide",
|
|
134
|
+
description: "Query style guide rules and architectural patterns from project documentation.",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
query: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: 'Query for specific style guide rules (e.g., "component naming", "service patterns")',
|
|
141
|
+
},
|
|
142
|
+
category: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: "Filter by category (naming, structure, patterns, testing)",
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
required: ["query"],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "get_team_patterns",
|
|
152
|
+
description: "Get actionable team pattern recommendations based on codebase analysis. " +
|
|
153
|
+
"Returns consensus patterns for DI, state management, testing, library wrappers, etc.",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
category: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "Pattern category to retrieve",
|
|
160
|
+
enum: ["all", "di", "state", "testing", "libraries"],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "get_component_usage",
|
|
167
|
+
description: "Find WHERE a library or component is used in the codebase. " +
|
|
168
|
+
"This is 'Find Usages' - returns all files that import a given package/module. " +
|
|
169
|
+
"Example: get_component_usage('@mycompany/utils') → shows all 34 files using it.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
name: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Import source to find usages for (e.g., 'primeng/table', '@mycompany/ui/button', 'lodash')",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
required: ["name"],
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
183
|
+
return { tools: TOOLS };
|
|
184
|
+
});
|
|
185
|
+
// MCP Resources - Proactive context injection
|
|
186
|
+
const RESOURCES = [
|
|
187
|
+
{
|
|
188
|
+
uri: "codebase://context",
|
|
189
|
+
name: "Codebase Intelligence",
|
|
190
|
+
description: "Automatic codebase context: libraries used, team patterns, and conventions. " +
|
|
191
|
+
"Read this BEFORE generating code to follow team standards.",
|
|
192
|
+
mimeType: "text/plain",
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
196
|
+
return { resources: RESOURCES };
|
|
197
|
+
});
|
|
198
|
+
async function generateCodebaseContext() {
|
|
199
|
+
const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json");
|
|
200
|
+
try {
|
|
201
|
+
const content = await fs.readFile(intelligencePath, "utf-8");
|
|
202
|
+
const intelligence = JSON.parse(content);
|
|
203
|
+
const lines = [];
|
|
204
|
+
lines.push("# Codebase Intelligence");
|
|
205
|
+
lines.push("");
|
|
206
|
+
lines.push("⚠️ CRITICAL: This is what YOUR codebase actually uses, not generic recommendations.");
|
|
207
|
+
lines.push("These are FACTS from analyzing your code, not best practices from the internet.");
|
|
208
|
+
lines.push("");
|
|
209
|
+
// Library usage - sorted by count
|
|
210
|
+
const libraryEntries = Object.entries(intelligence.libraryUsage || {})
|
|
211
|
+
.map(([lib, data]) => ({
|
|
212
|
+
lib,
|
|
213
|
+
count: data.count,
|
|
214
|
+
}))
|
|
215
|
+
.sort((a, b) => b.count - a.count);
|
|
216
|
+
if (libraryEntries.length > 0) {
|
|
217
|
+
lines.push("## Libraries Actually Used (Top 15)");
|
|
218
|
+
lines.push("");
|
|
219
|
+
for (const { lib, count } of libraryEntries.slice(0, 15)) {
|
|
220
|
+
lines.push(`- **${lib}** (${count} uses)`);
|
|
221
|
+
}
|
|
222
|
+
lines.push("");
|
|
223
|
+
}
|
|
224
|
+
// Show tsconfig paths if available (helps AI understand internal imports)
|
|
225
|
+
if (intelligence.tsconfigPaths && Object.keys(intelligence.tsconfigPaths).length > 0) {
|
|
226
|
+
lines.push("## Import Aliases (from tsconfig.json)");
|
|
227
|
+
lines.push("");
|
|
228
|
+
lines.push("These path aliases map to internal project code:");
|
|
229
|
+
for (const [alias, paths] of Object.entries(intelligence.tsconfigPaths)) {
|
|
230
|
+
lines.push(`- \`${alias}\` → ${paths.join(", ")}`);
|
|
231
|
+
}
|
|
232
|
+
lines.push("");
|
|
233
|
+
}
|
|
234
|
+
// Pattern consensus
|
|
235
|
+
if (intelligence.patterns && Object.keys(intelligence.patterns).length > 0) {
|
|
236
|
+
lines.push("## YOUR Codebase's Actual Patterns (Not Generic Best Practices)");
|
|
237
|
+
lines.push("");
|
|
238
|
+
lines.push("These patterns were detected by analyzing your actual code.");
|
|
239
|
+
lines.push("This is what YOUR team does in practice, not what tutorials recommend.");
|
|
240
|
+
lines.push("");
|
|
241
|
+
for (const [category, data] of Object.entries(intelligence.patterns)) {
|
|
242
|
+
const patternData = data;
|
|
243
|
+
const primary = patternData.primary;
|
|
244
|
+
if (!primary)
|
|
245
|
+
continue;
|
|
246
|
+
const percentage = parseInt(primary.frequency);
|
|
247
|
+
const categoryName = category
|
|
248
|
+
.replace(/([A-Z])/g, " $1")
|
|
249
|
+
.trim()
|
|
250
|
+
.replace(/^./, (str) => str.toUpperCase());
|
|
251
|
+
if (percentage === 100) {
|
|
252
|
+
lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - unanimous)`);
|
|
253
|
+
lines.push(` → Your codebase is 100% consistent - ALWAYS use ${primary.name}`);
|
|
254
|
+
}
|
|
255
|
+
else if (percentage >= 80) {
|
|
256
|
+
lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - strong consensus)`);
|
|
257
|
+
lines.push(` → Your team strongly prefers ${primary.name}`);
|
|
258
|
+
if (patternData.alsoDetected?.length) {
|
|
259
|
+
const alt = patternData.alsoDetected[0];
|
|
260
|
+
lines.push(` → Minority pattern: ${alt.name} (${alt.frequency}) - avoid for new code`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (percentage >= 60) {
|
|
264
|
+
lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - majority)`);
|
|
265
|
+
lines.push(` → Most code uses ${primary.name}, but not unanimous`);
|
|
266
|
+
if (patternData.alsoDetected?.length) {
|
|
267
|
+
lines.push(` → Also detected: ${patternData.alsoDetected[0].name} (${patternData.alsoDetected[0].frequency})`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// Split decision
|
|
272
|
+
lines.push(`### ${categoryName}: ⚠️ NO TEAM CONSENSUS`);
|
|
273
|
+
lines.push(` Your codebase is split between multiple approaches:`);
|
|
274
|
+
lines.push(` - ${primary.name} (${primary.frequency})`);
|
|
275
|
+
if (patternData.alsoDetected?.length) {
|
|
276
|
+
for (const alt of patternData.alsoDetected.slice(0, 2)) {
|
|
277
|
+
lines.push(` - ${alt.name} (${alt.frequency})`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
lines.push(` → ASK the team which approach to use for new features`);
|
|
281
|
+
}
|
|
282
|
+
lines.push("");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
lines.push("---");
|
|
286
|
+
lines.push(`Generated: ${intelligence.generatedAt || new Date().toISOString()}`);
|
|
287
|
+
return lines.join("\n");
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
return ("# Codebase Intelligence\n\n" +
|
|
291
|
+
"Intelligence data not yet generated. Run indexing first.\n" +
|
|
292
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
296
|
+
const uri = request.params.uri;
|
|
297
|
+
if (uri === "codebase://context") {
|
|
298
|
+
const content = await generateCodebaseContext();
|
|
299
|
+
return {
|
|
300
|
+
contents: [
|
|
301
|
+
{
|
|
302
|
+
uri,
|
|
303
|
+
mimeType: "text/plain",
|
|
304
|
+
text: content,
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
310
|
+
});
|
|
311
|
+
async function performIndexing() {
|
|
312
|
+
indexState.status = "indexing";
|
|
313
|
+
console.error(`Indexing: ${ROOT_PATH}`);
|
|
314
|
+
try {
|
|
315
|
+
let lastLoggedProgress = { phase: "", percentage: -1 };
|
|
316
|
+
const indexer = new CodebaseIndexer({
|
|
317
|
+
rootPath: ROOT_PATH,
|
|
318
|
+
onProgress: (progress) => {
|
|
319
|
+
// Only log when phase or percentage actually changes (prevents duplicate logs)
|
|
320
|
+
const shouldLog = progress.phase !== lastLoggedProgress.phase ||
|
|
321
|
+
(progress.percentage % 10 === 0 && progress.percentage !== lastLoggedProgress.percentage);
|
|
322
|
+
if (shouldLog) {
|
|
323
|
+
console.error(`[${progress.phase}] ${progress.percentage}%`);
|
|
324
|
+
lastLoggedProgress = { phase: progress.phase, percentage: progress.percentage };
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
indexState.indexer = indexer;
|
|
329
|
+
const stats = await indexer.index();
|
|
330
|
+
indexState.status = "ready";
|
|
331
|
+
indexState.lastIndexed = new Date();
|
|
332
|
+
indexState.stats = stats;
|
|
333
|
+
console.error(`Complete: ${stats.indexedFiles} files, ${stats.totalChunks} chunks in ${(stats.duration / 1000).toFixed(2)}s`);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
indexState.status = "error";
|
|
337
|
+
indexState.error = error instanceof Error ? error.message : String(error);
|
|
338
|
+
console.error("Indexing failed:", indexState.error);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function shouldReindex() {
|
|
342
|
+
const indexPath = path.join(ROOT_PATH, ".codebase-index.json");
|
|
343
|
+
try {
|
|
344
|
+
await fs.access(indexPath);
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
352
|
+
const { name, arguments: args } = request.params;
|
|
353
|
+
try {
|
|
354
|
+
switch (name) {
|
|
355
|
+
case "search_codebase": {
|
|
356
|
+
const { query, limit, filters } = args;
|
|
357
|
+
if (indexState.status === "indexing") {
|
|
358
|
+
return {
|
|
359
|
+
content: [
|
|
360
|
+
{
|
|
361
|
+
type: "text",
|
|
362
|
+
text: JSON.stringify({
|
|
363
|
+
status: "indexing",
|
|
364
|
+
message: "Index is still being built. Retry in a moment.",
|
|
365
|
+
progress: indexState.indexer?.getProgress(),
|
|
366
|
+
}, null, 2),
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (indexState.status === "error") {
|
|
372
|
+
return {
|
|
373
|
+
content: [
|
|
374
|
+
{
|
|
375
|
+
type: "text",
|
|
376
|
+
text: JSON.stringify({
|
|
377
|
+
status: "error",
|
|
378
|
+
message: `Indexing failed: ${indexState.error}`,
|
|
379
|
+
}, null, 2),
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const searcher = new CodebaseSearcher(ROOT_PATH);
|
|
385
|
+
const results = await searcher.search(query, limit || 5, filters);
|
|
386
|
+
return {
|
|
387
|
+
content: [
|
|
388
|
+
{
|
|
389
|
+
type: "text",
|
|
390
|
+
text: JSON.stringify({
|
|
391
|
+
status: "success",
|
|
392
|
+
results: results.map((r) => ({
|
|
393
|
+
summary: r.summary,
|
|
394
|
+
snippet: r.snippet,
|
|
395
|
+
filePath: `${r.filePath}:${r.startLine}-${r.endLine}`,
|
|
396
|
+
score: r.score,
|
|
397
|
+
relevanceReason: r.relevanceReason,
|
|
398
|
+
componentType: r.componentType,
|
|
399
|
+
layer: r.layer,
|
|
400
|
+
framework: r.framework,
|
|
401
|
+
})),
|
|
402
|
+
totalResults: results.length,
|
|
403
|
+
}, null, 2),
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
case "get_indexing_status": {
|
|
409
|
+
const progress = indexState.indexer?.getProgress();
|
|
410
|
+
return {
|
|
411
|
+
content: [
|
|
412
|
+
{
|
|
413
|
+
type: "text",
|
|
414
|
+
text: JSON.stringify({
|
|
415
|
+
status: indexState.status,
|
|
416
|
+
rootPath: ROOT_PATH,
|
|
417
|
+
lastIndexed: indexState.lastIndexed?.toISOString(),
|
|
418
|
+
stats: indexState.stats
|
|
419
|
+
? {
|
|
420
|
+
totalFiles: indexState.stats.totalFiles,
|
|
421
|
+
indexedFiles: indexState.stats.indexedFiles,
|
|
422
|
+
totalChunks: indexState.stats.totalChunks,
|
|
423
|
+
duration: `${(indexState.stats.duration / 1000).toFixed(2)}s`,
|
|
424
|
+
}
|
|
425
|
+
: undefined,
|
|
426
|
+
progress: progress
|
|
427
|
+
? {
|
|
428
|
+
phase: progress.phase,
|
|
429
|
+
percentage: progress.percentage,
|
|
430
|
+
filesProcessed: progress.filesProcessed,
|
|
431
|
+
totalFiles: progress.totalFiles,
|
|
432
|
+
}
|
|
433
|
+
: undefined,
|
|
434
|
+
error: indexState.error,
|
|
435
|
+
hint: "Use refresh_index to manually trigger re-indexing when needed.",
|
|
436
|
+
}, null, 2),
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
case "refresh_index": {
|
|
442
|
+
const { reason, incrementalOnly } = args;
|
|
443
|
+
const mode = incrementalOnly ? "incremental" : "full";
|
|
444
|
+
console.error(`Refresh requested (${mode}): ${reason || "Manual trigger"}`);
|
|
445
|
+
// TODO: When incremental indexing is implemented (Phase 2),
|
|
446
|
+
// use `incrementalOnly` to only re-index changed files.
|
|
447
|
+
// For now, always do full re-index but acknowledge the intention.
|
|
448
|
+
performIndexing();
|
|
449
|
+
return {
|
|
450
|
+
content: [
|
|
451
|
+
{
|
|
452
|
+
type: "text",
|
|
453
|
+
text: JSON.stringify({
|
|
454
|
+
status: "started",
|
|
455
|
+
mode,
|
|
456
|
+
message: incrementalOnly
|
|
457
|
+
? "Incremental re-indexing requested. Check status with get_indexing_status."
|
|
458
|
+
: "Full re-indexing started. Check status with get_indexing_status.",
|
|
459
|
+
reason,
|
|
460
|
+
note: incrementalOnly
|
|
461
|
+
? "Incremental mode requested. Full re-index for now; true incremental indexing coming in Phase 2."
|
|
462
|
+
: undefined,
|
|
463
|
+
}, null, 2),
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
case "get_codebase_metadata": {
|
|
469
|
+
const indexer = new CodebaseIndexer({ rootPath: ROOT_PATH });
|
|
470
|
+
const metadata = await indexer.detectMetadata();
|
|
471
|
+
// Load team patterns from intelligence file
|
|
472
|
+
let teamPatterns = {};
|
|
473
|
+
try {
|
|
474
|
+
const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json");
|
|
475
|
+
const intelligenceContent = await fs.readFile(intelligencePath, "utf-8");
|
|
476
|
+
const intelligence = JSON.parse(intelligenceContent);
|
|
477
|
+
if (intelligence.patterns) {
|
|
478
|
+
teamPatterns = {
|
|
479
|
+
dependencyInjection: intelligence.patterns.dependencyInjection,
|
|
480
|
+
stateManagement: intelligence.patterns.stateManagement,
|
|
481
|
+
componentInputs: intelligence.patterns.componentInputs,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
// No intelligence file or parsing error
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
content: [
|
|
490
|
+
{
|
|
491
|
+
type: "text",
|
|
492
|
+
text: JSON.stringify({
|
|
493
|
+
status: "success",
|
|
494
|
+
metadata: {
|
|
495
|
+
name: metadata.name,
|
|
496
|
+
framework: metadata.framework,
|
|
497
|
+
languages: metadata.languages,
|
|
498
|
+
dependencies: metadata.dependencies.slice(0, 20),
|
|
499
|
+
architecture: metadata.architecture,
|
|
500
|
+
projectStructure: metadata.projectStructure,
|
|
501
|
+
statistics: metadata.statistics,
|
|
502
|
+
teamPatterns,
|
|
503
|
+
},
|
|
504
|
+
}, null, 2),
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
case "get_style_guide": {
|
|
510
|
+
const { query, category } = args;
|
|
511
|
+
const styleGuidePatterns = [
|
|
512
|
+
"STYLE_GUIDE.md",
|
|
513
|
+
"CODING_STYLE.md",
|
|
514
|
+
"ARCHITECTURE.md",
|
|
515
|
+
"CONTRIBUTING.md",
|
|
516
|
+
"docs/style-guide.md",
|
|
517
|
+
"docs/coding-style.md",
|
|
518
|
+
"docs/ARCHITECTURE.md",
|
|
519
|
+
];
|
|
520
|
+
const foundGuides = [];
|
|
521
|
+
const queryLower = query.toLowerCase();
|
|
522
|
+
const queryTerms = queryLower.split(/\s+/);
|
|
523
|
+
for (const pattern of styleGuidePatterns) {
|
|
524
|
+
try {
|
|
525
|
+
const files = await glob(pattern, {
|
|
526
|
+
cwd: ROOT_PATH,
|
|
527
|
+
absolute: true,
|
|
528
|
+
});
|
|
529
|
+
for (const file of files) {
|
|
530
|
+
try {
|
|
531
|
+
// Normalize line endings to \n for consistent output
|
|
532
|
+
const rawContent = await fs.readFile(file, "utf-8");
|
|
533
|
+
const content = rawContent.replace(/\r\n/g, "\n");
|
|
534
|
+
const relativePath = path.relative(ROOT_PATH, file);
|
|
535
|
+
// Find relevant sections based on query
|
|
536
|
+
const sections = content.split(/^##\s+/m);
|
|
537
|
+
const relevantSections = [];
|
|
538
|
+
for (const section of sections) {
|
|
539
|
+
const sectionLower = section.toLowerCase();
|
|
540
|
+
const isRelevant = queryTerms.some((term) => sectionLower.includes(term));
|
|
541
|
+
if (isRelevant) {
|
|
542
|
+
// Limit section size to ~500 words
|
|
543
|
+
const words = section.split(/\s+/);
|
|
544
|
+
const truncated = words.slice(0, 500).join(" ");
|
|
545
|
+
relevantSections.push("## " +
|
|
546
|
+
(words.length > 500
|
|
547
|
+
? truncated + "..."
|
|
548
|
+
: section.trim()));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (relevantSections.length > 0) {
|
|
552
|
+
foundGuides.push({
|
|
553
|
+
file: relativePath,
|
|
554
|
+
content: content.slice(0, 200) + "...",
|
|
555
|
+
relevantSections: relevantSections.slice(0, 3), // Max 3 sections per file
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
// Skip unreadable files
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch (e) {
|
|
565
|
+
// Pattern didn't match, continue
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (foundGuides.length === 0) {
|
|
569
|
+
return {
|
|
570
|
+
content: [
|
|
571
|
+
{
|
|
572
|
+
type: "text",
|
|
573
|
+
text: JSON.stringify({
|
|
574
|
+
status: "no_results",
|
|
575
|
+
message: `No style guide content found matching: ${query}`,
|
|
576
|
+
searchedPatterns: styleGuidePatterns,
|
|
577
|
+
hint: "Try broader terms like 'naming', 'patterns', 'testing', 'components'",
|
|
578
|
+
}, null, 2),
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
content: [
|
|
585
|
+
{
|
|
586
|
+
type: "text",
|
|
587
|
+
text: JSON.stringify({
|
|
588
|
+
status: "success",
|
|
589
|
+
query,
|
|
590
|
+
category,
|
|
591
|
+
results: foundGuides,
|
|
592
|
+
totalFiles: foundGuides.length,
|
|
593
|
+
}, null, 2),
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
case "get_team_patterns": {
|
|
599
|
+
const { category } = args;
|
|
600
|
+
try {
|
|
601
|
+
const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json");
|
|
602
|
+
const content = await fs.readFile(intelligencePath, "utf-8");
|
|
603
|
+
const intelligence = JSON.parse(content);
|
|
604
|
+
const result = { status: "success" };
|
|
605
|
+
if (category === "all" || !category) {
|
|
606
|
+
result.patterns = intelligence.patterns || {};
|
|
607
|
+
result.goldenFiles = intelligence.goldenFiles || [];
|
|
608
|
+
if (intelligence.tsconfigPaths) {
|
|
609
|
+
result.tsconfigPaths = intelligence.tsconfigPaths;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
else if (category === "di") {
|
|
613
|
+
result.dependencyInjection = intelligence.patterns?.dependencyInjection;
|
|
614
|
+
}
|
|
615
|
+
else if (category === "state") {
|
|
616
|
+
result.stateManagement = intelligence.patterns?.stateManagement;
|
|
617
|
+
}
|
|
618
|
+
else if (category === "testing") {
|
|
619
|
+
result.testingFramework = intelligence.patterns?.testingFramework;
|
|
620
|
+
result.testMocking = intelligence.patterns?.testMocking;
|
|
621
|
+
}
|
|
622
|
+
else if (category === "libraries") {
|
|
623
|
+
result.topUsed = intelligence.importGraph?.topUsed || [];
|
|
624
|
+
if (intelligence.tsconfigPaths) {
|
|
625
|
+
result.tsconfigPaths = intelligence.tsconfigPaths;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
return {
|
|
634
|
+
content: [
|
|
635
|
+
{
|
|
636
|
+
type: "text",
|
|
637
|
+
text: JSON.stringify({
|
|
638
|
+
status: "error",
|
|
639
|
+
message: "Failed to load team patterns",
|
|
640
|
+
error: error instanceof Error ? error.message : String(error),
|
|
641
|
+
}, null, 2),
|
|
642
|
+
},
|
|
643
|
+
],
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
case "get_component_usage": {
|
|
648
|
+
const { name: componentName } = args;
|
|
649
|
+
try {
|
|
650
|
+
const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json");
|
|
651
|
+
const content = await fs.readFile(intelligencePath, "utf-8");
|
|
652
|
+
const intelligence = JSON.parse(content);
|
|
653
|
+
const importGraph = intelligence.importGraph || {};
|
|
654
|
+
const usages = importGraph.usages || {};
|
|
655
|
+
// Find matching usages (exact match or partial match)
|
|
656
|
+
let matchedUsage = usages[componentName];
|
|
657
|
+
// Try partial match if exact match not found
|
|
658
|
+
if (!matchedUsage) {
|
|
659
|
+
const matchingKeys = Object.keys(usages).filter(key => key.includes(componentName) || componentName.includes(key));
|
|
660
|
+
if (matchingKeys.length > 0) {
|
|
661
|
+
matchedUsage = usages[matchingKeys[0]];
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (matchedUsage) {
|
|
665
|
+
return {
|
|
666
|
+
content: [{
|
|
667
|
+
type: "text",
|
|
668
|
+
text: JSON.stringify({
|
|
669
|
+
status: "success",
|
|
670
|
+
component: componentName,
|
|
671
|
+
usageCount: matchedUsage.usageCount,
|
|
672
|
+
usedIn: matchedUsage.usedIn,
|
|
673
|
+
}, null, 2),
|
|
674
|
+
}],
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
// Show top used as alternatives
|
|
679
|
+
const topUsed = importGraph.topUsed || [];
|
|
680
|
+
return {
|
|
681
|
+
content: [{
|
|
682
|
+
type: "text",
|
|
683
|
+
text: JSON.stringify({
|
|
684
|
+
status: "not_found",
|
|
685
|
+
component: componentName,
|
|
686
|
+
message: `No usages found for '${componentName}'.`,
|
|
687
|
+
suggestions: topUsed.slice(0, 10),
|
|
688
|
+
}, null, 2),
|
|
689
|
+
}],
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
return {
|
|
695
|
+
content: [{
|
|
696
|
+
type: "text",
|
|
697
|
+
text: JSON.stringify({
|
|
698
|
+
status: "error",
|
|
699
|
+
message: "Failed to get component usage. Run indexing first.",
|
|
700
|
+
error: error instanceof Error ? error.message : String(error),
|
|
701
|
+
}, null, 2),
|
|
702
|
+
}],
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
default:
|
|
707
|
+
return {
|
|
708
|
+
content: [
|
|
709
|
+
{
|
|
710
|
+
type: "text",
|
|
711
|
+
text: JSON.stringify({
|
|
712
|
+
error: `Unknown tool: ${name}`,
|
|
713
|
+
}, null, 2),
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
isError: true,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
return {
|
|
722
|
+
content: [
|
|
723
|
+
{
|
|
724
|
+
type: "text",
|
|
725
|
+
text: JSON.stringify({
|
|
726
|
+
error: error instanceof Error ? error.message : String(error),
|
|
727
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
728
|
+
}, null, 2),
|
|
729
|
+
},
|
|
730
|
+
],
|
|
731
|
+
isError: true,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
async function main() {
|
|
736
|
+
console.error("Codebase Context MCP Server");
|
|
737
|
+
console.error(`Root: ${ROOT_PATH}`);
|
|
738
|
+
console.error(`Analyzers: ${analyzerRegistry
|
|
739
|
+
.getAll()
|
|
740
|
+
.map((a) => a.name)
|
|
741
|
+
.join(", ")}`);
|
|
742
|
+
// Validate root path exists and is a directory
|
|
743
|
+
try {
|
|
744
|
+
const stats = await fs.stat(ROOT_PATH);
|
|
745
|
+
if (!stats.isDirectory()) {
|
|
746
|
+
console.error(`ERROR: Root path is not a directory: ${ROOT_PATH}`);
|
|
747
|
+
console.error(`Please specify a valid project directory.`);
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
console.error(`ERROR: Root path does not exist: ${ROOT_PATH}`);
|
|
753
|
+
console.error(`Please specify a valid project directory.`);
|
|
754
|
+
process.exit(1);
|
|
755
|
+
}
|
|
756
|
+
// Check for package.json to confirm it's a project root
|
|
757
|
+
try {
|
|
758
|
+
await fs.access(path.join(ROOT_PATH, "package.json"));
|
|
759
|
+
console.error(`Project detected: ${path.basename(ROOT_PATH)}`);
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
console.error(`WARNING: No package.json found. This may not be a project root.`);
|
|
763
|
+
}
|
|
764
|
+
const needsIndex = await shouldReindex();
|
|
765
|
+
if (needsIndex) {
|
|
766
|
+
console.error("Starting indexing...");
|
|
767
|
+
performIndexing();
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
console.error("Index found. Ready.");
|
|
771
|
+
indexState.status = "ready";
|
|
772
|
+
indexState.lastIndexed = new Date();
|
|
773
|
+
}
|
|
774
|
+
const transport = new StdioServerTransport();
|
|
775
|
+
await server.connect(transport);
|
|
776
|
+
console.error("Server ready");
|
|
777
|
+
}
|
|
778
|
+
// Export server components for programmatic use
|
|
779
|
+
export { server, performIndexing, resolveRootPath, shouldReindex, TOOLS };
|
|
780
|
+
// Only auto-start when run directly as CLI (not when imported as module)
|
|
781
|
+
// Check if this module is the entry point
|
|
782
|
+
const isDirectRun = process.argv[1]?.replace(/\\/g, "/").endsWith("index.js") ||
|
|
783
|
+
process.argv[1]?.replace(/\\/g, "/").endsWith("index.ts");
|
|
784
|
+
if (isDirectRun) {
|
|
785
|
+
main().catch((error) => {
|
|
786
|
+
console.error("Fatal:", error);
|
|
787
|
+
process.exit(1);
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
//# sourceMappingURL=index.js.map
|