claude-eidetic 0.1.1 → 0.1.2
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 +333 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +29 -10
- package/dist/core/cleanup.d.ts +8 -0
- package/dist/core/cleanup.js +41 -0
- package/dist/core/doc-indexer.d.ts +13 -0
- package/dist/core/doc-indexer.js +76 -0
- package/dist/core/doc-searcher.d.ts +13 -0
- package/dist/core/doc-searcher.js +65 -0
- package/dist/core/file-category.d.ts +7 -0
- package/dist/core/file-category.js +75 -0
- package/dist/core/indexer.js +12 -4
- package/dist/core/preview.d.ts +1 -2
- package/dist/core/preview.js +2 -5
- package/dist/core/repo-map.d.ts +33 -0
- package/dist/core/repo-map.js +144 -0
- package/dist/core/searcher.d.ts +1 -13
- package/dist/core/searcher.js +20 -24
- package/dist/core/snapshot-io.js +2 -2
- package/dist/core/sync.d.ts +5 -25
- package/dist/core/sync.js +90 -65
- package/dist/core/targeted-indexer.d.ts +19 -0
- package/dist/core/targeted-indexer.js +127 -0
- package/dist/embedding/factory.d.ts +0 -13
- package/dist/embedding/factory.js +0 -17
- package/dist/embedding/openai.d.ts +2 -14
- package/dist/embedding/openai.js +7 -20
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/format.d.ts +12 -0
- package/dist/format.js +160 -31
- package/dist/hooks/post-tool-use.d.ts +13 -0
- package/dist/hooks/post-tool-use.js +113 -0
- package/dist/hooks/stop-hook.d.ts +11 -0
- package/dist/hooks/stop-hook.js +121 -0
- package/dist/hooks/targeted-runner.d.ts +11 -0
- package/dist/hooks/targeted-runner.js +66 -0
- package/dist/index.js +68 -9
- package/dist/infra/qdrant-bootstrap.js +14 -12
- package/dist/memory/history.d.ts +19 -0
- package/dist/memory/history.js +40 -0
- package/dist/memory/llm.d.ts +2 -0
- package/dist/memory/llm.js +56 -0
- package/dist/memory/prompts.d.ts +5 -0
- package/dist/memory/prompts.js +36 -0
- package/dist/memory/reconciler.d.ts +12 -0
- package/dist/memory/reconciler.js +36 -0
- package/dist/memory/store.d.ts +20 -0
- package/dist/memory/store.js +206 -0
- package/dist/memory/types.d.ts +28 -0
- package/dist/memory/types.js +2 -0
- package/dist/paths.d.ts +3 -4
- package/dist/paths.js +14 -4
- package/dist/precompact/hook.d.ts +9 -0
- package/dist/precompact/hook.js +170 -0
- package/dist/precompact/index-runner.d.ts +9 -0
- package/dist/precompact/index-runner.js +52 -0
- package/dist/precompact/note-writer.d.ts +15 -0
- package/dist/precompact/note-writer.js +109 -0
- package/dist/precompact/session-indexer.d.ts +13 -0
- package/dist/precompact/session-indexer.js +31 -0
- package/dist/precompact/tier0-inject.d.ts +16 -0
- package/dist/precompact/tier0-inject.js +88 -0
- package/dist/precompact/tier0-writer.d.ts +16 -0
- package/dist/precompact/tier0-writer.js +74 -0
- package/dist/precompact/transcript-parser.d.ts +10 -0
- package/dist/precompact/transcript-parser.js +148 -0
- package/dist/precompact/types.d.ts +93 -0
- package/dist/precompact/types.js +5 -0
- package/dist/precompact/utils.d.ts +29 -0
- package/dist/precompact/utils.js +95 -0
- package/dist/setup-message.d.ts +2 -2
- package/dist/setup-message.js +39 -20
- package/dist/splitter/ast.js +84 -22
- package/dist/splitter/line.d.ts +0 -4
- package/dist/splitter/line.js +1 -7
- package/dist/splitter/symbol-extract.d.ts +16 -0
- package/dist/splitter/symbol-extract.js +61 -0
- package/dist/splitter/types.d.ts +5 -0
- package/dist/splitter/types.js +1 -1
- package/dist/state/doc-metadata.d.ts +18 -0
- package/dist/state/doc-metadata.js +59 -0
- package/dist/state/registry.d.ts +1 -3
- package/dist/state/snapshot.d.ts +0 -1
- package/dist/state/snapshot.js +3 -19
- package/dist/tool-schemas.d.ts +251 -1
- package/dist/tool-schemas.js +307 -0
- package/dist/tools.d.ts +69 -0
- package/dist/tools.js +286 -17
- package/dist/vectordb/milvus.d.ts +7 -5
- package/dist/vectordb/milvus.js +116 -19
- package/dist/vectordb/qdrant.d.ts +8 -10
- package/dist/vectordb/qdrant.js +105 -33
- package/dist/vectordb/types.d.ts +20 -0
- package/messages.yaml +50 -0
- package/package.json +31 -6
package/dist/tools.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
1
2
|
import { normalizePath, pathToCollectionName } from './paths.js';
|
|
2
3
|
import { indexCodebase, previewCodebase, deleteSnapshot } from './core/indexer.js';
|
|
4
|
+
import { cleanupVectors } from './core/cleanup.js';
|
|
5
|
+
import { scanFiles, buildSnapshot, diffSnapshots } from './core/sync.js';
|
|
6
|
+
import { loadSnapshot } from './core/snapshot-io.js';
|
|
3
7
|
import { getConfig } from './config.js';
|
|
4
8
|
import { searchCode, formatSearchResults, formatCompactResults } from './core/searcher.js';
|
|
9
|
+
import { indexDocument } from './core/doc-indexer.js';
|
|
10
|
+
import { searchDocuments } from './core/doc-searcher.js';
|
|
11
|
+
import { generateRepoMap, listSymbolsTable, VectorDBSymbolSource } from './core/repo-map.js';
|
|
5
12
|
import { registerProject, resolveProject, listProjects } from './state/registry.js';
|
|
6
|
-
import { textResult, formatPreview, formatIndexResult, formatListIndexed } from './format.js';
|
|
13
|
+
import { textResult, formatCleanupResult, formatPreview, formatIndexResult, formatListIndexed, formatDocIndexResult, formatDocSearchResults, formatMemoryActions, formatMemorySearchResults, formatMemoryList, formatMemoryHistory, } from './format.js';
|
|
14
|
+
function getErrorMessage(err) {
|
|
15
|
+
return err instanceof Error ? err.message : String(err);
|
|
16
|
+
}
|
|
7
17
|
function resolvePath(args) {
|
|
8
18
|
const pathArg = args.path;
|
|
9
19
|
if (pathArg)
|
|
@@ -17,26 +27,25 @@ function noPathError() {
|
|
|
17
27
|
const projects = listProjects();
|
|
18
28
|
const names = Object.keys(projects);
|
|
19
29
|
if (names.length > 0) {
|
|
20
|
-
const list = names.map(n => ` - ${n} → ${projects[n]}`).join('\n');
|
|
30
|
+
const list = names.map((n) => ` - ${n} → ${projects[n]}`).join('\n');
|
|
21
31
|
return textResult(`Error: provide \`path\` or \`project\`. Registered projects:\n${list}`);
|
|
22
32
|
}
|
|
23
|
-
return textResult('Error: provide
|
|
33
|
+
return textResult('Error: provide `path` (absolute) or `project` (name). No projects registered yet — index a codebase first.');
|
|
24
34
|
}
|
|
25
35
|
const locks = new Map();
|
|
26
36
|
async function withMutex(key, fn) {
|
|
27
|
-
// Chain onto any existing operation for this key (FIFO ordering, no race)
|
|
28
37
|
const prev = locks.get(key) ?? Promise.resolve();
|
|
29
38
|
let resolve;
|
|
30
|
-
const current = new Promise(r => {
|
|
39
|
+
const current = new Promise((r) => {
|
|
40
|
+
resolve = r;
|
|
41
|
+
});
|
|
31
42
|
locks.set(key, current);
|
|
32
|
-
// Wait for previous operation to complete
|
|
33
43
|
await prev;
|
|
34
44
|
try {
|
|
35
45
|
return await fn();
|
|
36
46
|
}
|
|
37
47
|
finally {
|
|
38
48
|
resolve();
|
|
39
|
-
// Only delete if we're still the latest operation
|
|
40
49
|
if (locks.get(key) === current) {
|
|
41
50
|
locks.delete(key);
|
|
42
51
|
}
|
|
@@ -46,11 +55,15 @@ export class ToolHandlers {
|
|
|
46
55
|
embedding;
|
|
47
56
|
vectordb;
|
|
48
57
|
state;
|
|
58
|
+
memoryStore = null;
|
|
49
59
|
constructor(embedding, vectordb, state) {
|
|
50
60
|
this.embedding = embedding;
|
|
51
61
|
this.vectordb = vectordb;
|
|
52
62
|
this.state = state;
|
|
53
63
|
}
|
|
64
|
+
setMemoryStore(store) {
|
|
65
|
+
this.memoryStore = store;
|
|
66
|
+
}
|
|
54
67
|
async handleIndexCodebase(args) {
|
|
55
68
|
const normalizedPath = resolvePath(args);
|
|
56
69
|
if (!normalizedPath)
|
|
@@ -63,24 +76,26 @@ export class ToolHandlers {
|
|
|
63
76
|
const collectionName = pathToCollectionName(normalizedPath);
|
|
64
77
|
if (dryRun) {
|
|
65
78
|
try {
|
|
66
|
-
const preview = await previewCodebase(normalizedPath,
|
|
79
|
+
const preview = await previewCodebase(normalizedPath, customExt, customIgnore);
|
|
67
80
|
return textResult(formatPreview(preview, normalizedPath));
|
|
68
81
|
}
|
|
69
82
|
catch (err) {
|
|
70
|
-
const message =
|
|
83
|
+
const message = getErrorMessage(err);
|
|
71
84
|
return textResult(`Error previewing ${normalizedPath}: ${message}`);
|
|
72
85
|
}
|
|
73
86
|
}
|
|
74
87
|
return withMutex(normalizedPath, async () => {
|
|
75
88
|
this.state.setIndexing(normalizedPath, collectionName);
|
|
76
89
|
try {
|
|
77
|
-
const result = await indexCodebase(normalizedPath, this.embedding, this.vectordb, force, (pct, msg) =>
|
|
90
|
+
const result = await indexCodebase(normalizedPath, this.embedding, this.vectordb, force, (pct, msg) => {
|
|
91
|
+
this.state.updateProgress(normalizedPath, pct, msg);
|
|
92
|
+
}, customExt, customIgnore);
|
|
78
93
|
this.state.setIndexed(normalizedPath, result.totalFiles, result.totalChunks);
|
|
79
94
|
registerProject(normalizedPath);
|
|
80
95
|
return textResult(formatIndexResult(result, normalizedPath));
|
|
81
96
|
}
|
|
82
97
|
catch (err) {
|
|
83
|
-
const message =
|
|
98
|
+
const message = getErrorMessage(err);
|
|
84
99
|
this.state.setError(normalizedPath, message);
|
|
85
100
|
return textResult(`Error indexing ${normalizedPath}: ${message}`);
|
|
86
101
|
}
|
|
@@ -94,20 +109,21 @@ export class ToolHandlers {
|
|
|
94
109
|
if (!normalizedPath)
|
|
95
110
|
return noPathError();
|
|
96
111
|
const rawLimit = args.limit;
|
|
97
|
-
const limit =
|
|
98
|
-
? rawLimit
|
|
99
|
-
: undefined;
|
|
112
|
+
const limit = rawLimit !== undefined && Number.isFinite(rawLimit) && rawLimit >= 1 ? rawLimit : undefined;
|
|
100
113
|
const extensionFilter = args.extensionFilter;
|
|
101
114
|
const compact = args.compact !== false; // default true
|
|
102
115
|
try {
|
|
103
|
-
const results = await searchCode(normalizedPath, query, this.embedding, this.vectordb, {
|
|
116
|
+
const results = await searchCode(normalizedPath, query, this.embedding, this.vectordb, {
|
|
117
|
+
limit,
|
|
118
|
+
extensionFilter,
|
|
119
|
+
});
|
|
104
120
|
const formatted = compact
|
|
105
121
|
? formatCompactResults(results, query, normalizedPath)
|
|
106
122
|
: formatSearchResults(results, query, normalizedPath);
|
|
107
123
|
return textResult(formatted);
|
|
108
124
|
}
|
|
109
125
|
catch (err) {
|
|
110
|
-
const message =
|
|
126
|
+
const message = getErrorMessage(err);
|
|
111
127
|
return textResult(`Error: ${message}`);
|
|
112
128
|
}
|
|
113
129
|
}
|
|
@@ -124,7 +140,7 @@ export class ToolHandlers {
|
|
|
124
140
|
return textResult(`Index cleared for ${normalizedPath}.`);
|
|
125
141
|
}
|
|
126
142
|
catch (err) {
|
|
127
|
-
const message =
|
|
143
|
+
const message = getErrorMessage(err);
|
|
128
144
|
return textResult(`Error clearing index: ${message}`);
|
|
129
145
|
}
|
|
130
146
|
});
|
|
@@ -158,6 +174,7 @@ export class ToolHandlers {
|
|
|
158
174
|
}
|
|
159
175
|
return textResult(lines.join('\n'));
|
|
160
176
|
}
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
161
178
|
async handleListIndexed() {
|
|
162
179
|
const states = this.state.getAllStates();
|
|
163
180
|
if (states.length === 0) {
|
|
@@ -165,5 +182,257 @@ export class ToolHandlers {
|
|
|
165
182
|
}
|
|
166
183
|
return textResult(formatListIndexed(states));
|
|
167
184
|
}
|
|
185
|
+
async handleCleanupVectors(args) {
|
|
186
|
+
const normalizedPath = resolvePath(args);
|
|
187
|
+
if (!normalizedPath)
|
|
188
|
+
return noPathError();
|
|
189
|
+
const dryRun = args.dryRun ?? false;
|
|
190
|
+
const config = getConfig();
|
|
191
|
+
const customExt = args.customExtensions ?? config.customExtensions;
|
|
192
|
+
const customIgnore = args.customIgnorePatterns ?? config.customIgnorePatterns;
|
|
193
|
+
return withMutex(normalizedPath, async () => {
|
|
194
|
+
try {
|
|
195
|
+
if (dryRun) {
|
|
196
|
+
const previousSnapshot = loadSnapshot(normalizedPath);
|
|
197
|
+
if (!previousSnapshot) {
|
|
198
|
+
return textResult(`Error: No snapshot found for ${normalizedPath}. Index the codebase first before running cleanup.`);
|
|
199
|
+
}
|
|
200
|
+
const filePaths = await scanFiles(normalizedPath, customExt, customIgnore);
|
|
201
|
+
const currentSnapshot = buildSnapshot(normalizedPath, filePaths);
|
|
202
|
+
const { removed } = diffSnapshots(previousSnapshot, currentSnapshot);
|
|
203
|
+
const dryResult = { removedFiles: removed, totalRemoved: removed.length, durationMs: 0 };
|
|
204
|
+
return textResult(formatCleanupResult(dryResult, normalizedPath, true));
|
|
205
|
+
}
|
|
206
|
+
const result = await cleanupVectors(normalizedPath, this.vectordb, undefined, customExt, customIgnore);
|
|
207
|
+
return textResult(formatCleanupResult(result, normalizedPath, false));
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
const message = getErrorMessage(err);
|
|
211
|
+
return textResult(`Error during cleanup: ${message}`);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async handleIndexDocument(args) {
|
|
216
|
+
const content = args.content;
|
|
217
|
+
if (!content)
|
|
218
|
+
return textResult('Error: "content" is required. Provide the documentation text to cache.');
|
|
219
|
+
const source = args.source;
|
|
220
|
+
if (!source)
|
|
221
|
+
return textResult('Error: "source" is required. Provide the source URL or identifier.');
|
|
222
|
+
const library = args.library;
|
|
223
|
+
if (!library)
|
|
224
|
+
return textResult('Error: "library" is required. Provide the library name (e.g., "react", "langfuse").');
|
|
225
|
+
const topic = args.topic;
|
|
226
|
+
if (!topic)
|
|
227
|
+
return textResult('Error: "topic" is required. Provide the topic within the library (e.g., "hooks").');
|
|
228
|
+
const ttlDays = args.ttlDays ?? 7;
|
|
229
|
+
try {
|
|
230
|
+
const result = await indexDocument(content, source, library, topic, this.embedding, this.vectordb, ttlDays);
|
|
231
|
+
return textResult(formatDocIndexResult(result));
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
const message = getErrorMessage(err);
|
|
235
|
+
return textResult(`Error caching documentation: ${message}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async handleSearchDocuments(args) {
|
|
239
|
+
const query = args.query;
|
|
240
|
+
if (!query)
|
|
241
|
+
return textResult('Error: "query" is required. Provide a natural language search query.');
|
|
242
|
+
const library = args.library;
|
|
243
|
+
const rawLimit = args.limit;
|
|
244
|
+
const limit = rawLimit !== undefined && Number.isFinite(rawLimit) && rawLimit >= 1 ? rawLimit : undefined;
|
|
245
|
+
try {
|
|
246
|
+
const results = await searchDocuments(query, this.embedding, this.vectordb, {
|
|
247
|
+
library,
|
|
248
|
+
limit,
|
|
249
|
+
});
|
|
250
|
+
return textResult(formatDocSearchResults(results, query));
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
const message = getErrorMessage(err);
|
|
254
|
+
return textResult(`Error: ${message}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async handleAddMemory(args) {
|
|
258
|
+
if (!this.memoryStore)
|
|
259
|
+
return textResult('Error: Memory system not initialized.');
|
|
260
|
+
const content = args.content;
|
|
261
|
+
if (!content)
|
|
262
|
+
return textResult('Error: "content" is required. Provide text containing developer knowledge to extract.');
|
|
263
|
+
const source = args.source;
|
|
264
|
+
try {
|
|
265
|
+
const actions = await this.memoryStore.addMemory(content, source);
|
|
266
|
+
return textResult(formatMemoryActions(actions));
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
const message = getErrorMessage(err);
|
|
270
|
+
return textResult(`Error adding memory: ${message}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async handleSearchMemory(args) {
|
|
274
|
+
if (!this.memoryStore)
|
|
275
|
+
return textResult('Error: Memory system not initialized.');
|
|
276
|
+
const query = args.query;
|
|
277
|
+
if (!query)
|
|
278
|
+
return textResult('Error: "query" is required. Provide a natural language search query.');
|
|
279
|
+
const limit = args.limit ?? 10;
|
|
280
|
+
const category = args.category;
|
|
281
|
+
try {
|
|
282
|
+
const results = await this.memoryStore.searchMemory(query, limit, category);
|
|
283
|
+
return textResult(formatMemorySearchResults(results, query));
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
const message = getErrorMessage(err);
|
|
287
|
+
return textResult(`Error searching memories: ${message}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async handleListMemories(args) {
|
|
291
|
+
if (!this.memoryStore)
|
|
292
|
+
return textResult('Error: Memory system not initialized.');
|
|
293
|
+
const category = args.category;
|
|
294
|
+
const limit = args.limit ?? 50;
|
|
295
|
+
try {
|
|
296
|
+
const results = await this.memoryStore.listMemories(category, limit);
|
|
297
|
+
return textResult(formatMemoryList(results));
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
const message = getErrorMessage(err);
|
|
301
|
+
return textResult(`Error listing memories: ${message}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async handleDeleteMemory(args) {
|
|
305
|
+
if (!this.memoryStore)
|
|
306
|
+
return textResult('Error: Memory system not initialized.');
|
|
307
|
+
const id = args.id;
|
|
308
|
+
if (!id)
|
|
309
|
+
return textResult('Error: "id" is required. Provide the UUID of the memory to delete.');
|
|
310
|
+
try {
|
|
311
|
+
const deleted = await this.memoryStore.deleteMemory(id);
|
|
312
|
+
if (!deleted)
|
|
313
|
+
return textResult(`Memory not found: ${id}`);
|
|
314
|
+
return textResult(`Memory deleted: ${id}`);
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
const message = getErrorMessage(err);
|
|
318
|
+
return textResult(`Error deleting memory: ${message}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
322
|
+
async handleMemoryHistory(args) {
|
|
323
|
+
if (!this.memoryStore)
|
|
324
|
+
return textResult('Error: Memory system not initialized.');
|
|
325
|
+
const id = args.id;
|
|
326
|
+
if (!id)
|
|
327
|
+
return textResult('Error: "id" is required. Provide the UUID of the memory to view history for.');
|
|
328
|
+
try {
|
|
329
|
+
const entries = this.memoryStore.getHistory(id);
|
|
330
|
+
return textResult(formatMemoryHistory(entries, id));
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
const message = getErrorMessage(err);
|
|
334
|
+
return textResult(`Error retrieving memory history: ${message}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async handleBrowseStructure(args) {
|
|
338
|
+
const normalizedPath = resolvePath(args);
|
|
339
|
+
if (!normalizedPath)
|
|
340
|
+
return noPathError();
|
|
341
|
+
const pathFilter = args.pathFilter;
|
|
342
|
+
const kindFilter = args.kind;
|
|
343
|
+
const maxTokens = args.maxTokens;
|
|
344
|
+
try {
|
|
345
|
+
const source = new VectorDBSymbolSource(this.vectordb);
|
|
346
|
+
const map = await generateRepoMap(normalizedPath, source, {
|
|
347
|
+
pathFilter,
|
|
348
|
+
kindFilter,
|
|
349
|
+
maxTokens,
|
|
350
|
+
});
|
|
351
|
+
return textResult(map);
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
const message = getErrorMessage(err);
|
|
355
|
+
return textResult(`Error: ${message}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async handleListSymbols(args) {
|
|
359
|
+
const normalizedPath = resolvePath(args);
|
|
360
|
+
if (!normalizedPath)
|
|
361
|
+
return noPathError();
|
|
362
|
+
const pathFilter = args.pathFilter;
|
|
363
|
+
const kindFilter = args.kind;
|
|
364
|
+
const nameFilter = args.nameFilter;
|
|
365
|
+
try {
|
|
366
|
+
const source = new VectorDBSymbolSource(this.vectordb);
|
|
367
|
+
const table = await listSymbolsTable(normalizedPath, source, {
|
|
368
|
+
pathFilter,
|
|
369
|
+
kindFilter,
|
|
370
|
+
nameFilter,
|
|
371
|
+
});
|
|
372
|
+
return textResult(table);
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
const message = getErrorMessage(err);
|
|
376
|
+
return textResult(`Error: ${message}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const MAX_LINES = 10_000;
|
|
381
|
+
const DEFAULT_LINES = 5_000;
|
|
382
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
383
|
+
export async function handleReadFile(args) {
|
|
384
|
+
const rawPath = args.path;
|
|
385
|
+
if (!rawPath)
|
|
386
|
+
return textResult('Error: "path" is required. Provide an absolute file path.');
|
|
387
|
+
const filePath = normalizePath(rawPath);
|
|
388
|
+
const offset = Math.max(0, args.offset ?? 0);
|
|
389
|
+
const limit = Math.min(MAX_LINES, Math.max(1, args.limit ?? DEFAULT_LINES));
|
|
390
|
+
const lineNumbers = args.lineNumbers ?? false;
|
|
391
|
+
let raw;
|
|
392
|
+
try {
|
|
393
|
+
const fileStat = await stat(filePath);
|
|
394
|
+
if (fileStat.isDirectory())
|
|
395
|
+
return textResult(`Error: Path is a directory, not a file: ${filePath}`);
|
|
396
|
+
if (fileStat.size > MAX_FILE_SIZE) {
|
|
397
|
+
return textResult(`Error: File too large (${(fileStat.size / 1024 / 1024).toFixed(1)}MB). Maximum: 10MB.`);
|
|
398
|
+
}
|
|
399
|
+
raw = await readFile(filePath, 'utf-8');
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
const nodeErr = err;
|
|
403
|
+
if (nodeErr.code === 'ENOENT')
|
|
404
|
+
return textResult(`Error: File not found: ${filePath}`);
|
|
405
|
+
if (nodeErr.code === 'EACCES' || nodeErr.code === 'EPERM')
|
|
406
|
+
return textResult(`Error: Permission denied: ${filePath}`);
|
|
407
|
+
if (nodeErr.code === 'EISDIR')
|
|
408
|
+
return textResult(`Error: Path is a directory, not a file: ${filePath}`);
|
|
409
|
+
const message = getErrorMessage(err);
|
|
410
|
+
return textResult(`Error reading file: ${message}`);
|
|
411
|
+
}
|
|
412
|
+
if (raw.includes('\x00'))
|
|
413
|
+
return textResult(`Error: Binary file detected: ${filePath}`);
|
|
414
|
+
if (raw.length === 0) {
|
|
415
|
+
return textResult(`File: ${filePath} | Lines: 0 total | (empty file)`);
|
|
416
|
+
}
|
|
417
|
+
const allLines = raw.split('\n');
|
|
418
|
+
const totalLines = allLines.length;
|
|
419
|
+
// offset is 1-based line number; convert to 0-based index
|
|
420
|
+
const startIndex = offset > 0 ? offset - 1 : 0;
|
|
421
|
+
const sliced = allLines.slice(startIndex, startIndex + limit);
|
|
422
|
+
const startLine = startIndex + 1;
|
|
423
|
+
const endLine = startIndex + sliced.length;
|
|
424
|
+
let content;
|
|
425
|
+
if (lineNumbers) {
|
|
426
|
+
const pad = String(endLine).length;
|
|
427
|
+
content = sliced.map((line, i) => `${String(startLine + i).padStart(pad)} ${line}`).join('\n');
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
content = sliced.join('\n');
|
|
431
|
+
}
|
|
432
|
+
let meta = `File: ${filePath} | Lines: ${totalLines} total | Showing: ${startLine}–${endLine}`;
|
|
433
|
+
if (endLine < totalLines) {
|
|
434
|
+
meta += ` | Next: read_file(path="${filePath}", offset=${endLine + 1})`;
|
|
435
|
+
}
|
|
436
|
+
return textResult(meta + '\n\n' + content);
|
|
168
437
|
}
|
|
169
438
|
//# sourceMappingURL=tools.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VectorDB, CodeDocument, HybridSearchParams, SearchResult } from './types.js';
|
|
1
|
+
import type { VectorDB, CodeDocument, HybridSearchParams, SearchResult, SymbolEntry } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Detects the "data type 104 not supported" error from older Milvus versions
|
|
4
4
|
* that lack SparseFloatVector support (< v2.4).
|
|
@@ -21,12 +21,14 @@ export declare class MilvusVectorDB implements VectorDB {
|
|
|
21
21
|
private hybridSearch;
|
|
22
22
|
private denseOnlySearch;
|
|
23
23
|
private mapResults;
|
|
24
|
-
/**
|
|
25
|
-
* Detect whether a collection has the sparse_vector field (hybrid) or not.
|
|
26
|
-
* Caches result in hybridCollections set.
|
|
27
|
-
*/
|
|
28
24
|
private detectHybrid;
|
|
25
|
+
getById(name: string, id: string): Promise<{
|
|
26
|
+
payload: Record<string, unknown>;
|
|
27
|
+
vector: number[];
|
|
28
|
+
} | null>;
|
|
29
|
+
updatePoint(name: string, id: string, vector: number[], payload: Record<string, unknown>): Promise<void>;
|
|
29
30
|
deleteByPath(name: string, relativePath: string): Promise<void>;
|
|
31
|
+
listSymbols(name: string): Promise<SymbolEntry[]>;
|
|
30
32
|
private ensureLoaded;
|
|
31
33
|
private waitForLoad;
|
|
32
34
|
}
|
package/dist/vectordb/milvus.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VectorDBError } from '../errors.js';
|
|
2
2
|
import { getConfig } from '../config.js';
|
|
3
|
-
|
|
3
|
+
import { RRF_K } from './qdrant.js';
|
|
4
4
|
let MilvusClient;
|
|
5
5
|
let DataType;
|
|
6
6
|
let MetricType;
|
|
@@ -19,14 +19,13 @@ async function loadMilvusSDK() {
|
|
|
19
19
|
throw new VectorDBError('Milvus SDK not installed. Run: npm install @zilliz/milvus2-sdk-node');
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
const RRF_K = 60;
|
|
23
22
|
/**
|
|
24
23
|
* Detects the "data type 104 not supported" error from older Milvus versions
|
|
25
24
|
* that lack SparseFloatVector support (< v2.4).
|
|
26
25
|
*/
|
|
27
26
|
export function isSparseUnsupportedError(err) {
|
|
28
27
|
const msg = String(err && typeof err === 'object' && 'reason' in err ? err.reason : err);
|
|
29
|
-
return /data type[:\s]*104/i.test(msg) || /not supported/i.test(msg) &&
|
|
28
|
+
return /data type[:\s]*104/i.test(msg) || (/not supported/i.test(msg) && msg.includes('104'));
|
|
30
29
|
}
|
|
31
30
|
export class MilvusVectorDB {
|
|
32
31
|
client = null;
|
|
@@ -56,7 +55,6 @@ export class MilvusVectorDB {
|
|
|
56
55
|
}
|
|
57
56
|
async createCollection(name, dimension) {
|
|
58
57
|
await this.ready();
|
|
59
|
-
// Try hybrid first, fall back to dense-only if Milvus version doesn't support sparse
|
|
60
58
|
try {
|
|
61
59
|
await this.createHybridCollection(name, dimension);
|
|
62
60
|
this.hybridCollections.add(name);
|
|
@@ -67,12 +65,11 @@ export class MilvusVectorDB {
|
|
|
67
65
|
if (isSparseUnsupportedError(err)) {
|
|
68
66
|
console.warn(`Milvus does not support SparseFloatVector (requires >= v2.4). ` +
|
|
69
67
|
`Falling back to dense-only collection for "${name}".`);
|
|
70
|
-
// Clean up the failed collection attempt
|
|
71
68
|
try {
|
|
72
69
|
await this.client.dropCollection({ collection_name: name });
|
|
73
70
|
}
|
|
74
71
|
catch (cleanupErr) {
|
|
75
|
-
console.warn(`Failed to clean up collection "${name}"
|
|
72
|
+
console.warn(`Failed to clean up collection "${name}":`, cleanupErr);
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
75
|
else {
|
|
@@ -98,14 +95,21 @@ export class MilvusVectorDB {
|
|
|
98
95
|
{ name: 'endLine', data_type: DataType.Int64 },
|
|
99
96
|
{ name: 'fileExtension', data_type: DataType.VarChar, max_length: 32 },
|
|
100
97
|
{ name: 'language', data_type: DataType.VarChar, max_length: 64 },
|
|
98
|
+
{ name: 'fileCategory', data_type: DataType.VarChar, max_length: 32 },
|
|
99
|
+
{ name: 'symbolName', data_type: DataType.VarChar, max_length: 256 },
|
|
100
|
+
{ name: 'symbolKind', data_type: DataType.VarChar, max_length: 64 },
|
|
101
|
+
{ name: 'symbolSignature', data_type: DataType.VarChar, max_length: 512 },
|
|
102
|
+
{ name: 'parentSymbol', data_type: DataType.VarChar, max_length: 256 },
|
|
101
103
|
];
|
|
102
|
-
const functions = [
|
|
104
|
+
const functions = [
|
|
105
|
+
{
|
|
103
106
|
name: 'content_bm25',
|
|
104
107
|
type: FunctionType.BM25,
|
|
105
108
|
input_field_names: ['content'],
|
|
106
109
|
output_field_names: ['sparse_vector'],
|
|
107
110
|
params: {},
|
|
108
|
-
}
|
|
111
|
+
},
|
|
112
|
+
];
|
|
109
113
|
await this.client.createCollection({
|
|
110
114
|
collection_name: name,
|
|
111
115
|
fields: schema,
|
|
@@ -135,6 +139,11 @@ export class MilvusVectorDB {
|
|
|
135
139
|
{ name: 'endLine', data_type: DataType.Int64 },
|
|
136
140
|
{ name: 'fileExtension', data_type: DataType.VarChar, max_length: 32 },
|
|
137
141
|
{ name: 'language', data_type: DataType.VarChar, max_length: 64 },
|
|
142
|
+
{ name: 'fileCategory', data_type: DataType.VarChar, max_length: 32 },
|
|
143
|
+
{ name: 'symbolName', data_type: DataType.VarChar, max_length: 256 },
|
|
144
|
+
{ name: 'symbolKind', data_type: DataType.VarChar, max_length: 64 },
|
|
145
|
+
{ name: 'symbolSignature', data_type: DataType.VarChar, max_length: 512 },
|
|
146
|
+
{ name: 'parentSymbol', data_type: DataType.VarChar, max_length: 256 },
|
|
138
147
|
];
|
|
139
148
|
await this.client.createCollection({
|
|
140
149
|
collection_name: name,
|
|
@@ -176,7 +185,7 @@ export class MilvusVectorDB {
|
|
|
176
185
|
await this.ready();
|
|
177
186
|
await this.ensureLoaded(name);
|
|
178
187
|
try {
|
|
179
|
-
const data = documents.map(doc => ({
|
|
188
|
+
const data = documents.map((doc) => ({
|
|
180
189
|
id: doc.id,
|
|
181
190
|
content: doc.content,
|
|
182
191
|
vector: doc.vector,
|
|
@@ -185,6 +194,11 @@ export class MilvusVectorDB {
|
|
|
185
194
|
endLine: doc.endLine,
|
|
186
195
|
fileExtension: doc.fileExtension,
|
|
187
196
|
language: doc.language,
|
|
197
|
+
fileCategory: doc.fileCategory ?? 'source',
|
|
198
|
+
symbolName: doc.symbolName ?? '',
|
|
199
|
+
symbolKind: doc.symbolKind ?? '',
|
|
200
|
+
symbolSignature: doc.symbolSignature ?? '',
|
|
201
|
+
parentSymbol: doc.parentSymbol ?? '',
|
|
188
202
|
}));
|
|
189
203
|
await this.client.insert({ collection_name: name, data });
|
|
190
204
|
}
|
|
@@ -199,7 +213,9 @@ export class MilvusVectorDB {
|
|
|
199
213
|
try {
|
|
200
214
|
let expr;
|
|
201
215
|
if (params.extensionFilter?.length) {
|
|
202
|
-
const exts = params.extensionFilter
|
|
216
|
+
const exts = params.extensionFilter
|
|
217
|
+
.map((e) => `"${e.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`)
|
|
218
|
+
.join(', ');
|
|
203
219
|
expr = `fileExtension in [${exts}]`;
|
|
204
220
|
}
|
|
205
221
|
if (isHybrid) {
|
|
@@ -231,7 +247,16 @@ export class MilvusVectorDB {
|
|
|
231
247
|
],
|
|
232
248
|
limit: params.limit,
|
|
233
249
|
rerank: { strategy: 'rrf', params: { k: RRF_K } },
|
|
234
|
-
output_fields: [
|
|
250
|
+
output_fields: [
|
|
251
|
+
'id',
|
|
252
|
+
'content',
|
|
253
|
+
'relativePath',
|
|
254
|
+
'startLine',
|
|
255
|
+
'endLine',
|
|
256
|
+
'fileExtension',
|
|
257
|
+
'language',
|
|
258
|
+
'fileCategory',
|
|
259
|
+
],
|
|
235
260
|
};
|
|
236
261
|
if (expr)
|
|
237
262
|
searchParams.expr = expr;
|
|
@@ -243,7 +268,16 @@ export class MilvusVectorDB {
|
|
|
243
268
|
collection_name: name,
|
|
244
269
|
data: [params.queryVector],
|
|
245
270
|
limit: params.limit,
|
|
246
|
-
output_fields: [
|
|
271
|
+
output_fields: [
|
|
272
|
+
'id',
|
|
273
|
+
'content',
|
|
274
|
+
'relativePath',
|
|
275
|
+
'startLine',
|
|
276
|
+
'endLine',
|
|
277
|
+
'fileExtension',
|
|
278
|
+
'language',
|
|
279
|
+
'fileCategory',
|
|
280
|
+
],
|
|
247
281
|
};
|
|
248
282
|
if (expr)
|
|
249
283
|
searchParams.expr = expr;
|
|
@@ -261,12 +295,9 @@ export class MilvusVectorDB {
|
|
|
261
295
|
fileExtension: r.fileExtension ?? '',
|
|
262
296
|
language: r.language ?? '',
|
|
263
297
|
score: r.score ?? 0,
|
|
298
|
+
fileCategory: r.fileCategory ?? '',
|
|
264
299
|
}));
|
|
265
300
|
}
|
|
266
|
-
/**
|
|
267
|
-
* Detect whether a collection has the sparse_vector field (hybrid) or not.
|
|
268
|
-
* Caches result in hybridCollections set.
|
|
269
|
-
*/
|
|
270
301
|
async detectHybrid(name) {
|
|
271
302
|
if (this.hybridCollections.has(name))
|
|
272
303
|
return true;
|
|
@@ -283,6 +314,39 @@ export class MilvusVectorDB {
|
|
|
283
314
|
return false;
|
|
284
315
|
}
|
|
285
316
|
}
|
|
317
|
+
async getById(name, id) {
|
|
318
|
+
await this.ready();
|
|
319
|
+
try {
|
|
320
|
+
const result = await this.client.get({
|
|
321
|
+
collection_name: name,
|
|
322
|
+
ids: [id],
|
|
323
|
+
output_fields: ['*'],
|
|
324
|
+
});
|
|
325
|
+
if (!result.data?.length)
|
|
326
|
+
return null;
|
|
327
|
+
const point = result.data[0];
|
|
328
|
+
return {
|
|
329
|
+
payload: point,
|
|
330
|
+
vector: point.vector ?? [],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
throw new VectorDBError(`Failed to retrieve point "${id}" from "${name}"`, err);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async updatePoint(name, id, vector, payload) {
|
|
338
|
+
await this.ready();
|
|
339
|
+
await this.ensureLoaded(name);
|
|
340
|
+
try {
|
|
341
|
+
await this.client.upsert({
|
|
342
|
+
collection_name: name,
|
|
343
|
+
data: [{ id, vector, ...payload }],
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
throw new VectorDBError(`Failed to update point "${id}" in Milvus collection "${name}"`, err);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
286
350
|
async deleteByPath(name, relativePath) {
|
|
287
351
|
await this.ready();
|
|
288
352
|
await this.ensureLoaded(name);
|
|
@@ -297,6 +361,39 @@ export class MilvusVectorDB {
|
|
|
297
361
|
throw new VectorDBError(`Failed to delete by path "${relativePath}" from "${name}"`, err);
|
|
298
362
|
}
|
|
299
363
|
}
|
|
364
|
+
async listSymbols(name) {
|
|
365
|
+
await this.ready();
|
|
366
|
+
await this.ensureLoaded(name);
|
|
367
|
+
try {
|
|
368
|
+
const result = await this.client.query({
|
|
369
|
+
collection_name: name,
|
|
370
|
+
filter: 'symbolName != ""',
|
|
371
|
+
output_fields: [
|
|
372
|
+
'symbolName',
|
|
373
|
+
'symbolKind',
|
|
374
|
+
'relativePath',
|
|
375
|
+
'startLine',
|
|
376
|
+
'symbolSignature',
|
|
377
|
+
'parentSymbol',
|
|
378
|
+
],
|
|
379
|
+
limit: 16384,
|
|
380
|
+
});
|
|
381
|
+
const rows = result.data ?? [];
|
|
382
|
+
return rows
|
|
383
|
+
.filter((r) => r.symbolName)
|
|
384
|
+
.map((r) => ({
|
|
385
|
+
name: String(r.symbolName),
|
|
386
|
+
kind: String(r.symbolKind ?? ''),
|
|
387
|
+
relativePath: String(r.relativePath ?? ''),
|
|
388
|
+
startLine: Number(r.startLine ?? 0),
|
|
389
|
+
...(r.symbolSignature ? { signature: String(r.symbolSignature) } : {}),
|
|
390
|
+
...(r.parentSymbol ? { parentName: String(r.parentSymbol) } : {}),
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
throw new VectorDBError(`Failed to list symbols from "${name}"`, err);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
300
397
|
async ensureLoaded(name) {
|
|
301
398
|
try {
|
|
302
399
|
const result = await this.client.getLoadState({ collection_name: name });
|
|
@@ -305,7 +402,7 @@ export class MilvusVectorDB {
|
|
|
305
402
|
}
|
|
306
403
|
}
|
|
307
404
|
catch (err) {
|
|
308
|
-
console.warn(`Failed to ensure collection "${name}" is loaded
|
|
405
|
+
console.warn(`Failed to ensure collection "${name}" is loaded:`, err);
|
|
309
406
|
}
|
|
310
407
|
}
|
|
311
408
|
async waitForLoad(name, timeoutMs = 30_000) {
|
|
@@ -318,9 +415,9 @@ export class MilvusVectorDB {
|
|
|
318
415
|
return;
|
|
319
416
|
}
|
|
320
417
|
catch (err) {
|
|
321
|
-
console.warn(`Load state check failed for "${name}"
|
|
418
|
+
console.warn(`Load state check failed for "${name}":`, err);
|
|
322
419
|
}
|
|
323
|
-
await new Promise(r => setTimeout(r, 500));
|
|
420
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
324
421
|
}
|
|
325
422
|
throw new VectorDBError(`Collection "${name}" failed to load within ${timeoutMs}ms`);
|
|
326
423
|
}
|