snow-ai 0.3.31 → 0.3.32
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/dist/agents/codebaseIndexAgent.d.ts +102 -0
- package/dist/agents/codebaseIndexAgent.js +640 -0
- package/dist/api/embedding.d.ts +34 -0
- package/dist/api/embedding.js +74 -0
- package/dist/api/systemPrompt.d.ts +5 -1
- package/dist/api/systemPrompt.js +86 -16
- package/dist/hooks/useConversation.js +1 -1
- package/dist/mcp/aceCodeSearch.d.ts +0 -33
- package/dist/mcp/aceCodeSearch.js +0 -46
- package/dist/mcp/codebaseSearch.d.ts +44 -0
- package/dist/mcp/codebaseSearch.js +146 -0
- package/dist/ui/pages/ChatScreen.js +175 -1
- package/dist/ui/pages/CodeBaseConfigScreen.d.ts +8 -0
- package/dist/ui/pages/CodeBaseConfigScreen.js +323 -0
- package/dist/ui/pages/SubAgentConfigScreen.js +4 -0
- package/dist/ui/pages/WelcomeScreen.js +12 -1
- package/dist/utils/codebaseConfig.d.ts +20 -0
- package/dist/utils/codebaseConfig.js +75 -0
- package/dist/utils/codebaseDatabase.d.ts +102 -0
- package/dist/utils/codebaseDatabase.js +333 -0
- package/dist/utils/commands/home.js +14 -1
- package/dist/utils/mcpToolsManager.js +74 -10
- package/dist/utils/toolDisplayConfig.js +2 -0
- package/package.json +4 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { loadCodebaseConfig } from '../utils/codebaseConfig.js';
|
|
2
|
+
import { addProxyToFetchOptions } from '../utils/proxyUtils.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create embeddings for text array (single API call)
|
|
5
|
+
* @param options Embedding options
|
|
6
|
+
* @returns Embedding response with vectors
|
|
7
|
+
*/
|
|
8
|
+
export async function createEmbeddings(options) {
|
|
9
|
+
const config = loadCodebaseConfig();
|
|
10
|
+
// Use config defaults if not provided
|
|
11
|
+
const model = options.model || config.embedding.modelName;
|
|
12
|
+
const baseUrl = options.baseUrl || config.embedding.baseUrl;
|
|
13
|
+
const apiKey = options.apiKey || config.embedding.apiKey;
|
|
14
|
+
const dimensions = options.dimensions ?? config.embedding.dimensions;
|
|
15
|
+
const { input, task } = options;
|
|
16
|
+
if (!model) {
|
|
17
|
+
throw new Error('Embedding model name is required');
|
|
18
|
+
}
|
|
19
|
+
if (!baseUrl) {
|
|
20
|
+
throw new Error('Embedding base URL is required');
|
|
21
|
+
}
|
|
22
|
+
if (!apiKey) {
|
|
23
|
+
throw new Error('Embedding API key is required');
|
|
24
|
+
}
|
|
25
|
+
if (!input || input.length === 0) {
|
|
26
|
+
throw new Error('Input texts are required');
|
|
27
|
+
}
|
|
28
|
+
// Build request body
|
|
29
|
+
const requestBody = {
|
|
30
|
+
model,
|
|
31
|
+
input,
|
|
32
|
+
};
|
|
33
|
+
if (task) {
|
|
34
|
+
requestBody.task = task;
|
|
35
|
+
}
|
|
36
|
+
if (dimensions) {
|
|
37
|
+
requestBody.dimensions = dimensions;
|
|
38
|
+
}
|
|
39
|
+
// Use baseUrl directly, append /embeddings if needed
|
|
40
|
+
const url = baseUrl.endsWith('/embeddings')
|
|
41
|
+
? baseUrl
|
|
42
|
+
: `${baseUrl.replace(/\/$/, '')}/embeddings`;
|
|
43
|
+
const fetchOptions = addProxyToFetchOptions(url, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
Authorization: `Bearer ${apiKey}`,
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify(requestBody),
|
|
50
|
+
});
|
|
51
|
+
const response = await fetch(url, fetchOptions);
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
const errorText = await response.text();
|
|
54
|
+
throw new Error(`Embedding API error (${response.status}): ${errorText}`);
|
|
55
|
+
}
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create embedding for single text
|
|
61
|
+
* @param text Single text to embed
|
|
62
|
+
* @param options Optional embedding options
|
|
63
|
+
* @returns Embedding vector
|
|
64
|
+
*/
|
|
65
|
+
export async function createEmbedding(text, options) {
|
|
66
|
+
const response = await createEmbeddings({
|
|
67
|
+
input: [text],
|
|
68
|
+
...options,
|
|
69
|
+
});
|
|
70
|
+
if (response.data.length === 0) {
|
|
71
|
+
throw new Error('No embedding returned from API');
|
|
72
|
+
}
|
|
73
|
+
return response.data[0].embedding;
|
|
74
|
+
}
|
package/dist/api/systemPrompt.js
CHANGED
|
@@ -90,15 +90,7 @@ const SYSTEM_PROMPT_TEMPLATE = `You are Snow AI CLI, an intelligent command-line
|
|
|
90
90
|
- "Add validation to form" → Read form component + related validation utils → Add code → Done
|
|
91
91
|
- "Refactor error handling" → Read error handler + callers → Refactor → Done
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
1. Read the primary file(s) mentioned
|
|
95
|
-
2. Check dependencies/imports that directly impact the change
|
|
96
|
-
3. Read related files ONLY if they're critical to understanding the task
|
|
97
|
-
4. Write/modify code with proper context
|
|
98
|
-
5. Verify with build
|
|
99
|
-
6. ❌ NO excessive exploration beyond what's needed
|
|
100
|
-
7. ❌ NO reading entire modules "for reference"
|
|
101
|
-
8. ❌ NO over-planning multi-step workflows for simple tasks
|
|
93
|
+
PLACEHOLDER_FOR_WORKFLOW_SECTION
|
|
102
94
|
|
|
103
95
|
**Golden Rule: Read what you need to write correct code, nothing more.**
|
|
104
96
|
|
|
@@ -154,11 +146,8 @@ const SYSTEM_PROMPT_TEMPLATE = `You are Snow AI CLI, an intelligent command-line
|
|
|
154
146
|
- \`filesystem-edit\` - Modify existing files
|
|
155
147
|
- \`filesystem-create\` - Create new files
|
|
156
148
|
|
|
157
|
-
**Code Search
|
|
158
|
-
|
|
159
|
-
- \`ace-find-definition\` - Go to definition
|
|
160
|
-
- \`ace-find-references\` - Find all usages
|
|
161
|
-
- \`ace-text-search\` - Fast text/regex search
|
|
149
|
+
**Code Search:**
|
|
150
|
+
PLACEHOLDER_FOR_CODE_SEARCH_SECTION
|
|
162
151
|
|
|
163
152
|
**IDE Diagnostics:**
|
|
164
153
|
- \`ide-get_diagnostics\` - Get real-time diagnostics (errors, warnings, hints) from connected IDE
|
|
@@ -314,11 +303,92 @@ Guidance and recommendations:
|
|
|
314
303
|
- This file may not exist. If you can't find it, please ignore it.
|
|
315
304
|
|
|
316
305
|
Remember: **ACTION > ANALYSIS**. Write code first, investigate only when blocked.`;
|
|
306
|
+
/**
|
|
307
|
+
* Check if codebase-search tool is available
|
|
308
|
+
*/
|
|
309
|
+
function hasCodebaseSearchTool(tools) {
|
|
310
|
+
if (!tools)
|
|
311
|
+
return false;
|
|
312
|
+
return tools.some(tool => tool.function.name === 'codebase-search');
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Generate workflow section based on available tools
|
|
316
|
+
*/
|
|
317
|
+
function getWorkflowSection(hasCodebase) {
|
|
318
|
+
if (hasCodebase) {
|
|
319
|
+
return `**Your workflow:**
|
|
320
|
+
1. **Understand the task** - For conceptual questions, try \\\`codebase-search\\\` FIRST (semantic search)
|
|
321
|
+
2. Read the primary file(s) mentioned (or files found by codebase search)
|
|
322
|
+
3. Check dependencies/imports that directly impact the change
|
|
323
|
+
4. For precise symbol lookup, use \\\`ace-search-symbols\\\`, \\\`ace-find-definition\\\`, or \\\`ace-find-references\\\`
|
|
324
|
+
5. Read related files ONLY if they're critical to understanding the task
|
|
325
|
+
6. Write/modify code with proper context
|
|
326
|
+
7. Verify with build
|
|
327
|
+
8. ❌ NO excessive exploration beyond what's needed
|
|
328
|
+
9. ❌ NO reading entire modules "for reference"
|
|
329
|
+
10. ❌ NO over-planning multi-step workflows for simple tasks`;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
return `**Your workflow:**
|
|
333
|
+
1. Read the primary file(s) mentioned
|
|
334
|
+
2. Use \\\`ace-search-symbols\\\`, \\\`ace-find-definition\\\`, or \\\`ace-find-references\\\` to find related code
|
|
335
|
+
3. Check dependencies/imports that directly impact the change
|
|
336
|
+
4. Read related files ONLY if they're critical to understanding the task
|
|
337
|
+
5. Write/modify code with proper context
|
|
338
|
+
6. Verify with build
|
|
339
|
+
7. ❌ NO excessive exploration beyond what's needed
|
|
340
|
+
8. ❌ NO reading entire modules "for reference"
|
|
341
|
+
9. ❌ NO over-planning multi-step workflows for simple tasks`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Generate code search section based on available tools
|
|
346
|
+
*/
|
|
347
|
+
function getCodeSearchSection(hasCodebase) {
|
|
348
|
+
if (hasCodebase) {
|
|
349
|
+
// When codebase tool is available, prioritize it
|
|
350
|
+
return `**Code Search:**
|
|
351
|
+
|
|
352
|
+
🎯 **Priority Order (use in this sequence):**
|
|
353
|
+
|
|
354
|
+
1. **Codebase Semantic Search** (⚡ HIGHEST PRIORITY):
|
|
355
|
+
- \\\`codebase-search\\\` - Semantic search using embeddings
|
|
356
|
+
- 🔍 Find code by MEANING, not just keywords
|
|
357
|
+
- 🎯 Best for: "how is authentication handled", "error handling patterns"
|
|
358
|
+
- 📊 Returns: Full code content + similarity scores + file locations
|
|
359
|
+
- 💡 **IMPORTANT**: Always try this FIRST for conceptual queries!
|
|
360
|
+
- 🚀 **When to use**: Understanding concepts, finding similar code, pattern discovery
|
|
361
|
+
- ❌ **When to skip**: Exact symbol names, file-specific searches (use ACE instead)
|
|
362
|
+
|
|
363
|
+
2. **ACE Code Search** (Fallback for precise lookups):
|
|
364
|
+
- \\\`ace-search-symbols\\\` - Find functions/classes/variables by exact name
|
|
365
|
+
- \\\`ace-find-definition\\\` - Go to definition of a symbol
|
|
366
|
+
- \\\`ace-find-references\\\` - Find all usages of a symbol
|
|
367
|
+
- \\\`ace-text-search\\\` - Fast text/regex search across files
|
|
368
|
+
- 💡 **When to use**: Exact symbol lookup, reference finding, regex patterns`;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
// When codebase tool is NOT available, only show ACE
|
|
372
|
+
return `**Code Search (ACE):**
|
|
373
|
+
- \\\`ace-search-symbols\\\` - Find functions/classes/variables by exact name
|
|
374
|
+
- \\\`ace-find-definition\\\` - Go to definition of a symbol
|
|
375
|
+
- \\\`ace-find-references\\\` - Find all usages of a symbol
|
|
376
|
+
- \\\`ace-text-search\\\` - Fast text/regex search across files`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
317
379
|
// Export SYSTEM_PROMPT as a getter function for real-time ROLE.md updates
|
|
318
|
-
export function getSystemPrompt() {
|
|
380
|
+
export function getSystemPrompt(tools) {
|
|
319
381
|
const basePrompt = getSystemPromptWithRole();
|
|
320
382
|
const systemEnv = getSystemEnvironmentInfo();
|
|
321
|
-
|
|
383
|
+
const hasCodebase = hasCodebaseSearchTool(tools);
|
|
384
|
+
// Generate dynamic sections
|
|
385
|
+
const workflowSection = getWorkflowSection(hasCodebase);
|
|
386
|
+
const codeSearchSection = getCodeSearchSection(hasCodebase);
|
|
387
|
+
// Replace placeholders with actual content
|
|
388
|
+
let finalPrompt = basePrompt
|
|
389
|
+
.replace('PLACEHOLDER_FOR_WORKFLOW_SECTION', workflowSection)
|
|
390
|
+
.replace('PLACEHOLDER_FOR_CODE_SEARCH_SECTION', codeSearchSection);
|
|
391
|
+
return `${finalPrompt}
|
|
322
392
|
|
|
323
393
|
## 💻 System Environment
|
|
324
394
|
|
|
@@ -37,7 +37,7 @@ export async function handleConversationWithTools(options) {
|
|
|
37
37
|
const mcpTools = await collectAllMCPTools();
|
|
38
38
|
// Build conversation history with TODO context as pinned user message
|
|
39
39
|
let conversationMessages = [
|
|
40
|
-
{ role: 'system', content: getSystemPrompt() },
|
|
40
|
+
{ role: 'system', content: getSystemPrompt(mcpTools) },
|
|
41
41
|
];
|
|
42
42
|
// If there are TODOs, add pinned context message at the front
|
|
43
43
|
if (existingTodoList && existingTodoList.todos.length > 0) {
|
|
@@ -86,19 +86,6 @@ export declare class ACECodeSearchService {
|
|
|
86
86
|
* Search with language-specific context (cross-reference search)
|
|
87
87
|
*/
|
|
88
88
|
semanticSearch(query: string, searchType?: 'definition' | 'usage' | 'implementation' | 'all', language?: string, maxResults?: number): Promise<SemanticSearchResult>;
|
|
89
|
-
/**
|
|
90
|
-
* Clear the symbol index cache and force full re-index on next search
|
|
91
|
-
*/
|
|
92
|
-
clearCache(): void;
|
|
93
|
-
/**
|
|
94
|
-
* Get index statistics
|
|
95
|
-
*/
|
|
96
|
-
getIndexStats(): {
|
|
97
|
-
totalFiles: number;
|
|
98
|
-
totalSymbols: number;
|
|
99
|
-
languageBreakdown: Record<string, number>;
|
|
100
|
-
cacheAge: number;
|
|
101
|
-
};
|
|
102
89
|
}
|
|
103
90
|
export declare const aceCodeSearchService: ACECodeSearchService;
|
|
104
91
|
export declare const mcpTools: ({
|
|
@@ -282,24 +269,4 @@ export declare const mcpTools: ({
|
|
|
282
269
|
};
|
|
283
270
|
required: string[];
|
|
284
271
|
};
|
|
285
|
-
} | {
|
|
286
|
-
name: string;
|
|
287
|
-
description: string;
|
|
288
|
-
inputSchema: {
|
|
289
|
-
type: string;
|
|
290
|
-
properties: {
|
|
291
|
-
query?: undefined;
|
|
292
|
-
symbolType?: undefined;
|
|
293
|
-
language?: undefined;
|
|
294
|
-
maxResults?: undefined;
|
|
295
|
-
symbolName?: undefined;
|
|
296
|
-
contextFile?: undefined;
|
|
297
|
-
searchType?: undefined;
|
|
298
|
-
filePath?: undefined;
|
|
299
|
-
pattern?: undefined;
|
|
300
|
-
fileGlob?: undefined;
|
|
301
|
-
isRegex?: undefined;
|
|
302
|
-
};
|
|
303
|
-
required?: undefined;
|
|
304
|
-
};
|
|
305
272
|
})[];
|
|
@@ -875,36 +875,6 @@ export class ACECodeSearchService {
|
|
|
875
875
|
searchTime,
|
|
876
876
|
};
|
|
877
877
|
}
|
|
878
|
-
/**
|
|
879
|
-
* Clear the symbol index cache and force full re-index on next search
|
|
880
|
-
*/
|
|
881
|
-
clearCache() {
|
|
882
|
-
this.indexCache.clear();
|
|
883
|
-
this.fileModTimes.clear();
|
|
884
|
-
this.allIndexedFiles.clear();
|
|
885
|
-
this.fileContentCache.clear();
|
|
886
|
-
this.lastIndexTime = 0;
|
|
887
|
-
}
|
|
888
|
-
/**
|
|
889
|
-
* Get index statistics
|
|
890
|
-
*/
|
|
891
|
-
getIndexStats() {
|
|
892
|
-
let totalSymbols = 0;
|
|
893
|
-
const languageBreakdown = {};
|
|
894
|
-
for (const symbols of this.indexCache.values()) {
|
|
895
|
-
totalSymbols += symbols.length;
|
|
896
|
-
for (const symbol of symbols) {
|
|
897
|
-
languageBreakdown[symbol.language] =
|
|
898
|
-
(languageBreakdown[symbol.language] || 0) + 1;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
return {
|
|
902
|
-
totalFiles: this.indexCache.size,
|
|
903
|
-
totalSymbols,
|
|
904
|
-
languageBreakdown,
|
|
905
|
-
cacheAge: Date.now() - this.lastIndexTime,
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
878
|
}
|
|
909
879
|
// Export a default instance
|
|
910
880
|
export const aceCodeSearchService = new ACECodeSearchService();
|
|
@@ -1075,20 +1045,4 @@ export const mcpTools = [
|
|
|
1075
1045
|
required: ['pattern'],
|
|
1076
1046
|
},
|
|
1077
1047
|
},
|
|
1078
|
-
{
|
|
1079
|
-
name: 'ace-index_stats',
|
|
1080
|
-
description: 'ACE Code Search: Get statistics about the code index. Shows number of indexed files, symbols, language breakdown, and cache status. Useful for understanding search coverage.',
|
|
1081
|
-
inputSchema: {
|
|
1082
|
-
type: 'object',
|
|
1083
|
-
properties: {},
|
|
1084
|
-
},
|
|
1085
|
-
},
|
|
1086
|
-
{
|
|
1087
|
-
name: 'ace-clear_cache',
|
|
1088
|
-
description: 'ACE Code Search: Clear the symbol index cache and force a full re-index on next search. Use when codebase has changed significantly or search results seem stale.',
|
|
1089
|
-
inputSchema: {
|
|
1090
|
-
type: 'object',
|
|
1091
|
-
properties: {},
|
|
1092
|
-
},
|
|
1093
|
-
},
|
|
1094
1048
|
];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codebase Search Service
|
|
3
|
+
* Provides semantic search capabilities for the codebase using embeddings
|
|
4
|
+
*/
|
|
5
|
+
declare class CodebaseSearchService {
|
|
6
|
+
/**
|
|
7
|
+
* Check if codebase index is available and has data
|
|
8
|
+
*/
|
|
9
|
+
private isCodebaseIndexAvailable;
|
|
10
|
+
/**
|
|
11
|
+
* Calculate cosine similarity between two vectors
|
|
12
|
+
*/
|
|
13
|
+
private cosineSimilarity;
|
|
14
|
+
/**
|
|
15
|
+
* Search codebase using semantic similarity
|
|
16
|
+
*/
|
|
17
|
+
search(query: string, topN?: number): Promise<any>;
|
|
18
|
+
}
|
|
19
|
+
export declare const codebaseSearchService: CodebaseSearchService;
|
|
20
|
+
/**
|
|
21
|
+
* MCP Tools Definition
|
|
22
|
+
*/
|
|
23
|
+
export declare const mcpTools: {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: string;
|
|
28
|
+
properties: {
|
|
29
|
+
query: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
topN: {
|
|
34
|
+
type: string;
|
|
35
|
+
description: string;
|
|
36
|
+
default: number;
|
|
37
|
+
minimum: number;
|
|
38
|
+
maximum: number;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
required: string[];
|
|
42
|
+
};
|
|
43
|
+
}[];
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { CodebaseDatabase } from '../utils/codebaseDatabase.js';
|
|
2
|
+
import { createEmbedding } from '../api/embedding.js';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
/**
|
|
7
|
+
* Codebase Search Service
|
|
8
|
+
* Provides semantic search capabilities for the codebase using embeddings
|
|
9
|
+
*/
|
|
10
|
+
class CodebaseSearchService {
|
|
11
|
+
/**
|
|
12
|
+
* Check if codebase index is available and has data
|
|
13
|
+
*/
|
|
14
|
+
isCodebaseIndexAvailable() {
|
|
15
|
+
try {
|
|
16
|
+
const projectRoot = process.cwd();
|
|
17
|
+
const dbPath = path.join(projectRoot, '.snow', 'codebase', 'embeddings.db');
|
|
18
|
+
// Check if database file exists
|
|
19
|
+
if (!fs.existsSync(dbPath)) {
|
|
20
|
+
return {
|
|
21
|
+
available: false,
|
|
22
|
+
reason: 'Codebase index not found. Please run codebase indexing first.',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// Initialize database and check for data
|
|
26
|
+
const db = new CodebaseDatabase(projectRoot);
|
|
27
|
+
db.initialize();
|
|
28
|
+
const totalChunks = db.getTotalChunks();
|
|
29
|
+
db.close();
|
|
30
|
+
if (totalChunks === 0) {
|
|
31
|
+
return {
|
|
32
|
+
available: false,
|
|
33
|
+
reason: 'Codebase index is empty. Please run indexing to build the index.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { available: true };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
logger.error('Error checking codebase index availability:', error);
|
|
40
|
+
return {
|
|
41
|
+
available: false,
|
|
42
|
+
reason: `Error checking codebase index: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Calculate cosine similarity between two vectors
|
|
48
|
+
*/
|
|
49
|
+
cosineSimilarity(a, b) {
|
|
50
|
+
if (a.length !== b.length) {
|
|
51
|
+
throw new Error('Vectors must have same length');
|
|
52
|
+
}
|
|
53
|
+
let dotProduct = 0;
|
|
54
|
+
let normA = 0;
|
|
55
|
+
let normB = 0;
|
|
56
|
+
for (let i = 0; i < a.length; i++) {
|
|
57
|
+
dotProduct += a[i] * b[i];
|
|
58
|
+
normA += a[i] * a[i];
|
|
59
|
+
normB += b[i] * b[i];
|
|
60
|
+
}
|
|
61
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Search codebase using semantic similarity
|
|
65
|
+
*/
|
|
66
|
+
async search(query, topN = 10) {
|
|
67
|
+
// Check if codebase index is available
|
|
68
|
+
const { available, reason } = this.isCodebaseIndexAvailable();
|
|
69
|
+
if (!available) {
|
|
70
|
+
return {
|
|
71
|
+
error: reason,
|
|
72
|
+
results: [],
|
|
73
|
+
totalResults: 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const projectRoot = process.cwd();
|
|
78
|
+
const db = new CodebaseDatabase(projectRoot);
|
|
79
|
+
db.initialize();
|
|
80
|
+
const totalChunks = db.getTotalChunks();
|
|
81
|
+
// Generate embedding for query
|
|
82
|
+
logger.info(`Generating embedding for query: "${query}"`);
|
|
83
|
+
const queryEmbedding = await createEmbedding(query);
|
|
84
|
+
// Search similar chunks
|
|
85
|
+
logger.info(`Searching top ${topN} similar chunks from ${totalChunks} total chunks`);
|
|
86
|
+
const results = db.searchSimilar(queryEmbedding, topN);
|
|
87
|
+
// Format results with similarity scores and full content (no truncation)
|
|
88
|
+
const formattedResults = results.map((chunk, index) => {
|
|
89
|
+
const score = this.cosineSimilarity(queryEmbedding, chunk.embedding);
|
|
90
|
+
const scorePercent = (score * 100).toFixed(2);
|
|
91
|
+
return {
|
|
92
|
+
rank: index + 1,
|
|
93
|
+
filePath: chunk.filePath,
|
|
94
|
+
startLine: chunk.startLine,
|
|
95
|
+
endLine: chunk.endLine,
|
|
96
|
+
content: chunk.content, // Full content, no truncation
|
|
97
|
+
similarityScore: scorePercent,
|
|
98
|
+
location: `${chunk.filePath}:${chunk.startLine}-${chunk.endLine}`,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
db.close();
|
|
102
|
+
return {
|
|
103
|
+
query,
|
|
104
|
+
totalChunks,
|
|
105
|
+
resultsCount: formattedResults.length,
|
|
106
|
+
results: formattedResults,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
logger.error('Codebase search failed:', error);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Export singleton instance
|
|
116
|
+
export const codebaseSearchService = new CodebaseSearchService();
|
|
117
|
+
/**
|
|
118
|
+
* MCP Tools Definition
|
|
119
|
+
*/
|
|
120
|
+
export const mcpTools = [
|
|
121
|
+
{
|
|
122
|
+
name: 'codebase-search',
|
|
123
|
+
description: '🔍 Semantic search across the codebase using embeddings. ' +
|
|
124
|
+
'Finds code snippets similar to your query based on meaning, not just keywords. ' +
|
|
125
|
+
'Returns full code content with similarity scores and file locations. ' +
|
|
126
|
+
'NOTE: Only available when codebase indexing is enabled and the index has been built. ' +
|
|
127
|
+
'If the index is not available, the tool will return an error message with instructions.',
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
query: {
|
|
132
|
+
type: 'string',
|
|
133
|
+
description: 'Search query describing the code you want to find (e.g., "database query", "error handling", "authentication logic")',
|
|
134
|
+
},
|
|
135
|
+
topN: {
|
|
136
|
+
type: 'number',
|
|
137
|
+
description: 'Maximum number of results to return (default: 10, max: 50)',
|
|
138
|
+
default: 10,
|
|
139
|
+
minimum: 1,
|
|
140
|
+
maximum: 50,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
required: ['query'],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
];
|