codebase-context 1.2.2 → 1.5.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 -21
- package/README.md +144 -87
- package/dist/analyzers/angular/index.d.ts +1 -1
- package/dist/analyzers/angular/index.d.ts.map +1 -1
- package/dist/analyzers/angular/index.js +298 -309
- package/dist/analyzers/angular/index.js.map +1 -1
- package/dist/analyzers/generic/index.d.ts +1 -1
- package/dist/analyzers/generic/index.d.ts.map +1 -1
- package/dist/analyzers/generic/index.js +93 -47
- package/dist/analyzers/generic/index.js.map +1 -1
- package/dist/constants/codebase-context.d.ts +6 -0
- package/dist/constants/codebase-context.d.ts.map +1 -0
- package/dist/constants/codebase-context.js +8 -0
- package/dist/constants/codebase-context.js.map +1 -0
- package/dist/core/analyzer-registry.d.ts.map +1 -1
- package/dist/core/analyzer-registry.js +5 -7
- package/dist/core/analyzer-registry.js.map +1 -1
- package/dist/core/indexer.d.ts +9 -1
- package/dist/core/indexer.d.ts.map +1 -1
- package/dist/core/indexer.js +206 -139
- package/dist/core/indexer.js.map +1 -1
- package/dist/core/search.d.ts +1 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +63 -59
- package/dist/core/search.js.map +1 -1
- package/dist/embeddings/openai.d.ts.map +1 -1
- package/dist/embeddings/openai.js +2 -2
- package/dist/embeddings/openai.js.map +1 -1
- package/dist/embeddings/transformers.d.ts +1 -1
- package/dist/embeddings/transformers.d.ts.map +1 -1
- package/dist/embeddings/transformers.js +19 -15
- package/dist/embeddings/transformers.js.map +1 -1
- package/dist/embeddings/types.d.ts +1 -1
- package/dist/embeddings/types.d.ts.map +1 -1
- package/dist/embeddings/types.js +3 -3
- package/dist/embeddings/types.js.map +1 -1
- package/dist/errors/index.d.ts +8 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +11 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +6 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +691 -335
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +18 -18
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +23 -23
- package/dist/lib.js.map +1 -1
- package/dist/memory/store.d.ts +22 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +97 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/storage/lancedb.d.ts.map +1 -1
- package/dist/storage/lancedb.js +27 -31
- package/dist/storage/lancedb.js.map +1 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +2 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/chunking.d.ts.map +1 -1
- package/dist/utils/chunking.js +10 -9
- package/dist/utils/chunking.js.map +1 -1
- package/dist/utils/dependency-detection.d.ts +18 -0
- package/dist/utils/dependency-detection.d.ts.map +1 -0
- package/dist/utils/dependency-detection.js +102 -0
- package/dist/utils/dependency-detection.js.map +1 -0
- package/dist/utils/git-dates.d.ts.map +1 -1
- package/dist/utils/git-dates.js +3 -3
- package/dist/utils/git-dates.js.map +1 -1
- package/dist/utils/language-detection.d.ts.map +1 -1
- package/dist/utils/language-detection.js +69 -17
- package/dist/utils/language-detection.js.map +1 -1
- package/dist/utils/usage-tracker.d.ts +2 -2
- package/dist/utils/usage-tracker.d.ts.map +1 -1
- package/dist/utils/usage-tracker.js +64 -32
- package/dist/utils/usage-tracker.js.map +1 -1
- package/dist/utils/workspace-detection.d.ts +32 -0
- package/dist/utils/workspace-detection.d.ts.map +1 -0
- package/dist/utils/workspace-detection.js +107 -0
- package/dist/utils/workspace-detection.js.map +1 -0
- package/package.json +114 -97
- package/dist/core/file-watcher.d.ts +0 -63
- package/dist/core/file-watcher.d.ts.map +0 -1
- package/dist/core/file-watcher.js +0 -210
- package/dist/core/file-watcher.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -36
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -111
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/pattern-detector.d.ts +0 -41
- package/dist/utils/pattern-detector.d.ts.map +0 -1
- package/dist/utils/pattern-detector.js +0 -101
- package/dist/utils/pattern-detector.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
3
|
/**
|
|
3
4
|
* MCP Server for Codebase Context
|
|
4
5
|
* Provides codebase indexing and semantic search capabilities
|
|
5
6
|
*/
|
|
6
|
-
import { promises as fs } from
|
|
7
|
-
import path from
|
|
8
|
-
import { glob } from
|
|
9
|
-
import { Server } from
|
|
10
|
-
import { StdioServerTransport } from
|
|
11
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema
|
|
12
|
-
import { CodebaseIndexer } from
|
|
13
|
-
import { CodebaseSearcher } from
|
|
14
|
-
import { analyzerRegistry } from
|
|
15
|
-
import { AngularAnalyzer } from
|
|
16
|
-
import { GenericAnalyzer } from
|
|
17
|
-
import { InternalFileGraph } from
|
|
7
|
+
import { promises as fs } from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { glob } from 'glob';
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { CodebaseIndexer } from './core/indexer.js';
|
|
14
|
+
import { CodebaseSearcher } from './core/search.js';
|
|
15
|
+
import { analyzerRegistry } from './core/analyzer-registry.js';
|
|
16
|
+
import { AngularAnalyzer } from './analyzers/angular/index.js';
|
|
17
|
+
import { GenericAnalyzer } from './analyzers/generic/index.js';
|
|
18
|
+
import { InternalFileGraph } from './utils/usage-tracker.js';
|
|
19
|
+
import { IndexCorruptedError } from './errors/index.js';
|
|
20
|
+
import { CODEBASE_CONTEXT_DIRNAME, MEMORY_FILENAME, INTELLIGENCE_FILENAME, KEYWORD_INDEX_FILENAME, VECTOR_DB_DIRNAME } from './constants/codebase-context.js';
|
|
21
|
+
import { appendMemoryFile, readMemoriesFile, filterMemories, applyUnfilteredLimit } from './memory/store.js';
|
|
18
22
|
analyzerRegistry.register(new AngularAnalyzer());
|
|
19
23
|
analyzerRegistry.register(new GenericAnalyzer());
|
|
20
24
|
// Resolve root path with validation
|
|
@@ -32,167 +36,304 @@ function resolveRootPath() {
|
|
|
32
36
|
return rootPath;
|
|
33
37
|
}
|
|
34
38
|
const ROOT_PATH = resolveRootPath();
|
|
39
|
+
// File paths (new structure)
|
|
40
|
+
const PATHS = {
|
|
41
|
+
baseDir: path.join(ROOT_PATH, CODEBASE_CONTEXT_DIRNAME),
|
|
42
|
+
memory: path.join(ROOT_PATH, CODEBASE_CONTEXT_DIRNAME, MEMORY_FILENAME),
|
|
43
|
+
intelligence: path.join(ROOT_PATH, CODEBASE_CONTEXT_DIRNAME, INTELLIGENCE_FILENAME),
|
|
44
|
+
keywordIndex: path.join(ROOT_PATH, CODEBASE_CONTEXT_DIRNAME, KEYWORD_INDEX_FILENAME),
|
|
45
|
+
vectorDb: path.join(ROOT_PATH, CODEBASE_CONTEXT_DIRNAME, VECTOR_DB_DIRNAME)
|
|
46
|
+
};
|
|
47
|
+
// Legacy paths for migration
|
|
48
|
+
const LEGACY_PATHS = {
|
|
49
|
+
// Pre-v1.5
|
|
50
|
+
intelligence: path.join(ROOT_PATH, '.codebase-intelligence.json'),
|
|
51
|
+
keywordIndex: path.join(ROOT_PATH, '.codebase-index.json'),
|
|
52
|
+
vectorDb: path.join(ROOT_PATH, '.codebase-index')
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Check if file/directory exists
|
|
56
|
+
*/
|
|
57
|
+
async function fileExists(filePath) {
|
|
58
|
+
try {
|
|
59
|
+
await fs.access(filePath);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Migrate legacy file structure to .codebase-context/ folder.
|
|
68
|
+
* Idempotent, fail-safe. Rollback compatibility is not required.
|
|
69
|
+
*/
|
|
70
|
+
async function migrateToNewStructure() {
|
|
71
|
+
let migrated = false;
|
|
72
|
+
try {
|
|
73
|
+
await fs.mkdir(PATHS.baseDir, { recursive: true });
|
|
74
|
+
// intelligence.json
|
|
75
|
+
if (!(await fileExists(PATHS.intelligence))) {
|
|
76
|
+
if (await fileExists(LEGACY_PATHS.intelligence)) {
|
|
77
|
+
await fs.copyFile(LEGACY_PATHS.intelligence, PATHS.intelligence);
|
|
78
|
+
migrated = true;
|
|
79
|
+
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
80
|
+
console.error('[DEBUG] Migrated intelligence.json');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// index.json (keyword index)
|
|
85
|
+
if (!(await fileExists(PATHS.keywordIndex))) {
|
|
86
|
+
if (await fileExists(LEGACY_PATHS.keywordIndex)) {
|
|
87
|
+
await fs.copyFile(LEGACY_PATHS.keywordIndex, PATHS.keywordIndex);
|
|
88
|
+
migrated = true;
|
|
89
|
+
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
90
|
+
console.error('[DEBUG] Migrated index.json');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Vector DB directory
|
|
95
|
+
if (!(await fileExists(PATHS.vectorDb))) {
|
|
96
|
+
if (await fileExists(LEGACY_PATHS.vectorDb)) {
|
|
97
|
+
await fs.rename(LEGACY_PATHS.vectorDb, PATHS.vectorDb);
|
|
98
|
+
migrated = true;
|
|
99
|
+
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
100
|
+
console.error('[DEBUG] Migrated vector database');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return migrated;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
108
|
+
console.error('[DEBUG] Migration error:', error);
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
35
113
|
const indexState = {
|
|
36
|
-
status:
|
|
114
|
+
status: 'idle'
|
|
37
115
|
};
|
|
38
116
|
const server = new Server({
|
|
39
|
-
name:
|
|
40
|
-
version:
|
|
117
|
+
name: 'codebase-context',
|
|
118
|
+
version: '1.4.0'
|
|
41
119
|
}, {
|
|
42
120
|
capabilities: {
|
|
43
121
|
tools: {},
|
|
44
|
-
resources: {}
|
|
45
|
-
}
|
|
122
|
+
resources: {}
|
|
123
|
+
}
|
|
46
124
|
});
|
|
47
125
|
const TOOLS = [
|
|
48
126
|
{
|
|
49
|
-
name:
|
|
50
|
-
description:
|
|
51
|
-
|
|
52
|
-
|
|
127
|
+
name: 'search_codebase',
|
|
128
|
+
description: 'Search the indexed codebase using natural language queries. Returns code summaries with file locations. ' +
|
|
129
|
+
'Supports framework-specific queries and architectural layer filtering. ' +
|
|
130
|
+
'Use the returned filePath with other tools to read complete file contents.',
|
|
53
131
|
inputSchema: {
|
|
54
|
-
type:
|
|
132
|
+
type: 'object',
|
|
55
133
|
properties: {
|
|
56
134
|
query: {
|
|
57
|
-
type:
|
|
58
|
-
description:
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Natural language search query'
|
|
59
137
|
},
|
|
60
138
|
limit: {
|
|
61
|
-
type:
|
|
62
|
-
description:
|
|
63
|
-
default: 5
|
|
139
|
+
type: 'number',
|
|
140
|
+
description: 'Maximum number of results to return (default: 5)',
|
|
141
|
+
default: 5
|
|
64
142
|
},
|
|
65
143
|
filters: {
|
|
66
|
-
type:
|
|
67
|
-
description:
|
|
144
|
+
type: 'object',
|
|
145
|
+
description: 'Optional filters',
|
|
68
146
|
properties: {
|
|
69
147
|
framework: {
|
|
70
|
-
type:
|
|
71
|
-
description:
|
|
148
|
+
type: 'string',
|
|
149
|
+
description: 'Filter by framework (angular, react, vue)'
|
|
72
150
|
},
|
|
73
151
|
language: {
|
|
74
|
-
type:
|
|
75
|
-
description:
|
|
152
|
+
type: 'string',
|
|
153
|
+
description: 'Filter by programming language'
|
|
76
154
|
},
|
|
77
155
|
componentType: {
|
|
78
|
-
type:
|
|
79
|
-
description:
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: 'Filter by component type (component, service, directive, etc.)'
|
|
80
158
|
},
|
|
81
159
|
layer: {
|
|
82
|
-
type:
|
|
83
|
-
description:
|
|
160
|
+
type: 'string',
|
|
161
|
+
description: 'Filter by architectural layer (presentation, business, data, state, core, shared)'
|
|
84
162
|
},
|
|
85
163
|
tags: {
|
|
86
|
-
type:
|
|
87
|
-
items: { type:
|
|
88
|
-
description:
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
164
|
+
type: 'array',
|
|
165
|
+
items: { type: 'string' },
|
|
166
|
+
description: 'Filter by tags'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
92
170
|
},
|
|
93
|
-
required: [
|
|
94
|
-
}
|
|
171
|
+
required: ['query']
|
|
172
|
+
}
|
|
95
173
|
},
|
|
96
174
|
{
|
|
97
|
-
name:
|
|
98
|
-
description:
|
|
99
|
-
|
|
175
|
+
name: 'get_codebase_metadata',
|
|
176
|
+
description: 'Get codebase metadata including framework information, dependencies, architecture patterns, ' +
|
|
177
|
+
'and project statistics.',
|
|
100
178
|
inputSchema: {
|
|
101
|
-
type:
|
|
102
|
-
properties: {}
|
|
103
|
-
}
|
|
179
|
+
type: 'object',
|
|
180
|
+
properties: {}
|
|
181
|
+
}
|
|
104
182
|
},
|
|
105
183
|
{
|
|
106
|
-
name:
|
|
107
|
-
description:
|
|
108
|
-
|
|
184
|
+
name: 'get_indexing_status',
|
|
185
|
+
description: 'Get current indexing status: state, statistics, and progress. ' +
|
|
186
|
+
'Use refresh_index to manually trigger re-indexing when needed.',
|
|
109
187
|
inputSchema: {
|
|
110
|
-
type:
|
|
111
|
-
properties: {}
|
|
112
|
-
}
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {}
|
|
190
|
+
}
|
|
113
191
|
},
|
|
114
192
|
{
|
|
115
|
-
name:
|
|
116
|
-
description:
|
|
117
|
-
|
|
193
|
+
name: 'refresh_index',
|
|
194
|
+
description: 'Re-index the codebase. Supports full re-index or incremental mode. ' +
|
|
195
|
+
'Use incrementalOnly=true to only process files changed since last index.',
|
|
118
196
|
inputSchema: {
|
|
119
|
-
type:
|
|
197
|
+
type: 'object',
|
|
120
198
|
properties: {
|
|
121
199
|
reason: {
|
|
122
|
-
type:
|
|
123
|
-
description:
|
|
200
|
+
type: 'string',
|
|
201
|
+
description: 'Reason for refreshing the index (for logging)'
|
|
124
202
|
},
|
|
125
203
|
incrementalOnly: {
|
|
126
|
-
type:
|
|
127
|
-
description:
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
204
|
+
type: 'boolean',
|
|
205
|
+
description: 'If true, only re-index files changed since last full index (faster). Default: false (full re-index)'
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
131
209
|
},
|
|
132
210
|
{
|
|
133
|
-
name:
|
|
134
|
-
description:
|
|
211
|
+
name: 'get_style_guide',
|
|
212
|
+
description: 'Query style guide rules and architectural patterns from project documentation.',
|
|
135
213
|
inputSchema: {
|
|
136
|
-
type:
|
|
214
|
+
type: 'object',
|
|
137
215
|
properties: {
|
|
138
216
|
query: {
|
|
139
|
-
type:
|
|
140
|
-
description: 'Query for specific style guide rules (e.g., "component naming", "service patterns")'
|
|
217
|
+
type: 'string',
|
|
218
|
+
description: 'Query for specific style guide rules (e.g., "component naming", "service patterns")'
|
|
141
219
|
},
|
|
142
220
|
category: {
|
|
143
|
-
type:
|
|
144
|
-
description:
|
|
145
|
-
}
|
|
221
|
+
type: 'string',
|
|
222
|
+
description: 'Filter by category (naming, structure, patterns, testing)'
|
|
223
|
+
}
|
|
146
224
|
},
|
|
147
|
-
required: [
|
|
148
|
-
}
|
|
225
|
+
required: ['query']
|
|
226
|
+
}
|
|
149
227
|
},
|
|
150
228
|
{
|
|
151
|
-
name:
|
|
152
|
-
description:
|
|
153
|
-
|
|
229
|
+
name: 'get_team_patterns',
|
|
230
|
+
description: 'Get actionable team pattern recommendations based on codebase analysis. ' +
|
|
231
|
+
'Returns consensus patterns for DI, state management, testing, library wrappers, etc.',
|
|
154
232
|
inputSchema: {
|
|
155
|
-
type:
|
|
233
|
+
type: 'object',
|
|
156
234
|
properties: {
|
|
157
235
|
category: {
|
|
158
|
-
type:
|
|
159
|
-
description:
|
|
160
|
-
enum: [
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
236
|
+
type: 'string',
|
|
237
|
+
description: 'Pattern category to retrieve',
|
|
238
|
+
enum: ['all', 'di', 'state', 'testing', 'libraries']
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
164
242
|
},
|
|
165
243
|
{
|
|
166
|
-
name:
|
|
167
|
-
description:
|
|
244
|
+
name: 'get_component_usage',
|
|
245
|
+
description: 'Find WHERE a library or component is used in the codebase. ' +
|
|
168
246
|
"This is 'Find Usages' - returns all files that import a given package/module. " +
|
|
169
247
|
"Example: get_component_usage('@mycompany/utils') → shows all 34 files using it.",
|
|
170
248
|
inputSchema: {
|
|
171
|
-
type:
|
|
249
|
+
type: 'object',
|
|
172
250
|
properties: {
|
|
173
251
|
name: {
|
|
174
|
-
type:
|
|
175
|
-
description: "Import source to find usages for (e.g., 'primeng/table', '@mycompany/ui/button', 'lodash')"
|
|
176
|
-
}
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: "Import source to find usages for (e.g., 'primeng/table', '@mycompany/ui/button', 'lodash')"
|
|
254
|
+
}
|
|
177
255
|
},
|
|
178
|
-
required: [
|
|
179
|
-
}
|
|
256
|
+
required: ['name']
|
|
257
|
+
}
|
|
180
258
|
},
|
|
181
259
|
{
|
|
182
|
-
name:
|
|
183
|
-
description:
|
|
184
|
-
|
|
185
|
-
|
|
260
|
+
name: 'detect_circular_dependencies',
|
|
261
|
+
description: 'Analyze the import graph to detect circular dependencies between files. ' +
|
|
262
|
+
'Circular dependencies can cause initialization issues, tight coupling, and maintenance problems. ' +
|
|
263
|
+
'Returns all detected cycles sorted by length (shorter cycles are often more problematic).',
|
|
186
264
|
inputSchema: {
|
|
187
|
-
type:
|
|
265
|
+
type: 'object',
|
|
188
266
|
properties: {
|
|
189
267
|
scope: {
|
|
190
|
-
type:
|
|
191
|
-
description: "Optional path prefix to limit analysis (e.g., 'src/features', 'libs/shared')"
|
|
268
|
+
type: 'string',
|
|
269
|
+
description: "Optional path prefix to limit analysis (e.g., 'src/features', 'libs/shared')"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'remember',
|
|
276
|
+
description: '📝 CALL IMMEDIATELY when user explicitly asks to remember/record something.\n\n' +
|
|
277
|
+
'USER TRIGGERS:\n' +
|
|
278
|
+
'• "Remember this: [X]"\n' +
|
|
279
|
+
'• "Record this: [Y]"\n' +
|
|
280
|
+
'• "Save this for next time: [Z]"\n\n' +
|
|
281
|
+
'⚠️ DO NOT call unless user explicitly requests it.\n\n' +
|
|
282
|
+
'HOW TO WRITE:\n' +
|
|
283
|
+
'• ONE convention per memory (if user lists 5 things, call this 5 times)\n' +
|
|
284
|
+
'• memory: 5-10 words (the specific rule)\n' +
|
|
285
|
+
'• reason: 1 sentence (why it matters)\n' +
|
|
286
|
+
'• Skip: one-time features, code examples, essays',
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: 'object',
|
|
289
|
+
properties: {
|
|
290
|
+
type: {
|
|
291
|
+
type: 'string',
|
|
292
|
+
enum: ['convention', 'decision', 'gotcha'],
|
|
293
|
+
description: 'Type of memory being recorded'
|
|
294
|
+
},
|
|
295
|
+
category: {
|
|
296
|
+
type: 'string',
|
|
297
|
+
description: 'Broader category for filtering',
|
|
298
|
+
enum: ['tooling', 'architecture', 'testing', 'dependencies', 'conventions']
|
|
299
|
+
},
|
|
300
|
+
memory: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
description: 'What to remember (concise)'
|
|
192
303
|
},
|
|
304
|
+
reason: {
|
|
305
|
+
type: 'string',
|
|
306
|
+
description: 'Why this matters or what breaks otherwise'
|
|
307
|
+
}
|
|
193
308
|
},
|
|
194
|
-
|
|
309
|
+
required: ['type', 'category', 'memory', 'reason']
|
|
310
|
+
}
|
|
195
311
|
},
|
|
312
|
+
{
|
|
313
|
+
name: 'get_memory',
|
|
314
|
+
description: 'Retrieves team conventions, architectural decisions, and known gotchas.\n' +
|
|
315
|
+
'CALL BEFORE suggesting patterns, libraries, or architecture.\n\n' +
|
|
316
|
+
'Filters: category (tooling/architecture/testing/dependencies/conventions), type (convention/decision/gotcha), query (keyword search).',
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: 'object',
|
|
319
|
+
properties: {
|
|
320
|
+
category: {
|
|
321
|
+
type: 'string',
|
|
322
|
+
description: 'Filter by category',
|
|
323
|
+
enum: ['tooling', 'architecture', 'testing', 'dependencies', 'conventions']
|
|
324
|
+
},
|
|
325
|
+
type: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
description: 'Filter by memory type',
|
|
328
|
+
enum: ['convention', 'decision', 'gotcha']
|
|
329
|
+
},
|
|
330
|
+
query: {
|
|
331
|
+
type: 'string',
|
|
332
|
+
description: 'Keyword search across memory and reason'
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
196
337
|
];
|
|
197
338
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
198
339
|
return { tools: TOOLS };
|
|
@@ -200,59 +341,59 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
200
341
|
// MCP Resources - Proactive context injection
|
|
201
342
|
const RESOURCES = [
|
|
202
343
|
{
|
|
203
|
-
uri:
|
|
204
|
-
name:
|
|
205
|
-
description:
|
|
206
|
-
|
|
207
|
-
mimeType:
|
|
208
|
-
}
|
|
344
|
+
uri: 'codebase://context',
|
|
345
|
+
name: 'Codebase Intelligence',
|
|
346
|
+
description: 'Automatic codebase context: libraries used, team patterns, and conventions. ' +
|
|
347
|
+
'Read this BEFORE generating code to follow team standards.',
|
|
348
|
+
mimeType: 'text/plain'
|
|
349
|
+
}
|
|
209
350
|
];
|
|
210
351
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
211
352
|
return { resources: RESOURCES };
|
|
212
353
|
});
|
|
213
354
|
async function generateCodebaseContext() {
|
|
214
|
-
const intelligencePath =
|
|
355
|
+
const intelligencePath = PATHS.intelligence;
|
|
215
356
|
try {
|
|
216
|
-
const content = await fs.readFile(intelligencePath,
|
|
357
|
+
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
217
358
|
const intelligence = JSON.parse(content);
|
|
218
359
|
const lines = [];
|
|
219
|
-
lines.push(
|
|
220
|
-
lines.push(
|
|
221
|
-
lines.push(
|
|
222
|
-
lines.push(
|
|
223
|
-
lines.push(
|
|
360
|
+
lines.push('# Codebase Intelligence');
|
|
361
|
+
lines.push('');
|
|
362
|
+
lines.push('⚠️ CRITICAL: This is what YOUR codebase actually uses, not generic recommendations.');
|
|
363
|
+
lines.push('These are FACTS from analyzing your code, not best practices from the internet.');
|
|
364
|
+
lines.push('');
|
|
224
365
|
// Library usage - sorted by count
|
|
225
366
|
const libraryEntries = Object.entries(intelligence.libraryUsage || {})
|
|
226
367
|
.map(([lib, data]) => ({
|
|
227
368
|
lib,
|
|
228
|
-
count: data.count
|
|
369
|
+
count: data.count
|
|
229
370
|
}))
|
|
230
371
|
.sort((a, b) => b.count - a.count);
|
|
231
372
|
if (libraryEntries.length > 0) {
|
|
232
|
-
lines.push(
|
|
233
|
-
lines.push(
|
|
373
|
+
lines.push('## Libraries Actually Used (Top 15)');
|
|
374
|
+
lines.push('');
|
|
234
375
|
for (const { lib, count } of libraryEntries.slice(0, 15)) {
|
|
235
376
|
lines.push(`- **${lib}** (${count} uses)`);
|
|
236
377
|
}
|
|
237
|
-
lines.push(
|
|
378
|
+
lines.push('');
|
|
238
379
|
}
|
|
239
380
|
// Show tsconfig paths if available (helps AI understand internal imports)
|
|
240
381
|
if (intelligence.tsconfigPaths && Object.keys(intelligence.tsconfigPaths).length > 0) {
|
|
241
|
-
lines.push(
|
|
242
|
-
lines.push(
|
|
243
|
-
lines.push(
|
|
382
|
+
lines.push('## Import Aliases (from tsconfig.json)');
|
|
383
|
+
lines.push('');
|
|
384
|
+
lines.push('These path aliases map to internal project code:');
|
|
244
385
|
for (const [alias, paths] of Object.entries(intelligence.tsconfigPaths)) {
|
|
245
|
-
lines.push(`- \`${alias}\` → ${paths.join(
|
|
386
|
+
lines.push(`- \`${alias}\` → ${paths.join(', ')}`);
|
|
246
387
|
}
|
|
247
|
-
lines.push(
|
|
388
|
+
lines.push('');
|
|
248
389
|
}
|
|
249
390
|
// Pattern consensus
|
|
250
391
|
if (intelligence.patterns && Object.keys(intelligence.patterns).length > 0) {
|
|
251
392
|
lines.push("## YOUR Codebase's Actual Patterns (Not Generic Best Practices)");
|
|
252
|
-
lines.push(
|
|
253
|
-
lines.push(
|
|
254
|
-
lines.push(
|
|
255
|
-
lines.push(
|
|
393
|
+
lines.push('');
|
|
394
|
+
lines.push('These patterns were detected by analyzing your actual code.');
|
|
395
|
+
lines.push('This is what YOUR team does in practice, not what tutorials recommend.');
|
|
396
|
+
lines.push('');
|
|
256
397
|
for (const [category, data] of Object.entries(intelligence.patterns)) {
|
|
257
398
|
const patternData = data;
|
|
258
399
|
const primary = patternData.primary;
|
|
@@ -260,7 +401,7 @@ async function generateCodebaseContext() {
|
|
|
260
401
|
continue;
|
|
261
402
|
const percentage = parseInt(primary.frequency);
|
|
262
403
|
const categoryName = category
|
|
263
|
-
.replace(/([A-Z])/g,
|
|
404
|
+
.replace(/([A-Z])/g, ' $1')
|
|
264
405
|
.trim()
|
|
265
406
|
.replace(/^./, (str) => str.toUpperCase());
|
|
266
407
|
if (percentage === 100) {
|
|
@@ -294,40 +435,40 @@ async function generateCodebaseContext() {
|
|
|
294
435
|
}
|
|
295
436
|
lines.push(` → ASK the team which approach to use for new features`);
|
|
296
437
|
}
|
|
297
|
-
lines.push(
|
|
438
|
+
lines.push('');
|
|
298
439
|
}
|
|
299
440
|
}
|
|
300
|
-
lines.push(
|
|
441
|
+
lines.push('---');
|
|
301
442
|
lines.push(`Generated: ${intelligence.generatedAt || new Date().toISOString()}`);
|
|
302
|
-
return lines.join(
|
|
443
|
+
return lines.join('\n');
|
|
303
444
|
}
|
|
304
445
|
catch (error) {
|
|
305
|
-
return (
|
|
306
|
-
|
|
446
|
+
return ('# Codebase Intelligence\n\n' +
|
|
447
|
+
'Intelligence data not yet generated. Run indexing first.\n' +
|
|
307
448
|
`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
308
449
|
}
|
|
309
450
|
}
|
|
310
451
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
311
452
|
const uri = request.params.uri;
|
|
312
|
-
if (uri ===
|
|
453
|
+
if (uri === 'codebase://context') {
|
|
313
454
|
const content = await generateCodebaseContext();
|
|
314
455
|
return {
|
|
315
456
|
contents: [
|
|
316
457
|
{
|
|
317
458
|
uri,
|
|
318
|
-
mimeType:
|
|
319
|
-
text: content
|
|
320
|
-
}
|
|
321
|
-
]
|
|
459
|
+
mimeType: 'text/plain',
|
|
460
|
+
text: content
|
|
461
|
+
}
|
|
462
|
+
]
|
|
322
463
|
};
|
|
323
464
|
}
|
|
324
465
|
throw new Error(`Unknown resource: ${uri}`);
|
|
325
466
|
});
|
|
326
467
|
async function performIndexing() {
|
|
327
|
-
indexState.status =
|
|
468
|
+
indexState.status = 'indexing';
|
|
328
469
|
console.error(`Indexing: ${ROOT_PATH}`);
|
|
329
470
|
try {
|
|
330
|
-
let lastLoggedProgress = { phase:
|
|
471
|
+
let lastLoggedProgress = { phase: '', percentage: -1 };
|
|
331
472
|
const indexer = new CodebaseIndexer({
|
|
332
473
|
rootPath: ROOT_PATH,
|
|
333
474
|
onProgress: (progress) => {
|
|
@@ -338,23 +479,23 @@ async function performIndexing() {
|
|
|
338
479
|
console.error(`[${progress.phase}] ${progress.percentage}%`);
|
|
339
480
|
lastLoggedProgress = { phase: progress.phase, percentage: progress.percentage };
|
|
340
481
|
}
|
|
341
|
-
}
|
|
482
|
+
}
|
|
342
483
|
});
|
|
343
484
|
indexState.indexer = indexer;
|
|
344
485
|
const stats = await indexer.index();
|
|
345
|
-
indexState.status =
|
|
486
|
+
indexState.status = 'ready';
|
|
346
487
|
indexState.lastIndexed = new Date();
|
|
347
488
|
indexState.stats = stats;
|
|
348
489
|
console.error(`Complete: ${stats.indexedFiles} files, ${stats.totalChunks} chunks in ${(stats.duration / 1000).toFixed(2)}s`);
|
|
349
490
|
}
|
|
350
491
|
catch (error) {
|
|
351
|
-
indexState.status =
|
|
492
|
+
indexState.status = 'error';
|
|
352
493
|
indexState.error = error instanceof Error ? error.message : String(error);
|
|
353
|
-
console.error(
|
|
494
|
+
console.error('Indexing failed:', indexState.error);
|
|
354
495
|
}
|
|
355
496
|
}
|
|
356
497
|
async function shouldReindex() {
|
|
357
|
-
const indexPath =
|
|
498
|
+
const indexPath = PATHS.keywordIndex;
|
|
358
499
|
try {
|
|
359
500
|
await fs.access(indexPath);
|
|
360
501
|
return false;
|
|
@@ -367,43 +508,99 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
367
508
|
const { name, arguments: args } = request.params;
|
|
368
509
|
try {
|
|
369
510
|
switch (name) {
|
|
370
|
-
case
|
|
511
|
+
case 'search_codebase': {
|
|
371
512
|
const { query, limit, filters } = args;
|
|
372
|
-
if (indexState.status ===
|
|
513
|
+
if (indexState.status === 'indexing') {
|
|
373
514
|
return {
|
|
374
515
|
content: [
|
|
375
516
|
{
|
|
376
|
-
type:
|
|
517
|
+
type: 'text',
|
|
377
518
|
text: JSON.stringify({
|
|
378
|
-
status:
|
|
379
|
-
message:
|
|
380
|
-
progress: indexState.indexer?.getProgress()
|
|
381
|
-
}, null, 2)
|
|
382
|
-
}
|
|
383
|
-
]
|
|
519
|
+
status: 'indexing',
|
|
520
|
+
message: 'Index is still being built. Retry in a moment.',
|
|
521
|
+
progress: indexState.indexer?.getProgress()
|
|
522
|
+
}, null, 2)
|
|
523
|
+
}
|
|
524
|
+
]
|
|
384
525
|
};
|
|
385
526
|
}
|
|
386
|
-
if (indexState.status ===
|
|
527
|
+
if (indexState.status === 'error') {
|
|
387
528
|
return {
|
|
388
529
|
content: [
|
|
389
530
|
{
|
|
390
|
-
type:
|
|
531
|
+
type: 'text',
|
|
391
532
|
text: JSON.stringify({
|
|
392
|
-
status:
|
|
393
|
-
message: `Indexing failed: ${indexState.error}
|
|
394
|
-
}, null, 2)
|
|
395
|
-
}
|
|
396
|
-
]
|
|
533
|
+
status: 'error',
|
|
534
|
+
message: `Indexing failed: ${indexState.error}`
|
|
535
|
+
}, null, 2)
|
|
536
|
+
}
|
|
537
|
+
]
|
|
397
538
|
};
|
|
398
539
|
}
|
|
399
540
|
const searcher = new CodebaseSearcher(ROOT_PATH);
|
|
400
|
-
|
|
541
|
+
let results;
|
|
542
|
+
try {
|
|
543
|
+
results = await searcher.search(query, limit || 5, filters);
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
if (error instanceof IndexCorruptedError) {
|
|
547
|
+
console.error('[Auto-Heal] Index corrupted. Triggering full re-index...');
|
|
548
|
+
await performIndexing();
|
|
549
|
+
if (indexState.status === 'ready') {
|
|
550
|
+
console.error('[Auto-Heal] Success. Retrying search...');
|
|
551
|
+
const freshSearcher = new CodebaseSearcher(ROOT_PATH);
|
|
552
|
+
try {
|
|
553
|
+
results = await freshSearcher.search(query, limit || 5, filters);
|
|
554
|
+
}
|
|
555
|
+
catch (retryError) {
|
|
556
|
+
return {
|
|
557
|
+
content: [
|
|
558
|
+
{
|
|
559
|
+
type: 'text',
|
|
560
|
+
text: JSON.stringify({
|
|
561
|
+
status: 'error',
|
|
562
|
+
message: `Auto-heal retry failed: ${retryError instanceof Error ? retryError.message : String(retryError)}`
|
|
563
|
+
}, null, 2)
|
|
564
|
+
}
|
|
565
|
+
]
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
return {
|
|
571
|
+
content: [
|
|
572
|
+
{
|
|
573
|
+
type: 'text',
|
|
574
|
+
text: JSON.stringify({
|
|
575
|
+
status: 'error',
|
|
576
|
+
message: `Auto-heal failed: Indexing ended with status '${indexState.status}'`,
|
|
577
|
+
error: indexState.error
|
|
578
|
+
}, null, 2)
|
|
579
|
+
}
|
|
580
|
+
]
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
throw error; // Propagate unexpected errors
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Load memories for keyword matching
|
|
589
|
+
const allMemories = await readMemoriesFile(PATHS.memory);
|
|
590
|
+
const findRelatedMemories = (queryTerms) => {
|
|
591
|
+
return allMemories.filter((m) => {
|
|
592
|
+
const searchText = `${m.memory} ${m.reason}`.toLowerCase();
|
|
593
|
+
return queryTerms.some((term) => searchText.includes(term));
|
|
594
|
+
});
|
|
595
|
+
};
|
|
596
|
+
const queryTerms = query.toLowerCase().split(/\s+/);
|
|
597
|
+
const relatedMemories = findRelatedMemories(queryTerms);
|
|
401
598
|
return {
|
|
402
599
|
content: [
|
|
403
600
|
{
|
|
404
|
-
type:
|
|
601
|
+
type: 'text',
|
|
405
602
|
text: JSON.stringify({
|
|
406
|
-
status:
|
|
603
|
+
status: 'success',
|
|
407
604
|
results: results.map((r) => ({
|
|
408
605
|
summary: r.summary,
|
|
409
606
|
snippet: r.snippet,
|
|
@@ -413,22 +610,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
413
610
|
componentType: r.componentType,
|
|
414
611
|
layer: r.layer,
|
|
415
612
|
framework: r.framework,
|
|
416
|
-
// v1.2: Pattern momentum awareness
|
|
417
613
|
trend: r.trend,
|
|
418
|
-
patternWarning: r.patternWarning
|
|
614
|
+
patternWarning: r.patternWarning
|
|
419
615
|
})),
|
|
420
616
|
totalResults: results.length,
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
617
|
+
...(relatedMemories.length > 0 && { relatedMemories })
|
|
618
|
+
}, null, 2)
|
|
619
|
+
}
|
|
620
|
+
]
|
|
424
621
|
};
|
|
425
622
|
}
|
|
426
|
-
case
|
|
623
|
+
case 'get_indexing_status': {
|
|
427
624
|
const progress = indexState.indexer?.getProgress();
|
|
428
625
|
return {
|
|
429
626
|
content: [
|
|
430
627
|
{
|
|
431
|
-
type:
|
|
628
|
+
type: 'text',
|
|
432
629
|
text: JSON.stringify({
|
|
433
630
|
status: indexState.status,
|
|
434
631
|
rootPath: ROOT_PATH,
|
|
@@ -438,7 +635,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
438
635
|
totalFiles: indexState.stats.totalFiles,
|
|
439
636
|
indexedFiles: indexState.stats.indexedFiles,
|
|
440
637
|
totalChunks: indexState.stats.totalChunks,
|
|
441
|
-
duration: `${(indexState.stats.duration / 1000).toFixed(2)}s
|
|
638
|
+
duration: `${(indexState.stats.duration / 1000).toFixed(2)}s`
|
|
442
639
|
}
|
|
443
640
|
: undefined,
|
|
444
641
|
progress: progress
|
|
@@ -446,20 +643,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
446
643
|
phase: progress.phase,
|
|
447
644
|
percentage: progress.percentage,
|
|
448
645
|
filesProcessed: progress.filesProcessed,
|
|
449
|
-
totalFiles: progress.totalFiles
|
|
646
|
+
totalFiles: progress.totalFiles
|
|
450
647
|
}
|
|
451
648
|
: undefined,
|
|
452
649
|
error: indexState.error,
|
|
453
|
-
hint:
|
|
454
|
-
}, null, 2)
|
|
455
|
-
}
|
|
456
|
-
]
|
|
650
|
+
hint: 'Use refresh_index to manually trigger re-indexing when needed.'
|
|
651
|
+
}, null, 2)
|
|
652
|
+
}
|
|
653
|
+
]
|
|
457
654
|
};
|
|
458
655
|
}
|
|
459
|
-
case
|
|
656
|
+
case 'refresh_index': {
|
|
460
657
|
const { reason, incrementalOnly } = args;
|
|
461
|
-
const mode = incrementalOnly ?
|
|
462
|
-
console.error(`Refresh requested (${mode}): ${reason ||
|
|
658
|
+
const mode = incrementalOnly ? 'incremental' : 'full';
|
|
659
|
+
console.error(`Refresh requested (${mode}): ${reason || 'Manual trigger'}`);
|
|
463
660
|
// TODO: When incremental indexing is implemented (Phase 2),
|
|
464
661
|
// use `incrementalOnly` to only re-index changed files.
|
|
465
662
|
// For now, always do full re-index but acknowledge the intention.
|
|
@@ -467,48 +664,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
467
664
|
return {
|
|
468
665
|
content: [
|
|
469
666
|
{
|
|
470
|
-
type:
|
|
667
|
+
type: 'text',
|
|
471
668
|
text: JSON.stringify({
|
|
472
|
-
status:
|
|
669
|
+
status: 'started',
|
|
473
670
|
mode,
|
|
474
671
|
message: incrementalOnly
|
|
475
|
-
?
|
|
476
|
-
:
|
|
672
|
+
? 'Incremental re-indexing requested. Check status with get_indexing_status.'
|
|
673
|
+
: 'Full re-indexing started. Check status with get_indexing_status.',
|
|
477
674
|
reason,
|
|
478
675
|
note: incrementalOnly
|
|
479
|
-
?
|
|
480
|
-
: undefined
|
|
481
|
-
}, null, 2)
|
|
482
|
-
}
|
|
483
|
-
]
|
|
676
|
+
? 'Incremental mode requested. Full re-index for now; true incremental indexing coming in Phase 2.'
|
|
677
|
+
: undefined
|
|
678
|
+
}, null, 2)
|
|
679
|
+
}
|
|
680
|
+
]
|
|
484
681
|
};
|
|
485
682
|
}
|
|
486
|
-
case
|
|
683
|
+
case 'get_codebase_metadata': {
|
|
487
684
|
const indexer = new CodebaseIndexer({ rootPath: ROOT_PATH });
|
|
488
685
|
const metadata = await indexer.detectMetadata();
|
|
489
686
|
// Load team patterns from intelligence file
|
|
490
687
|
let teamPatterns = {};
|
|
491
688
|
try {
|
|
492
|
-
const intelligencePath =
|
|
493
|
-
const intelligenceContent = await fs.readFile(intelligencePath,
|
|
689
|
+
const intelligencePath = PATHS.intelligence;
|
|
690
|
+
const intelligenceContent = await fs.readFile(intelligencePath, 'utf-8');
|
|
494
691
|
const intelligence = JSON.parse(intelligenceContent);
|
|
495
692
|
if (intelligence.patterns) {
|
|
496
693
|
teamPatterns = {
|
|
497
694
|
dependencyInjection: intelligence.patterns.dependencyInjection,
|
|
498
695
|
stateManagement: intelligence.patterns.stateManagement,
|
|
499
|
-
componentInputs: intelligence.patterns.componentInputs
|
|
696
|
+
componentInputs: intelligence.patterns.componentInputs
|
|
500
697
|
};
|
|
501
698
|
}
|
|
502
699
|
}
|
|
503
|
-
catch (
|
|
700
|
+
catch (_error) {
|
|
504
701
|
// No intelligence file or parsing error
|
|
505
702
|
}
|
|
506
703
|
return {
|
|
507
704
|
content: [
|
|
508
705
|
{
|
|
509
|
-
type:
|
|
706
|
+
type: 'text',
|
|
510
707
|
text: JSON.stringify({
|
|
511
|
-
status:
|
|
708
|
+
status: 'success',
|
|
512
709
|
metadata: {
|
|
513
710
|
name: metadata.name,
|
|
514
711
|
framework: metadata.framework,
|
|
@@ -517,23 +714,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
517
714
|
architecture: metadata.architecture,
|
|
518
715
|
projectStructure: metadata.projectStructure,
|
|
519
716
|
statistics: metadata.statistics,
|
|
520
|
-
teamPatterns
|
|
521
|
-
}
|
|
522
|
-
}, null, 2)
|
|
523
|
-
}
|
|
524
|
-
]
|
|
717
|
+
teamPatterns
|
|
718
|
+
}
|
|
719
|
+
}, null, 2)
|
|
720
|
+
}
|
|
721
|
+
]
|
|
525
722
|
};
|
|
526
723
|
}
|
|
527
|
-
case
|
|
724
|
+
case 'get_style_guide': {
|
|
528
725
|
const { query, category } = args;
|
|
529
726
|
const styleGuidePatterns = [
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
727
|
+
'STYLE_GUIDE.md',
|
|
728
|
+
'CODING_STYLE.md',
|
|
729
|
+
'ARCHITECTURE.md',
|
|
730
|
+
'CONTRIBUTING.md',
|
|
731
|
+
'docs/style-guide.md',
|
|
732
|
+
'docs/coding-style.md',
|
|
733
|
+
'docs/ARCHITECTURE.md'
|
|
537
734
|
];
|
|
538
735
|
const foundGuides = [];
|
|
539
736
|
const queryLower = query.toLowerCase();
|
|
@@ -542,13 +739,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
542
739
|
try {
|
|
543
740
|
const files = await glob(pattern, {
|
|
544
741
|
cwd: ROOT_PATH,
|
|
545
|
-
absolute: true
|
|
742
|
+
absolute: true
|
|
546
743
|
});
|
|
547
744
|
for (const file of files) {
|
|
548
745
|
try {
|
|
549
746
|
// Normalize line endings to \n for consistent output
|
|
550
|
-
const rawContent = await fs.readFile(file,
|
|
551
|
-
const content = rawContent.replace(/\r\n/g,
|
|
747
|
+
const rawContent = await fs.readFile(file, 'utf-8');
|
|
748
|
+
const content = rawContent.replace(/\r\n/g, '\n');
|
|
552
749
|
const relativePath = path.relative(ROOT_PATH, file);
|
|
553
750
|
// Find relevant sections based on query
|
|
554
751
|
const sections = content.split(/^##\s+/m);
|
|
@@ -559,27 +756,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
559
756
|
if (isRelevant) {
|
|
560
757
|
// Limit section size to ~500 words
|
|
561
758
|
const words = section.split(/\s+/);
|
|
562
|
-
const truncated = words.slice(0, 500).join(
|
|
563
|
-
relevantSections.push(
|
|
564
|
-
(words.length > 500
|
|
565
|
-
? truncated + "..."
|
|
566
|
-
: section.trim()));
|
|
759
|
+
const truncated = words.slice(0, 500).join(' ');
|
|
760
|
+
relevantSections.push('## ' + (words.length > 500 ? truncated + '...' : section.trim()));
|
|
567
761
|
}
|
|
568
762
|
}
|
|
569
763
|
if (relevantSections.length > 0) {
|
|
570
764
|
foundGuides.push({
|
|
571
765
|
file: relativePath,
|
|
572
|
-
content: content.slice(0, 200) +
|
|
573
|
-
relevantSections: relevantSections.slice(0, 3)
|
|
766
|
+
content: content.slice(0, 200) + '...',
|
|
767
|
+
relevantSections: relevantSections.slice(0, 3) // Max 3 sections per file
|
|
574
768
|
});
|
|
575
769
|
}
|
|
576
770
|
}
|
|
577
|
-
catch (
|
|
771
|
+
catch (_e) {
|
|
578
772
|
// Skip unreadable files
|
|
579
773
|
}
|
|
580
774
|
}
|
|
581
775
|
}
|
|
582
|
-
catch (
|
|
776
|
+
catch (_e) {
|
|
583
777
|
// Pattern didn't match, continue
|
|
584
778
|
}
|
|
585
779
|
}
|
|
@@ -587,86 +781,106 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
587
781
|
return {
|
|
588
782
|
content: [
|
|
589
783
|
{
|
|
590
|
-
type:
|
|
784
|
+
type: 'text',
|
|
591
785
|
text: JSON.stringify({
|
|
592
|
-
status:
|
|
786
|
+
status: 'no_results',
|
|
593
787
|
message: `No style guide content found matching: ${query}`,
|
|
594
788
|
searchedPatterns: styleGuidePatterns,
|
|
595
|
-
hint: "Try broader terms like 'naming', 'patterns', 'testing', 'components'"
|
|
596
|
-
}, null, 2)
|
|
597
|
-
}
|
|
598
|
-
]
|
|
789
|
+
hint: "Try broader terms like 'naming', 'patterns', 'testing', 'components'"
|
|
790
|
+
}, null, 2)
|
|
791
|
+
}
|
|
792
|
+
]
|
|
599
793
|
};
|
|
600
794
|
}
|
|
601
795
|
return {
|
|
602
796
|
content: [
|
|
603
797
|
{
|
|
604
|
-
type:
|
|
798
|
+
type: 'text',
|
|
605
799
|
text: JSON.stringify({
|
|
606
|
-
status:
|
|
800
|
+
status: 'success',
|
|
607
801
|
query,
|
|
608
802
|
category,
|
|
609
803
|
results: foundGuides,
|
|
610
|
-
totalFiles: foundGuides.length
|
|
611
|
-
}, null, 2)
|
|
612
|
-
}
|
|
613
|
-
]
|
|
804
|
+
totalFiles: foundGuides.length
|
|
805
|
+
}, null, 2)
|
|
806
|
+
}
|
|
807
|
+
]
|
|
614
808
|
};
|
|
615
809
|
}
|
|
616
|
-
case
|
|
810
|
+
case 'get_team_patterns': {
|
|
617
811
|
const { category } = args;
|
|
618
812
|
try {
|
|
619
|
-
const intelligencePath =
|
|
620
|
-
const content = await fs.readFile(intelligencePath,
|
|
813
|
+
const intelligencePath = PATHS.intelligence;
|
|
814
|
+
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
621
815
|
const intelligence = JSON.parse(content);
|
|
622
|
-
const result = { status:
|
|
623
|
-
if (category ===
|
|
816
|
+
const result = { status: 'success' };
|
|
817
|
+
if (category === 'all' || !category) {
|
|
624
818
|
result.patterns = intelligence.patterns || {};
|
|
625
819
|
result.goldenFiles = intelligence.goldenFiles || [];
|
|
626
820
|
if (intelligence.tsconfigPaths) {
|
|
627
821
|
result.tsconfigPaths = intelligence.tsconfigPaths;
|
|
628
822
|
}
|
|
629
823
|
}
|
|
630
|
-
else if (category ===
|
|
824
|
+
else if (category === 'di') {
|
|
631
825
|
result.dependencyInjection = intelligence.patterns?.dependencyInjection;
|
|
632
826
|
}
|
|
633
|
-
else if (category ===
|
|
827
|
+
else if (category === 'state') {
|
|
634
828
|
result.stateManagement = intelligence.patterns?.stateManagement;
|
|
635
829
|
}
|
|
636
|
-
else if (category ===
|
|
830
|
+
else if (category === 'testing') {
|
|
637
831
|
result.testingFramework = intelligence.patterns?.testingFramework;
|
|
638
832
|
result.testMocking = intelligence.patterns?.testMocking;
|
|
639
833
|
}
|
|
640
|
-
else if (category ===
|
|
834
|
+
else if (category === 'libraries') {
|
|
641
835
|
result.topUsed = intelligence.importGraph?.topUsed || [];
|
|
642
836
|
if (intelligence.tsconfigPaths) {
|
|
643
837
|
result.tsconfigPaths = intelligence.tsconfigPaths;
|
|
644
838
|
}
|
|
645
839
|
}
|
|
840
|
+
// Load and append matching memories
|
|
841
|
+
try {
|
|
842
|
+
const allMemories = await readMemoriesFile(PATHS.memory);
|
|
843
|
+
// Map pattern categories to decision categories
|
|
844
|
+
const categoryMap = {
|
|
845
|
+
all: ['tooling', 'architecture', 'testing', 'dependencies', 'conventions'],
|
|
846
|
+
di: ['architecture', 'conventions'],
|
|
847
|
+
state: ['architecture', 'conventions'],
|
|
848
|
+
testing: ['testing'],
|
|
849
|
+
libraries: ['dependencies']
|
|
850
|
+
};
|
|
851
|
+
const relevantCategories = categoryMap[category || 'all'] || [];
|
|
852
|
+
const matchingMemories = allMemories.filter((m) => relevantCategories.includes(m.category));
|
|
853
|
+
if (matchingMemories.length > 0) {
|
|
854
|
+
result.memories = matchingMemories;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
catch (_error) {
|
|
858
|
+
// No memory file yet, that's fine - don't fail the whole request
|
|
859
|
+
}
|
|
646
860
|
return {
|
|
647
|
-
content: [{ type:
|
|
861
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
648
862
|
};
|
|
649
863
|
}
|
|
650
864
|
catch (error) {
|
|
651
865
|
return {
|
|
652
866
|
content: [
|
|
653
867
|
{
|
|
654
|
-
type:
|
|
868
|
+
type: 'text',
|
|
655
869
|
text: JSON.stringify({
|
|
656
|
-
status:
|
|
657
|
-
message:
|
|
658
|
-
error: error instanceof Error ? error.message : String(error)
|
|
659
|
-
}, null, 2)
|
|
660
|
-
}
|
|
661
|
-
]
|
|
870
|
+
status: 'error',
|
|
871
|
+
message: 'Failed to load team patterns',
|
|
872
|
+
error: error instanceof Error ? error.message : String(error)
|
|
873
|
+
}, null, 2)
|
|
874
|
+
}
|
|
875
|
+
]
|
|
662
876
|
};
|
|
663
877
|
}
|
|
664
878
|
}
|
|
665
|
-
case
|
|
879
|
+
case 'get_component_usage': {
|
|
666
880
|
const { name: componentName } = args;
|
|
667
881
|
try {
|
|
668
|
-
const intelligencePath =
|
|
669
|
-
const content = await fs.readFile(intelligencePath,
|
|
882
|
+
const intelligencePath = PATHS.intelligence;
|
|
883
|
+
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
670
884
|
const intelligence = JSON.parse(content);
|
|
671
885
|
const importGraph = intelligence.importGraph || {};
|
|
672
886
|
const usages = importGraph.usages || {};
|
|
@@ -674,68 +888,76 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
674
888
|
let matchedUsage = usages[componentName];
|
|
675
889
|
// Try partial match if exact match not found
|
|
676
890
|
if (!matchedUsage) {
|
|
677
|
-
const matchingKeys = Object.keys(usages).filter(key => key.includes(componentName) || componentName.includes(key));
|
|
891
|
+
const matchingKeys = Object.keys(usages).filter((key) => key.includes(componentName) || componentName.includes(key));
|
|
678
892
|
if (matchingKeys.length > 0) {
|
|
679
893
|
matchedUsage = usages[matchingKeys[0]];
|
|
680
894
|
}
|
|
681
895
|
}
|
|
682
896
|
if (matchedUsage) {
|
|
683
897
|
return {
|
|
684
|
-
content: [
|
|
685
|
-
|
|
898
|
+
content: [
|
|
899
|
+
{
|
|
900
|
+
type: 'text',
|
|
686
901
|
text: JSON.stringify({
|
|
687
|
-
status:
|
|
902
|
+
status: 'success',
|
|
688
903
|
component: componentName,
|
|
689
904
|
usageCount: matchedUsage.usageCount,
|
|
690
|
-
usedIn: matchedUsage.usedIn
|
|
691
|
-
}, null, 2)
|
|
692
|
-
}
|
|
905
|
+
usedIn: matchedUsage.usedIn
|
|
906
|
+
}, null, 2)
|
|
907
|
+
}
|
|
908
|
+
]
|
|
693
909
|
};
|
|
694
910
|
}
|
|
695
911
|
else {
|
|
696
912
|
// Show top used as alternatives
|
|
697
913
|
const topUsed = importGraph.topUsed || [];
|
|
698
914
|
return {
|
|
699
|
-
content: [
|
|
700
|
-
|
|
915
|
+
content: [
|
|
916
|
+
{
|
|
917
|
+
type: 'text',
|
|
701
918
|
text: JSON.stringify({
|
|
702
|
-
status:
|
|
919
|
+
status: 'not_found',
|
|
703
920
|
component: componentName,
|
|
704
921
|
message: `No usages found for '${componentName}'.`,
|
|
705
|
-
suggestions: topUsed.slice(0, 10)
|
|
706
|
-
}, null, 2)
|
|
707
|
-
}
|
|
922
|
+
suggestions: topUsed.slice(0, 10)
|
|
923
|
+
}, null, 2)
|
|
924
|
+
}
|
|
925
|
+
]
|
|
708
926
|
};
|
|
709
927
|
}
|
|
710
928
|
}
|
|
711
929
|
catch (error) {
|
|
712
930
|
return {
|
|
713
|
-
content: [
|
|
714
|
-
|
|
931
|
+
content: [
|
|
932
|
+
{
|
|
933
|
+
type: 'text',
|
|
715
934
|
text: JSON.stringify({
|
|
716
|
-
status:
|
|
717
|
-
message:
|
|
718
|
-
error: error instanceof Error ? error.message : String(error)
|
|
719
|
-
}, null, 2)
|
|
720
|
-
}
|
|
935
|
+
status: 'error',
|
|
936
|
+
message: 'Failed to get component usage. Run indexing first.',
|
|
937
|
+
error: error instanceof Error ? error.message : String(error)
|
|
938
|
+
}, null, 2)
|
|
939
|
+
}
|
|
940
|
+
]
|
|
721
941
|
};
|
|
722
942
|
}
|
|
723
943
|
}
|
|
724
|
-
case
|
|
944
|
+
case 'detect_circular_dependencies': {
|
|
725
945
|
const { scope } = args;
|
|
726
946
|
try {
|
|
727
|
-
const intelligencePath =
|
|
728
|
-
const content = await fs.readFile(intelligencePath,
|
|
947
|
+
const intelligencePath = PATHS.intelligence;
|
|
948
|
+
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
729
949
|
const intelligence = JSON.parse(content);
|
|
730
950
|
if (!intelligence.internalFileGraph) {
|
|
731
951
|
return {
|
|
732
|
-
content: [
|
|
733
|
-
|
|
952
|
+
content: [
|
|
953
|
+
{
|
|
954
|
+
type: 'text',
|
|
734
955
|
text: JSON.stringify({
|
|
735
|
-
status:
|
|
736
|
-
message:
|
|
737
|
-
}, null, 2)
|
|
738
|
-
}
|
|
956
|
+
status: 'error',
|
|
957
|
+
message: 'Internal file graph not found. Please run refresh_index to rebuild the index with cycle detection support.'
|
|
958
|
+
}, null, 2)
|
|
959
|
+
}
|
|
960
|
+
]
|
|
739
961
|
};
|
|
740
962
|
}
|
|
741
963
|
// Reconstruct the graph from stored data
|
|
@@ -744,48 +966,169 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
744
966
|
const graphStats = intelligence.internalFileGraph.stats || graph.getStats();
|
|
745
967
|
if (cycles.length === 0) {
|
|
746
968
|
return {
|
|
747
|
-
content: [
|
|
748
|
-
|
|
969
|
+
content: [
|
|
970
|
+
{
|
|
971
|
+
type: 'text',
|
|
749
972
|
text: JSON.stringify({
|
|
750
|
-
status:
|
|
973
|
+
status: 'success',
|
|
751
974
|
message: scope
|
|
752
975
|
? `No circular dependencies detected in scope: ${scope}`
|
|
753
|
-
:
|
|
976
|
+
: 'No circular dependencies detected in the codebase.',
|
|
754
977
|
scope,
|
|
755
|
-
graphStats
|
|
756
|
-
}, null, 2)
|
|
757
|
-
}
|
|
978
|
+
graphStats
|
|
979
|
+
}, null, 2)
|
|
980
|
+
}
|
|
981
|
+
]
|
|
758
982
|
};
|
|
759
983
|
}
|
|
760
984
|
return {
|
|
761
|
-
content: [
|
|
762
|
-
|
|
985
|
+
content: [
|
|
986
|
+
{
|
|
987
|
+
type: 'text',
|
|
763
988
|
text: JSON.stringify({
|
|
764
|
-
status:
|
|
989
|
+
status: 'warning',
|
|
765
990
|
message: `Found ${cycles.length} circular dependency cycle(s).`,
|
|
766
991
|
scope,
|
|
767
|
-
cycles: cycles.map(c => ({
|
|
992
|
+
cycles: cycles.map((c) => ({
|
|
768
993
|
files: c.files,
|
|
769
994
|
length: c.length,
|
|
770
|
-
severity: c.length === 2 ?
|
|
995
|
+
severity: c.length === 2 ? 'high' : c.length <= 3 ? 'medium' : 'low'
|
|
771
996
|
})),
|
|
772
997
|
count: cycles.length,
|
|
773
998
|
graphStats,
|
|
774
|
-
advice:
|
|
775
|
-
}, null, 2)
|
|
776
|
-
}
|
|
999
|
+
advice: 'Shorter cycles (length 2-3) are typically more problematic. Consider breaking the cycle by extracting shared dependencies.'
|
|
1000
|
+
}, null, 2)
|
|
1001
|
+
}
|
|
1002
|
+
]
|
|
777
1003
|
};
|
|
778
1004
|
}
|
|
779
1005
|
catch (error) {
|
|
780
1006
|
return {
|
|
781
|
-
content: [
|
|
782
|
-
|
|
1007
|
+
content: [
|
|
1008
|
+
{
|
|
1009
|
+
type: 'text',
|
|
783
1010
|
text: JSON.stringify({
|
|
784
|
-
status:
|
|
785
|
-
message:
|
|
786
|
-
error: error instanceof Error ? error.message : String(error)
|
|
787
|
-
}, null, 2)
|
|
788
|
-
}
|
|
1011
|
+
status: 'error',
|
|
1012
|
+
message: 'Failed to detect circular dependencies. Run indexing first.',
|
|
1013
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1014
|
+
}, null, 2)
|
|
1015
|
+
}
|
|
1016
|
+
]
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
case 'remember': {
|
|
1021
|
+
const args_typed = args;
|
|
1022
|
+
const { type = 'decision', category, memory, reason } = args_typed;
|
|
1023
|
+
try {
|
|
1024
|
+
const crypto = await import('crypto');
|
|
1025
|
+
const memoryPath = PATHS.memory;
|
|
1026
|
+
const hashContent = `${type}:${category}:${memory}:${reason}`;
|
|
1027
|
+
const hash = crypto.createHash('sha256').update(hashContent).digest('hex');
|
|
1028
|
+
const id = hash.substring(0, 12);
|
|
1029
|
+
const newMemory = {
|
|
1030
|
+
id,
|
|
1031
|
+
type,
|
|
1032
|
+
category,
|
|
1033
|
+
memory,
|
|
1034
|
+
reason,
|
|
1035
|
+
date: new Date().toISOString()
|
|
1036
|
+
};
|
|
1037
|
+
const result = await appendMemoryFile(memoryPath, newMemory);
|
|
1038
|
+
if (result.status === 'duplicate') {
|
|
1039
|
+
return {
|
|
1040
|
+
content: [
|
|
1041
|
+
{
|
|
1042
|
+
type: 'text',
|
|
1043
|
+
text: JSON.stringify({
|
|
1044
|
+
status: 'info',
|
|
1045
|
+
message: 'This memory was already recorded.',
|
|
1046
|
+
memory: result.memory
|
|
1047
|
+
}, null, 2)
|
|
1048
|
+
}
|
|
1049
|
+
]
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
return {
|
|
1053
|
+
content: [
|
|
1054
|
+
{
|
|
1055
|
+
type: 'text',
|
|
1056
|
+
text: JSON.stringify({
|
|
1057
|
+
status: 'success',
|
|
1058
|
+
message: 'Memory recorded successfully.',
|
|
1059
|
+
memory: result.memory
|
|
1060
|
+
}, null, 2)
|
|
1061
|
+
}
|
|
1062
|
+
]
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
catch (error) {
|
|
1066
|
+
return {
|
|
1067
|
+
content: [
|
|
1068
|
+
{
|
|
1069
|
+
type: 'text',
|
|
1070
|
+
text: JSON.stringify({
|
|
1071
|
+
status: 'error',
|
|
1072
|
+
message: 'Failed to record memory.',
|
|
1073
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1074
|
+
}, null, 2)
|
|
1075
|
+
}
|
|
1076
|
+
]
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
case 'get_memory': {
|
|
1081
|
+
const { category, type, query } = args;
|
|
1082
|
+
try {
|
|
1083
|
+
const memoryPath = PATHS.memory;
|
|
1084
|
+
const allMemories = await readMemoriesFile(memoryPath);
|
|
1085
|
+
if (allMemories.length === 0) {
|
|
1086
|
+
return {
|
|
1087
|
+
content: [
|
|
1088
|
+
{
|
|
1089
|
+
type: 'text',
|
|
1090
|
+
text: JSON.stringify({
|
|
1091
|
+
status: 'success',
|
|
1092
|
+
message: "No team conventions recorded yet. Use 'remember' to build tribal knowledge or memory when the user corrects you over a repeatable pattern.",
|
|
1093
|
+
memories: [],
|
|
1094
|
+
count: 0
|
|
1095
|
+
}, null, 2)
|
|
1096
|
+
}
|
|
1097
|
+
]
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
const filtered = filterMemories(allMemories, { category, type, query });
|
|
1101
|
+
const limited = applyUnfilteredLimit(filtered, { category, type, query }, 20);
|
|
1102
|
+
return {
|
|
1103
|
+
content: [
|
|
1104
|
+
{
|
|
1105
|
+
type: 'text',
|
|
1106
|
+
text: JSON.stringify({
|
|
1107
|
+
status: 'success',
|
|
1108
|
+
count: limited.memories.length,
|
|
1109
|
+
totalCount: limited.totalCount,
|
|
1110
|
+
truncated: limited.truncated,
|
|
1111
|
+
message: limited.truncated
|
|
1112
|
+
? 'Showing 20 most recent. Use filters (category/type/query) for targeted results.'
|
|
1113
|
+
: undefined,
|
|
1114
|
+
memories: limited.memories
|
|
1115
|
+
}, null, 2)
|
|
1116
|
+
}
|
|
1117
|
+
]
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
catch (error) {
|
|
1121
|
+
return {
|
|
1122
|
+
content: [
|
|
1123
|
+
{
|
|
1124
|
+
type: 'text',
|
|
1125
|
+
text: JSON.stringify({
|
|
1126
|
+
status: 'error',
|
|
1127
|
+
message: 'Failed to retrieve memories.',
|
|
1128
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1129
|
+
}, null, 2)
|
|
1130
|
+
}
|
|
1131
|
+
]
|
|
789
1132
|
};
|
|
790
1133
|
}
|
|
791
1134
|
}
|
|
@@ -793,13 +1136,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
793
1136
|
return {
|
|
794
1137
|
content: [
|
|
795
1138
|
{
|
|
796
|
-
type:
|
|
1139
|
+
type: 'text',
|
|
797
1140
|
text: JSON.stringify({
|
|
798
|
-
error: `Unknown tool: ${name}
|
|
799
|
-
}, null, 2)
|
|
800
|
-
}
|
|
1141
|
+
error: `Unknown tool: ${name}`
|
|
1142
|
+
}, null, 2)
|
|
1143
|
+
}
|
|
801
1144
|
],
|
|
802
|
-
isError: true
|
|
1145
|
+
isError: true
|
|
803
1146
|
};
|
|
804
1147
|
}
|
|
805
1148
|
}
|
|
@@ -807,27 +1150,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
807
1150
|
return {
|
|
808
1151
|
content: [
|
|
809
1152
|
{
|
|
810
|
-
type:
|
|
1153
|
+
type: 'text',
|
|
811
1154
|
text: JSON.stringify({
|
|
812
1155
|
error: error instanceof Error ? error.message : String(error),
|
|
813
|
-
stack: error instanceof Error ? error.stack : undefined
|
|
814
|
-
}, null, 2)
|
|
815
|
-
}
|
|
1156
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
1157
|
+
}, null, 2)
|
|
1158
|
+
}
|
|
816
1159
|
],
|
|
817
|
-
isError: true
|
|
1160
|
+
isError: true
|
|
818
1161
|
};
|
|
819
1162
|
}
|
|
820
1163
|
});
|
|
821
1164
|
async function main() {
|
|
822
|
-
// Server startup banner (guarded to avoid stderr during MCP STDIO handshake)
|
|
823
|
-
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
824
|
-
console.error("[DEBUG] Codebase Context MCP Server");
|
|
825
|
-
console.error(`[DEBUG] Root: ${ROOT_PATH}`);
|
|
826
|
-
console.error(`[DEBUG] Analyzers: ${analyzerRegistry
|
|
827
|
-
.getAll()
|
|
828
|
-
.map((a) => a.name)
|
|
829
|
-
.join(", ")}`);
|
|
830
|
-
}
|
|
831
1165
|
// Validate root path exists and is a directory
|
|
832
1166
|
try {
|
|
833
1167
|
const stats = await fs.stat(ROOT_PATH);
|
|
@@ -837,15 +1171,37 @@ async function main() {
|
|
|
837
1171
|
process.exit(1);
|
|
838
1172
|
}
|
|
839
1173
|
}
|
|
840
|
-
catch (
|
|
1174
|
+
catch (_error) {
|
|
841
1175
|
console.error(`ERROR: Root path does not exist: ${ROOT_PATH}`);
|
|
842
1176
|
console.error(`Please specify a valid project directory.`);
|
|
843
1177
|
process.exit(1);
|
|
844
1178
|
}
|
|
1179
|
+
// Migrate legacy structure before server starts
|
|
1180
|
+
try {
|
|
1181
|
+
const migrated = await migrateToNewStructure();
|
|
1182
|
+
if (migrated && process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
1183
|
+
console.error('[DEBUG] Migrated to .codebase-context/ structure');
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
catch (error) {
|
|
1187
|
+
// Non-fatal: continue with current paths
|
|
1188
|
+
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
1189
|
+
console.error('[DEBUG] Migration failed:', error);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
// Server startup banner (guarded to avoid stderr during MCP STDIO handshake)
|
|
1193
|
+
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
1194
|
+
console.error('[DEBUG] Codebase Context MCP Server');
|
|
1195
|
+
console.error(`[DEBUG] Root: ${ROOT_PATH}`);
|
|
1196
|
+
console.error(`[DEBUG] Analyzers: ${analyzerRegistry
|
|
1197
|
+
.getAll()
|
|
1198
|
+
.map((a) => a.name)
|
|
1199
|
+
.join(', ')}`);
|
|
1200
|
+
}
|
|
845
1201
|
// Check for package.json to confirm it's a project root (guarded to avoid stderr during handshake)
|
|
846
1202
|
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
847
1203
|
try {
|
|
848
|
-
await fs.access(path.join(ROOT_PATH,
|
|
1204
|
+
await fs.access(path.join(ROOT_PATH, 'package.json'));
|
|
849
1205
|
console.error(`[DEBUG] Project detected: ${path.basename(ROOT_PATH)}`);
|
|
850
1206
|
}
|
|
851
1207
|
catch {
|
|
@@ -855,29 +1211,29 @@ async function main() {
|
|
|
855
1211
|
const needsIndex = await shouldReindex();
|
|
856
1212
|
if (needsIndex) {
|
|
857
1213
|
if (process.env.CODEBASE_CONTEXT_DEBUG)
|
|
858
|
-
console.error(
|
|
1214
|
+
console.error('[DEBUG] Starting indexing...');
|
|
859
1215
|
performIndexing();
|
|
860
1216
|
}
|
|
861
1217
|
else {
|
|
862
1218
|
if (process.env.CODEBASE_CONTEXT_DEBUG)
|
|
863
|
-
console.error(
|
|
864
|
-
indexState.status =
|
|
1219
|
+
console.error('[DEBUG] Index found. Ready.');
|
|
1220
|
+
indexState.status = 'ready';
|
|
865
1221
|
indexState.lastIndexed = new Date();
|
|
866
1222
|
}
|
|
867
1223
|
const transport = new StdioServerTransport();
|
|
868
1224
|
await server.connect(transport);
|
|
869
1225
|
if (process.env.CODEBASE_CONTEXT_DEBUG)
|
|
870
|
-
console.error(
|
|
1226
|
+
console.error('[DEBUG] Server ready');
|
|
871
1227
|
}
|
|
872
1228
|
// Export server components for programmatic use
|
|
873
1229
|
export { server, performIndexing, resolveRootPath, shouldReindex, TOOLS };
|
|
874
1230
|
// Only auto-start when run directly as CLI (not when imported as module)
|
|
875
1231
|
// Check if this module is the entry point
|
|
876
|
-
const isDirectRun = process.argv[1]?.replace(/\\/g,
|
|
877
|
-
process.argv[1]?.replace(/\\/g,
|
|
1232
|
+
const isDirectRun = process.argv[1]?.replace(/\\/g, '/').endsWith('index.js') ||
|
|
1233
|
+
process.argv[1]?.replace(/\\/g, '/').endsWith('index.ts');
|
|
878
1234
|
if (isDirectRun) {
|
|
879
1235
|
main().catch((error) => {
|
|
880
|
-
console.error(
|
|
1236
|
+
console.error('Fatal:', error);
|
|
881
1237
|
process.exit(1);
|
|
882
1238
|
});
|
|
883
1239
|
}
|