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.
Files changed (96) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +144 -87
  3. package/dist/analyzers/angular/index.d.ts +1 -1
  4. package/dist/analyzers/angular/index.d.ts.map +1 -1
  5. package/dist/analyzers/angular/index.js +298 -309
  6. package/dist/analyzers/angular/index.js.map +1 -1
  7. package/dist/analyzers/generic/index.d.ts +1 -1
  8. package/dist/analyzers/generic/index.d.ts.map +1 -1
  9. package/dist/analyzers/generic/index.js +93 -47
  10. package/dist/analyzers/generic/index.js.map +1 -1
  11. package/dist/constants/codebase-context.d.ts +6 -0
  12. package/dist/constants/codebase-context.d.ts.map +1 -0
  13. package/dist/constants/codebase-context.js +8 -0
  14. package/dist/constants/codebase-context.js.map +1 -0
  15. package/dist/core/analyzer-registry.d.ts.map +1 -1
  16. package/dist/core/analyzer-registry.js +5 -7
  17. package/dist/core/analyzer-registry.js.map +1 -1
  18. package/dist/core/indexer.d.ts +9 -1
  19. package/dist/core/indexer.d.ts.map +1 -1
  20. package/dist/core/indexer.js +206 -139
  21. package/dist/core/indexer.js.map +1 -1
  22. package/dist/core/search.d.ts +1 -1
  23. package/dist/core/search.d.ts.map +1 -1
  24. package/dist/core/search.js +63 -59
  25. package/dist/core/search.js.map +1 -1
  26. package/dist/embeddings/openai.d.ts.map +1 -1
  27. package/dist/embeddings/openai.js +2 -2
  28. package/dist/embeddings/openai.js.map +1 -1
  29. package/dist/embeddings/transformers.d.ts +1 -1
  30. package/dist/embeddings/transformers.d.ts.map +1 -1
  31. package/dist/embeddings/transformers.js +19 -15
  32. package/dist/embeddings/transformers.js.map +1 -1
  33. package/dist/embeddings/types.d.ts +1 -1
  34. package/dist/embeddings/types.d.ts.map +1 -1
  35. package/dist/embeddings/types.js +3 -3
  36. package/dist/embeddings/types.js.map +1 -1
  37. package/dist/errors/index.d.ts +8 -0
  38. package/dist/errors/index.d.ts.map +1 -0
  39. package/dist/errors/index.js +11 -0
  40. package/dist/errors/index.js.map +1 -0
  41. package/dist/index.d.ts +6 -28
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +691 -336
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib.d.ts +18 -18
  46. package/dist/lib.d.ts.map +1 -1
  47. package/dist/lib.js +23 -23
  48. package/dist/lib.js.map +1 -1
  49. package/dist/memory/store.d.ts +22 -0
  50. package/dist/memory/store.d.ts.map +1 -0
  51. package/dist/memory/store.js +97 -0
  52. package/dist/memory/store.js.map +1 -0
  53. package/dist/storage/lancedb.d.ts.map +1 -1
  54. package/dist/storage/lancedb.js +27 -31
  55. package/dist/storage/lancedb.js.map +1 -1
  56. package/dist/storage/types.d.ts.map +1 -1
  57. package/dist/storage/types.js +2 -1
  58. package/dist/storage/types.js.map +1 -1
  59. package/dist/types/index.d.ts +27 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/types/index.js +1 -0
  62. package/dist/types/index.js.map +1 -1
  63. package/dist/utils/chunking.d.ts.map +1 -1
  64. package/dist/utils/chunking.js +10 -9
  65. package/dist/utils/chunking.js.map +1 -1
  66. package/dist/utils/dependency-detection.d.ts +18 -0
  67. package/dist/utils/dependency-detection.d.ts.map +1 -0
  68. package/dist/utils/dependency-detection.js +102 -0
  69. package/dist/utils/dependency-detection.js.map +1 -0
  70. package/dist/utils/git-dates.d.ts.map +1 -1
  71. package/dist/utils/git-dates.js +3 -3
  72. package/dist/utils/git-dates.js.map +1 -1
  73. package/dist/utils/language-detection.d.ts.map +1 -1
  74. package/dist/utils/language-detection.js +69 -17
  75. package/dist/utils/language-detection.js.map +1 -1
  76. package/dist/utils/usage-tracker.d.ts +2 -2
  77. package/dist/utils/usage-tracker.d.ts.map +1 -1
  78. package/dist/utils/usage-tracker.js +64 -32
  79. package/dist/utils/usage-tracker.js.map +1 -1
  80. package/dist/utils/workspace-detection.d.ts +32 -0
  81. package/dist/utils/workspace-detection.d.ts.map +1 -0
  82. package/dist/utils/workspace-detection.js +107 -0
  83. package/dist/utils/workspace-detection.js.map +1 -0
  84. package/package.json +114 -97
  85. package/dist/core/file-watcher.d.ts +0 -63
  86. package/dist/core/file-watcher.d.ts.map +0 -1
  87. package/dist/core/file-watcher.js +0 -210
  88. package/dist/core/file-watcher.js.map +0 -1
  89. package/dist/utils/logger.d.ts +0 -36
  90. package/dist/utils/logger.d.ts.map +0 -1
  91. package/dist/utils/logger.js +0 -111
  92. package/dist/utils/logger.js.map +0 -1
  93. package/dist/utils/pattern-detector.d.ts +0 -41
  94. package/dist/utils/pattern-detector.d.ts.map +0 -1
  95. package/dist/utils/pattern-detector.js +0 -101
  96. 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 "fs";
7
- import path from "path";
8
- import { glob } from "glob";
9
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
- import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
12
- import { CodebaseIndexer } from "./core/indexer.js";
13
- import { CodebaseSearcher } from "./core/search.js";
14
- import { analyzerRegistry } from "./core/analyzer-registry.js";
15
- import { AngularAnalyzer } from "./analyzers/angular/index.js";
16
- import { GenericAnalyzer } from "./analyzers/generic/index.js";
17
- import { InternalFileGraph } from "./utils/usage-tracker.js";
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: "idle",
114
+ status: 'idle'
37
115
  };
38
116
  const server = new Server({
39
- name: "codebase-context-mcp",
40
- version: "1.0.0",
117
+ name: 'codebase-context',
118
+ version: '1.4.0'
41
119
  }, {
42
120
  capabilities: {
43
121
  tools: {},
44
- resources: {},
45
- logging: {}, // Enable structured logging for clients that support it
46
- },
122
+ resources: {}
123
+ }
47
124
  });
48
125
  const TOOLS = [
49
126
  {
50
- name: "search_codebase",
51
- description: "Search the indexed codebase using natural language queries. Returns code summaries with file locations. " +
52
- "Supports framework-specific queries and architectural layer filtering. " +
53
- "Use the returned filePath with other tools to read complete file contents.",
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: "object",
132
+ type: 'object',
56
133
  properties: {
57
134
  query: {
58
- type: "string",
59
- description: "Natural language search query",
135
+ type: 'string',
136
+ description: 'Natural language search query'
60
137
  },
61
138
  limit: {
62
- type: "number",
63
- description: "Maximum number of results to return (default: 5)",
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: "object",
68
- description: "Optional filters",
144
+ type: 'object',
145
+ description: 'Optional filters',
69
146
  properties: {
70
147
  framework: {
71
- type: "string",
72
- description: "Filter by framework (angular, react, vue)",
148
+ type: 'string',
149
+ description: 'Filter by framework (angular, react, vue)'
73
150
  },
74
151
  language: {
75
- type: "string",
76
- description: "Filter by programming language",
152
+ type: 'string',
153
+ description: 'Filter by programming language'
77
154
  },
78
155
  componentType: {
79
- type: "string",
80
- description: "Filter by component type (component, service, directive, etc.)",
156
+ type: 'string',
157
+ description: 'Filter by component type (component, service, directive, etc.)'
81
158
  },
82
159
  layer: {
83
- type: "string",
84
- description: "Filter by architectural layer (presentation, business, data, state, core, shared)",
160
+ type: 'string',
161
+ description: 'Filter by architectural layer (presentation, business, data, state, core, shared)'
85
162
  },
86
163
  tags: {
87
- type: "array",
88
- items: { type: "string" },
89
- description: "Filter by tags",
90
- },
91
- },
92
- },
164
+ type: 'array',
165
+ items: { type: 'string' },
166
+ description: 'Filter by tags'
167
+ }
168
+ }
169
+ }
93
170
  },
94
- required: ["query"],
95
- },
171
+ required: ['query']
172
+ }
96
173
  },
97
174
  {
98
- name: "get_codebase_metadata",
99
- description: "Get codebase metadata including framework information, dependencies, architecture patterns, " +
100
- "and project statistics.",
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: "object",
103
- properties: {},
104
- },
179
+ type: 'object',
180
+ properties: {}
181
+ }
105
182
  },
106
183
  {
107
- name: "get_indexing_status",
108
- description: "Get current indexing status: state, statistics, and progress. " +
109
- "Use refresh_index to manually trigger re-indexing when needed.",
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: "object",
112
- properties: {},
113
- },
188
+ type: 'object',
189
+ properties: {}
190
+ }
114
191
  },
115
192
  {
116
- name: "refresh_index",
117
- description: "Re-index the codebase. Supports full re-index or incremental mode. " +
118
- "Use incrementalOnly=true to only process files changed since last index.",
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: "object",
197
+ type: 'object',
121
198
  properties: {
122
199
  reason: {
123
- type: "string",
124
- description: "Reason for refreshing the index (for logging)",
200
+ type: 'string',
201
+ description: 'Reason for refreshing the index (for logging)'
125
202
  },
126
203
  incrementalOnly: {
127
- type: "boolean",
128
- description: "If true, only re-index files changed since last full index (faster). Default: false (full re-index)",
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: "get_style_guide",
135
- description: "Query style guide rules and architectural patterns from project documentation.",
211
+ name: 'get_style_guide',
212
+ description: 'Query style guide rules and architectural patterns from project documentation.',
136
213
  inputSchema: {
137
- type: "object",
214
+ type: 'object',
138
215
  properties: {
139
216
  query: {
140
- type: "string",
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: "string",
145
- description: "Filter by category (naming, structure, patterns, testing)",
146
- },
221
+ type: 'string',
222
+ description: 'Filter by category (naming, structure, patterns, testing)'
223
+ }
147
224
  },
148
- required: ["query"],
149
- },
225
+ required: ['query']
226
+ }
150
227
  },
151
228
  {
152
- name: "get_team_patterns",
153
- description: "Get actionable team pattern recommendations based on codebase analysis. " +
154
- "Returns consensus patterns for DI, state management, testing, library wrappers, etc.",
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: "object",
233
+ type: 'object',
157
234
  properties: {
158
235
  category: {
159
- type: "string",
160
- description: "Pattern category to retrieve",
161
- enum: ["all", "di", "state", "testing", "libraries"],
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: "get_component_usage",
168
- description: "Find WHERE a library or component is used in the codebase. " +
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: "object",
249
+ type: 'object',
173
250
  properties: {
174
251
  name: {
175
- type: "string",
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: ["name"],
180
- },
256
+ required: ['name']
257
+ }
181
258
  },
182
259
  {
183
- name: "detect_circular_dependencies",
184
- description: "Analyze the import graph to detect circular dependencies between files. " +
185
- "Circular dependencies can cause initialization issues, tight coupling, and maintenance problems. " +
186
- "Returns all detected cycles sorted by length (shorter cycles are often more problematic).",
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: "object",
265
+ type: 'object',
189
266
  properties: {
190
267
  scope: {
191
- type: "string",
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: "codebase://context",
205
- name: "Codebase Intelligence",
206
- description: "Automatic codebase context: libraries used, team patterns, and conventions. " +
207
- "Read this BEFORE generating code to follow team standards.",
208
- mimeType: "text/plain",
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 = path.join(ROOT_PATH, ".codebase-intelligence.json");
355
+ const intelligencePath = PATHS.intelligence;
216
356
  try {
217
- const content = await fs.readFile(intelligencePath, "utf-8");
357
+ const content = await fs.readFile(intelligencePath, 'utf-8');
218
358
  const intelligence = JSON.parse(content);
219
359
  const lines = [];
220
- lines.push("# Codebase Intelligence");
221
- lines.push("");
222
- lines.push("⚠️ CRITICAL: This is what YOUR codebase actually uses, not generic recommendations.");
223
- lines.push("These are FACTS from analyzing your code, not best practices from the internet.");
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("## Libraries Actually Used (Top 15)");
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("## Import Aliases (from tsconfig.json)");
243
- lines.push("");
244
- lines.push("These path aliases map to internal project code:");
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("These patterns were detected by analyzing your actual code.");
255
- lines.push("This is what YOUR team does in practice, not what tutorials recommend.");
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, " $1")
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("\n");
443
+ return lines.join('\n');
304
444
  }
305
445
  catch (error) {
306
- return ("# Codebase Intelligence\n\n" +
307
- "Intelligence data not yet generated. Run indexing first.\n" +
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 === "codebase://context") {
453
+ if (uri === 'codebase://context') {
314
454
  const content = await generateCodebaseContext();
315
455
  return {
316
456
  contents: [
317
457
  {
318
458
  uri,
319
- mimeType: "text/plain",
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 = "indexing";
468
+ indexState.status = 'indexing';
329
469
  console.error(`Indexing: ${ROOT_PATH}`);
330
470
  try {
331
- let lastLoggedProgress = { phase: "", percentage: -1 };
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 = "ready";
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 = "error";
492
+ indexState.status = 'error';
353
493
  indexState.error = error instanceof Error ? error.message : String(error);
354
- console.error("Indexing failed:", indexState.error);
494
+ console.error('Indexing failed:', indexState.error);
355
495
  }
356
496
  }
357
497
  async function shouldReindex() {
358
- const indexPath = path.join(ROOT_PATH, ".codebase-index.json");
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 "search_codebase": {
511
+ case 'search_codebase': {
372
512
  const { query, limit, filters } = args;
373
- if (indexState.status === "indexing") {
513
+ if (indexState.status === 'indexing') {
374
514
  return {
375
515
  content: [
376
516
  {
377
- type: "text",
517
+ type: 'text',
378
518
  text: JSON.stringify({
379
- status: "indexing",
380
- message: "Index is still being built. Retry in a moment.",
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 === "error") {
527
+ if (indexState.status === 'error') {
388
528
  return {
389
529
  content: [
390
530
  {
391
- type: "text",
531
+ type: 'text',
392
532
  text: JSON.stringify({
393
- status: "error",
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
- const results = await searcher.search(query, limit || 5, filters);
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: "text",
601
+ type: 'text',
406
602
  text: JSON.stringify({
407
- status: "success",
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
- }, null, 2),
423
- },
424
- ],
617
+ ...(relatedMemories.length > 0 && { relatedMemories })
618
+ }, null, 2)
619
+ }
620
+ ]
425
621
  };
426
622
  }
427
- case "get_indexing_status": {
623
+ case 'get_indexing_status': {
428
624
  const progress = indexState.indexer?.getProgress();
429
625
  return {
430
626
  content: [
431
627
  {
432
- type: "text",
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: "Use refresh_index to manually trigger re-indexing when needed.",
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 "refresh_index": {
656
+ case 'refresh_index': {
461
657
  const { reason, incrementalOnly } = args;
462
- const mode = incrementalOnly ? "incremental" : "full";
463
- console.error(`Refresh requested (${mode}): ${reason || "Manual trigger"}`);
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: "text",
667
+ type: 'text',
472
668
  text: JSON.stringify({
473
- status: "started",
669
+ status: 'started',
474
670
  mode,
475
671
  message: incrementalOnly
476
- ? "Incremental re-indexing requested. Check status with get_indexing_status."
477
- : "Full re-indexing started. Check status with get_indexing_status.",
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
- ? "Incremental mode requested. Full re-index for now; true incremental indexing coming in Phase 2."
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 "get_codebase_metadata": {
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 = path.join(ROOT_PATH, ".codebase-intelligence.json");
494
- const intelligenceContent = await fs.readFile(intelligencePath, "utf-8");
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 (error) {
700
+ catch (_error) {
505
701
  // No intelligence file or parsing error
506
702
  }
507
703
  return {
508
704
  content: [
509
705
  {
510
- type: "text",
706
+ type: 'text',
511
707
  text: JSON.stringify({
512
- status: "success",
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 "get_style_guide": {
724
+ case 'get_style_guide': {
529
725
  const { query, category } = args;
530
726
  const styleGuidePatterns = [
531
- "STYLE_GUIDE.md",
532
- "CODING_STYLE.md",
533
- "ARCHITECTURE.md",
534
- "CONTRIBUTING.md",
535
- "docs/style-guide.md",
536
- "docs/coding-style.md",
537
- "docs/ARCHITECTURE.md",
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, "utf-8");
552
- const content = rawContent.replace(/\r\n/g, "\n");
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), // Max 3 sections per file
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 (e) {
771
+ catch (_e) {
579
772
  // Skip unreadable files
580
773
  }
581
774
  }
582
775
  }
583
- catch (e) {
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: "text",
784
+ type: 'text',
592
785
  text: JSON.stringify({
593
- status: "no_results",
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: "text",
798
+ type: 'text',
606
799
  text: JSON.stringify({
607
- status: "success",
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 "get_team_patterns": {
810
+ case 'get_team_patterns': {
618
811
  const { category } = args;
619
812
  try {
620
- const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json");
621
- const content = await fs.readFile(intelligencePath, "utf-8");
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: "success" };
624
- if (category === "all" || !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 === "di") {
824
+ else if (category === 'di') {
632
825
  result.dependencyInjection = intelligence.patterns?.dependencyInjection;
633
826
  }
634
- else if (category === "state") {
827
+ else if (category === 'state') {
635
828
  result.stateManagement = intelligence.patterns?.stateManagement;
636
829
  }
637
- else if (category === "testing") {
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 === "libraries") {
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: "text", text: JSON.stringify(result, null, 2) }],
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: "text",
868
+ type: 'text',
656
869
  text: JSON.stringify({
657
- status: "error",
658
- message: "Failed to load team patterns",
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 "get_component_usage": {
879
+ case 'get_component_usage': {
667
880
  const { name: componentName } = args;
668
881
  try {
669
- const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json");
670
- const content = await fs.readFile(intelligencePath, "utf-8");
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
- type: "text",
898
+ content: [
899
+ {
900
+ type: 'text',
687
901
  text: JSON.stringify({
688
- status: "success",
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
- type: "text",
915
+ content: [
916
+ {
917
+ type: 'text',
702
918
  text: JSON.stringify({
703
- status: "not_found",
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
- type: "text",
931
+ content: [
932
+ {
933
+ type: 'text',
716
934
  text: JSON.stringify({
717
- status: "error",
718
- message: "Failed to get component usage. Run indexing first.",
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 "detect_circular_dependencies": {
944
+ case 'detect_circular_dependencies': {
726
945
  const { scope } = args;
727
946
  try {
728
- const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json");
729
- const content = await fs.readFile(intelligencePath, "utf-8");
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
- type: "text",
952
+ content: [
953
+ {
954
+ type: 'text',
735
955
  text: JSON.stringify({
736
- status: "error",
737
- message: "Internal file graph not found. Please run refresh_index to rebuild the index with cycle detection support.",
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
- type: "text",
969
+ content: [
970
+ {
971
+ type: 'text',
750
972
  text: JSON.stringify({
751
- status: "success",
973
+ status: 'success',
752
974
  message: scope
753
975
  ? `No circular dependencies detected in scope: ${scope}`
754
- : "No circular dependencies detected in the codebase.",
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
- type: "text",
985
+ content: [
986
+ {
987
+ type: 'text',
764
988
  text: JSON.stringify({
765
- status: "warning",
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 ? "high" : c.length <= 3 ? "medium" : "low",
995
+ severity: c.length === 2 ? 'high' : c.length <= 3 ? 'medium' : 'low'
772
996
  })),
773
997
  count: cycles.length,
774
998
  graphStats,
775
- advice: "Shorter cycles (length 2-3) are typically more problematic. Consider breaking the cycle by extracting shared dependencies.",
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
- type: "text",
1007
+ content: [
1008
+ {
1009
+ type: 'text',
784
1010
  text: JSON.stringify({
785
- status: "error",
786
- message: "Failed to detect circular dependencies. Run indexing first.",
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: "text",
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: "text",
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 (error) {
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, "package.json"));
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("[DEBUG] Starting indexing...");
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("[DEBUG] Index found. Ready.");
865
- indexState.status = "ready";
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("[DEBUG] Server ready");
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, "/").endsWith("index.js") ||
878
- process.argv[1]?.replace(/\\/g, "/").endsWith("index.ts");
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("Fatal:", error);
1236
+ console.error('Fatal:', error);
882
1237
  process.exit(1);
883
1238
  });
884
1239
  }