codebase-context 1.2.2 → 1.5.0

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