snow-ai 0.3.31 → 0.3.33
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/app.js +2 -2
- 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/CustomHeadersScreen.d.ts +2 -2
- package/dist/ui/pages/CustomHeadersScreen.js +486 -96
- package/dist/ui/pages/SubAgentConfigScreen.js +4 -0
- package/dist/ui/pages/SystemPromptConfigScreen.d.ts +2 -2
- package/dist/ui/pages/SystemPromptConfigScreen.js +313 -78
- package/dist/ui/pages/WelcomeScreen.js +25 -2
- package/dist/utils/apiConfig.d.ts +52 -3
- package/dist/utils/apiConfig.js +150 -35
- 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 +76 -11
- package/dist/utils/toolDisplayConfig.js +2 -0
- package/package.json +4 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* Codebase SQLite database manager
|
|
7
|
+
* Handles embedding storage with vector support
|
|
8
|
+
*/
|
|
9
|
+
export class CodebaseDatabase {
|
|
10
|
+
constructor(projectRoot) {
|
|
11
|
+
Object.defineProperty(this, "db", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: null
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(this, "dbPath", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(this, "initialized", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: false
|
|
28
|
+
});
|
|
29
|
+
// Store database in .snow/codebase directory
|
|
30
|
+
const snowDir = path.join(projectRoot, '.snow', 'codebase');
|
|
31
|
+
if (!fs.existsSync(snowDir)) {
|
|
32
|
+
fs.mkdirSync(snowDir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
this.dbPath = path.join(snowDir, 'embeddings.db');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Initialize database and create tables
|
|
38
|
+
*/
|
|
39
|
+
initialize() {
|
|
40
|
+
if (this.initialized)
|
|
41
|
+
return;
|
|
42
|
+
try {
|
|
43
|
+
// Open database with better-sqlite3
|
|
44
|
+
this.db = new Database(this.dbPath);
|
|
45
|
+
// Enable WAL mode for better concurrency
|
|
46
|
+
this.db.pragma('journal_mode = WAL');
|
|
47
|
+
// Create tables
|
|
48
|
+
this.createTables();
|
|
49
|
+
this.initialized = true;
|
|
50
|
+
logger.info('Codebase database initialized', { path: this.dbPath });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
logger.error('Failed to initialize codebase database', error);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create database tables
|
|
59
|
+
*/
|
|
60
|
+
createTables() {
|
|
61
|
+
if (!this.db)
|
|
62
|
+
throw new Error('Database not initialized');
|
|
63
|
+
// Code chunks table with embeddings
|
|
64
|
+
this.db.exec(`
|
|
65
|
+
CREATE TABLE IF NOT EXISTS code_chunks (
|
|
66
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
67
|
+
file_path TEXT NOT NULL,
|
|
68
|
+
content TEXT NOT NULL,
|
|
69
|
+
start_line INTEGER NOT NULL,
|
|
70
|
+
end_line INTEGER NOT NULL,
|
|
71
|
+
embedding BLOB NOT NULL,
|
|
72
|
+
file_hash TEXT NOT NULL,
|
|
73
|
+
created_at INTEGER NOT NULL,
|
|
74
|
+
updated_at INTEGER NOT NULL
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_file_path ON code_chunks(file_path);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_file_hash ON code_chunks(file_hash);
|
|
79
|
+
`);
|
|
80
|
+
// Indexing progress table
|
|
81
|
+
this.db.exec(`
|
|
82
|
+
CREATE TABLE IF NOT EXISTS index_progress (
|
|
83
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
84
|
+
total_files INTEGER NOT NULL DEFAULT 0,
|
|
85
|
+
processed_files INTEGER NOT NULL DEFAULT 0,
|
|
86
|
+
total_chunks INTEGER NOT NULL DEFAULT 0,
|
|
87
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
88
|
+
last_error TEXT,
|
|
89
|
+
last_processed_file TEXT,
|
|
90
|
+
started_at INTEGER,
|
|
91
|
+
completed_at INTEGER,
|
|
92
|
+
updated_at INTEGER NOT NULL,
|
|
93
|
+
watcher_enabled INTEGER NOT NULL DEFAULT 0
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
-- Initialize progress record if not exists
|
|
97
|
+
INSERT OR IGNORE INTO index_progress (id, updated_at) VALUES (1, ${Date.now()});
|
|
98
|
+
`);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Insert or update code chunks (batch operation)
|
|
102
|
+
*/
|
|
103
|
+
insertChunks(chunks) {
|
|
104
|
+
if (!this.db)
|
|
105
|
+
throw new Error('Database not initialized');
|
|
106
|
+
const insert = this.db.prepare(`
|
|
107
|
+
INSERT INTO code_chunks (
|
|
108
|
+
file_path, content, start_line, end_line,
|
|
109
|
+
embedding, file_hash, created_at, updated_at
|
|
110
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
111
|
+
`);
|
|
112
|
+
const transaction = this.db.transaction((chunks) => {
|
|
113
|
+
for (const chunk of chunks) {
|
|
114
|
+
// Convert embedding array to Buffer for storage
|
|
115
|
+
const embeddingBuffer = Buffer.from(new Float32Array(chunk.embedding).buffer);
|
|
116
|
+
insert.run(chunk.filePath, chunk.content, chunk.startLine, chunk.endLine, embeddingBuffer, chunk.fileHash, chunk.createdAt, chunk.updatedAt);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
transaction(chunks);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Delete chunks by file path
|
|
123
|
+
*/
|
|
124
|
+
deleteChunksByFile(filePath) {
|
|
125
|
+
if (!this.db)
|
|
126
|
+
throw new Error('Database not initialized');
|
|
127
|
+
const stmt = this.db.prepare('DELETE FROM code_chunks WHERE file_path = ?');
|
|
128
|
+
stmt.run(filePath);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get chunks by file path
|
|
132
|
+
*/
|
|
133
|
+
getChunksByFile(filePath) {
|
|
134
|
+
if (!this.db)
|
|
135
|
+
throw new Error('Database not initialized');
|
|
136
|
+
const stmt = this.db.prepare('SELECT * FROM code_chunks WHERE file_path = ?');
|
|
137
|
+
const rows = stmt.all(filePath);
|
|
138
|
+
return rows.map(row => this.rowToChunk(row));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Check if file has been indexed by hash
|
|
142
|
+
*/
|
|
143
|
+
hasFileHash(fileHash) {
|
|
144
|
+
if (!this.db)
|
|
145
|
+
throw new Error('Database not initialized');
|
|
146
|
+
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM code_chunks WHERE file_hash = ?');
|
|
147
|
+
const result = stmt.get(fileHash);
|
|
148
|
+
return result.count > 0;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get total chunks count
|
|
152
|
+
*/
|
|
153
|
+
getTotalChunks() {
|
|
154
|
+
if (!this.db)
|
|
155
|
+
throw new Error('Database not initialized');
|
|
156
|
+
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM code_chunks');
|
|
157
|
+
const result = stmt.get();
|
|
158
|
+
return result.count;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Search similar code chunks by embedding
|
|
162
|
+
* Uses cosine similarity
|
|
163
|
+
*/
|
|
164
|
+
searchSimilar(queryEmbedding, limit = 10) {
|
|
165
|
+
if (!this.db)
|
|
166
|
+
throw new Error('Database not initialized');
|
|
167
|
+
// Get all chunks (in production, use approximate nearest neighbor)
|
|
168
|
+
const stmt = this.db.prepare('SELECT * FROM code_chunks');
|
|
169
|
+
const rows = stmt.all();
|
|
170
|
+
// Calculate cosine similarity for each chunk
|
|
171
|
+
const results = rows.map(row => {
|
|
172
|
+
const chunk = this.rowToChunk(row);
|
|
173
|
+
const similarity = this.cosineSimilarity(queryEmbedding, chunk.embedding);
|
|
174
|
+
return { chunk, similarity };
|
|
175
|
+
});
|
|
176
|
+
// Sort by similarity and return top N
|
|
177
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
178
|
+
return results.slice(0, limit).map(r => r.chunk);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Update indexing progress
|
|
182
|
+
*/
|
|
183
|
+
updateProgress(progress) {
|
|
184
|
+
if (!this.db || !this.initialized) {
|
|
185
|
+
// Silently ignore if database is not initialized
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const fields = [];
|
|
189
|
+
const values = [];
|
|
190
|
+
if (progress.totalFiles !== undefined) {
|
|
191
|
+
fields.push('total_files = ?');
|
|
192
|
+
values.push(progress.totalFiles);
|
|
193
|
+
}
|
|
194
|
+
if (progress.processedFiles !== undefined) {
|
|
195
|
+
fields.push('processed_files = ?');
|
|
196
|
+
values.push(progress.processedFiles);
|
|
197
|
+
}
|
|
198
|
+
if (progress.totalChunks !== undefined) {
|
|
199
|
+
fields.push('total_chunks = ?');
|
|
200
|
+
values.push(progress.totalChunks);
|
|
201
|
+
}
|
|
202
|
+
if (progress.status !== undefined) {
|
|
203
|
+
fields.push('status = ?');
|
|
204
|
+
values.push(progress.status);
|
|
205
|
+
}
|
|
206
|
+
if (progress.lastError !== undefined) {
|
|
207
|
+
fields.push('last_error = ?');
|
|
208
|
+
values.push(progress.lastError);
|
|
209
|
+
}
|
|
210
|
+
if (progress.lastProcessedFile !== undefined) {
|
|
211
|
+
fields.push('last_processed_file = ?');
|
|
212
|
+
values.push(progress.lastProcessedFile);
|
|
213
|
+
}
|
|
214
|
+
if (progress.startedAt !== undefined) {
|
|
215
|
+
fields.push('started_at = ?');
|
|
216
|
+
values.push(progress.startedAt);
|
|
217
|
+
}
|
|
218
|
+
if (progress.completedAt !== undefined) {
|
|
219
|
+
fields.push('completed_at = ?');
|
|
220
|
+
values.push(progress.completedAt);
|
|
221
|
+
}
|
|
222
|
+
fields.push('updated_at = ?');
|
|
223
|
+
values.push(Date.now());
|
|
224
|
+
const sql = `UPDATE index_progress SET ${fields.join(', ')} WHERE id = 1`;
|
|
225
|
+
this.db.prepare(sql).run(...values);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get current indexing progress
|
|
229
|
+
*/
|
|
230
|
+
getProgress() {
|
|
231
|
+
if (!this.db)
|
|
232
|
+
throw new Error('Database not initialized');
|
|
233
|
+
const stmt = this.db.prepare('SELECT * FROM index_progress WHERE id = 1');
|
|
234
|
+
const row = stmt.get();
|
|
235
|
+
return {
|
|
236
|
+
totalFiles: row.total_files,
|
|
237
|
+
processedFiles: row.processed_files,
|
|
238
|
+
totalChunks: row.total_chunks,
|
|
239
|
+
status: row.status,
|
|
240
|
+
lastError: row.last_error,
|
|
241
|
+
lastProcessedFile: row.last_processed_file,
|
|
242
|
+
startedAt: row.started_at,
|
|
243
|
+
completedAt: row.completed_at,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Set watcher enabled status
|
|
248
|
+
*/
|
|
249
|
+
setWatcherEnabled(enabled) {
|
|
250
|
+
if (!this.db)
|
|
251
|
+
throw new Error('Database not initialized');
|
|
252
|
+
this.db
|
|
253
|
+
.prepare('UPDATE index_progress SET watcher_enabled = ? WHERE id = 1')
|
|
254
|
+
.run(enabled ? 1 : 0);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get watcher enabled status
|
|
258
|
+
*/
|
|
259
|
+
isWatcherEnabled() {
|
|
260
|
+
if (!this.db)
|
|
261
|
+
throw new Error('Database not initialized');
|
|
262
|
+
const stmt = this.db.prepare('SELECT watcher_enabled FROM index_progress WHERE id = 1');
|
|
263
|
+
const result = stmt.get();
|
|
264
|
+
return result.watcher_enabled === 1;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Clear all chunks and reset progress
|
|
268
|
+
*/
|
|
269
|
+
clear() {
|
|
270
|
+
if (!this.db)
|
|
271
|
+
throw new Error('Database not initialized');
|
|
272
|
+
this.db.exec('DELETE FROM code_chunks');
|
|
273
|
+
this.db.exec(`
|
|
274
|
+
UPDATE index_progress
|
|
275
|
+
SET total_files = 0,
|
|
276
|
+
processed_files = 0,
|
|
277
|
+
total_chunks = 0,
|
|
278
|
+
status = 'idle',
|
|
279
|
+
last_error = NULL,
|
|
280
|
+
last_processed_file = NULL,
|
|
281
|
+
started_at = NULL,
|
|
282
|
+
completed_at = NULL,
|
|
283
|
+
updated_at = ${Date.now()}
|
|
284
|
+
WHERE id = 1
|
|
285
|
+
`);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Close database connection
|
|
289
|
+
*/
|
|
290
|
+
close() {
|
|
291
|
+
if (this.db) {
|
|
292
|
+
this.db.close();
|
|
293
|
+
this.db = null;
|
|
294
|
+
this.initialized = false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Convert database row to CodeChunk
|
|
299
|
+
*/
|
|
300
|
+
rowToChunk(row) {
|
|
301
|
+
// Convert Buffer back to number array
|
|
302
|
+
const embeddingBuffer = row.embedding;
|
|
303
|
+
const embedding = Array.from(new Float32Array(embeddingBuffer.buffer, embeddingBuffer.byteOffset, embeddingBuffer.byteLength / 4));
|
|
304
|
+
return {
|
|
305
|
+
id: row.id,
|
|
306
|
+
filePath: row.file_path,
|
|
307
|
+
content: row.content,
|
|
308
|
+
startLine: row.start_line,
|
|
309
|
+
endLine: row.end_line,
|
|
310
|
+
embedding,
|
|
311
|
+
fileHash: row.file_hash,
|
|
312
|
+
createdAt: row.created_at,
|
|
313
|
+
updatedAt: row.updated_at,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Calculate cosine similarity between two vectors
|
|
318
|
+
*/
|
|
319
|
+
cosineSimilarity(a, b) {
|
|
320
|
+
if (a.length !== b.length) {
|
|
321
|
+
throw new Error('Vectors must have same length');
|
|
322
|
+
}
|
|
323
|
+
let dotProduct = 0;
|
|
324
|
+
let normA = 0;
|
|
325
|
+
let normB = 0;
|
|
326
|
+
for (let i = 0; i < a.length; i++) {
|
|
327
|
+
dotProduct += a[i] * b[i];
|
|
328
|
+
normA += a[i] * a[i];
|
|
329
|
+
normB += b[i] * b[i];
|
|
330
|
+
}
|
|
331
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -5,7 +5,20 @@ import { resetOpenAIClient as resetChatClient } from '../../api/chat.js';
|
|
|
5
5
|
import { resetOpenAIClient as resetResponseClient } from '../../api/responses.js';
|
|
6
6
|
// Home command handler - returns to welcome screen
|
|
7
7
|
registerCommand('home', {
|
|
8
|
-
execute: () => {
|
|
8
|
+
execute: async () => {
|
|
9
|
+
// Stop codebase indexing if running (to avoid database errors)
|
|
10
|
+
if (global.__stopCodebaseIndexing) {
|
|
11
|
+
try {
|
|
12
|
+
// Show stopping message
|
|
13
|
+
console.log('\n⏸ Pausing codebase indexing...');
|
|
14
|
+
await global.__stopCodebaseIndexing();
|
|
15
|
+
console.log('✓ Indexing paused, progress saved\n');
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
// Ignore errors during stop
|
|
19
|
+
console.error('Failed to stop codebase indexing:', error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
9
22
|
// Clear all API configuration caches
|
|
10
23
|
resetAnthropicClient();
|
|
11
24
|
resetGeminiClient();
|
|
@@ -8,6 +8,7 @@ import { mcpTools as terminalTools } from '../mcp/bash.js';
|
|
|
8
8
|
import { mcpTools as aceCodeSearchTools } from '../mcp/aceCodeSearch.js';
|
|
9
9
|
import { mcpTools as websearchTools } from '../mcp/websearch.js';
|
|
10
10
|
import { mcpTools as ideDiagnosticsTools } from '../mcp/ideDiagnostics.js';
|
|
11
|
+
import { mcpTools as codebaseSearchTools } from '../mcp/codebaseSearch.js';
|
|
11
12
|
import { TodoService } from '../mcp/todo.js';
|
|
12
13
|
import { mcpTools as notebookTools, executeNotebookTool, } from '../mcp/notebook.js';
|
|
13
14
|
import { getMCPTools as getSubAgentTools, subAgentService, } from '../mcp/subagent.js';
|
|
@@ -32,13 +33,17 @@ export function getTodoService() {
|
|
|
32
33
|
/**
|
|
33
34
|
* Generate a hash of the current MCP configuration and sub-agents
|
|
34
35
|
*/
|
|
35
|
-
function generateConfigHash() {
|
|
36
|
+
async function generateConfigHash() {
|
|
36
37
|
try {
|
|
37
38
|
const mcpConfig = getMCPConfig();
|
|
38
39
|
const subAgents = getSubAgentTools(); // Include sub-agents in hash
|
|
40
|
+
// 🔥 CRITICAL: Include codebase enabled status in hash
|
|
41
|
+
const { loadCodebaseConfig } = await import('./codebaseConfig.js');
|
|
42
|
+
const codebaseConfig = loadCodebaseConfig();
|
|
39
43
|
return JSON.stringify({
|
|
40
44
|
mcpServers: mcpConfig.mcpServers,
|
|
41
45
|
subAgents: subAgents.map(t => t.name), // Only track agent names for hash
|
|
46
|
+
codebaseEnabled: codebaseConfig.enabled, // 🔥 Must include to invalidate cache on enable/disable
|
|
42
47
|
});
|
|
43
48
|
}
|
|
44
49
|
catch {
|
|
@@ -48,19 +53,20 @@ function generateConfigHash() {
|
|
|
48
53
|
/**
|
|
49
54
|
* Check if the cache is valid and not expired
|
|
50
55
|
*/
|
|
51
|
-
function isCacheValid() {
|
|
56
|
+
async function isCacheValid() {
|
|
52
57
|
if (!toolsCache)
|
|
53
58
|
return false;
|
|
54
59
|
const now = Date.now();
|
|
55
60
|
const isExpired = now - toolsCache.lastUpdate > CACHE_DURATION;
|
|
56
|
-
const
|
|
61
|
+
const configHash = await generateConfigHash();
|
|
62
|
+
const configChanged = toolsCache.configHash !== configHash;
|
|
57
63
|
return !isExpired && !configChanged;
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
60
66
|
* Get cached tools or build cache if needed
|
|
61
67
|
*/
|
|
62
68
|
async function getCachedTools() {
|
|
63
|
-
if (isCacheValid()) {
|
|
69
|
+
if (await isCacheValid()) {
|
|
64
70
|
return toolsCache.tools;
|
|
65
71
|
}
|
|
66
72
|
await refreshToolsCache();
|
|
@@ -248,6 +254,54 @@ async function refreshToolsCache() {
|
|
|
248
254
|
});
|
|
249
255
|
}
|
|
250
256
|
}
|
|
257
|
+
// Add built-in Codebase Search tools (conditionally loaded if enabled and index is available)
|
|
258
|
+
try {
|
|
259
|
+
// First check if codebase feature is enabled in config
|
|
260
|
+
const { loadCodebaseConfig } = await import('./codebaseConfig.js');
|
|
261
|
+
const codebaseConfig = loadCodebaseConfig();
|
|
262
|
+
// Only proceed if feature is enabled
|
|
263
|
+
if (codebaseConfig.enabled) {
|
|
264
|
+
const projectRoot = process.cwd();
|
|
265
|
+
const dbPath = path.join(projectRoot, '.snow', 'codebase', 'embeddings.db');
|
|
266
|
+
const fs = await import('node:fs');
|
|
267
|
+
// Only add if database file exists
|
|
268
|
+
if (fs.existsSync(dbPath)) {
|
|
269
|
+
// Check if database has data by importing CodebaseDatabase
|
|
270
|
+
const { CodebaseDatabase } = await import('./codebaseDatabase.js');
|
|
271
|
+
const db = new CodebaseDatabase(projectRoot);
|
|
272
|
+
db.initialize();
|
|
273
|
+
const totalChunks = db.getTotalChunks();
|
|
274
|
+
db.close();
|
|
275
|
+
if (totalChunks > 0) {
|
|
276
|
+
const codebaseSearchServiceTools = codebaseSearchTools.map(tool => ({
|
|
277
|
+
name: tool.name.replace('codebase-', ''),
|
|
278
|
+
description: tool.description,
|
|
279
|
+
inputSchema: tool.inputSchema,
|
|
280
|
+
}));
|
|
281
|
+
servicesInfo.push({
|
|
282
|
+
serviceName: 'codebase',
|
|
283
|
+
tools: codebaseSearchServiceTools,
|
|
284
|
+
isBuiltIn: true,
|
|
285
|
+
connected: true,
|
|
286
|
+
});
|
|
287
|
+
for (const tool of codebaseSearchTools) {
|
|
288
|
+
allTools.push({
|
|
289
|
+
type: 'function',
|
|
290
|
+
function: {
|
|
291
|
+
name: tool.name,
|
|
292
|
+
description: tool.description,
|
|
293
|
+
parameters: tool.inputSchema,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
// Silently ignore if codebase search tools are not available
|
|
303
|
+
logger.debug('Codebase search tools not available:', error);
|
|
304
|
+
}
|
|
251
305
|
// Add user-configured MCP server tools (probe for availability but don't maintain connections)
|
|
252
306
|
try {
|
|
253
307
|
const mcpConfig = getMCPConfig();
|
|
@@ -290,7 +344,7 @@ async function refreshToolsCache() {
|
|
|
290
344
|
tools: allTools,
|
|
291
345
|
servicesInfo,
|
|
292
346
|
lastUpdate: Date.now(),
|
|
293
|
-
configHash: generateConfigHash(),
|
|
347
|
+
configHash: await generateConfigHash(),
|
|
294
348
|
};
|
|
295
349
|
}
|
|
296
350
|
/**
|
|
@@ -316,6 +370,7 @@ export async function reconnectMCPService(serviceName) {
|
|
|
316
370
|
serviceName === 'todo' ||
|
|
317
371
|
serviceName === 'ace' ||
|
|
318
372
|
serviceName === 'websearch' ||
|
|
373
|
+
serviceName === 'codebase' ||
|
|
319
374
|
serviceName === 'subagent') {
|
|
320
375
|
return;
|
|
321
376
|
}
|
|
@@ -390,7 +445,8 @@ export async function getMCPServicesInfo() {
|
|
|
390
445
|
if (!isCacheValid()) {
|
|
391
446
|
await refreshToolsCache();
|
|
392
447
|
}
|
|
393
|
-
|
|
448
|
+
// Ensure toolsCache is not null before accessing
|
|
449
|
+
return toolsCache?.servicesInfo || [];
|
|
394
450
|
}
|
|
395
451
|
/**
|
|
396
452
|
* Quick probe of MCP service tools without maintaining connections
|
|
@@ -603,6 +659,10 @@ export async function executeMCPTool(toolName, args, abortSignal, onTokenUpdate)
|
|
|
603
659
|
serviceName = 'ide';
|
|
604
660
|
actualToolName = toolName.substring('ide-'.length);
|
|
605
661
|
}
|
|
662
|
+
else if (toolName.startsWith('codebase-')) {
|
|
663
|
+
serviceName = 'codebase';
|
|
664
|
+
actualToolName = toolName.substring('codebase-'.length);
|
|
665
|
+
}
|
|
606
666
|
else if (toolName.startsWith('subagent-')) {
|
|
607
667
|
serviceName = 'subagent';
|
|
608
668
|
actualToolName = toolName.substring('subagent-'.length);
|
|
@@ -690,11 +750,6 @@ export async function executeMCPTool(toolName, args, abortSignal, onTokenUpdate)
|
|
|
690
750
|
return await aceCodeSearchService.getFileOutline(args.filePath);
|
|
691
751
|
case 'text_search':
|
|
692
752
|
return await aceCodeSearchService.textSearch(args.pattern, args.fileGlob, args.isRegex, args.maxResults);
|
|
693
|
-
case 'index_stats':
|
|
694
|
-
return aceCodeSearchService.getIndexStats();
|
|
695
|
-
case 'clear_cache':
|
|
696
|
-
aceCodeSearchService.clearCache();
|
|
697
|
-
return { message: 'ACE Code Search cache cleared successfully' };
|
|
698
753
|
default:
|
|
699
754
|
throw new Error(`Unknown ACE tool: ${actualToolName}`);
|
|
700
755
|
}
|
|
@@ -734,6 +789,16 @@ export async function executeMCPTool(toolName, args, abortSignal, onTokenUpdate)
|
|
|
734
789
|
throw new Error(`Unknown IDE tool: ${actualToolName}`);
|
|
735
790
|
}
|
|
736
791
|
}
|
|
792
|
+
else if (serviceName === 'codebase') {
|
|
793
|
+
// Handle built-in Codebase Search tools (no connection needed)
|
|
794
|
+
const { codebaseSearchService } = await import('../mcp/codebaseSearch.js');
|
|
795
|
+
switch (actualToolName) {
|
|
796
|
+
case 'search':
|
|
797
|
+
return await codebaseSearchService.search(args.query, args.topN);
|
|
798
|
+
default:
|
|
799
|
+
throw new Error(`Unknown codebase tool: ${actualToolName}`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
737
802
|
else if (serviceName === 'subagent') {
|
|
738
803
|
// Handle sub-agent tools
|
|
739
804
|
// actualToolName is the agent ID
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.33",
|
|
4
4
|
"description": "Intelligent Command Line Assistant powered by AI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -41,12 +41,15 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@inkjs/ui": "^2.0.0",
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.17.3",
|
|
44
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
45
|
+
"better-sqlite3": "^12.4.1",
|
|
44
46
|
"cli-highlight": "^2.1.11",
|
|
45
47
|
"cli-markdown": "^3.5.1",
|
|
46
48
|
"diff": "^8.0.2",
|
|
47
49
|
"fzf": "^0.5.2",
|
|
48
50
|
"http-proxy-agent": "^7.0.2",
|
|
49
51
|
"https-proxy-agent": "^7.0.6",
|
|
52
|
+
"ignore": "^7.0.5",
|
|
50
53
|
"ink": "^5.2.1",
|
|
51
54
|
"ink-gradient": "^3.0.0",
|
|
52
55
|
"ink-select-input": "^6.2.0",
|