codebase-context 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -46
- package/dist/analyzers/angular/index.d.ts +4 -0
- package/dist/analyzers/angular/index.d.ts.map +1 -1
- package/dist/analyzers/angular/index.js +51 -10
- package/dist/analyzers/angular/index.js.map +1 -1
- package/dist/analyzers/generic/index.d.ts +1 -0
- package/dist/analyzers/generic/index.d.ts.map +1 -1
- package/dist/analyzers/generic/index.js +89 -10
- package/dist/analyzers/generic/index.js.map +1 -1
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +352 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants/codebase-context.d.ts +13 -0
- package/dist/constants/codebase-context.d.ts.map +1 -1
- package/dist/constants/codebase-context.js +13 -0
- package/dist/constants/codebase-context.js.map +1 -1
- package/dist/core/index-meta.d.ts +24 -0
- package/dist/core/index-meta.d.ts.map +1 -0
- package/dist/core/index-meta.js +204 -0
- package/dist/core/index-meta.js.map +1 -0
- package/dist/core/indexer.d.ts +0 -5
- package/dist/core/indexer.d.ts.map +1 -1
- package/dist/core/indexer.js +265 -60
- package/dist/core/indexer.js.map +1 -1
- package/dist/core/search.d.ts +1 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +60 -5
- package/dist/core/search.js.map +1 -1
- package/dist/core/symbol-references.d.ts +21 -0
- package/dist/core/symbol-references.d.ts.map +1 -0
- package/dist/core/symbol-references.js +91 -0
- package/dist/core/symbol-references.js.map +1 -0
- package/dist/eval/harness.d.ts +5 -0
- package/dist/eval/harness.d.ts.map +1 -0
- package/dist/eval/harness.js +153 -0
- package/dist/eval/harness.js.map +1 -0
- package/dist/eval/types.d.ts +59 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/eval/types.js +2 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/grammars/manifest.d.ts +26 -0
- package/dist/grammars/manifest.d.ts.map +1 -0
- package/dist/grammars/manifest.js +63 -0
- package/dist/grammars/manifest.js.map +1 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -1231
- package/dist/index.js.map +1 -1
- package/dist/memory/store.d.ts +3 -0
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +9 -0
- package/dist/memory/store.js.map +1 -1
- package/dist/patterns/semantics.d.ts +6 -0
- package/dist/patterns/semantics.d.ts.map +1 -1
- package/dist/patterns/semantics.js +22 -6
- package/dist/patterns/semantics.js.map +1 -1
- package/dist/preflight/evidence-lock.d.ts +8 -0
- package/dist/preflight/evidence-lock.d.ts.map +1 -1
- package/dist/preflight/evidence-lock.js +49 -3
- package/dist/preflight/evidence-lock.js.map +1 -1
- package/dist/storage/lancedb.d.ts +9 -1
- package/dist/storage/lancedb.d.ts.map +1 -1
- package/dist/storage/lancedb.js +26 -8
- package/dist/storage/lancedb.js.map +1 -1
- package/dist/tools/detect-circular-dependencies.d.ts +5 -0
- package/dist/tools/detect-circular-dependencies.d.ts.map +1 -0
- package/dist/tools/detect-circular-dependencies.js +117 -0
- package/dist/tools/detect-circular-dependencies.js.map +1 -0
- package/dist/tools/get-codebase-metadata.d.ts +5 -0
- package/dist/tools/get-codebase-metadata.d.ts.map +1 -0
- package/dist/tools/get-codebase-metadata.js +53 -0
- package/dist/tools/get-codebase-metadata.js.map +1 -0
- package/dist/tools/get-component-usage.d.ts +5 -0
- package/dist/tools/get-component-usage.d.ts.map +1 -0
- package/dist/tools/get-component-usage.js +83 -0
- package/dist/tools/get-component-usage.js.map +1 -0
- package/dist/tools/get-indexing-status.d.ts +5 -0
- package/dist/tools/get-indexing-status.d.ts.map +1 -0
- package/dist/tools/get-indexing-status.js +44 -0
- package/dist/tools/get-indexing-status.js.map +1 -0
- package/dist/tools/get-memory.d.ts +5 -0
- package/dist/tools/get-memory.d.ts.map +1 -0
- package/dist/tools/get-memory.js +89 -0
- package/dist/tools/get-memory.js.map +1 -0
- package/dist/tools/get-style-guide.d.ts +5 -0
- package/dist/tools/get-style-guide.d.ts.map +1 -0
- package/dist/tools/get-style-guide.js +151 -0
- package/dist/tools/get-style-guide.js.map +1 -0
- package/dist/tools/get-symbol-references.d.ts +5 -0
- package/dist/tools/get-symbol-references.d.ts.map +1 -0
- package/dist/tools/get-symbol-references.js +70 -0
- package/dist/tools/get-symbol-references.js.map +1 -0
- package/dist/tools/get-team-patterns.d.ts +5 -0
- package/dist/tools/get-team-patterns.d.ts.map +1 -0
- package/dist/tools/get-team-patterns.js +131 -0
- package/dist/tools/get-team-patterns.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +41 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/refresh-index.d.ts +5 -0
- package/dist/tools/refresh-index.d.ts.map +1 -0
- package/dist/tools/refresh-index.js +40 -0
- package/dist/tools/refresh-index.js.map +1 -0
- package/dist/tools/remember.d.ts +5 -0
- package/dist/tools/remember.d.ts.map +1 -0
- package/dist/tools/remember.js +101 -0
- package/dist/tools/remember.js.map +1 -0
- package/dist/tools/search-codebase.d.ts +5 -0
- package/dist/tools/search-codebase.d.ts.map +1 -0
- package/dist/tools/search-codebase.js +638 -0
- package/dist/tools/search-codebase.js.map +1 -0
- package/dist/tools/types.d.ts +31 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/ast-chunker.d.ts +71 -0
- package/dist/utils/ast-chunker.d.ts.map +1 -0
- package/dist/utils/ast-chunker.js +453 -0
- package/dist/utils/ast-chunker.js.map +1 -0
- package/dist/utils/chunking.d.ts.map +1 -1
- package/dist/utils/chunking.js +10 -3
- package/dist/utils/chunking.js.map +1 -1
- package/dist/utils/dependency-detection.d.ts +2 -1
- package/dist/utils/dependency-detection.d.ts.map +1 -1
- package/dist/utils/dependency-detection.js.map +1 -1
- package/dist/utils/language-detection.d.ts.map +1 -1
- package/dist/utils/language-detection.js +20 -0
- package/dist/utils/language-detection.js.map +1 -1
- package/dist/utils/tree-sitter.d.ts +17 -0
- package/dist/utils/tree-sitter.d.ts.map +1 -0
- package/dist/utils/tree-sitter.js +311 -0
- package/dist/utils/tree-sitter.js.map +1 -0
- package/docs/capabilities.md +131 -37
- package/grammars/.gitkeep +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +12 -5
package/dist/index.js
CHANGED
|
@@ -6,26 +6,22 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { promises as fs } from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
|
-
import { glob } from 'glob';
|
|
10
9
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
11
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
13
12
|
import { CodebaseIndexer } from './core/indexer.js';
|
|
14
|
-
import { CodebaseSearcher } from './core/search.js';
|
|
15
13
|
import { analyzerRegistry } from './core/analyzer-registry.js';
|
|
16
14
|
import { AngularAnalyzer } from './analyzers/angular/index.js';
|
|
17
15
|
import { GenericAnalyzer } from './analyzers/generic/index.js';
|
|
18
|
-
import { InternalFileGraph } from './utils/usage-tracker.js';
|
|
19
|
-
import { getFileCommitDates } from './utils/git-dates.js';
|
|
20
16
|
import { IndexCorruptedError } from './errors/index.js';
|
|
21
17
|
import { CODEBASE_CONTEXT_DIRNAME, MEMORY_FILENAME, INTELLIGENCE_FILENAME, KEYWORD_INDEX_FILENAME, VECTOR_DB_DIRNAME } from './constants/codebase-context.js';
|
|
22
|
-
import { appendMemoryFile
|
|
18
|
+
import { appendMemoryFile } from './memory/store.js';
|
|
19
|
+
import { handleCliCommand } from './cli.js';
|
|
23
20
|
import { parseGitLogLineToMemory } from './memory/git-memory.js';
|
|
24
|
-
import {
|
|
25
|
-
import { shouldIncludePatternConflictCategory } from './preflight/query-scope.js';
|
|
26
|
-
import { isComplementaryPatternCategory, isComplementaryPatternConflict, shouldSkipLegacyTestingFrameworkCategory } from './patterns/semantics.js';
|
|
21
|
+
import { isComplementaryPatternCategory, shouldSkipLegacyTestingFrameworkCategory } from './patterns/semantics.js';
|
|
27
22
|
import { CONTEXT_RESOURCE_URI, isContextResourceUri } from './resources/uri.js';
|
|
28
|
-
import {
|
|
23
|
+
import { readIndexMeta, validateIndexArtifacts } from './core/index-meta.js';
|
|
24
|
+
import { TOOLS, dispatchTool } from './tools/index.js';
|
|
29
25
|
analyzerRegistry.register(new AngularAnalyzer());
|
|
30
26
|
analyzerRegistry.register(new GenericAnalyzer());
|
|
31
27
|
// Resolve root path with validation
|
|
@@ -56,6 +52,71 @@ const LEGACY_PATHS = {
|
|
|
56
52
|
keywordIndex: path.join(ROOT_PATH, '.codebase-index.json'),
|
|
57
53
|
vectorDb: path.join(ROOT_PATH, '.codebase-index')
|
|
58
54
|
};
|
|
55
|
+
export const INDEX_CONSUMING_TOOL_NAMES = [
|
|
56
|
+
'search_codebase',
|
|
57
|
+
'get_symbol_references',
|
|
58
|
+
'get_component_usage',
|
|
59
|
+
'detect_circular_dependencies',
|
|
60
|
+
'get_team_patterns',
|
|
61
|
+
'get_codebase_metadata'
|
|
62
|
+
];
|
|
63
|
+
export const INDEX_CONSUMING_RESOURCE_NAMES = ['Codebase Intelligence'];
|
|
64
|
+
async function requireValidIndex(rootPath) {
|
|
65
|
+
const meta = await readIndexMeta(rootPath);
|
|
66
|
+
await validateIndexArtifacts(rootPath, meta);
|
|
67
|
+
// Optional artifact presence informs confidence.
|
|
68
|
+
const hasIntelligence = await fileExists(PATHS.intelligence);
|
|
69
|
+
return {
|
|
70
|
+
status: 'ready',
|
|
71
|
+
confidence: hasIntelligence ? 'high' : 'low',
|
|
72
|
+
action: 'served',
|
|
73
|
+
...(hasIntelligence ? {} : { reason: 'Optional intelligence artifact missing' })
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function ensureValidIndexOrAutoHeal() {
|
|
77
|
+
if (indexState.status === 'indexing') {
|
|
78
|
+
return {
|
|
79
|
+
status: 'indexing',
|
|
80
|
+
confidence: 'low',
|
|
81
|
+
action: 'served',
|
|
82
|
+
reason: 'Indexing in progress'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return await requireValidIndex(ROOT_PATH);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof IndexCorruptedError) {
|
|
90
|
+
const reason = error.message;
|
|
91
|
+
console.error(`[Index] ${reason}`);
|
|
92
|
+
console.error('[Auto-Heal] Triggering full re-index...');
|
|
93
|
+
await performIndexing();
|
|
94
|
+
if (indexState.status === 'ready') {
|
|
95
|
+
try {
|
|
96
|
+
let validated = await requireValidIndex(ROOT_PATH);
|
|
97
|
+
validated = { ...validated, action: 'rebuilt-and-served', reason };
|
|
98
|
+
return validated;
|
|
99
|
+
}
|
|
100
|
+
catch (revalidateError) {
|
|
101
|
+
const msg = revalidateError instanceof Error ? revalidateError.message : String(revalidateError);
|
|
102
|
+
return {
|
|
103
|
+
status: 'rebuild-required',
|
|
104
|
+
confidence: 'low',
|
|
105
|
+
action: 'rebuild-failed',
|
|
106
|
+
reason: `Auto-heal completed but index did not validate: ${msg}`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
status: 'rebuild-required',
|
|
112
|
+
confidence: 'low',
|
|
113
|
+
action: 'rebuild-failed',
|
|
114
|
+
reason: `Auto-heal failed: ${indexState.error || reason}`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
59
120
|
/**
|
|
60
121
|
* Check if file/directory exists
|
|
61
122
|
*/
|
|
@@ -129,230 +190,6 @@ const server = new Server({
|
|
|
129
190
|
resources: {}
|
|
130
191
|
}
|
|
131
192
|
});
|
|
132
|
-
const TOOLS = [
|
|
133
|
-
{
|
|
134
|
-
name: 'search_codebase',
|
|
135
|
-
description: 'Search the indexed codebase using natural language queries. Returns code summaries with file locations. ' +
|
|
136
|
-
'Supports framework-specific queries and architectural layer filtering. ' +
|
|
137
|
-
'Always returns searchQuality and may surface related memories. Results may be enriched with pattern momentum (trend/patternWarning) ' +
|
|
138
|
-
'and lightweight relationships (imports/importedBy/testedIn/lastModified) when intelligence is available. ' +
|
|
139
|
-
'When intent is "edit", "refactor", or "migrate", returns a preflight card (when intelligence is available) with risk level, ' +
|
|
140
|
-
'patterns to prefer/avoid, impact candidates, failure warnings, and an evidence lock score - all in one call. ' +
|
|
141
|
-
'Use the returned filePath with other tools to read complete file contents.',
|
|
142
|
-
inputSchema: {
|
|
143
|
-
type: 'object',
|
|
144
|
-
properties: {
|
|
145
|
-
query: {
|
|
146
|
-
type: 'string',
|
|
147
|
-
description: 'Natural language search query'
|
|
148
|
-
},
|
|
149
|
-
intent: {
|
|
150
|
-
type: 'string',
|
|
151
|
-
enum: ['explore', 'edit', 'refactor', 'migrate'],
|
|
152
|
-
description: 'Search intent. Use "explore" (default) for read-only browsing. ' +
|
|
153
|
-
'Use "edit", "refactor", or "migrate" to get a preflight card (when intelligence is available) with risk assessment, ' +
|
|
154
|
-
'patterns to prefer/avoid, affected files, relevant team memories, and ready-to-edit evidence checks.'
|
|
155
|
-
},
|
|
156
|
-
limit: {
|
|
157
|
-
type: 'number',
|
|
158
|
-
description: 'Maximum number of results to return (default: 5)',
|
|
159
|
-
default: 5
|
|
160
|
-
},
|
|
161
|
-
filters: {
|
|
162
|
-
type: 'object',
|
|
163
|
-
description: 'Optional filters',
|
|
164
|
-
properties: {
|
|
165
|
-
framework: {
|
|
166
|
-
type: 'string',
|
|
167
|
-
description: 'Filter by framework (angular, react, vue)'
|
|
168
|
-
},
|
|
169
|
-
language: {
|
|
170
|
-
type: 'string',
|
|
171
|
-
description: 'Filter by programming language'
|
|
172
|
-
},
|
|
173
|
-
componentType: {
|
|
174
|
-
type: 'string',
|
|
175
|
-
description: 'Filter by component type (component, service, directive, etc.)'
|
|
176
|
-
},
|
|
177
|
-
layer: {
|
|
178
|
-
type: 'string',
|
|
179
|
-
description: 'Filter by architectural layer (presentation, business, data, state, core, shared)'
|
|
180
|
-
},
|
|
181
|
-
tags: {
|
|
182
|
-
type: 'array',
|
|
183
|
-
items: { type: 'string' },
|
|
184
|
-
description: 'Filter by tags'
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
required: ['query']
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
name: 'get_codebase_metadata',
|
|
194
|
-
description: 'Get codebase metadata including framework information, dependencies, architecture patterns, ' +
|
|
195
|
-
'and project statistics.',
|
|
196
|
-
inputSchema: {
|
|
197
|
-
type: 'object',
|
|
198
|
-
properties: {}
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: 'get_indexing_status',
|
|
203
|
-
description: 'Get current indexing status: state, statistics, and progress. ' +
|
|
204
|
-
'Use refresh_index to manually trigger re-indexing when needed.',
|
|
205
|
-
inputSchema: {
|
|
206
|
-
type: 'object',
|
|
207
|
-
properties: {}
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: 'refresh_index',
|
|
212
|
-
description: 'Re-index the codebase. Supports full re-index or incremental mode. ' +
|
|
213
|
-
'Use incrementalOnly=true to only process files changed since last index.',
|
|
214
|
-
inputSchema: {
|
|
215
|
-
type: 'object',
|
|
216
|
-
properties: {
|
|
217
|
-
reason: {
|
|
218
|
-
type: 'string',
|
|
219
|
-
description: 'Reason for refreshing the index (for logging)'
|
|
220
|
-
},
|
|
221
|
-
incrementalOnly: {
|
|
222
|
-
type: 'boolean',
|
|
223
|
-
description: 'If true, only re-index files changed since last full index (faster). Default: false (full re-index)'
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
name: 'get_style_guide',
|
|
230
|
-
description: 'Query style guide rules and architectural patterns from project documentation.',
|
|
231
|
-
inputSchema: {
|
|
232
|
-
type: 'object',
|
|
233
|
-
properties: {
|
|
234
|
-
query: {
|
|
235
|
-
type: 'string',
|
|
236
|
-
description: 'Query for specific style guide rules (e.g., "component naming", "service patterns")'
|
|
237
|
-
},
|
|
238
|
-
category: {
|
|
239
|
-
type: 'string',
|
|
240
|
-
description: 'Filter by category (naming, structure, patterns, testing)'
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
name: 'get_team_patterns',
|
|
247
|
-
description: 'Get actionable team pattern recommendations based on codebase analysis. ' +
|
|
248
|
-
'Returns consensus patterns for DI, state management, testing, library wrappers, etc.',
|
|
249
|
-
inputSchema: {
|
|
250
|
-
type: 'object',
|
|
251
|
-
properties: {
|
|
252
|
-
category: {
|
|
253
|
-
type: 'string',
|
|
254
|
-
description: 'Pattern category to retrieve',
|
|
255
|
-
enum: ['all', 'di', 'state', 'testing', 'libraries']
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: 'get_component_usage',
|
|
262
|
-
description: 'Find WHERE a library or component is used in the codebase. ' +
|
|
263
|
-
"This is 'Find Usages' - returns all files that import a given package/module. " +
|
|
264
|
-
"Example: get_component_usage('@mycompany/utils') -> shows all files using it.",
|
|
265
|
-
inputSchema: {
|
|
266
|
-
type: 'object',
|
|
267
|
-
properties: {
|
|
268
|
-
name: {
|
|
269
|
-
type: 'string',
|
|
270
|
-
description: "Import source to find usages for (e.g., 'primeng/table', '@mycompany/ui/button', 'lodash')"
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
required: ['name']
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
name: 'detect_circular_dependencies',
|
|
278
|
-
description: 'Analyze the import graph to detect circular dependencies between files. ' +
|
|
279
|
-
'Circular dependencies can cause initialization issues, tight coupling, and maintenance problems. ' +
|
|
280
|
-
'Returns all detected cycles sorted by length (shorter cycles are often more problematic).',
|
|
281
|
-
inputSchema: {
|
|
282
|
-
type: 'object',
|
|
283
|
-
properties: {
|
|
284
|
-
scope: {
|
|
285
|
-
type: 'string',
|
|
286
|
-
description: "Optional path prefix to limit analysis (e.g., 'src/features', 'libs/shared')"
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
name: 'remember',
|
|
293
|
-
description: 'CALL IMMEDIATELY when user explicitly asks to remember/record something.\n\n' +
|
|
294
|
-
'USER TRIGGERS:\n' +
|
|
295
|
-
'- "Remember this: [X]"\n' +
|
|
296
|
-
'- "Record this: [Y]"\n' +
|
|
297
|
-
'- "Save this for next time: [Z]"\n\n' +
|
|
298
|
-
'DO NOT call unless user explicitly requests it.\n\n' +
|
|
299
|
-
'HOW TO WRITE:\n' +
|
|
300
|
-
'- ONE convention per memory (if user lists 5 things, call this 5 times)\n' +
|
|
301
|
-
'- memory: 5-10 words (the specific rule)\n' +
|
|
302
|
-
'- reason: 1 sentence (why it matters)\n' +
|
|
303
|
-
'- Skip: one-time features, code examples, essays',
|
|
304
|
-
inputSchema: {
|
|
305
|
-
type: 'object',
|
|
306
|
-
properties: {
|
|
307
|
-
type: {
|
|
308
|
-
type: 'string',
|
|
309
|
-
enum: ['convention', 'decision', 'gotcha', 'failure'],
|
|
310
|
-
description: 'Type of memory being recorded. Use "failure" for things that were tried and failed - ' +
|
|
311
|
-
'prevents repeating the same mistakes.'
|
|
312
|
-
},
|
|
313
|
-
category: {
|
|
314
|
-
type: 'string',
|
|
315
|
-
description: 'Broader category for filtering',
|
|
316
|
-
enum: ['tooling', 'architecture', 'testing', 'dependencies', 'conventions']
|
|
317
|
-
},
|
|
318
|
-
memory: {
|
|
319
|
-
type: 'string',
|
|
320
|
-
description: 'What to remember (concise)'
|
|
321
|
-
},
|
|
322
|
-
reason: {
|
|
323
|
-
type: 'string',
|
|
324
|
-
description: 'Why this matters or what breaks otherwise'
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
required: ['type', 'category', 'memory', 'reason']
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
name: 'get_memory',
|
|
332
|
-
description: 'Retrieves team conventions, architectural decisions, and known gotchas.\n' +
|
|
333
|
-
'CALL BEFORE suggesting patterns, libraries, or architecture.\n\n' +
|
|
334
|
-
'Filters: category (tooling/architecture/testing/dependencies/conventions), type (convention/decision/gotcha), query (keyword search).',
|
|
335
|
-
inputSchema: {
|
|
336
|
-
type: 'object',
|
|
337
|
-
properties: {
|
|
338
|
-
category: {
|
|
339
|
-
type: 'string',
|
|
340
|
-
description: 'Filter by category',
|
|
341
|
-
enum: ['tooling', 'architecture', 'testing', 'dependencies', 'conventions']
|
|
342
|
-
},
|
|
343
|
-
type: {
|
|
344
|
-
type: 'string',
|
|
345
|
-
description: 'Filter by memory type',
|
|
346
|
-
enum: ['convention', 'decision', 'gotcha', 'failure']
|
|
347
|
-
},
|
|
348
|
-
query: {
|
|
349
|
-
type: 'string',
|
|
350
|
-
description: 'Keyword search across memory and reason'
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
];
|
|
356
193
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
357
194
|
return { tools: TOOLS };
|
|
358
195
|
});
|
|
@@ -371,12 +208,27 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
371
208
|
});
|
|
372
209
|
async function generateCodebaseContext() {
|
|
373
210
|
const intelligencePath = PATHS.intelligence;
|
|
211
|
+
const index = await ensureValidIndexOrAutoHeal();
|
|
212
|
+
if (index.status === 'indexing') {
|
|
213
|
+
return ('# Codebase Intelligence\n\n' +
|
|
214
|
+
'Index is still being built. Retry in a moment.\n\n' +
|
|
215
|
+
`Index: ${index.status} (${index.confidence}, ${index.action})` +
|
|
216
|
+
(index.reason ? `\nReason: ${index.reason}` : ''));
|
|
217
|
+
}
|
|
218
|
+
if (index.action === 'rebuild-failed') {
|
|
219
|
+
return ('# Codebase Intelligence\n\n' +
|
|
220
|
+
'Index rebuild required before intelligence can be served.\n\n' +
|
|
221
|
+
`Index: ${index.status} (${index.confidence}, ${index.action})` +
|
|
222
|
+
(index.reason ? `\nReason: ${index.reason}` : ''));
|
|
223
|
+
}
|
|
374
224
|
try {
|
|
375
225
|
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
376
226
|
const intelligence = JSON.parse(content);
|
|
377
227
|
const lines = [];
|
|
378
228
|
lines.push('# Codebase Intelligence');
|
|
379
229
|
lines.push('');
|
|
230
|
+
lines.push(`Index: ${index.status} (${index.confidence}, ${index.action})${index.reason ? ` — ${index.reason}` : ''}`);
|
|
231
|
+
lines.push('');
|
|
380
232
|
lines.push('WARNING: This is what YOUR codebase actually uses, not generic recommendations.');
|
|
381
233
|
lines.push('These are FACTS from analyzing your code, not best practices from the internet.');
|
|
382
234
|
lines.push('');
|
|
@@ -592,1036 +444,79 @@ async function shouldReindex() {
|
|
|
592
444
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
593
445
|
const { name, arguments: args } = request.params;
|
|
594
446
|
try {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (!queryStr) {
|
|
600
|
-
return {
|
|
601
|
-
content: [
|
|
602
|
-
{
|
|
603
|
-
type: 'text',
|
|
604
|
-
text: JSON.stringify({
|
|
605
|
-
status: 'error',
|
|
606
|
-
errorCode: 'invalid_params',
|
|
607
|
-
message: "Invalid params: 'query' is required and must be a non-empty string.",
|
|
608
|
-
hint: "Provide a query like 'how are routes configured' or 'AlbumApiService'."
|
|
609
|
-
}, null, 2)
|
|
610
|
-
}
|
|
611
|
-
],
|
|
612
|
-
isError: true
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
if (indexState.status === 'indexing') {
|
|
616
|
-
return {
|
|
617
|
-
content: [
|
|
618
|
-
{
|
|
619
|
-
type: 'text',
|
|
620
|
-
text: JSON.stringify({
|
|
621
|
-
status: 'indexing',
|
|
622
|
-
message: 'Index is still being built. Retry in a moment.',
|
|
623
|
-
progress: indexState.indexer?.getProgress()
|
|
624
|
-
}, null, 2)
|
|
625
|
-
}
|
|
626
|
-
]
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
if (indexState.status === 'error') {
|
|
630
|
-
return {
|
|
631
|
-
content: [
|
|
632
|
-
{
|
|
633
|
-
type: 'text',
|
|
634
|
-
text: JSON.stringify({
|
|
635
|
-
status: 'error',
|
|
636
|
-
message: `Indexing failed: ${indexState.error}`
|
|
637
|
-
}, null, 2)
|
|
638
|
-
}
|
|
639
|
-
]
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
const searcher = new CodebaseSearcher(ROOT_PATH);
|
|
643
|
-
let results;
|
|
644
|
-
const searchProfile = intent && ['explore', 'edit', 'refactor', 'migrate'].includes(intent)
|
|
645
|
-
? intent
|
|
646
|
-
: 'explore';
|
|
647
|
-
try {
|
|
648
|
-
results = await searcher.search(queryStr, limit || 5, filters, {
|
|
649
|
-
profile: searchProfile
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
catch (error) {
|
|
653
|
-
if (error instanceof IndexCorruptedError) {
|
|
654
|
-
console.error('[Auto-Heal] Index corrupted. Triggering full re-index...');
|
|
655
|
-
await performIndexing();
|
|
656
|
-
if (indexState.status === 'ready') {
|
|
657
|
-
console.error('[Auto-Heal] Success. Retrying search...');
|
|
658
|
-
const freshSearcher = new CodebaseSearcher(ROOT_PATH);
|
|
659
|
-
try {
|
|
660
|
-
results = await freshSearcher.search(queryStr, limit || 5, filters, {
|
|
661
|
-
profile: searchProfile
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
catch (retryError) {
|
|
665
|
-
return {
|
|
666
|
-
content: [
|
|
667
|
-
{
|
|
668
|
-
type: 'text',
|
|
669
|
-
text: JSON.stringify({
|
|
670
|
-
status: 'error',
|
|
671
|
-
message: `Auto-heal retry failed: ${retryError instanceof Error ? retryError.message : String(retryError)}`
|
|
672
|
-
}, null, 2)
|
|
673
|
-
}
|
|
674
|
-
]
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
else {
|
|
679
|
-
return {
|
|
680
|
-
content: [
|
|
681
|
-
{
|
|
682
|
-
type: 'text',
|
|
683
|
-
text: JSON.stringify({
|
|
684
|
-
status: 'error',
|
|
685
|
-
message: `Auto-heal failed: Indexing ended with status '${indexState.status}'`,
|
|
686
|
-
error: indexState.error
|
|
687
|
-
}, null, 2)
|
|
688
|
-
}
|
|
689
|
-
]
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
throw error; // Propagate unexpected errors
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
// Load memories for keyword matching, enriched with confidence
|
|
698
|
-
const allMemories = await readMemoriesFile(PATHS.memory);
|
|
699
|
-
const allMemoriesWithConf = withConfidence(allMemories);
|
|
700
|
-
const queryTerms = queryStr.toLowerCase().split(/\s+/).filter(Boolean);
|
|
701
|
-
const relatedMemories = allMemoriesWithConf
|
|
702
|
-
.filter((m) => {
|
|
703
|
-
const searchText = `${m.memory} ${m.reason}`.toLowerCase();
|
|
704
|
-
return queryTerms.some((term) => searchText.includes(term));
|
|
705
|
-
})
|
|
706
|
-
.sort((a, b) => b.effectiveConfidence - a.effectiveConfidence);
|
|
707
|
-
// Load intelligence data for enrichment (all intents, not just preflight)
|
|
708
|
-
let intelligence = null;
|
|
709
|
-
try {
|
|
710
|
-
const intelligenceContent = await fs.readFile(PATHS.intelligence, 'utf-8');
|
|
711
|
-
intelligence = JSON.parse(intelligenceContent);
|
|
712
|
-
}
|
|
713
|
-
catch {
|
|
714
|
-
/* graceful degradation — intelligence file may not exist yet */
|
|
715
|
-
}
|
|
716
|
-
// Build reverse import map from intelligence graph
|
|
717
|
-
const reverseImports = new Map();
|
|
718
|
-
if (intelligence?.internalFileGraph?.imports) {
|
|
719
|
-
for (const [file, deps] of Object.entries(intelligence.internalFileGraph.imports)) {
|
|
720
|
-
for (const dep of deps) {
|
|
721
|
-
if (!reverseImports.has(dep))
|
|
722
|
-
reverseImports.set(dep, []);
|
|
723
|
-
reverseImports.get(dep).push(file);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
// Load git dates for lastModified enrichment
|
|
728
|
-
let gitDates = null;
|
|
729
|
-
try {
|
|
730
|
-
gitDates = await getFileCommitDates(ROOT_PATH);
|
|
731
|
-
}
|
|
732
|
-
catch {
|
|
733
|
-
/* not a git repo */
|
|
734
|
-
}
|
|
735
|
-
// Enrich a search result with relationship data
|
|
736
|
-
function enrichResult(r) {
|
|
737
|
-
const rPath = r.filePath;
|
|
738
|
-
// importedBy: files that import this result (reverse lookup)
|
|
739
|
-
const importedBy = [];
|
|
740
|
-
for (const [dep, importers] of reverseImports) {
|
|
741
|
-
if (dep.endsWith(rPath) || rPath.endsWith(dep)) {
|
|
742
|
-
importedBy.push(...importers);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
// imports: files this result depends on (forward lookup)
|
|
746
|
-
const imports = [];
|
|
747
|
-
if (intelligence?.internalFileGraph?.imports) {
|
|
748
|
-
for (const [file, deps] of Object.entries(intelligence.internalFileGraph.imports)) {
|
|
749
|
-
if (file.endsWith(rPath) || rPath.endsWith(file)) {
|
|
750
|
-
imports.push(...deps);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
// testedIn: heuristic — same basename with .spec/.test extension
|
|
755
|
-
const testedIn = [];
|
|
756
|
-
const baseName = path.basename(rPath).replace(/\.[^.]+$/, '');
|
|
757
|
-
if (intelligence?.internalFileGraph?.imports) {
|
|
758
|
-
for (const file of Object.keys(intelligence.internalFileGraph.imports)) {
|
|
759
|
-
const fileBase = path.basename(file);
|
|
760
|
-
if ((fileBase.includes('.spec.') || fileBase.includes('.test.')) &&
|
|
761
|
-
fileBase.startsWith(baseName)) {
|
|
762
|
-
testedIn.push(file);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
// lastModified: from git dates
|
|
767
|
-
let lastModified;
|
|
768
|
-
if (gitDates) {
|
|
769
|
-
// Try matching by relative path (git dates use repo-relative forward-slash paths)
|
|
770
|
-
const relPath = path.relative(ROOT_PATH, rPath).replace(/\\/g, '/');
|
|
771
|
-
const date = gitDates.get(relPath);
|
|
772
|
-
if (date) {
|
|
773
|
-
lastModified = date.toISOString();
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
// Only return if we have at least one piece of data
|
|
777
|
-
if (importedBy.length === 0 &&
|
|
778
|
-
imports.length === 0 &&
|
|
779
|
-
testedIn.length === 0 &&
|
|
780
|
-
!lastModified) {
|
|
781
|
-
return undefined;
|
|
782
|
-
}
|
|
783
|
-
return {
|
|
784
|
-
...(importedBy.length > 0 && { importedBy }),
|
|
785
|
-
...(imports.length > 0 && { imports }),
|
|
786
|
-
...(testedIn.length > 0 && { testedIn }),
|
|
787
|
-
...(lastModified && { lastModified })
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
const searchQuality = assessSearchQuality(query, results);
|
|
791
|
-
// Compose preflight card for edit/refactor/migrate intents
|
|
792
|
-
let preflight = undefined;
|
|
793
|
-
const preflightIntents = ['edit', 'refactor', 'migrate'];
|
|
794
|
-
if (intent && preflightIntents.includes(intent) && intelligence) {
|
|
795
|
-
try {
|
|
796
|
-
// --- Avoid / Prefer patterns ---
|
|
797
|
-
const avoidPatterns = [];
|
|
798
|
-
const preferredPatterns = [];
|
|
799
|
-
const patterns = intelligence.patterns || {};
|
|
800
|
-
for (const [category, data] of Object.entries(patterns)) {
|
|
801
|
-
// Primary pattern = preferred if Rising or Stable
|
|
802
|
-
if (data.primary) {
|
|
803
|
-
const p = data.primary;
|
|
804
|
-
if (p.trend === 'Rising' || p.trend === 'Stable') {
|
|
805
|
-
preferredPatterns.push({
|
|
806
|
-
pattern: p.name,
|
|
807
|
-
category,
|
|
808
|
-
adoption: p.frequency,
|
|
809
|
-
trend: p.trend,
|
|
810
|
-
guidance: p.guidance,
|
|
811
|
-
...(p.canonicalExample && { example: p.canonicalExample.file })
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
// Also-detected patterns that are Declining = avoid
|
|
816
|
-
if (data.alsoDetected) {
|
|
817
|
-
for (const alt of data.alsoDetected) {
|
|
818
|
-
if (alt.trend === 'Declining') {
|
|
819
|
-
avoidPatterns.push({
|
|
820
|
-
pattern: alt.name,
|
|
821
|
-
category,
|
|
822
|
-
adoption: alt.frequency,
|
|
823
|
-
trend: 'Declining',
|
|
824
|
-
guidance: alt.guidance
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
// --- Impact candidates (files importing the result files) ---
|
|
831
|
-
const impactCandidates = [];
|
|
832
|
-
const resultPaths = results.map((r) => r.filePath);
|
|
833
|
-
if (intelligence.internalFileGraph?.imports) {
|
|
834
|
-
const allImports = intelligence.internalFileGraph.imports;
|
|
835
|
-
for (const [file, deps] of Object.entries(allImports)) {
|
|
836
|
-
if (deps.some((dep) => resultPaths.some((rp) => dep.endsWith(rp) || rp.endsWith(dep)))) {
|
|
837
|
-
if (!resultPaths.some((rp) => file.endsWith(rp) || rp.endsWith(file))) {
|
|
838
|
-
impactCandidates.push(file);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
// --- Risk level (based on circular deps + impact breadth) ---
|
|
844
|
-
let riskLevel = 'low';
|
|
845
|
-
let cycleCount = 0;
|
|
846
|
-
if (intelligence.internalFileGraph) {
|
|
847
|
-
try {
|
|
848
|
-
const graph = InternalFileGraph.fromJSON(intelligence.internalFileGraph, ROOT_PATH);
|
|
849
|
-
// Use directory prefixes as scope (not full file paths)
|
|
850
|
-
// findCycles(scope) filters files by startsWith, so a full path would only match itself
|
|
851
|
-
const scopes = new Set(resultPaths.map((rp) => {
|
|
852
|
-
const lastSlash = rp.lastIndexOf('/');
|
|
853
|
-
return lastSlash > 0 ? rp.substring(0, lastSlash + 1) : rp;
|
|
854
|
-
}));
|
|
855
|
-
for (const scope of scopes) {
|
|
856
|
-
const cycles = graph.findCycles(scope);
|
|
857
|
-
cycleCount += cycles.length;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
catch {
|
|
861
|
-
// Graph reconstruction failed — skip cycle check
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
if (cycleCount > 0 || impactCandidates.length > 10) {
|
|
865
|
-
riskLevel = 'high';
|
|
866
|
-
}
|
|
867
|
-
else if (impactCandidates.length > 3) {
|
|
868
|
-
riskLevel = 'medium';
|
|
869
|
-
}
|
|
870
|
-
// --- Golden files (exemplar code) ---
|
|
871
|
-
const goldenFiles = (intelligence.goldenFiles || []).slice(0, 3).map((g) => ({
|
|
872
|
-
file: g.file,
|
|
873
|
-
score: g.score
|
|
874
|
-
}));
|
|
875
|
-
// --- Confidence (index freshness) ---
|
|
876
|
-
let confidence = 'stale';
|
|
877
|
-
if (intelligence.generatedAt) {
|
|
878
|
-
const indexAge = Date.now() - new Date(intelligence.generatedAt).getTime();
|
|
879
|
-
const hoursOld = indexAge / (1000 * 60 * 60);
|
|
880
|
-
if (hoursOld < 24) {
|
|
881
|
-
confidence = 'fresh';
|
|
882
|
-
}
|
|
883
|
-
else if (hoursOld < 168) {
|
|
884
|
-
confidence = 'aging';
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
// --- Failure memories (1.5x relevance boost) ---
|
|
888
|
-
const failureWarnings = relatedMemories
|
|
889
|
-
.filter((m) => m.type === 'failure' && !m.stale)
|
|
890
|
-
.map((m) => ({
|
|
891
|
-
memory: m.memory,
|
|
892
|
-
reason: m.reason,
|
|
893
|
-
confidence: m.effectiveConfidence
|
|
894
|
-
}))
|
|
895
|
-
.slice(0, 3);
|
|
896
|
-
const preferredPatternsForOutput = preferredPatterns.slice(0, 5);
|
|
897
|
-
const avoidPatternsForOutput = avoidPatterns.slice(0, 5);
|
|
898
|
-
// --- Pattern conflicts (split decisions within categories) ---
|
|
899
|
-
const patternConflicts = [];
|
|
900
|
-
const hasUnitTestFramework = Boolean(patterns.unitTestFramework?.primary);
|
|
901
|
-
for (const [cat, data] of Object.entries(patterns)) {
|
|
902
|
-
if (shouldSkipLegacyTestingFrameworkCategory(cat, patterns))
|
|
903
|
-
continue;
|
|
904
|
-
if (!shouldIncludePatternConflictCategory(cat, query))
|
|
905
|
-
continue;
|
|
906
|
-
if (!data.primary || !data.alsoDetected?.length)
|
|
907
|
-
continue;
|
|
908
|
-
const primaryFreq = parseFloat(data.primary.frequency) || 100;
|
|
909
|
-
if (primaryFreq >= 80)
|
|
910
|
-
continue;
|
|
911
|
-
for (const alt of data.alsoDetected) {
|
|
912
|
-
const altFreq = parseFloat(alt.frequency) || 0;
|
|
913
|
-
if (altFreq >= 20) {
|
|
914
|
-
if (isComplementaryPatternConflict(cat, data.primary.name, alt.name))
|
|
915
|
-
continue;
|
|
916
|
-
if (hasUnitTestFramework && cat === 'testingFramework')
|
|
917
|
-
continue;
|
|
918
|
-
patternConflicts.push({
|
|
919
|
-
category: cat,
|
|
920
|
-
primary: { name: data.primary.name, adoption: data.primary.frequency },
|
|
921
|
-
alternative: { name: alt.name, adoption: alt.frequency }
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
const evidenceLock = buildEvidenceLock({
|
|
927
|
-
results,
|
|
928
|
-
preferredPatterns: preferredPatternsForOutput,
|
|
929
|
-
relatedMemories,
|
|
930
|
-
failureWarnings,
|
|
931
|
-
patternConflicts
|
|
932
|
-
});
|
|
933
|
-
// Bump risk if there are active failure memories for this area
|
|
934
|
-
if (failureWarnings.length > 0 && riskLevel === 'low') {
|
|
935
|
-
riskLevel = 'medium';
|
|
936
|
-
}
|
|
937
|
-
// If evidence triangulation is weak, avoid claiming low risk
|
|
938
|
-
if (evidenceLock.status === 'block' && riskLevel === 'low') {
|
|
939
|
-
riskLevel = 'medium';
|
|
940
|
-
}
|
|
941
|
-
// If epistemic stress says abstain, bump risk
|
|
942
|
-
if (evidenceLock.epistemicStress?.abstain && riskLevel === 'low') {
|
|
943
|
-
riskLevel = 'medium';
|
|
944
|
-
}
|
|
945
|
-
preflight = {
|
|
946
|
-
intent,
|
|
947
|
-
riskLevel,
|
|
948
|
-
confidence,
|
|
949
|
-
evidenceLock,
|
|
950
|
-
...(preferredPatternsForOutput.length > 0 && {
|
|
951
|
-
preferredPatterns: preferredPatternsForOutput
|
|
952
|
-
}),
|
|
953
|
-
...(avoidPatternsForOutput.length > 0 && {
|
|
954
|
-
avoidPatterns: avoidPatternsForOutput
|
|
955
|
-
}),
|
|
956
|
-
...(goldenFiles.length > 0 && { goldenFiles }),
|
|
957
|
-
...(impactCandidates.length > 0 && {
|
|
958
|
-
impactCandidates: impactCandidates.slice(0, 10)
|
|
959
|
-
}),
|
|
960
|
-
...(cycleCount > 0 && { circularDependencies: cycleCount }),
|
|
961
|
-
...(failureWarnings.length > 0 && { failureWarnings })
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
catch {
|
|
965
|
-
// Preflight construction failed — skip preflight, don't fail the search
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
return {
|
|
969
|
-
content: [
|
|
970
|
-
{
|
|
971
|
-
type: 'text',
|
|
972
|
-
text: JSON.stringify({
|
|
973
|
-
status: 'success',
|
|
974
|
-
...(preflight && { preflight }),
|
|
975
|
-
searchQuality,
|
|
976
|
-
results: results.map((r) => {
|
|
977
|
-
const relationships = enrichResult(r);
|
|
978
|
-
return {
|
|
979
|
-
summary: r.summary,
|
|
980
|
-
snippet: r.snippet,
|
|
981
|
-
filePath: `${r.filePath}:${r.startLine}-${r.endLine}`,
|
|
982
|
-
score: r.score,
|
|
983
|
-
relevanceReason: r.relevanceReason,
|
|
984
|
-
componentType: r.componentType,
|
|
985
|
-
layer: r.layer,
|
|
986
|
-
framework: r.framework,
|
|
987
|
-
trend: r.trend,
|
|
988
|
-
patternWarning: r.patternWarning,
|
|
989
|
-
...(relationships && { relationships })
|
|
990
|
-
};
|
|
991
|
-
}),
|
|
992
|
-
totalResults: results.length,
|
|
993
|
-
...(relatedMemories.length > 0 && {
|
|
994
|
-
relatedMemories: relatedMemories.slice(0, 5)
|
|
995
|
-
})
|
|
996
|
-
}, null, 2)
|
|
997
|
-
}
|
|
998
|
-
]
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
case 'get_indexing_status': {
|
|
1002
|
-
const progress = indexState.indexer?.getProgress();
|
|
447
|
+
// Gate INDEX_CONSUMING tools on a valid, healthy index
|
|
448
|
+
let indexSignal;
|
|
449
|
+
if (INDEX_CONSUMING_TOOL_NAMES.includes(name)) {
|
|
450
|
+
if (indexState.status === 'indexing') {
|
|
1003
451
|
return {
|
|
1004
452
|
content: [
|
|
1005
453
|
{
|
|
1006
454
|
type: 'text',
|
|
1007
455
|
text: JSON.stringify({
|
|
1008
|
-
status:
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
stats: indexState.stats
|
|
1012
|
-
? {
|
|
1013
|
-
totalFiles: indexState.stats.totalFiles,
|
|
1014
|
-
indexedFiles: indexState.stats.indexedFiles,
|
|
1015
|
-
totalChunks: indexState.stats.totalChunks,
|
|
1016
|
-
duration: `${(indexState.stats.duration / 1000).toFixed(2)}s`,
|
|
1017
|
-
incremental: indexState.stats.incremental
|
|
1018
|
-
}
|
|
1019
|
-
: undefined,
|
|
1020
|
-
progress: progress
|
|
1021
|
-
? {
|
|
1022
|
-
phase: progress.phase,
|
|
1023
|
-
percentage: progress.percentage,
|
|
1024
|
-
filesProcessed: progress.filesProcessed,
|
|
1025
|
-
totalFiles: progress.totalFiles
|
|
1026
|
-
}
|
|
1027
|
-
: undefined,
|
|
1028
|
-
error: indexState.error,
|
|
1029
|
-
hint: 'Use refresh_index to manually trigger re-indexing when needed.'
|
|
1030
|
-
}, null, 2)
|
|
456
|
+
status: 'indexing',
|
|
457
|
+
message: 'Index build in progress — please retry shortly'
|
|
458
|
+
})
|
|
1031
459
|
}
|
|
1032
460
|
]
|
|
1033
461
|
};
|
|
1034
462
|
}
|
|
1035
|
-
|
|
1036
|
-
const { reason, incrementalOnly } = args;
|
|
1037
|
-
const mode = incrementalOnly ? 'incremental' : 'full';
|
|
1038
|
-
console.error(`Refresh requested (${mode}): ${reason || 'Manual trigger'}`);
|
|
1039
|
-
performIndexing(incrementalOnly);
|
|
463
|
+
if (indexState.status === 'error') {
|
|
1040
464
|
return {
|
|
1041
465
|
content: [
|
|
1042
466
|
{
|
|
1043
467
|
type: 'text',
|
|
1044
468
|
text: JSON.stringify({
|
|
1045
|
-
status: '
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
? 'Incremental re-indexing started. Only changed files will be re-embedded.'
|
|
1049
|
-
: 'Full re-indexing started. Check status with get_indexing_status.',
|
|
1050
|
-
reason
|
|
1051
|
-
}, null, 2)
|
|
469
|
+
status: 'error',
|
|
470
|
+
message: `Indexer error: ${indexState.error}`
|
|
471
|
+
})
|
|
1052
472
|
}
|
|
1053
473
|
]
|
|
1054
474
|
};
|
|
1055
475
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const metadata = await indexer.detectMetadata();
|
|
1059
|
-
// Load team patterns from intelligence file
|
|
1060
|
-
let teamPatterns = {};
|
|
1061
|
-
try {
|
|
1062
|
-
const intelligencePath = PATHS.intelligence;
|
|
1063
|
-
const intelligenceContent = await fs.readFile(intelligencePath, 'utf-8');
|
|
1064
|
-
const intelligence = JSON.parse(intelligenceContent);
|
|
1065
|
-
if (intelligence.patterns) {
|
|
1066
|
-
teamPatterns = {
|
|
1067
|
-
dependencyInjection: intelligence.patterns.dependencyInjection,
|
|
1068
|
-
stateManagement: intelligence.patterns.stateManagement,
|
|
1069
|
-
componentInputs: intelligence.patterns.componentInputs
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
catch (_error) {
|
|
1074
|
-
// No intelligence file or parsing error
|
|
1075
|
-
}
|
|
476
|
+
indexSignal = await ensureValidIndexOrAutoHeal();
|
|
477
|
+
if (indexSignal.action === 'rebuild-failed') {
|
|
1076
478
|
return {
|
|
1077
479
|
content: [
|
|
1078
480
|
{
|
|
1079
481
|
type: 'text',
|
|
1080
482
|
text: JSON.stringify({
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
framework: metadata.framework,
|
|
1085
|
-
languages: metadata.languages,
|
|
1086
|
-
dependencies: metadata.dependencies.slice(0, 20),
|
|
1087
|
-
architecture: metadata.architecture,
|
|
1088
|
-
projectStructure: metadata.projectStructure,
|
|
1089
|
-
statistics: metadata.statistics,
|
|
1090
|
-
teamPatterns
|
|
1091
|
-
}
|
|
1092
|
-
}, null, 2)
|
|
483
|
+
error: 'Index is corrupt and could not be rebuilt automatically.',
|
|
484
|
+
index: indexSignal
|
|
485
|
+
})
|
|
1093
486
|
}
|
|
1094
|
-
]
|
|
487
|
+
],
|
|
488
|
+
isError: true
|
|
1095
489
|
};
|
|
1096
490
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
'
|
|
1111
|
-
|
|
1112
|
-
'docs/coding-style.md',
|
|
1113
|
-
'docs/ARCHITECTURE.md'
|
|
1114
|
-
];
|
|
1115
|
-
const foundGuides = [];
|
|
1116
|
-
for (const pattern of styleGuidePatterns) {
|
|
1117
|
-
try {
|
|
1118
|
-
const files = await glob(pattern, {
|
|
1119
|
-
cwd: ROOT_PATH,
|
|
1120
|
-
absolute: true
|
|
1121
|
-
});
|
|
1122
|
-
for (const file of files) {
|
|
1123
|
-
try {
|
|
1124
|
-
// Normalize line endings to \n for consistent output
|
|
1125
|
-
const rawContent = await fs.readFile(file, 'utf-8');
|
|
1126
|
-
const content = rawContent.replace(/\r\n/g, '\n');
|
|
1127
|
-
const relativePath = path.relative(ROOT_PATH, file);
|
|
1128
|
-
// Find relevant sections based on query
|
|
1129
|
-
const sections = content.split(/^##\s+/m);
|
|
1130
|
-
const relevantSections = [];
|
|
1131
|
-
if (limitedMode) {
|
|
1132
|
-
const headings = (content.match(/^##\s+.+$/gm) || [])
|
|
1133
|
-
.map((h) => h.trim())
|
|
1134
|
-
.filter(Boolean)
|
|
1135
|
-
.slice(0, LIMITED_MAX_SECTIONS_PER_FILE);
|
|
1136
|
-
if (headings.length > 0) {
|
|
1137
|
-
relevantSections.push(...headings);
|
|
1138
|
-
}
|
|
1139
|
-
else {
|
|
1140
|
-
const words = content.split(/\s+/).filter(Boolean);
|
|
1141
|
-
if (words.length > 0) {
|
|
1142
|
-
relevantSections.push(`Overview: ${words.slice(0, 80).join(' ')}...`);
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
else {
|
|
1147
|
-
for (const section of sections) {
|
|
1148
|
-
const sectionLower = section.toLowerCase();
|
|
1149
|
-
const isRelevant = queryTerms.some((term) => sectionLower.includes(term));
|
|
1150
|
-
if (isRelevant) {
|
|
1151
|
-
// Limit section size to ~500 words
|
|
1152
|
-
const words = section.split(/\s+/);
|
|
1153
|
-
const truncated = words.slice(0, 500).join(' ');
|
|
1154
|
-
relevantSections.push('## ' + (words.length > 500 ? truncated + '...' : section.trim()));
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
const categoryMatch = !categoryLower ||
|
|
1159
|
-
relativePath.toLowerCase().includes(categoryLower) ||
|
|
1160
|
-
relevantSections.some((section) => section.toLowerCase().includes(categoryLower));
|
|
1161
|
-
if (!categoryMatch) {
|
|
1162
|
-
continue;
|
|
1163
|
-
}
|
|
1164
|
-
if (relevantSections.length > 0) {
|
|
1165
|
-
foundGuides.push({
|
|
1166
|
-
file: relativePath,
|
|
1167
|
-
content: content.slice(0, 200) + '...',
|
|
1168
|
-
relevantSections: relevantSections.slice(0, limitedMode ? LIMITED_MAX_SECTIONS_PER_FILE : 3)
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
catch (_e) {
|
|
1173
|
-
// Skip unreadable files
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
catch (_e) {
|
|
1178
|
-
// Pattern didn't match, continue
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
const results = limitedMode ? foundGuides.slice(0, LIMITED_MAX_FILES) : foundGuides;
|
|
1182
|
-
if (results.length === 0) {
|
|
1183
|
-
return {
|
|
1184
|
-
content: [
|
|
1185
|
-
{
|
|
1186
|
-
type: 'text',
|
|
1187
|
-
text: JSON.stringify({
|
|
1188
|
-
status: 'no_results',
|
|
1189
|
-
message: limitedMode
|
|
1190
|
-
? 'No style guide files found in the default locations.'
|
|
1191
|
-
: `No style guide content found matching: ${queryStr}`,
|
|
1192
|
-
searchedPatterns: styleGuidePatterns,
|
|
1193
|
-
hint: limitedMode
|
|
1194
|
-
? "Run get_style_guide with a query or category (e.g. category: 'testing') for targeted results."
|
|
1195
|
-
: "Try broader terms like 'naming', 'patterns', 'testing', 'components'"
|
|
1196
|
-
}, null, 2)
|
|
1197
|
-
}
|
|
1198
|
-
]
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
|
-
return {
|
|
1202
|
-
content: [
|
|
1203
|
-
{
|
|
1204
|
-
type: 'text',
|
|
1205
|
-
text: JSON.stringify({
|
|
1206
|
-
status: 'success',
|
|
1207
|
-
query: queryStr || undefined,
|
|
1208
|
-
category,
|
|
1209
|
-
limited: limitedMode,
|
|
1210
|
-
notice: limitedMode
|
|
1211
|
-
? 'No query provided. Results are capped. Provide query and/or category for targeted guidance.'
|
|
1212
|
-
: undefined,
|
|
1213
|
-
resultLimits: limitedMode
|
|
1214
|
-
? {
|
|
1215
|
-
maxFiles: LIMITED_MAX_FILES,
|
|
1216
|
-
maxSectionsPerFile: LIMITED_MAX_SECTIONS_PER_FILE
|
|
1217
|
-
}
|
|
1218
|
-
: undefined,
|
|
1219
|
-
results,
|
|
1220
|
-
totalFiles: results.length,
|
|
1221
|
-
totalMatches: foundGuides.length
|
|
1222
|
-
}, null, 2)
|
|
1223
|
-
}
|
|
1224
|
-
]
|
|
491
|
+
}
|
|
492
|
+
const ctx = {
|
|
493
|
+
indexState,
|
|
494
|
+
paths: PATHS,
|
|
495
|
+
rootPath: ROOT_PATH,
|
|
496
|
+
performIndexing
|
|
497
|
+
};
|
|
498
|
+
const result = await dispatchTool(name, args ?? {}, ctx);
|
|
499
|
+
// Inject IndexSignal into response so callers can inspect index health
|
|
500
|
+
if (indexSignal !== undefined && result.content?.[0]) {
|
|
501
|
+
try {
|
|
502
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
503
|
+
result.content[0] = {
|
|
504
|
+
type: 'text',
|
|
505
|
+
text: JSON.stringify({ ...parsed, index: indexSignal })
|
|
1225
506
|
};
|
|
1226
507
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
try {
|
|
1230
|
-
const intelligencePath = PATHS.intelligence;
|
|
1231
|
-
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
1232
|
-
const intelligence = JSON.parse(content);
|
|
1233
|
-
const result = { status: 'success' };
|
|
1234
|
-
if (category === 'all' || !category) {
|
|
1235
|
-
result.patterns = intelligence.patterns || {};
|
|
1236
|
-
result.goldenFiles = intelligence.goldenFiles || [];
|
|
1237
|
-
if (intelligence.tsconfigPaths) {
|
|
1238
|
-
result.tsconfigPaths = intelligence.tsconfigPaths;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
else if (category === 'di') {
|
|
1242
|
-
result.dependencyInjection = intelligence.patterns?.dependencyInjection;
|
|
1243
|
-
}
|
|
1244
|
-
else if (category === 'state') {
|
|
1245
|
-
result.stateManagement = intelligence.patterns?.stateManagement;
|
|
1246
|
-
}
|
|
1247
|
-
else if (category === 'testing') {
|
|
1248
|
-
result.unitTestFramework = intelligence.patterns?.unitTestFramework;
|
|
1249
|
-
result.e2eFramework = intelligence.patterns?.e2eFramework;
|
|
1250
|
-
result.testingFramework = intelligence.patterns?.testingFramework;
|
|
1251
|
-
result.testMocking = intelligence.patterns?.testMocking;
|
|
1252
|
-
}
|
|
1253
|
-
else if (category === 'libraries') {
|
|
1254
|
-
result.topUsed = intelligence.importGraph?.topUsed || [];
|
|
1255
|
-
if (intelligence.tsconfigPaths) {
|
|
1256
|
-
result.tsconfigPaths = intelligence.tsconfigPaths;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
// Load and append matching memories
|
|
1260
|
-
try {
|
|
1261
|
-
const allMemories = await readMemoriesFile(PATHS.memory);
|
|
1262
|
-
// Map pattern categories to decision categories
|
|
1263
|
-
const categoryMap = {
|
|
1264
|
-
all: ['tooling', 'architecture', 'testing', 'dependencies', 'conventions'],
|
|
1265
|
-
di: ['architecture', 'conventions'],
|
|
1266
|
-
state: ['architecture', 'conventions'],
|
|
1267
|
-
testing: ['testing'],
|
|
1268
|
-
libraries: ['dependencies']
|
|
1269
|
-
};
|
|
1270
|
-
const relevantCategories = categoryMap[category || 'all'] || [];
|
|
1271
|
-
const matchingMemories = allMemories.filter((m) => relevantCategories.includes(m.category));
|
|
1272
|
-
if (matchingMemories.length > 0) {
|
|
1273
|
-
result.memories = matchingMemories;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
catch (_error) {
|
|
1277
|
-
// No memory file yet, that's fine - don't fail the whole request
|
|
1278
|
-
}
|
|
1279
|
-
// Detect pattern conflicts: primary < 80% and any alternative > 20%
|
|
1280
|
-
const conflicts = [];
|
|
1281
|
-
const patternsData = intelligence.patterns || {};
|
|
1282
|
-
const hasUnitTestFramework = Boolean(patternsData.unitTestFramework?.primary);
|
|
1283
|
-
for (const [cat, data] of Object.entries(patternsData)) {
|
|
1284
|
-
if (shouldSkipLegacyTestingFrameworkCategory(cat, patternsData))
|
|
1285
|
-
continue;
|
|
1286
|
-
if (category && category !== 'all' && cat !== category)
|
|
1287
|
-
continue;
|
|
1288
|
-
if (!data.primary || !data.alsoDetected?.length)
|
|
1289
|
-
continue;
|
|
1290
|
-
const primaryFreq = parseFloat(data.primary.frequency) || 100;
|
|
1291
|
-
if (primaryFreq >= 80)
|
|
1292
|
-
continue;
|
|
1293
|
-
for (const alt of data.alsoDetected) {
|
|
1294
|
-
const altFreq = parseFloat(alt.frequency) || 0;
|
|
1295
|
-
if (altFreq < 20)
|
|
1296
|
-
continue;
|
|
1297
|
-
if (isComplementaryPatternConflict(cat, data.primary.name, alt.name))
|
|
1298
|
-
continue;
|
|
1299
|
-
if (hasUnitTestFramework && cat === 'testingFramework')
|
|
1300
|
-
continue;
|
|
1301
|
-
conflicts.push({
|
|
1302
|
-
category: cat,
|
|
1303
|
-
primary: {
|
|
1304
|
-
name: data.primary.name,
|
|
1305
|
-
adoption: data.primary.frequency,
|
|
1306
|
-
trend: data.primary.trend
|
|
1307
|
-
},
|
|
1308
|
-
alternative: {
|
|
1309
|
-
name: alt.name,
|
|
1310
|
-
adoption: alt.frequency,
|
|
1311
|
-
trend: alt.trend
|
|
1312
|
-
},
|
|
1313
|
-
note: `Split decision: ${data.primary.frequency} ${data.primary.name} (${data.primary.trend || 'unknown'}) vs ${alt.frequency} ${alt.name} (${alt.trend || 'unknown'})`
|
|
1314
|
-
});
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
if (conflicts.length > 0) {
|
|
1318
|
-
result.conflicts = conflicts;
|
|
1319
|
-
}
|
|
1320
|
-
return {
|
|
1321
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
catch (error) {
|
|
1325
|
-
return {
|
|
1326
|
-
content: [
|
|
1327
|
-
{
|
|
1328
|
-
type: 'text',
|
|
1329
|
-
text: JSON.stringify({
|
|
1330
|
-
status: 'error',
|
|
1331
|
-
message: 'Failed to load team patterns',
|
|
1332
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1333
|
-
}, null, 2)
|
|
1334
|
-
}
|
|
1335
|
-
]
|
|
1336
|
-
};
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
case 'get_component_usage': {
|
|
1340
|
-
const { name: componentName } = args;
|
|
1341
|
-
try {
|
|
1342
|
-
const intelligencePath = PATHS.intelligence;
|
|
1343
|
-
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
1344
|
-
const intelligence = JSON.parse(content);
|
|
1345
|
-
const importGraph = intelligence.importGraph || {};
|
|
1346
|
-
const usages = importGraph.usages || {};
|
|
1347
|
-
// Find matching usages (exact match or partial match)
|
|
1348
|
-
let matchedUsage = usages[componentName];
|
|
1349
|
-
// Try partial match if exact match not found
|
|
1350
|
-
if (!matchedUsage) {
|
|
1351
|
-
const matchingKeys = Object.keys(usages).filter((key) => key.includes(componentName) || componentName.includes(key));
|
|
1352
|
-
if (matchingKeys.length > 0) {
|
|
1353
|
-
matchedUsage = usages[matchingKeys[0]];
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
if (matchedUsage) {
|
|
1357
|
-
return {
|
|
1358
|
-
content: [
|
|
1359
|
-
{
|
|
1360
|
-
type: 'text',
|
|
1361
|
-
text: JSON.stringify({
|
|
1362
|
-
status: 'success',
|
|
1363
|
-
component: componentName,
|
|
1364
|
-
usageCount: matchedUsage.usageCount,
|
|
1365
|
-
usedIn: matchedUsage.usedIn
|
|
1366
|
-
}, null, 2)
|
|
1367
|
-
}
|
|
1368
|
-
]
|
|
1369
|
-
};
|
|
1370
|
-
}
|
|
1371
|
-
else {
|
|
1372
|
-
// Show top used as alternatives
|
|
1373
|
-
const topUsed = importGraph.topUsed || [];
|
|
1374
|
-
return {
|
|
1375
|
-
content: [
|
|
1376
|
-
{
|
|
1377
|
-
type: 'text',
|
|
1378
|
-
text: JSON.stringify({
|
|
1379
|
-
status: 'not_found',
|
|
1380
|
-
component: componentName,
|
|
1381
|
-
message: `No usages found for '${componentName}'.`,
|
|
1382
|
-
suggestions: topUsed.slice(0, 10)
|
|
1383
|
-
}, null, 2)
|
|
1384
|
-
}
|
|
1385
|
-
]
|
|
1386
|
-
};
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
catch (error) {
|
|
1390
|
-
return {
|
|
1391
|
-
content: [
|
|
1392
|
-
{
|
|
1393
|
-
type: 'text',
|
|
1394
|
-
text: JSON.stringify({
|
|
1395
|
-
status: 'error',
|
|
1396
|
-
message: 'Failed to get component usage. Run indexing first.',
|
|
1397
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1398
|
-
}, null, 2)
|
|
1399
|
-
}
|
|
1400
|
-
]
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
case 'detect_circular_dependencies': {
|
|
1405
|
-
const { scope } = args;
|
|
1406
|
-
try {
|
|
1407
|
-
const intelligencePath = PATHS.intelligence;
|
|
1408
|
-
const content = await fs.readFile(intelligencePath, 'utf-8');
|
|
1409
|
-
const intelligence = JSON.parse(content);
|
|
1410
|
-
if (!intelligence.internalFileGraph) {
|
|
1411
|
-
return {
|
|
1412
|
-
content: [
|
|
1413
|
-
{
|
|
1414
|
-
type: 'text',
|
|
1415
|
-
text: JSON.stringify({
|
|
1416
|
-
status: 'error',
|
|
1417
|
-
message: 'Internal file graph not found. Please run refresh_index to rebuild the index with cycle detection support.'
|
|
1418
|
-
}, null, 2)
|
|
1419
|
-
}
|
|
1420
|
-
]
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1423
|
-
// Reconstruct the graph from stored data
|
|
1424
|
-
const graph = InternalFileGraph.fromJSON(intelligence.internalFileGraph, ROOT_PATH);
|
|
1425
|
-
const cycles = graph.findCycles(scope);
|
|
1426
|
-
const graphStats = intelligence.internalFileGraph.stats || graph.getStats();
|
|
1427
|
-
if (cycles.length === 0) {
|
|
1428
|
-
return {
|
|
1429
|
-
content: [
|
|
1430
|
-
{
|
|
1431
|
-
type: 'text',
|
|
1432
|
-
text: JSON.stringify({
|
|
1433
|
-
status: 'success',
|
|
1434
|
-
message: scope
|
|
1435
|
-
? `No circular dependencies detected in scope: ${scope}`
|
|
1436
|
-
: 'No circular dependencies detected in the codebase.',
|
|
1437
|
-
scope,
|
|
1438
|
-
graphStats
|
|
1439
|
-
}, null, 2)
|
|
1440
|
-
}
|
|
1441
|
-
]
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
|
-
return {
|
|
1445
|
-
content: [
|
|
1446
|
-
{
|
|
1447
|
-
type: 'text',
|
|
1448
|
-
text: JSON.stringify({
|
|
1449
|
-
status: 'warning',
|
|
1450
|
-
message: `Found ${cycles.length} circular dependency cycle(s).`,
|
|
1451
|
-
scope,
|
|
1452
|
-
cycles: cycles.map((c) => ({
|
|
1453
|
-
files: c.files,
|
|
1454
|
-
length: c.length,
|
|
1455
|
-
severity: c.length === 2 ? 'high' : c.length <= 3 ? 'medium' : 'low'
|
|
1456
|
-
})),
|
|
1457
|
-
count: cycles.length,
|
|
1458
|
-
graphStats,
|
|
1459
|
-
advice: 'Shorter cycles (length 2-3) are typically more problematic. Consider breaking the cycle by extracting shared dependencies.'
|
|
1460
|
-
}, null, 2)
|
|
1461
|
-
}
|
|
1462
|
-
]
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
catch (error) {
|
|
1466
|
-
return {
|
|
1467
|
-
content: [
|
|
1468
|
-
{
|
|
1469
|
-
type: 'text',
|
|
1470
|
-
text: JSON.stringify({
|
|
1471
|
-
status: 'error',
|
|
1472
|
-
message: 'Failed to detect circular dependencies. Run indexing first.',
|
|
1473
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1474
|
-
}, null, 2)
|
|
1475
|
-
}
|
|
1476
|
-
]
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
case 'remember': {
|
|
1481
|
-
const args_typed = args;
|
|
1482
|
-
const { type = 'decision', category, memory, reason } = args_typed;
|
|
1483
|
-
try {
|
|
1484
|
-
const crypto = await import('crypto');
|
|
1485
|
-
const memoryPath = PATHS.memory;
|
|
1486
|
-
const hashContent = `${type}:${category}:${memory}:${reason}`;
|
|
1487
|
-
const hash = crypto.createHash('sha256').update(hashContent).digest('hex');
|
|
1488
|
-
const id = hash.substring(0, 12);
|
|
1489
|
-
const newMemory = {
|
|
1490
|
-
id,
|
|
1491
|
-
type,
|
|
1492
|
-
category,
|
|
1493
|
-
memory,
|
|
1494
|
-
reason,
|
|
1495
|
-
date: new Date().toISOString()
|
|
1496
|
-
};
|
|
1497
|
-
const result = await appendMemoryFile(memoryPath, newMemory);
|
|
1498
|
-
if (result.status === 'duplicate') {
|
|
1499
|
-
return {
|
|
1500
|
-
content: [
|
|
1501
|
-
{
|
|
1502
|
-
type: 'text',
|
|
1503
|
-
text: JSON.stringify({
|
|
1504
|
-
status: 'info',
|
|
1505
|
-
message: 'This memory was already recorded.',
|
|
1506
|
-
memory: result.memory
|
|
1507
|
-
}, null, 2)
|
|
1508
|
-
}
|
|
1509
|
-
]
|
|
1510
|
-
};
|
|
1511
|
-
}
|
|
1512
|
-
return {
|
|
1513
|
-
content: [
|
|
1514
|
-
{
|
|
1515
|
-
type: 'text',
|
|
1516
|
-
text: JSON.stringify({
|
|
1517
|
-
status: 'success',
|
|
1518
|
-
message: 'Memory recorded successfully.',
|
|
1519
|
-
memory: result.memory
|
|
1520
|
-
}, null, 2)
|
|
1521
|
-
}
|
|
1522
|
-
]
|
|
1523
|
-
};
|
|
1524
|
-
}
|
|
1525
|
-
catch (error) {
|
|
1526
|
-
return {
|
|
1527
|
-
content: [
|
|
1528
|
-
{
|
|
1529
|
-
type: 'text',
|
|
1530
|
-
text: JSON.stringify({
|
|
1531
|
-
status: 'error',
|
|
1532
|
-
message: 'Failed to record memory.',
|
|
1533
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1534
|
-
}, null, 2)
|
|
1535
|
-
}
|
|
1536
|
-
]
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
508
|
+
catch {
|
|
509
|
+
/* response wasn't JSON, skip injection */
|
|
1539
510
|
}
|
|
1540
|
-
case 'get_memory': {
|
|
1541
|
-
const { category, type, query } = args;
|
|
1542
|
-
try {
|
|
1543
|
-
const memoryPath = PATHS.memory;
|
|
1544
|
-
const allMemories = await readMemoriesFile(memoryPath);
|
|
1545
|
-
if (allMemories.length === 0) {
|
|
1546
|
-
return {
|
|
1547
|
-
content: [
|
|
1548
|
-
{
|
|
1549
|
-
type: 'text',
|
|
1550
|
-
text: JSON.stringify({
|
|
1551
|
-
status: 'success',
|
|
1552
|
-
message: "No team conventions recorded yet. Use 'remember' to build tribal knowledge or memory when the user corrects you over a repeatable pattern.",
|
|
1553
|
-
memories: [],
|
|
1554
|
-
count: 0
|
|
1555
|
-
}, null, 2)
|
|
1556
|
-
}
|
|
1557
|
-
]
|
|
1558
|
-
};
|
|
1559
|
-
}
|
|
1560
|
-
const filtered = filterMemories(allMemories, { category, type, query });
|
|
1561
|
-
const limited = applyUnfilteredLimit(filtered, { category, type, query }, 20);
|
|
1562
|
-
// Enrich with confidence decay
|
|
1563
|
-
const enriched = withConfidence(limited.memories);
|
|
1564
|
-
const staleCount = enriched.filter((m) => m.stale).length;
|
|
1565
|
-
return {
|
|
1566
|
-
content: [
|
|
1567
|
-
{
|
|
1568
|
-
type: 'text',
|
|
1569
|
-
text: JSON.stringify({
|
|
1570
|
-
status: 'success',
|
|
1571
|
-
count: enriched.length,
|
|
1572
|
-
totalCount: limited.totalCount,
|
|
1573
|
-
truncated: limited.truncated,
|
|
1574
|
-
...(staleCount > 0 && {
|
|
1575
|
-
staleCount,
|
|
1576
|
-
staleNote: `${staleCount} memor${staleCount === 1 ? 'y' : 'ies'} below 30% confidence. Consider reviewing or removing.`
|
|
1577
|
-
}),
|
|
1578
|
-
message: limited.truncated
|
|
1579
|
-
? 'Showing 20 most recent. Use filters (category/type/query) for targeted results.'
|
|
1580
|
-
: undefined,
|
|
1581
|
-
memories: enriched
|
|
1582
|
-
}, null, 2)
|
|
1583
|
-
}
|
|
1584
|
-
]
|
|
1585
|
-
};
|
|
1586
|
-
}
|
|
1587
|
-
catch (error) {
|
|
1588
|
-
return {
|
|
1589
|
-
content: [
|
|
1590
|
-
{
|
|
1591
|
-
type: 'text',
|
|
1592
|
-
text: JSON.stringify({
|
|
1593
|
-
status: 'error',
|
|
1594
|
-
message: 'Failed to retrieve memories.',
|
|
1595
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1596
|
-
}, null, 2)
|
|
1597
|
-
}
|
|
1598
|
-
]
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
default:
|
|
1603
|
-
return {
|
|
1604
|
-
content: [
|
|
1605
|
-
{
|
|
1606
|
-
type: 'text',
|
|
1607
|
-
text: JSON.stringify({
|
|
1608
|
-
error: `Unknown tool: ${name}`
|
|
1609
|
-
}, null, 2)
|
|
1610
|
-
}
|
|
1611
|
-
],
|
|
1612
|
-
isError: true
|
|
1613
|
-
};
|
|
1614
511
|
}
|
|
512
|
+
return result;
|
|
1615
513
|
}
|
|
1616
514
|
catch (error) {
|
|
1617
515
|
return {
|
|
1618
516
|
content: [
|
|
1619
517
|
{
|
|
1620
518
|
type: 'text',
|
|
1621
|
-
text:
|
|
1622
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1623
|
-
stack: error instanceof Error ? error.stack : undefined
|
|
1624
|
-
}, null, 2)
|
|
519
|
+
text: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`
|
|
1625
520
|
}
|
|
1626
521
|
],
|
|
1627
522
|
isError: true
|
|
@@ -1698,10 +593,30 @@ export { server, performIndexing, resolveRootPath, shouldReindex, TOOLS };
|
|
|
1698
593
|
// Check if this module is the entry point
|
|
1699
594
|
const isDirectRun = process.argv[1]?.replace(/\\/g, '/').endsWith('index.js') ||
|
|
1700
595
|
process.argv[1]?.replace(/\\/g, '/').endsWith('index.ts');
|
|
596
|
+
const CLI_SUBCOMMANDS = [
|
|
597
|
+
'memory',
|
|
598
|
+
'search',
|
|
599
|
+
'metadata',
|
|
600
|
+
'status',
|
|
601
|
+
'reindex',
|
|
602
|
+
'style-guide',
|
|
603
|
+
'patterns',
|
|
604
|
+
'refs',
|
|
605
|
+
'cycles'
|
|
606
|
+
];
|
|
1701
607
|
if (isDirectRun) {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
process.
|
|
1705
|
-
|
|
608
|
+
const subcommand = process.argv[2];
|
|
609
|
+
if (CLI_SUBCOMMANDS.includes(subcommand) || subcommand === '--help') {
|
|
610
|
+
handleCliCommand(process.argv.slice(2)).catch((error) => {
|
|
611
|
+
console.error('Error:', error instanceof Error ? error.message : String(error));
|
|
612
|
+
process.exit(1);
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
main().catch((error) => {
|
|
617
|
+
console.error('Fatal:', error);
|
|
618
|
+
process.exit(1);
|
|
619
|
+
});
|
|
620
|
+
}
|
|
1706
621
|
}
|
|
1707
622
|
//# sourceMappingURL=index.js.map
|