codebase-context 1.2.1 → 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 -336
- 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,168 +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
|
-
|
|
46
|
-
},
|
|
122
|
+
resources: {}
|
|
123
|
+
}
|
|
47
124
|
});
|
|
48
125
|
const TOOLS = [
|
|
49
126
|
{
|
|
50
|
-
name:
|
|
51
|
-
description:
|
|
52
|
-
|
|
53
|
-
|
|
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.',
|
|
54
131
|
inputSchema: {
|
|
55
|
-
type:
|
|
132
|
+
type: 'object',
|
|
56
133
|
properties: {
|
|
57
134
|
query: {
|
|
58
|
-
type:
|
|
59
|
-
description:
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Natural language search query'
|
|
60
137
|
},
|
|
61
138
|
limit: {
|
|
62
|
-
type:
|
|
63
|
-
description:
|
|
64
|
-
default: 5
|
|
139
|
+
type: 'number',
|
|
140
|
+
description: 'Maximum number of results to return (default: 5)',
|
|
141
|
+
default: 5
|
|
65
142
|
},
|
|
66
143
|
filters: {
|
|
67
|
-
type:
|
|
68
|
-
description:
|
|
144
|
+
type: 'object',
|
|
145
|
+
description: 'Optional filters',
|
|
69
146
|
properties: {
|
|
70
147
|
framework: {
|
|
71
|
-
type:
|
|
72
|
-
description:
|
|
148
|
+
type: 'string',
|
|
149
|
+
description: 'Filter by framework (angular, react, vue)'
|
|
73
150
|
},
|
|
74
151
|
language: {
|
|
75
|
-
type:
|
|
76
|
-
description:
|
|
152
|
+
type: 'string',
|
|
153
|
+
description: 'Filter by programming language'
|
|
77
154
|
},
|
|
78
155
|
componentType: {
|
|
79
|
-
type:
|
|
80
|
-
description:
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: 'Filter by component type (component, service, directive, etc.)'
|
|
81
158
|
},
|
|
82
159
|
layer: {
|
|
83
|
-
type:
|
|
84
|
-
description:
|
|
160
|
+
type: 'string',
|
|
161
|
+
description: 'Filter by architectural layer (presentation, business, data, state, core, shared)'
|
|
85
162
|
},
|
|
86
163
|
tags: {
|
|
87
|
-
type:
|
|
88
|
-
items: { type:
|
|
89
|
-
description:
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
164
|
+
type: 'array',
|
|
165
|
+
items: { type: 'string' },
|
|
166
|
+
description: 'Filter by tags'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
93
170
|
},
|
|
94
|
-
required: [
|
|
95
|
-
}
|
|
171
|
+
required: ['query']
|
|
172
|
+
}
|
|
96
173
|
},
|
|
97
174
|
{
|
|
98
|
-
name:
|
|
99
|
-
description:
|
|
100
|
-
|
|
175
|
+
name: 'get_codebase_metadata',
|
|
176
|
+
description: 'Get codebase metadata including framework information, dependencies, architecture patterns, ' +
|
|
177
|
+
'and project statistics.',
|
|
101
178
|
inputSchema: {
|
|
102
|
-
type:
|
|
103
|
-
properties: {}
|
|
104
|
-
}
|
|
179
|
+
type: 'object',
|
|
180
|
+
properties: {}
|
|
181
|
+
}
|
|
105
182
|
},
|
|
106
183
|
{
|
|
107
|
-
name:
|
|
108
|
-
description:
|
|
109
|
-
|
|
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.',
|
|
110
187
|
inputSchema: {
|
|
111
|
-
type:
|
|
112
|
-
properties: {}
|
|
113
|
-
}
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {}
|
|
190
|
+
}
|
|
114
191
|
},
|
|
115
192
|
{
|
|
116
|
-
name:
|
|
117
|
-
description:
|
|
118
|
-
|
|
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.',
|
|
119
196
|
inputSchema: {
|
|
120
|
-
type:
|
|
197
|
+
type: 'object',
|
|
121
198
|
properties: {
|
|
122
199
|
reason: {
|
|
123
|
-
type:
|
|
124
|
-
description:
|
|
200
|
+
type: 'string',
|
|
201
|
+
description: 'Reason for refreshing the index (for logging)'
|
|
125
202
|
},
|
|
126
203
|
incrementalOnly: {
|
|
127
|
-
type:
|
|
128
|
-
description:
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
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
|
+
}
|
|
132
209
|
},
|
|
133
210
|
{
|
|
134
|
-
name:
|
|
135
|
-
description:
|
|
211
|
+
name: 'get_style_guide',
|
|
212
|
+
description: 'Query style guide rules and architectural patterns from project documentation.',
|
|
136
213
|
inputSchema: {
|
|
137
|
-
type:
|
|
214
|
+
type: 'object',
|
|
138
215
|
properties: {
|
|
139
216
|
query: {
|
|
140
|
-
type:
|
|
141
|
-
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")'
|
|
142
219
|
},
|
|
143
220
|
category: {
|
|
144
|
-
type:
|
|
145
|
-
description:
|
|
146
|
-
}
|
|
221
|
+
type: 'string',
|
|
222
|
+
description: 'Filter by category (naming, structure, patterns, testing)'
|
|
223
|
+
}
|
|
147
224
|
},
|
|
148
|
-
required: [
|
|
149
|
-
}
|
|
225
|
+
required: ['query']
|
|
226
|
+
}
|
|
150
227
|
},
|
|
151
228
|
{
|
|
152
|
-
name:
|
|
153
|
-
description:
|
|
154
|
-
|
|
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.',
|
|
155
232
|
inputSchema: {
|
|
156
|
-
type:
|
|
233
|
+
type: 'object',
|
|
157
234
|
properties: {
|
|
158
235
|
category: {
|
|
159
|
-
type:
|
|
160
|
-
description:
|
|
161
|
-
enum: [
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
236
|
+
type: 'string',
|
|
237
|
+
description: 'Pattern category to retrieve',
|
|
238
|
+
enum: ['all', 'di', 'state', 'testing', 'libraries']
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
165
242
|
},
|
|
166
243
|
{
|
|
167
|
-
name:
|
|
168
|
-
description:
|
|
244
|
+
name: 'get_component_usage',
|
|
245
|
+
description: 'Find WHERE a library or component is used in the codebase. ' +
|
|
169
246
|
"This is 'Find Usages' - returns all files that import a given package/module. " +
|
|
170
247
|
"Example: get_component_usage('@mycompany/utils') → shows all 34 files using it.",
|
|
171
248
|
inputSchema: {
|
|
172
|
-
type:
|
|
249
|
+
type: 'object',
|
|
173
250
|
properties: {
|
|
174
251
|
name: {
|
|
175
|
-
type:
|
|
176
|
-
description: "Import source to find usages for (e.g., 'primeng/table', '@mycompany/ui/button', 'lodash')"
|
|
177
|
-
}
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: "Import source to find usages for (e.g., 'primeng/table', '@mycompany/ui/button', 'lodash')"
|
|
254
|
+
}
|
|
178
255
|
},
|
|
179
|
-
required: [
|
|
180
|
-
}
|
|
256
|
+
required: ['name']
|
|
257
|
+
}
|
|
181
258
|
},
|
|
182
259
|
{
|
|
183
|
-
name:
|
|
184
|
-
description:
|
|
185
|
-
|
|
186
|
-
|
|
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).',
|
|
187
264
|
inputSchema: {
|
|
188
|
-
type:
|
|
265
|
+
type: 'object',
|
|
189
266
|
properties: {
|
|
190
267
|
scope: {
|
|
191
|
-
type:
|
|
192
|
-
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)'
|
|
193
303
|
},
|
|
304
|
+
reason: {
|
|
305
|
+
type: 'string',
|
|
306
|
+
description: 'Why this matters or what breaks otherwise'
|
|
307
|
+
}
|
|
194
308
|
},
|
|
195
|
-
|
|
309
|
+
required: ['type', 'category', 'memory', 'reason']
|
|
310
|
+
}
|
|
196
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
|
+
}
|
|
197
337
|
];
|
|
198
338
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
199
339
|
return { tools: TOOLS };
|
|
@@ -201,59 +341,59 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
201
341
|
// MCP Resources - Proactive context injection
|
|
202
342
|
const RESOURCES = [
|
|
203
343
|
{
|
|
204
|
-
uri:
|
|
205
|
-
name:
|
|
206
|
-
description:
|
|
207
|
-
|
|
208
|
-
mimeType:
|
|
209
|
-
}
|
|
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
|
+
}
|
|
210
350
|
];
|
|
211
351
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
212
352
|
return { resources: RESOURCES };
|
|
213
353
|
});
|
|
214
354
|
async function generateCodebaseContext() {
|
|
215
|
-
const intelligencePath =
|
|
355
|
+
const intelligencePath = PATHS.intelligence;
|
|
216
356
|
try {
|
|
217
|
-
const content = await fs.readFile(intelligencePath,
|
|
357
|
+
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
218
358
|
const intelligence = JSON.parse(content);
|
|
219
359
|
const lines = [];
|
|
220
|
-
lines.push(
|
|
221
|
-
lines.push(
|
|
222
|
-
lines.push(
|
|
223
|
-
lines.push(
|
|
224
|
-
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('');
|
|
225
365
|
// Library usage - sorted by count
|
|
226
366
|
const libraryEntries = Object.entries(intelligence.libraryUsage || {})
|
|
227
367
|
.map(([lib, data]) => ({
|
|
228
368
|
lib,
|
|
229
|
-
count: data.count
|
|
369
|
+
count: data.count
|
|
230
370
|
}))
|
|
231
371
|
.sort((a, b) => b.count - a.count);
|
|
232
372
|
if (libraryEntries.length > 0) {
|
|
233
|
-
lines.push(
|
|
234
|
-
lines.push(
|
|
373
|
+
lines.push('## Libraries Actually Used (Top 15)');
|
|
374
|
+
lines.push('');
|
|
235
375
|
for (const { lib, count } of libraryEntries.slice(0, 15)) {
|
|
236
376
|
lines.push(`- **${lib}** (${count} uses)`);
|
|
237
377
|
}
|
|
238
|
-
lines.push(
|
|
378
|
+
lines.push('');
|
|
239
379
|
}
|
|
240
380
|
// Show tsconfig paths if available (helps AI understand internal imports)
|
|
241
381
|
if (intelligence.tsconfigPaths && Object.keys(intelligence.tsconfigPaths).length > 0) {
|
|
242
|
-
lines.push(
|
|
243
|
-
lines.push(
|
|
244
|
-
lines.push(
|
|
382
|
+
lines.push('## Import Aliases (from tsconfig.json)');
|
|
383
|
+
lines.push('');
|
|
384
|
+
lines.push('These path aliases map to internal project code:');
|
|
245
385
|
for (const [alias, paths] of Object.entries(intelligence.tsconfigPaths)) {
|
|
246
|
-
lines.push(`- \`${alias}\` → ${paths.join(
|
|
386
|
+
lines.push(`- \`${alias}\` → ${paths.join(', ')}`);
|
|
247
387
|
}
|
|
248
|
-
lines.push(
|
|
388
|
+
lines.push('');
|
|
249
389
|
}
|
|
250
390
|
// Pattern consensus
|
|
251
391
|
if (intelligence.patterns && Object.keys(intelligence.patterns).length > 0) {
|
|
252
392
|
lines.push("## YOUR Codebase's Actual Patterns (Not Generic Best Practices)");
|
|
253
|
-
lines.push(
|
|
254
|
-
lines.push(
|
|
255
|
-
lines.push(
|
|
256
|
-
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('');
|
|
257
397
|
for (const [category, data] of Object.entries(intelligence.patterns)) {
|
|
258
398
|
const patternData = data;
|
|
259
399
|
const primary = patternData.primary;
|
|
@@ -261,7 +401,7 @@ async function generateCodebaseContext() {
|
|
|
261
401
|
continue;
|
|
262
402
|
const percentage = parseInt(primary.frequency);
|
|
263
403
|
const categoryName = category
|
|
264
|
-
.replace(/([A-Z])/g,
|
|
404
|
+
.replace(/([A-Z])/g, ' $1')
|
|
265
405
|
.trim()
|
|
266
406
|
.replace(/^./, (str) => str.toUpperCase());
|
|
267
407
|
if (percentage === 100) {
|
|
@@ -295,40 +435,40 @@ async function generateCodebaseContext() {
|
|
|
295
435
|
}
|
|
296
436
|
lines.push(` → ASK the team which approach to use for new features`);
|
|
297
437
|
}
|
|
298
|
-
lines.push(
|
|
438
|
+
lines.push('');
|
|
299
439
|
}
|
|
300
440
|
}
|
|
301
|
-
lines.push(
|
|
441
|
+
lines.push('---');
|
|
302
442
|
lines.push(`Generated: ${intelligence.generatedAt || new Date().toISOString()}`);
|
|
303
|
-
return lines.join(
|
|
443
|
+
return lines.join('\n');
|
|
304
444
|
}
|
|
305
445
|
catch (error) {
|
|
306
|
-
return (
|
|
307
|
-
|
|
446
|
+
return ('# Codebase Intelligence\n\n' +
|
|
447
|
+
'Intelligence data not yet generated. Run indexing first.\n' +
|
|
308
448
|
`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
309
449
|
}
|
|
310
450
|
}
|
|
311
451
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
312
452
|
const uri = request.params.uri;
|
|
313
|
-
if (uri ===
|
|
453
|
+
if (uri === 'codebase://context') {
|
|
314
454
|
const content = await generateCodebaseContext();
|
|
315
455
|
return {
|
|
316
456
|
contents: [
|
|
317
457
|
{
|
|
318
458
|
uri,
|
|
319
|
-
mimeType:
|
|
320
|
-
text: content
|
|
321
|
-
}
|
|
322
|
-
]
|
|
459
|
+
mimeType: 'text/plain',
|
|
460
|
+
text: content
|
|
461
|
+
}
|
|
462
|
+
]
|
|
323
463
|
};
|
|
324
464
|
}
|
|
325
465
|
throw new Error(`Unknown resource: ${uri}`);
|
|
326
466
|
});
|
|
327
467
|
async function performIndexing() {
|
|
328
|
-
indexState.status =
|
|
468
|
+
indexState.status = 'indexing';
|
|
329
469
|
console.error(`Indexing: ${ROOT_PATH}`);
|
|
330
470
|
try {
|
|
331
|
-
let lastLoggedProgress = { phase:
|
|
471
|
+
let lastLoggedProgress = { phase: '', percentage: -1 };
|
|
332
472
|
const indexer = new CodebaseIndexer({
|
|
333
473
|
rootPath: ROOT_PATH,
|
|
334
474
|
onProgress: (progress) => {
|
|
@@ -339,23 +479,23 @@ async function performIndexing() {
|
|
|
339
479
|
console.error(`[${progress.phase}] ${progress.percentage}%`);
|
|
340
480
|
lastLoggedProgress = { phase: progress.phase, percentage: progress.percentage };
|
|
341
481
|
}
|
|
342
|
-
}
|
|
482
|
+
}
|
|
343
483
|
});
|
|
344
484
|
indexState.indexer = indexer;
|
|
345
485
|
const stats = await indexer.index();
|
|
346
|
-
indexState.status =
|
|
486
|
+
indexState.status = 'ready';
|
|
347
487
|
indexState.lastIndexed = new Date();
|
|
348
488
|
indexState.stats = stats;
|
|
349
489
|
console.error(`Complete: ${stats.indexedFiles} files, ${stats.totalChunks} chunks in ${(stats.duration / 1000).toFixed(2)}s`);
|
|
350
490
|
}
|
|
351
491
|
catch (error) {
|
|
352
|
-
indexState.status =
|
|
492
|
+
indexState.status = 'error';
|
|
353
493
|
indexState.error = error instanceof Error ? error.message : String(error);
|
|
354
|
-
console.error(
|
|
494
|
+
console.error('Indexing failed:', indexState.error);
|
|
355
495
|
}
|
|
356
496
|
}
|
|
357
497
|
async function shouldReindex() {
|
|
358
|
-
const indexPath =
|
|
498
|
+
const indexPath = PATHS.keywordIndex;
|
|
359
499
|
try {
|
|
360
500
|
await fs.access(indexPath);
|
|
361
501
|
return false;
|
|
@@ -368,43 +508,99 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
368
508
|
const { name, arguments: args } = request.params;
|
|
369
509
|
try {
|
|
370
510
|
switch (name) {
|
|
371
|
-
case
|
|
511
|
+
case 'search_codebase': {
|
|
372
512
|
const { query, limit, filters } = args;
|
|
373
|
-
if (indexState.status ===
|
|
513
|
+
if (indexState.status === 'indexing') {
|
|
374
514
|
return {
|
|
375
515
|
content: [
|
|
376
516
|
{
|
|
377
|
-
type:
|
|
517
|
+
type: 'text',
|
|
378
518
|
text: JSON.stringify({
|
|
379
|
-
status:
|
|
380
|
-
message:
|
|
381
|
-
progress: indexState.indexer?.getProgress()
|
|
382
|
-
}, null, 2)
|
|
383
|
-
}
|
|
384
|
-
]
|
|
519
|
+
status: 'indexing',
|
|
520
|
+
message: 'Index is still being built. Retry in a moment.',
|
|
521
|
+
progress: indexState.indexer?.getProgress()
|
|
522
|
+
}, null, 2)
|
|
523
|
+
}
|
|
524
|
+
]
|
|
385
525
|
};
|
|
386
526
|
}
|
|
387
|
-
if (indexState.status ===
|
|
527
|
+
if (indexState.status === 'error') {
|
|
388
528
|
return {
|
|
389
529
|
content: [
|
|
390
530
|
{
|
|
391
|
-
type:
|
|
531
|
+
type: 'text',
|
|
392
532
|
text: JSON.stringify({
|
|
393
|
-
status:
|
|
394
|
-
message: `Indexing failed: ${indexState.error}
|
|
395
|
-
}, null, 2)
|
|
396
|
-
}
|
|
397
|
-
]
|
|
533
|
+
status: 'error',
|
|
534
|
+
message: `Indexing failed: ${indexState.error}`
|
|
535
|
+
}, null, 2)
|
|
536
|
+
}
|
|
537
|
+
]
|
|
398
538
|
};
|
|
399
539
|
}
|
|
400
540
|
const searcher = new CodebaseSearcher(ROOT_PATH);
|
|
401
|
-
|
|
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);
|
|
402
598
|
return {
|
|
403
599
|
content: [
|
|
404
600
|
{
|
|
405
|
-
type:
|
|
601
|
+
type: 'text',
|
|
406
602
|
text: JSON.stringify({
|
|
407
|
-
status:
|
|
603
|
+
status: 'success',
|
|
408
604
|
results: results.map((r) => ({
|
|
409
605
|
summary: r.summary,
|
|
410
606
|
snippet: r.snippet,
|
|
@@ -414,22 +610,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
414
610
|
componentType: r.componentType,
|
|
415
611
|
layer: r.layer,
|
|
416
612
|
framework: r.framework,
|
|
417
|
-
// v1.2: Pattern momentum awareness
|
|
418
613
|
trend: r.trend,
|
|
419
|
-
patternWarning: r.patternWarning
|
|
614
|
+
patternWarning: r.patternWarning
|
|
420
615
|
})),
|
|
421
616
|
totalResults: results.length,
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
617
|
+
...(relatedMemories.length > 0 && { relatedMemories })
|
|
618
|
+
}, null, 2)
|
|
619
|
+
}
|
|
620
|
+
]
|
|
425
621
|
};
|
|
426
622
|
}
|
|
427
|
-
case
|
|
623
|
+
case 'get_indexing_status': {
|
|
428
624
|
const progress = indexState.indexer?.getProgress();
|
|
429
625
|
return {
|
|
430
626
|
content: [
|
|
431
627
|
{
|
|
432
|
-
type:
|
|
628
|
+
type: 'text',
|
|
433
629
|
text: JSON.stringify({
|
|
434
630
|
status: indexState.status,
|
|
435
631
|
rootPath: ROOT_PATH,
|
|
@@ -439,7 +635,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
439
635
|
totalFiles: indexState.stats.totalFiles,
|
|
440
636
|
indexedFiles: indexState.stats.indexedFiles,
|
|
441
637
|
totalChunks: indexState.stats.totalChunks,
|
|
442
|
-
duration: `${(indexState.stats.duration / 1000).toFixed(2)}s
|
|
638
|
+
duration: `${(indexState.stats.duration / 1000).toFixed(2)}s`
|
|
443
639
|
}
|
|
444
640
|
: undefined,
|
|
445
641
|
progress: progress
|
|
@@ -447,20 +643,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
447
643
|
phase: progress.phase,
|
|
448
644
|
percentage: progress.percentage,
|
|
449
645
|
filesProcessed: progress.filesProcessed,
|
|
450
|
-
totalFiles: progress.totalFiles
|
|
646
|
+
totalFiles: progress.totalFiles
|
|
451
647
|
}
|
|
452
648
|
: undefined,
|
|
453
649
|
error: indexState.error,
|
|
454
|
-
hint:
|
|
455
|
-
}, null, 2)
|
|
456
|
-
}
|
|
457
|
-
]
|
|
650
|
+
hint: 'Use refresh_index to manually trigger re-indexing when needed.'
|
|
651
|
+
}, null, 2)
|
|
652
|
+
}
|
|
653
|
+
]
|
|
458
654
|
};
|
|
459
655
|
}
|
|
460
|
-
case
|
|
656
|
+
case 'refresh_index': {
|
|
461
657
|
const { reason, incrementalOnly } = args;
|
|
462
|
-
const mode = incrementalOnly ?
|
|
463
|
-
console.error(`Refresh requested (${mode}): ${reason ||
|
|
658
|
+
const mode = incrementalOnly ? 'incremental' : 'full';
|
|
659
|
+
console.error(`Refresh requested (${mode}): ${reason || 'Manual trigger'}`);
|
|
464
660
|
// TODO: When incremental indexing is implemented (Phase 2),
|
|
465
661
|
// use `incrementalOnly` to only re-index changed files.
|
|
466
662
|
// For now, always do full re-index but acknowledge the intention.
|
|
@@ -468,48 +664,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
468
664
|
return {
|
|
469
665
|
content: [
|
|
470
666
|
{
|
|
471
|
-
type:
|
|
667
|
+
type: 'text',
|
|
472
668
|
text: JSON.stringify({
|
|
473
|
-
status:
|
|
669
|
+
status: 'started',
|
|
474
670
|
mode,
|
|
475
671
|
message: incrementalOnly
|
|
476
|
-
?
|
|
477
|
-
:
|
|
672
|
+
? 'Incremental re-indexing requested. Check status with get_indexing_status.'
|
|
673
|
+
: 'Full re-indexing started. Check status with get_indexing_status.',
|
|
478
674
|
reason,
|
|
479
675
|
note: incrementalOnly
|
|
480
|
-
?
|
|
481
|
-
: undefined
|
|
482
|
-
}, null, 2)
|
|
483
|
-
}
|
|
484
|
-
]
|
|
676
|
+
? 'Incremental mode requested. Full re-index for now; true incremental indexing coming in Phase 2.'
|
|
677
|
+
: undefined
|
|
678
|
+
}, null, 2)
|
|
679
|
+
}
|
|
680
|
+
]
|
|
485
681
|
};
|
|
486
682
|
}
|
|
487
|
-
case
|
|
683
|
+
case 'get_codebase_metadata': {
|
|
488
684
|
const indexer = new CodebaseIndexer({ rootPath: ROOT_PATH });
|
|
489
685
|
const metadata = await indexer.detectMetadata();
|
|
490
686
|
// Load team patterns from intelligence file
|
|
491
687
|
let teamPatterns = {};
|
|
492
688
|
try {
|
|
493
|
-
const intelligencePath =
|
|
494
|
-
const intelligenceContent = await fs.readFile(intelligencePath,
|
|
689
|
+
const intelligencePath = PATHS.intelligence;
|
|
690
|
+
const intelligenceContent = await fs.readFile(intelligencePath, 'utf-8');
|
|
495
691
|
const intelligence = JSON.parse(intelligenceContent);
|
|
496
692
|
if (intelligence.patterns) {
|
|
497
693
|
teamPatterns = {
|
|
498
694
|
dependencyInjection: intelligence.patterns.dependencyInjection,
|
|
499
695
|
stateManagement: intelligence.patterns.stateManagement,
|
|
500
|
-
componentInputs: intelligence.patterns.componentInputs
|
|
696
|
+
componentInputs: intelligence.patterns.componentInputs
|
|
501
697
|
};
|
|
502
698
|
}
|
|
503
699
|
}
|
|
504
|
-
catch (
|
|
700
|
+
catch (_error) {
|
|
505
701
|
// No intelligence file or parsing error
|
|
506
702
|
}
|
|
507
703
|
return {
|
|
508
704
|
content: [
|
|
509
705
|
{
|
|
510
|
-
type:
|
|
706
|
+
type: 'text',
|
|
511
707
|
text: JSON.stringify({
|
|
512
|
-
status:
|
|
708
|
+
status: 'success',
|
|
513
709
|
metadata: {
|
|
514
710
|
name: metadata.name,
|
|
515
711
|
framework: metadata.framework,
|
|
@@ -518,23 +714,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
518
714
|
architecture: metadata.architecture,
|
|
519
715
|
projectStructure: metadata.projectStructure,
|
|
520
716
|
statistics: metadata.statistics,
|
|
521
|
-
teamPatterns
|
|
522
|
-
}
|
|
523
|
-
}, null, 2)
|
|
524
|
-
}
|
|
525
|
-
]
|
|
717
|
+
teamPatterns
|
|
718
|
+
}
|
|
719
|
+
}, null, 2)
|
|
720
|
+
}
|
|
721
|
+
]
|
|
526
722
|
};
|
|
527
723
|
}
|
|
528
|
-
case
|
|
724
|
+
case 'get_style_guide': {
|
|
529
725
|
const { query, category } = args;
|
|
530
726
|
const styleGuidePatterns = [
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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'
|
|
538
734
|
];
|
|
539
735
|
const foundGuides = [];
|
|
540
736
|
const queryLower = query.toLowerCase();
|
|
@@ -543,13 +739,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
543
739
|
try {
|
|
544
740
|
const files = await glob(pattern, {
|
|
545
741
|
cwd: ROOT_PATH,
|
|
546
|
-
absolute: true
|
|
742
|
+
absolute: true
|
|
547
743
|
});
|
|
548
744
|
for (const file of files) {
|
|
549
745
|
try {
|
|
550
746
|
// Normalize line endings to \n for consistent output
|
|
551
|
-
const rawContent = await fs.readFile(file,
|
|
552
|
-
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');
|
|
553
749
|
const relativePath = path.relative(ROOT_PATH, file);
|
|
554
750
|
// Find relevant sections based on query
|
|
555
751
|
const sections = content.split(/^##\s+/m);
|
|
@@ -560,27 +756,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
560
756
|
if (isRelevant) {
|
|
561
757
|
// Limit section size to ~500 words
|
|
562
758
|
const words = section.split(/\s+/);
|
|
563
|
-
const truncated = words.slice(0, 500).join(
|
|
564
|
-
relevantSections.push(
|
|
565
|
-
(words.length > 500
|
|
566
|
-
? truncated + "..."
|
|
567
|
-
: section.trim()));
|
|
759
|
+
const truncated = words.slice(0, 500).join(' ');
|
|
760
|
+
relevantSections.push('## ' + (words.length > 500 ? truncated + '...' : section.trim()));
|
|
568
761
|
}
|
|
569
762
|
}
|
|
570
763
|
if (relevantSections.length > 0) {
|
|
571
764
|
foundGuides.push({
|
|
572
765
|
file: relativePath,
|
|
573
|
-
content: content.slice(0, 200) +
|
|
574
|
-
relevantSections: relevantSections.slice(0, 3)
|
|
766
|
+
content: content.slice(0, 200) + '...',
|
|
767
|
+
relevantSections: relevantSections.slice(0, 3) // Max 3 sections per file
|
|
575
768
|
});
|
|
576
769
|
}
|
|
577
770
|
}
|
|
578
|
-
catch (
|
|
771
|
+
catch (_e) {
|
|
579
772
|
// Skip unreadable files
|
|
580
773
|
}
|
|
581
774
|
}
|
|
582
775
|
}
|
|
583
|
-
catch (
|
|
776
|
+
catch (_e) {
|
|
584
777
|
// Pattern didn't match, continue
|
|
585
778
|
}
|
|
586
779
|
}
|
|
@@ -588,86 +781,106 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
588
781
|
return {
|
|
589
782
|
content: [
|
|
590
783
|
{
|
|
591
|
-
type:
|
|
784
|
+
type: 'text',
|
|
592
785
|
text: JSON.stringify({
|
|
593
|
-
status:
|
|
786
|
+
status: 'no_results',
|
|
594
787
|
message: `No style guide content found matching: ${query}`,
|
|
595
788
|
searchedPatterns: styleGuidePatterns,
|
|
596
|
-
hint: "Try broader terms like 'naming', 'patterns', 'testing', 'components'"
|
|
597
|
-
}, null, 2)
|
|
598
|
-
}
|
|
599
|
-
]
|
|
789
|
+
hint: "Try broader terms like 'naming', 'patterns', 'testing', 'components'"
|
|
790
|
+
}, null, 2)
|
|
791
|
+
}
|
|
792
|
+
]
|
|
600
793
|
};
|
|
601
794
|
}
|
|
602
795
|
return {
|
|
603
796
|
content: [
|
|
604
797
|
{
|
|
605
|
-
type:
|
|
798
|
+
type: 'text',
|
|
606
799
|
text: JSON.stringify({
|
|
607
|
-
status:
|
|
800
|
+
status: 'success',
|
|
608
801
|
query,
|
|
609
802
|
category,
|
|
610
803
|
results: foundGuides,
|
|
611
|
-
totalFiles: foundGuides.length
|
|
612
|
-
}, null, 2)
|
|
613
|
-
}
|
|
614
|
-
]
|
|
804
|
+
totalFiles: foundGuides.length
|
|
805
|
+
}, null, 2)
|
|
806
|
+
}
|
|
807
|
+
]
|
|
615
808
|
};
|
|
616
809
|
}
|
|
617
|
-
case
|
|
810
|
+
case 'get_team_patterns': {
|
|
618
811
|
const { category } = args;
|
|
619
812
|
try {
|
|
620
|
-
const intelligencePath =
|
|
621
|
-
const content = await fs.readFile(intelligencePath,
|
|
813
|
+
const intelligencePath = PATHS.intelligence;
|
|
814
|
+
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
622
815
|
const intelligence = JSON.parse(content);
|
|
623
|
-
const result = { status:
|
|
624
|
-
if (category ===
|
|
816
|
+
const result = { status: 'success' };
|
|
817
|
+
if (category === 'all' || !category) {
|
|
625
818
|
result.patterns = intelligence.patterns || {};
|
|
626
819
|
result.goldenFiles = intelligence.goldenFiles || [];
|
|
627
820
|
if (intelligence.tsconfigPaths) {
|
|
628
821
|
result.tsconfigPaths = intelligence.tsconfigPaths;
|
|
629
822
|
}
|
|
630
823
|
}
|
|
631
|
-
else if (category ===
|
|
824
|
+
else if (category === 'di') {
|
|
632
825
|
result.dependencyInjection = intelligence.patterns?.dependencyInjection;
|
|
633
826
|
}
|
|
634
|
-
else if (category ===
|
|
827
|
+
else if (category === 'state') {
|
|
635
828
|
result.stateManagement = intelligence.patterns?.stateManagement;
|
|
636
829
|
}
|
|
637
|
-
else if (category ===
|
|
830
|
+
else if (category === 'testing') {
|
|
638
831
|
result.testingFramework = intelligence.patterns?.testingFramework;
|
|
639
832
|
result.testMocking = intelligence.patterns?.testMocking;
|
|
640
833
|
}
|
|
641
|
-
else if (category ===
|
|
834
|
+
else if (category === 'libraries') {
|
|
642
835
|
result.topUsed = intelligence.importGraph?.topUsed || [];
|
|
643
836
|
if (intelligence.tsconfigPaths) {
|
|
644
837
|
result.tsconfigPaths = intelligence.tsconfigPaths;
|
|
645
838
|
}
|
|
646
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
|
+
}
|
|
647
860
|
return {
|
|
648
|
-
content: [{ type:
|
|
861
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
649
862
|
};
|
|
650
863
|
}
|
|
651
864
|
catch (error) {
|
|
652
865
|
return {
|
|
653
866
|
content: [
|
|
654
867
|
{
|
|
655
|
-
type:
|
|
868
|
+
type: 'text',
|
|
656
869
|
text: JSON.stringify({
|
|
657
|
-
status:
|
|
658
|
-
message:
|
|
659
|
-
error: error instanceof Error ? error.message : String(error)
|
|
660
|
-
}, null, 2)
|
|
661
|
-
}
|
|
662
|
-
]
|
|
870
|
+
status: 'error',
|
|
871
|
+
message: 'Failed to load team patterns',
|
|
872
|
+
error: error instanceof Error ? error.message : String(error)
|
|
873
|
+
}, null, 2)
|
|
874
|
+
}
|
|
875
|
+
]
|
|
663
876
|
};
|
|
664
877
|
}
|
|
665
878
|
}
|
|
666
|
-
case
|
|
879
|
+
case 'get_component_usage': {
|
|
667
880
|
const { name: componentName } = args;
|
|
668
881
|
try {
|
|
669
|
-
const intelligencePath =
|
|
670
|
-
const content = await fs.readFile(intelligencePath,
|
|
882
|
+
const intelligencePath = PATHS.intelligence;
|
|
883
|
+
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
671
884
|
const intelligence = JSON.parse(content);
|
|
672
885
|
const importGraph = intelligence.importGraph || {};
|
|
673
886
|
const usages = importGraph.usages || {};
|
|
@@ -675,68 +888,76 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
675
888
|
let matchedUsage = usages[componentName];
|
|
676
889
|
// Try partial match if exact match not found
|
|
677
890
|
if (!matchedUsage) {
|
|
678
|
-
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));
|
|
679
892
|
if (matchingKeys.length > 0) {
|
|
680
893
|
matchedUsage = usages[matchingKeys[0]];
|
|
681
894
|
}
|
|
682
895
|
}
|
|
683
896
|
if (matchedUsage) {
|
|
684
897
|
return {
|
|
685
|
-
content: [
|
|
686
|
-
|
|
898
|
+
content: [
|
|
899
|
+
{
|
|
900
|
+
type: 'text',
|
|
687
901
|
text: JSON.stringify({
|
|
688
|
-
status:
|
|
902
|
+
status: 'success',
|
|
689
903
|
component: componentName,
|
|
690
904
|
usageCount: matchedUsage.usageCount,
|
|
691
|
-
usedIn: matchedUsage.usedIn
|
|
692
|
-
}, null, 2)
|
|
693
|
-
}
|
|
905
|
+
usedIn: matchedUsage.usedIn
|
|
906
|
+
}, null, 2)
|
|
907
|
+
}
|
|
908
|
+
]
|
|
694
909
|
};
|
|
695
910
|
}
|
|
696
911
|
else {
|
|
697
912
|
// Show top used as alternatives
|
|
698
913
|
const topUsed = importGraph.topUsed || [];
|
|
699
914
|
return {
|
|
700
|
-
content: [
|
|
701
|
-
|
|
915
|
+
content: [
|
|
916
|
+
{
|
|
917
|
+
type: 'text',
|
|
702
918
|
text: JSON.stringify({
|
|
703
|
-
status:
|
|
919
|
+
status: 'not_found',
|
|
704
920
|
component: componentName,
|
|
705
921
|
message: `No usages found for '${componentName}'.`,
|
|
706
|
-
suggestions: topUsed.slice(0, 10)
|
|
707
|
-
}, null, 2)
|
|
708
|
-
}
|
|
922
|
+
suggestions: topUsed.slice(0, 10)
|
|
923
|
+
}, null, 2)
|
|
924
|
+
}
|
|
925
|
+
]
|
|
709
926
|
};
|
|
710
927
|
}
|
|
711
928
|
}
|
|
712
929
|
catch (error) {
|
|
713
930
|
return {
|
|
714
|
-
content: [
|
|
715
|
-
|
|
931
|
+
content: [
|
|
932
|
+
{
|
|
933
|
+
type: 'text',
|
|
716
934
|
text: JSON.stringify({
|
|
717
|
-
status:
|
|
718
|
-
message:
|
|
719
|
-
error: error instanceof Error ? error.message : String(error)
|
|
720
|
-
}, null, 2)
|
|
721
|
-
}
|
|
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
|
+
]
|
|
722
941
|
};
|
|
723
942
|
}
|
|
724
943
|
}
|
|
725
|
-
case
|
|
944
|
+
case 'detect_circular_dependencies': {
|
|
726
945
|
const { scope } = args;
|
|
727
946
|
try {
|
|
728
|
-
const intelligencePath =
|
|
729
|
-
const content = await fs.readFile(intelligencePath,
|
|
947
|
+
const intelligencePath = PATHS.intelligence;
|
|
948
|
+
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
730
949
|
const intelligence = JSON.parse(content);
|
|
731
950
|
if (!intelligence.internalFileGraph) {
|
|
732
951
|
return {
|
|
733
|
-
content: [
|
|
734
|
-
|
|
952
|
+
content: [
|
|
953
|
+
{
|
|
954
|
+
type: 'text',
|
|
735
955
|
text: JSON.stringify({
|
|
736
|
-
status:
|
|
737
|
-
message:
|
|
738
|
-
}, null, 2)
|
|
739
|
-
}
|
|
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
|
+
]
|
|
740
961
|
};
|
|
741
962
|
}
|
|
742
963
|
// Reconstruct the graph from stored data
|
|
@@ -745,48 +966,169 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
745
966
|
const graphStats = intelligence.internalFileGraph.stats || graph.getStats();
|
|
746
967
|
if (cycles.length === 0) {
|
|
747
968
|
return {
|
|
748
|
-
content: [
|
|
749
|
-
|
|
969
|
+
content: [
|
|
970
|
+
{
|
|
971
|
+
type: 'text',
|
|
750
972
|
text: JSON.stringify({
|
|
751
|
-
status:
|
|
973
|
+
status: 'success',
|
|
752
974
|
message: scope
|
|
753
975
|
? `No circular dependencies detected in scope: ${scope}`
|
|
754
|
-
:
|
|
976
|
+
: 'No circular dependencies detected in the codebase.',
|
|
755
977
|
scope,
|
|
756
|
-
graphStats
|
|
757
|
-
}, null, 2)
|
|
758
|
-
}
|
|
978
|
+
graphStats
|
|
979
|
+
}, null, 2)
|
|
980
|
+
}
|
|
981
|
+
]
|
|
759
982
|
};
|
|
760
983
|
}
|
|
761
984
|
return {
|
|
762
|
-
content: [
|
|
763
|
-
|
|
985
|
+
content: [
|
|
986
|
+
{
|
|
987
|
+
type: 'text',
|
|
764
988
|
text: JSON.stringify({
|
|
765
|
-
status:
|
|
989
|
+
status: 'warning',
|
|
766
990
|
message: `Found ${cycles.length} circular dependency cycle(s).`,
|
|
767
991
|
scope,
|
|
768
|
-
cycles: cycles.map(c => ({
|
|
992
|
+
cycles: cycles.map((c) => ({
|
|
769
993
|
files: c.files,
|
|
770
994
|
length: c.length,
|
|
771
|
-
severity: c.length === 2 ?
|
|
995
|
+
severity: c.length === 2 ? 'high' : c.length <= 3 ? 'medium' : 'low'
|
|
772
996
|
})),
|
|
773
997
|
count: cycles.length,
|
|
774
998
|
graphStats,
|
|
775
|
-
advice:
|
|
776
|
-
}, null, 2)
|
|
777
|
-
}
|
|
999
|
+
advice: 'Shorter cycles (length 2-3) are typically more problematic. Consider breaking the cycle by extracting shared dependencies.'
|
|
1000
|
+
}, null, 2)
|
|
1001
|
+
}
|
|
1002
|
+
]
|
|
778
1003
|
};
|
|
779
1004
|
}
|
|
780
1005
|
catch (error) {
|
|
781
1006
|
return {
|
|
782
|
-
content: [
|
|
783
|
-
|
|
1007
|
+
content: [
|
|
1008
|
+
{
|
|
1009
|
+
type: 'text',
|
|
784
1010
|
text: JSON.stringify({
|
|
785
|
-
status:
|
|
786
|
-
message:
|
|
787
|
-
error: error instanceof Error ? error.message : String(error)
|
|
788
|
-
}, null, 2)
|
|
789
|
-
}
|
|
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
|
+
]
|
|
790
1132
|
};
|
|
791
1133
|
}
|
|
792
1134
|
}
|
|
@@ -794,13 +1136,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
794
1136
|
return {
|
|
795
1137
|
content: [
|
|
796
1138
|
{
|
|
797
|
-
type:
|
|
1139
|
+
type: 'text',
|
|
798
1140
|
text: JSON.stringify({
|
|
799
|
-
error: `Unknown tool: ${name}
|
|
800
|
-
}, null, 2)
|
|
801
|
-
}
|
|
1141
|
+
error: `Unknown tool: ${name}`
|
|
1142
|
+
}, null, 2)
|
|
1143
|
+
}
|
|
802
1144
|
],
|
|
803
|
-
isError: true
|
|
1145
|
+
isError: true
|
|
804
1146
|
};
|
|
805
1147
|
}
|
|
806
1148
|
}
|
|
@@ -808,27 +1150,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
808
1150
|
return {
|
|
809
1151
|
content: [
|
|
810
1152
|
{
|
|
811
|
-
type:
|
|
1153
|
+
type: 'text',
|
|
812
1154
|
text: JSON.stringify({
|
|
813
1155
|
error: error instanceof Error ? error.message : String(error),
|
|
814
|
-
stack: error instanceof Error ? error.stack : undefined
|
|
815
|
-
}, null, 2)
|
|
816
|
-
}
|
|
1156
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
1157
|
+
}, null, 2)
|
|
1158
|
+
}
|
|
817
1159
|
],
|
|
818
|
-
isError: true
|
|
1160
|
+
isError: true
|
|
819
1161
|
};
|
|
820
1162
|
}
|
|
821
1163
|
});
|
|
822
1164
|
async function main() {
|
|
823
|
-
// Server startup banner (guarded to avoid stderr during MCP STDIO handshake)
|
|
824
|
-
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
825
|
-
console.error("[DEBUG] Codebase Context MCP Server");
|
|
826
|
-
console.error(`[DEBUG] Root: ${ROOT_PATH}`);
|
|
827
|
-
console.error(`[DEBUG] Analyzers: ${analyzerRegistry
|
|
828
|
-
.getAll()
|
|
829
|
-
.map((a) => a.name)
|
|
830
|
-
.join(", ")}`);
|
|
831
|
-
}
|
|
832
1165
|
// Validate root path exists and is a directory
|
|
833
1166
|
try {
|
|
834
1167
|
const stats = await fs.stat(ROOT_PATH);
|
|
@@ -838,15 +1171,37 @@ async function main() {
|
|
|
838
1171
|
process.exit(1);
|
|
839
1172
|
}
|
|
840
1173
|
}
|
|
841
|
-
catch (
|
|
1174
|
+
catch (_error) {
|
|
842
1175
|
console.error(`ERROR: Root path does not exist: ${ROOT_PATH}`);
|
|
843
1176
|
console.error(`Please specify a valid project directory.`);
|
|
844
1177
|
process.exit(1);
|
|
845
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
|
+
}
|
|
846
1201
|
// Check for package.json to confirm it's a project root (guarded to avoid stderr during handshake)
|
|
847
1202
|
if (process.env.CODEBASE_CONTEXT_DEBUG) {
|
|
848
1203
|
try {
|
|
849
|
-
await fs.access(path.join(ROOT_PATH,
|
|
1204
|
+
await fs.access(path.join(ROOT_PATH, 'package.json'));
|
|
850
1205
|
console.error(`[DEBUG] Project detected: ${path.basename(ROOT_PATH)}`);
|
|
851
1206
|
}
|
|
852
1207
|
catch {
|
|
@@ -856,29 +1211,29 @@ async function main() {
|
|
|
856
1211
|
const needsIndex = await shouldReindex();
|
|
857
1212
|
if (needsIndex) {
|
|
858
1213
|
if (process.env.CODEBASE_CONTEXT_DEBUG)
|
|
859
|
-
console.error(
|
|
1214
|
+
console.error('[DEBUG] Starting indexing...');
|
|
860
1215
|
performIndexing();
|
|
861
1216
|
}
|
|
862
1217
|
else {
|
|
863
1218
|
if (process.env.CODEBASE_CONTEXT_DEBUG)
|
|
864
|
-
console.error(
|
|
865
|
-
indexState.status =
|
|
1219
|
+
console.error('[DEBUG] Index found. Ready.');
|
|
1220
|
+
indexState.status = 'ready';
|
|
866
1221
|
indexState.lastIndexed = new Date();
|
|
867
1222
|
}
|
|
868
1223
|
const transport = new StdioServerTransport();
|
|
869
1224
|
await server.connect(transport);
|
|
870
1225
|
if (process.env.CODEBASE_CONTEXT_DEBUG)
|
|
871
|
-
console.error(
|
|
1226
|
+
console.error('[DEBUG] Server ready');
|
|
872
1227
|
}
|
|
873
1228
|
// Export server components for programmatic use
|
|
874
1229
|
export { server, performIndexing, resolveRootPath, shouldReindex, TOOLS };
|
|
875
1230
|
// Only auto-start when run directly as CLI (not when imported as module)
|
|
876
1231
|
// Check if this module is the entry point
|
|
877
|
-
const isDirectRun = process.argv[1]?.replace(/\\/g,
|
|
878
|
-
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');
|
|
879
1234
|
if (isDirectRun) {
|
|
880
1235
|
main().catch((error) => {
|
|
881
|
-
console.error(
|
|
1236
|
+
console.error('Fatal:', error);
|
|
882
1237
|
process.exit(1);
|
|
883
1238
|
});
|
|
884
1239
|
}
|