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
|
@@ -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
|
export declare const RRF_K = 5;
|
|
3
3
|
export declare const RRF_ALPHA = 0.7;
|
|
4
4
|
export declare class QdrantVectorDB implements VectorDB {
|
|
@@ -9,22 +9,19 @@ export declare class QdrantVectorDB implements VectorDB {
|
|
|
9
9
|
dropCollection(name: string): Promise<void>;
|
|
10
10
|
insert(name: string, documents: CodeDocument[]): Promise<void>;
|
|
11
11
|
search(name: string, params: HybridSearchParams): Promise<SearchResult[]>;
|
|
12
|
+
getById(name: string, id: string): Promise<{
|
|
13
|
+
payload: Record<string, unknown>;
|
|
14
|
+
vector: number[];
|
|
15
|
+
} | null>;
|
|
16
|
+
updatePoint(name: string, id: string, vector: number[], payload: Record<string, unknown>): Promise<void>;
|
|
12
17
|
deleteByPath(name: string, relativePath: string): Promise<void>;
|
|
18
|
+
listSymbols(name: string): Promise<SymbolEntry[]>;
|
|
13
19
|
}
|
|
14
20
|
interface RankedPoint {
|
|
15
21
|
id: string | number;
|
|
16
22
|
payload?: Record<string, unknown> | null;
|
|
17
23
|
rawScore: number;
|
|
18
24
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Rank text-match results by normalized term frequency.
|
|
21
|
-
*
|
|
22
|
-
* Qdrant's scroll API returns text-filtered points in storage order (not by
|
|
23
|
-
* relevance). Before feeding these into RRF, we score each result by how many
|
|
24
|
-
* query terms appear in its content, normalized by word count to avoid bias
|
|
25
|
-
* toward longer chunks. Returns points sorted best-first with rawScore attached
|
|
26
|
-
* so RRF can blend rank position with content-based signal.
|
|
27
|
-
*/
|
|
28
25
|
export declare function rankByTermFrequency(points: {
|
|
29
26
|
id: string | number;
|
|
30
27
|
payload?: Record<string, unknown> | null;
|
|
@@ -37,6 +34,7 @@ interface ScoredPayload {
|
|
|
37
34
|
endLine: number;
|
|
38
35
|
fileExtension: string;
|
|
39
36
|
language: string;
|
|
37
|
+
fileCategory: string;
|
|
40
38
|
}
|
|
41
39
|
export declare function extractPayload(point: {
|
|
42
40
|
id: string | number;
|
package/dist/vectordb/qdrant.js
CHANGED
|
@@ -10,7 +10,7 @@ export class QdrantVectorDB {
|
|
|
10
10
|
const config = getConfig();
|
|
11
11
|
this.client = new QdrantClient({
|
|
12
12
|
url: url ?? config.qdrantUrl,
|
|
13
|
-
...(apiKey ?? config.qdrantApiKey ? { apiKey: apiKey ?? config.qdrantApiKey } : {}),
|
|
13
|
+
...((apiKey ?? config.qdrantApiKey) ? { apiKey: apiKey ?? config.qdrantApiKey } : {}),
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
async createCollection(name, dimension) {
|
|
@@ -20,7 +20,6 @@ export class QdrantVectorDB {
|
|
|
20
20
|
dense: { size: dimension, distance: 'Cosine' },
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
-
// Create payload indexes for filtering and full-text search
|
|
24
23
|
await Promise.all([
|
|
25
24
|
this.client.createPayloadIndex(name, {
|
|
26
25
|
field_name: 'content',
|
|
@@ -37,6 +36,11 @@ export class QdrantVectorDB {
|
|
|
37
36
|
field_schema: 'keyword',
|
|
38
37
|
wait: true,
|
|
39
38
|
}),
|
|
39
|
+
this.client.createPayloadIndex(name, {
|
|
40
|
+
field_name: 'fileCategory',
|
|
41
|
+
field_schema: 'keyword',
|
|
42
|
+
wait: true,
|
|
43
|
+
}),
|
|
40
44
|
]);
|
|
41
45
|
}
|
|
42
46
|
catch (err) {
|
|
@@ -71,7 +75,7 @@ export class QdrantVectorDB {
|
|
|
71
75
|
const batch = documents.slice(i, i + batchSize);
|
|
72
76
|
await this.client.upsert(name, {
|
|
73
77
|
wait: true,
|
|
74
|
-
points: batch.map(doc => ({
|
|
78
|
+
points: batch.map((doc) => ({
|
|
75
79
|
id: doc.id ?? randomUUID(),
|
|
76
80
|
vector: { dense: doc.vector },
|
|
77
81
|
payload: {
|
|
@@ -81,6 +85,11 @@ export class QdrantVectorDB {
|
|
|
81
85
|
endLine: doc.endLine,
|
|
82
86
|
fileExtension: doc.fileExtension,
|
|
83
87
|
language: doc.language,
|
|
88
|
+
fileCategory: doc.fileCategory ?? 'source',
|
|
89
|
+
symbolName: doc.symbolName ?? '',
|
|
90
|
+
symbolKind: doc.symbolKind ?? '',
|
|
91
|
+
symbolSignature: doc.symbolSignature ?? '',
|
|
92
|
+
parentSymbol: doc.parentSymbol ?? '',
|
|
84
93
|
},
|
|
85
94
|
})),
|
|
86
95
|
});
|
|
@@ -95,7 +104,7 @@ export class QdrantVectorDB {
|
|
|
95
104
|
const fetchLimit = params.limit * 2;
|
|
96
105
|
const extensionFilter = params.extensionFilter?.length
|
|
97
106
|
? {
|
|
98
|
-
should: params.extensionFilter.map(ext => ({
|
|
107
|
+
should: params.extensionFilter.map((ext) => ({
|
|
99
108
|
key: 'fileExtension',
|
|
100
109
|
match: { value: ext },
|
|
101
110
|
})),
|
|
@@ -118,15 +127,51 @@ export class QdrantVectorDB {
|
|
|
118
127
|
limit: fetchLimit,
|
|
119
128
|
with_payload: true,
|
|
120
129
|
});
|
|
121
|
-
// Rank text results by query term frequency so RRF receives a meaningful ordering
|
|
122
130
|
const rankedTextResults = rankByTermFrequency(textResponse.points, params.queryText);
|
|
123
|
-
// 3. Client-side RRF fusion blending rank position with raw similarity scores
|
|
124
131
|
return reciprocalRankFusion(denseResults, rankedTextResults, params.limit);
|
|
125
132
|
}
|
|
126
133
|
catch (err) {
|
|
127
134
|
throw new VectorDBError(`Search failed in collection "${name}"`, err);
|
|
128
135
|
}
|
|
129
136
|
}
|
|
137
|
+
async getById(name, id) {
|
|
138
|
+
try {
|
|
139
|
+
const results = await this.client.retrieve(name, {
|
|
140
|
+
ids: [id],
|
|
141
|
+
with_payload: true,
|
|
142
|
+
with_vector: true,
|
|
143
|
+
});
|
|
144
|
+
if (results.length === 0)
|
|
145
|
+
return null;
|
|
146
|
+
const point = results[0];
|
|
147
|
+
const vectors = point.vector;
|
|
148
|
+
const vector = Array.isArray(vectors) ? vectors : (vectors?.dense ?? []);
|
|
149
|
+
return {
|
|
150
|
+
payload: point.payload ?? {},
|
|
151
|
+
vector,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
throw new VectorDBError(`Failed to retrieve point "${id}" from "${name}"`, err);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async updatePoint(name, id, vector, payload) {
|
|
159
|
+
try {
|
|
160
|
+
await this.client.upsert(name, {
|
|
161
|
+
wait: true,
|
|
162
|
+
points: [
|
|
163
|
+
{
|
|
164
|
+
id,
|
|
165
|
+
vector: { dense: vector },
|
|
166
|
+
payload,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
throw new VectorDBError(`Failed to update point "${id}" in "${name}"`, err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
130
175
|
async deleteByPath(name, relativePath) {
|
|
131
176
|
try {
|
|
132
177
|
await this.client.delete(name, {
|
|
@@ -140,43 +185,74 @@ export class QdrantVectorDB {
|
|
|
140
185
|
throw new VectorDBError(`Failed to delete documents for path "${relativePath}" from "${name}"`, err);
|
|
141
186
|
}
|
|
142
187
|
}
|
|
188
|
+
async listSymbols(name) {
|
|
189
|
+
try {
|
|
190
|
+
const results = [];
|
|
191
|
+
let offset = undefined;
|
|
192
|
+
const pageSize = 1000;
|
|
193
|
+
while (true) {
|
|
194
|
+
const response = await this.client.scroll(name, {
|
|
195
|
+
filter: {
|
|
196
|
+
must_not: [{ is_empty: { key: 'symbolName' } }],
|
|
197
|
+
},
|
|
198
|
+
limit: pageSize,
|
|
199
|
+
with_payload: true,
|
|
200
|
+
with_vector: false,
|
|
201
|
+
...(offset !== undefined ? { offset } : {}),
|
|
202
|
+
});
|
|
203
|
+
for (const point of response.points) {
|
|
204
|
+
const p = point.payload ?? {};
|
|
205
|
+
const symName = String(p.symbolName ?? '');
|
|
206
|
+
if (!symName)
|
|
207
|
+
continue;
|
|
208
|
+
results.push({
|
|
209
|
+
name: symName,
|
|
210
|
+
kind: String(p.symbolKind ?? ''),
|
|
211
|
+
relativePath: String(p.relativePath ?? ''),
|
|
212
|
+
startLine: Number(p.startLine ?? 0),
|
|
213
|
+
...(p.symbolSignature ? { signature: String(p.symbolSignature) } : {}),
|
|
214
|
+
...(p.parentSymbol ? { parentName: String(p.parentSymbol) } : {}),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (response.next_page_offset == null || response.points.length < pageSize)
|
|
218
|
+
break;
|
|
219
|
+
offset = response.next_page_offset;
|
|
220
|
+
}
|
|
221
|
+
return results;
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
throw new VectorDBError(`Failed to list symbols from "${name}"`, err);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
143
227
|
}
|
|
144
|
-
|
|
145
|
-
* Rank text-match results by normalized term frequency.
|
|
146
|
-
*
|
|
147
|
-
* Qdrant's scroll API returns text-filtered points in storage order (not by
|
|
148
|
-
* relevance). Before feeding these into RRF, we score each result by how many
|
|
149
|
-
* query terms appear in its content, normalized by word count to avoid bias
|
|
150
|
-
* toward longer chunks. Returns points sorted best-first with rawScore attached
|
|
151
|
-
* so RRF can blend rank position with content-based signal.
|
|
152
|
-
*/
|
|
228
|
+
// Rank text-match results by normalized term frequency so RRF receives a meaningful ordering.
|
|
153
229
|
export function rankByTermFrequency(points, queryText) {
|
|
154
230
|
if (points.length === 0)
|
|
155
231
|
return [];
|
|
156
|
-
|
|
157
|
-
|
|
232
|
+
const terms = [
|
|
233
|
+
...new Set(queryText
|
|
234
|
+
.toLowerCase()
|
|
235
|
+
.split(/\s+/)
|
|
236
|
+
.filter((t) => t.length > 0)),
|
|
237
|
+
];
|
|
158
238
|
if (terms.length === 0)
|
|
159
|
-
return points.map(p => ({ ...p, rawScore: 0 }));
|
|
160
|
-
const termPatterns = terms.map(t => new RegExp(t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'));
|
|
161
|
-
const scored = points.map(point => {
|
|
239
|
+
return points.map((p) => ({ ...p, rawScore: 0 }));
|
|
240
|
+
const termPatterns = terms.map((t) => new RegExp(t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'));
|
|
241
|
+
const scored = points.map((point) => {
|
|
162
242
|
const content = point.payload?.content ?? '';
|
|
163
243
|
const wordCount = Math.max(1, content.split(/\s+/).length);
|
|
164
|
-
// Count total term occurrences across all query terms
|
|
165
244
|
let hits = 0;
|
|
166
245
|
for (const pattern of termPatterns) {
|
|
167
|
-
pattern.lastIndex = 0;
|
|
246
|
+
pattern.lastIndex = 0;
|
|
168
247
|
const matches = content.match(pattern);
|
|
169
248
|
hits += matches ? matches.length : 0;
|
|
170
249
|
}
|
|
171
|
-
// Normalize by word count (TF = hits / total words)
|
|
172
250
|
const tf = hits / wordCount;
|
|
173
251
|
return { point, tf };
|
|
174
252
|
});
|
|
175
|
-
// Sort by TF descending (best matches first)
|
|
176
253
|
scored.sort((a, b) => b.tf - a.tf);
|
|
177
|
-
// Normalize TF to 0-1 range for blending with cosine similarity
|
|
178
254
|
const maxTf = scored[0].tf;
|
|
179
|
-
return scored.map(s => ({
|
|
255
|
+
return scored.map((s) => ({
|
|
180
256
|
...s.point,
|
|
181
257
|
rawScore: maxTf > 0 ? s.tf / maxTf : 0,
|
|
182
258
|
}));
|
|
@@ -191,19 +267,15 @@ export function extractPayload(point) {
|
|
|
191
267
|
endLine: Number(p.endLine ?? 0),
|
|
192
268
|
fileExtension: String(p.fileExtension ?? ''),
|
|
193
269
|
language: String(p.language ?? ''),
|
|
270
|
+
fileCategory: String(p.fileCategory ?? ''),
|
|
194
271
|
};
|
|
195
272
|
}
|
|
196
273
|
export function reciprocalRankFusion(denseResults, textResults, limit) {
|
|
197
274
|
const scoreMap = new Map();
|
|
198
|
-
// Hybrid RRF blended with raw similarity:
|
|
199
|
-
// score = alpha * (1/(k + rank + 1)) + (1-alpha) * rawSimilarity
|
|
200
|
-
// alpha=0.7 preserves rank-fusion stability while 0.3 raw similarity
|
|
201
|
-
// injects query-specific signal so identical ranks get different scores.
|
|
202
275
|
const blendedScore = (rank, rawSimilarity) => RRF_ALPHA * (1 / (RRF_K + rank + 1)) + (1 - RRF_ALPHA) * rawSimilarity;
|
|
203
|
-
// Score from dense results (cosine similarity from Qdrant)
|
|
204
276
|
for (let rank = 0; rank < denseResults.length; rank++) {
|
|
205
277
|
const point = denseResults[rank];
|
|
206
|
-
const rawSim = point.score ?? 0;
|
|
278
|
+
const rawSim = point.score ?? 0;
|
|
207
279
|
const score = blendedScore(rank, rawSim);
|
|
208
280
|
const existing = scoreMap.get(point.id);
|
|
209
281
|
const payload = extractPayload(point);
|
|
@@ -214,7 +286,6 @@ export function reciprocalRankFusion(denseResults, textResults, limit) {
|
|
|
214
286
|
scoreMap.set(point.id, { score, payload });
|
|
215
287
|
}
|
|
216
288
|
}
|
|
217
|
-
// Score from text results (normalized TF from rankByTermFrequency)
|
|
218
289
|
for (let rank = 0; rank < textResults.length; rank++) {
|
|
219
290
|
const point = textResults[rank];
|
|
220
291
|
const score = blendedScore(rank, point.rawScore);
|
|
@@ -236,6 +307,7 @@ export function reciprocalRankFusion(denseResults, textResults, limit) {
|
|
|
236
307
|
fileExtension: payload.fileExtension,
|
|
237
308
|
language: payload.language,
|
|
238
309
|
score,
|
|
310
|
+
fileCategory: payload.fileCategory,
|
|
239
311
|
}));
|
|
240
312
|
}
|
|
241
313
|
//# sourceMappingURL=qdrant.js.map
|
package/dist/vectordb/types.d.ts
CHANGED
|
@@ -8,6 +8,19 @@ export interface CodeDocument {
|
|
|
8
8
|
endLine: number;
|
|
9
9
|
fileExtension: string;
|
|
10
10
|
language: string;
|
|
11
|
+
fileCategory?: string;
|
|
12
|
+
symbolName?: string;
|
|
13
|
+
symbolKind?: string;
|
|
14
|
+
symbolSignature?: string;
|
|
15
|
+
parentSymbol?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SymbolEntry {
|
|
18
|
+
name: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
relativePath: string;
|
|
21
|
+
startLine: number;
|
|
22
|
+
signature?: string;
|
|
23
|
+
parentName?: string;
|
|
11
24
|
}
|
|
12
25
|
export interface HybridSearchParams {
|
|
13
26
|
queryVector: EmbeddingVector;
|
|
@@ -23,6 +36,7 @@ export interface SearchResult {
|
|
|
23
36
|
fileExtension: string;
|
|
24
37
|
language: string;
|
|
25
38
|
score: number;
|
|
39
|
+
fileCategory?: string;
|
|
26
40
|
}
|
|
27
41
|
export interface VectorDB {
|
|
28
42
|
createCollection(name: string, dimension: number): Promise<void>;
|
|
@@ -31,5 +45,11 @@ export interface VectorDB {
|
|
|
31
45
|
insert(name: string, documents: CodeDocument[]): Promise<void>;
|
|
32
46
|
search(name: string, params: HybridSearchParams): Promise<SearchResult[]>;
|
|
33
47
|
deleteByPath(name: string, relativePath: string): Promise<void>;
|
|
48
|
+
getById(name: string, id: string): Promise<{
|
|
49
|
+
payload: Record<string, unknown>;
|
|
50
|
+
vector: number[];
|
|
51
|
+
} | null>;
|
|
52
|
+
updatePoint(name: string, id: string, vector: number[], payload: Record<string, unknown>): Promise<void>;
|
|
53
|
+
listSymbols(name: string): Promise<SymbolEntry[]>;
|
|
34
54
|
}
|
|
35
55
|
//# sourceMappingURL=types.d.ts.map
|
package/messages.yaml
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Eidetic user-facing messages — edit here, everything else reads from this file.
|
|
2
|
+
|
|
3
|
+
setup:
|
|
4
|
+
missing:
|
|
5
|
+
header: "Eidetic setup required: No API key configured."
|
|
6
|
+
diagnosis: >
|
|
7
|
+
`OPENAI_API_KEY` is not set and no alternative provider is configured.
|
|
8
|
+
step1: "**Get an API key**: https://platform.openai.com/api-keys"
|
|
9
|
+
|
|
10
|
+
invalid:
|
|
11
|
+
header: "Eidetic setup failed: {error}"
|
|
12
|
+
diagnosis: >
|
|
13
|
+
Key/provider is configured but initialization failed.
|
|
14
|
+
The key may be invalid, expired, or the provider may be unreachable.
|
|
15
|
+
step1: "**Verify your key** is valid and has embedding permissions"
|
|
16
|
+
|
|
17
|
+
unknown:
|
|
18
|
+
header: "Eidetic setup failed: {error}"
|
|
19
|
+
diagnosis: ""
|
|
20
|
+
step1: "**Get an API key**: https://platform.openai.com/api-keys"
|
|
21
|
+
|
|
22
|
+
# Shared across all contexts
|
|
23
|
+
config_instructions: |
|
|
24
|
+
### Windows
|
|
25
|
+
```
|
|
26
|
+
setx OPENAI_API_KEY sk-your-key-here
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### macOS / Linux
|
|
30
|
+
Add to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.):
|
|
31
|
+
```
|
|
32
|
+
export OPENAI_API_KEY="sk-your-key-here"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Manual
|
|
36
|
+
|
|
37
|
+
Edit `~/.claude/plugins/claude-eidetic/config.json`:
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"env": {
|
|
41
|
+
"OPENAI_API_KEY": "sk-your-key-here"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Using Ollama instead (free, local)
|
|
47
|
+
|
|
48
|
+
Set `EMBEDDING_PROVIDER=ollama` in the same env block (requires Ollama running locally with `nomic-embed-text`).
|
|
49
|
+
|
|
50
|
+
footer: "**Restart Claude Code** for changes to take effect."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-eidetic",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Semantic code search MCP server — lean, correct, fast",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,27 +11,37 @@
|
|
|
11
11
|
"dist/**/*.js",
|
|
12
12
|
"dist/**/*.d.ts",
|
|
13
13
|
"!dist/**/*.test.*",
|
|
14
|
-
"!dist/
|
|
15
|
-
"!dist
|
|
14
|
+
"!dist/__tests__/**",
|
|
15
|
+
"!dist/**/tests/**",
|
|
16
|
+
"!dist/e2e/**",
|
|
17
|
+
"messages.yaml"
|
|
16
18
|
],
|
|
17
19
|
"scripts": {
|
|
18
20
|
"build": "tsc",
|
|
19
21
|
"dev": "tsx --watch src/index.ts",
|
|
20
22
|
"start": "node dist/index.js",
|
|
21
23
|
"clean": "rimraf dist",
|
|
24
|
+
"lint": "eslint src/",
|
|
25
|
+
"lint:fix": "eslint src/ --fix",
|
|
26
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
27
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
22
28
|
"typecheck": "tsc --noEmit",
|
|
23
29
|
"test": "vitest run",
|
|
24
30
|
"test:watch": "vitest",
|
|
25
31
|
"test:coverage": "vitest run --coverage",
|
|
26
32
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
27
33
|
"test:all": "vitest run && vitest run --config vitest.integration.config.ts",
|
|
28
|
-
"test:
|
|
29
|
-
"
|
|
34
|
+
"test:eval": "tsx scripts/eval/recall-at-k.ts && tsx scripts/eval/fusion-lift.ts && tsx scripts/eval/ndcg.ts",
|
|
35
|
+
"test:eval:generate": "tsx scripts/eval/generate-queries.ts",
|
|
36
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
37
|
+
"release": "bash scripts/release.sh"
|
|
30
38
|
},
|
|
31
39
|
"dependencies": {
|
|
40
|
+
"@anthropic-ai/sdk": "^0.78.0",
|
|
32
41
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
33
42
|
"@qdrant/js-client-rest": "^1.13.0",
|
|
34
|
-
"
|
|
43
|
+
"better-sqlite3": "^12.6.2",
|
|
44
|
+
"glob": "^11.0.0",
|
|
35
45
|
"openai": "^5.1.1",
|
|
36
46
|
"tree-sitter": "^0.21.1",
|
|
37
47
|
"tree-sitter-c-sharp": "^0.21.0",
|
|
@@ -42,21 +52,36 @@
|
|
|
42
52
|
"tree-sitter-python": "^0.21.0",
|
|
43
53
|
"tree-sitter-rust": "^0.21.0",
|
|
44
54
|
"tree-sitter-typescript": "^0.21.0",
|
|
55
|
+
"yaml": "^2.8.2",
|
|
45
56
|
"zod": "^3.25.0"
|
|
46
57
|
},
|
|
47
58
|
"optionalDependencies": {
|
|
48
59
|
"@zilliz/milvus2-sdk-node": "^2.5.10"
|
|
49
60
|
},
|
|
50
61
|
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^9.39.3",
|
|
63
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
51
64
|
"@types/node": "^20.0.0",
|
|
52
65
|
"@vitest/coverage-v8": "^4.0.18",
|
|
66
|
+
"eslint": "^9.39.3",
|
|
67
|
+
"eslint-config-prettier": "^10.1.8",
|
|
68
|
+
"prettier": "^3.8.1",
|
|
53
69
|
"rimraf": "^6.0.1",
|
|
54
70
|
"tsx": "^4.19.4",
|
|
55
71
|
"typescript": "^5.8.3",
|
|
72
|
+
"typescript-eslint": "^8.56.0",
|
|
56
73
|
"vitest": "^4.0.18"
|
|
57
74
|
},
|
|
75
|
+
"overrides": {
|
|
76
|
+
"minimatch": "^10.2.1",
|
|
77
|
+
"@grpc/grpc-js": "^1.8.22"
|
|
78
|
+
},
|
|
58
79
|
"engines": {
|
|
59
80
|
"node": ">=20.0.0"
|
|
60
81
|
},
|
|
82
|
+
"repository": {
|
|
83
|
+
"type": "git",
|
|
84
|
+
"url": "https://github.com/eidetics/claude-eidetic"
|
|
85
|
+
},
|
|
61
86
|
"license": "MIT"
|
|
62
87
|
}
|