@renseiai/agentfactory-code-intelligence 0.8.8 → 0.8.9
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/src/embedding/__tests__/embedding.test.d.ts +2 -0
- package/dist/src/embedding/__tests__/embedding.test.d.ts.map +1 -0
- package/dist/src/embedding/__tests__/embedding.test.js +339 -0
- package/dist/src/embedding/chunker.d.ts +40 -0
- package/dist/src/embedding/chunker.d.ts.map +1 -0
- package/dist/src/embedding/chunker.js +135 -0
- package/dist/src/embedding/embedding-provider.d.ts +15 -0
- package/dist/src/embedding/embedding-provider.d.ts.map +1 -0
- package/dist/src/embedding/embedding-provider.js +1 -0
- package/dist/src/embedding/voyage-provider.d.ts +39 -0
- package/dist/src/embedding/voyage-provider.d.ts.map +1 -0
- package/dist/src/embedding/voyage-provider.js +146 -0
- package/dist/src/index.d.ts +14 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -1
- package/dist/src/indexing/__tests__/vector-indexing.test.d.ts +2 -0
- package/dist/src/indexing/__tests__/vector-indexing.test.d.ts.map +1 -0
- package/dist/src/indexing/__tests__/vector-indexing.test.js +291 -0
- package/dist/src/indexing/incremental-indexer.d.ts +4 -0
- package/dist/src/indexing/incremental-indexer.d.ts.map +1 -1
- package/dist/src/indexing/incremental-indexer.js +45 -0
- package/dist/src/indexing/vector-indexer.d.ts +63 -0
- package/dist/src/indexing/vector-indexer.d.ts.map +1 -0
- package/dist/src/indexing/vector-indexer.js +197 -0
- package/dist/src/plugin/code-intelligence-plugin.d.ts.map +1 -1
- package/dist/src/plugin/code-intelligence-plugin.js +4 -2
- package/dist/src/reranking/__tests__/reranker.test.d.ts +2 -0
- package/dist/src/reranking/__tests__/reranker.test.d.ts.map +1 -0
- package/dist/src/reranking/__tests__/reranker.test.js +503 -0
- package/dist/src/reranking/cohere-reranker.d.ts +26 -0
- package/dist/src/reranking/cohere-reranker.d.ts.map +1 -0
- package/dist/src/reranking/cohere-reranker.js +110 -0
- package/dist/src/reranking/reranker-provider.d.ts +40 -0
- package/dist/src/reranking/reranker-provider.d.ts.map +1 -0
- package/dist/src/reranking/reranker-provider.js +6 -0
- package/dist/src/reranking/voyage-reranker.d.ts +27 -0
- package/dist/src/reranking/voyage-reranker.d.ts.map +1 -0
- package/dist/src/reranking/voyage-reranker.js +111 -0
- package/dist/src/search/__tests__/hybrid-search.test.d.ts +2 -0
- package/dist/src/search/__tests__/hybrid-search.test.d.ts.map +1 -0
- package/dist/src/search/__tests__/hybrid-search.test.js +437 -0
- package/dist/src/search/__tests__/query-classifier.test.d.ts +2 -0
- package/dist/src/search/__tests__/query-classifier.test.d.ts.map +1 -0
- package/dist/src/search/__tests__/query-classifier.test.js +136 -0
- package/dist/src/search/hybrid-search.d.ts +56 -0
- package/dist/src/search/hybrid-search.d.ts.map +1 -0
- package/dist/src/search/hybrid-search.js +299 -0
- package/dist/src/search/query-classifier.d.ts +20 -0
- package/dist/src/search/query-classifier.d.ts.map +1 -0
- package/dist/src/search/query-classifier.js +58 -0
- package/dist/src/search/score-normalizer.d.ts +16 -0
- package/dist/src/search/score-normalizer.d.ts.map +1 -0
- package/dist/src/search/score-normalizer.js +26 -0
- package/dist/src/types.d.ts +83 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +36 -2
- package/dist/src/vector/__tests__/vector-store.test.d.ts +2 -0
- package/dist/src/vector/__tests__/vector-store.test.d.ts.map +1 -0
- package/dist/src/vector/__tests__/vector-store.test.js +278 -0
- package/dist/src/vector/hnsw-store.d.ts +48 -0
- package/dist/src/vector/hnsw-store.d.ts.map +1 -0
- package/dist/src/vector/hnsw-store.js +437 -0
- package/dist/src/vector/vector-store.d.ts +15 -0
- package/dist/src/vector/vector-store.d.ts.map +1 -0
- package/dist/src/vector/vector-store.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const VOYAGE_API_URL = 'https://api.voyageai.com/v1/embeddings';
|
|
2
|
+
const DEFAULT_MODEL = 'voyage-code-3';
|
|
3
|
+
const DEFAULT_DIMENSIONS = 256;
|
|
4
|
+
const DEFAULT_BATCH_SIZE = 128;
|
|
5
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
6
|
+
const BASE_RETRY_DELAY_MS = 1000;
|
|
7
|
+
/**
|
|
8
|
+
* Embedding provider for Voyage AI's voyage-code-3 model.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - 32K token context window
|
|
12
|
+
* - 2048 native dimensions with Matryoshka support (256–2048)
|
|
13
|
+
* - Batched requests (max 128 texts per API call)
|
|
14
|
+
* - Exponential backoff retry on 429 / 5xx errors
|
|
15
|
+
*
|
|
16
|
+
* Requires the VOYAGE_API_KEY environment variable.
|
|
17
|
+
*/
|
|
18
|
+
export class VoyageCodeProvider {
|
|
19
|
+
model;
|
|
20
|
+
dimensions;
|
|
21
|
+
batchSize;
|
|
22
|
+
maxRetries;
|
|
23
|
+
apiKey;
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
const apiKey = process.env.VOYAGE_API_KEY;
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
throw new Error('VOYAGE_API_KEY environment variable is required. '
|
|
28
|
+
+ 'Get your API key at https://dash.voyageai.com/');
|
|
29
|
+
}
|
|
30
|
+
this.apiKey = apiKey;
|
|
31
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
32
|
+
this.dimensions = config.dimensions ?? DEFAULT_DIMENSIONS;
|
|
33
|
+
this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
34
|
+
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Embed multiple texts in batches.
|
|
38
|
+
* Returns one vector per input text, in the same order.
|
|
39
|
+
*/
|
|
40
|
+
async embed(texts) {
|
|
41
|
+
if (texts.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const results = new Array(texts.length);
|
|
44
|
+
const batches = this.splitIntoBatches(texts);
|
|
45
|
+
let offset = 0;
|
|
46
|
+
for (const batch of batches) {
|
|
47
|
+
const embeddings = await this.callAPI(batch, 'document');
|
|
48
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
49
|
+
results[offset + i] = embeddings[i];
|
|
50
|
+
}
|
|
51
|
+
offset += batch.length;
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Embed a single query text.
|
|
57
|
+
* Uses input_type "query" for asymmetric retrieval.
|
|
58
|
+
*/
|
|
59
|
+
async embedQuery(text) {
|
|
60
|
+
const [result] = await this.callAPI([text], 'query');
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
/** Split texts into batches of at most batchSize. */
|
|
64
|
+
splitIntoBatches(texts) {
|
|
65
|
+
const batches = [];
|
|
66
|
+
for (let i = 0; i < texts.length; i += this.batchSize) {
|
|
67
|
+
batches.push(texts.slice(i, i + this.batchSize));
|
|
68
|
+
}
|
|
69
|
+
return batches;
|
|
70
|
+
}
|
|
71
|
+
/** Call the Voyage embeddings API with retry logic. */
|
|
72
|
+
async callAPI(texts, inputType) {
|
|
73
|
+
const body = JSON.stringify({
|
|
74
|
+
model: this.model,
|
|
75
|
+
input: texts,
|
|
76
|
+
input_type: inputType,
|
|
77
|
+
output_dimension: this.dimensions,
|
|
78
|
+
});
|
|
79
|
+
let lastError;
|
|
80
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch(VOYAGE_API_URL, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
87
|
+
},
|
|
88
|
+
body,
|
|
89
|
+
});
|
|
90
|
+
if (response.ok) {
|
|
91
|
+
const json = (await response.json());
|
|
92
|
+
// Sort by index to preserve input order
|
|
93
|
+
const sorted = json.data.sort((a, b) => a.index - b.index);
|
|
94
|
+
return sorted.map((d) => d.embedding);
|
|
95
|
+
}
|
|
96
|
+
// Retry on rate limit or server errors
|
|
97
|
+
if (response.status === 429 || response.status >= 500) {
|
|
98
|
+
const errorBody = await response.text();
|
|
99
|
+
lastError = new Error(`Voyage API returned ${response.status}: ${errorBody}`);
|
|
100
|
+
if (attempt < this.maxRetries) {
|
|
101
|
+
await this.sleep(this.getRetryDelay(attempt, response));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Non-retryable error
|
|
107
|
+
const errorBody = await response.text();
|
|
108
|
+
let message;
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(errorBody);
|
|
111
|
+
message = parsed.detail ?? parsed.message ?? errorBody;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
message = errorBody;
|
|
115
|
+
}
|
|
116
|
+
throw new Error(`Voyage API error (${response.status}): ${message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error instanceof Error && error.message.startsWith('Voyage API error')) {
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
124
|
+
if (attempt < this.maxRetries) {
|
|
125
|
+
await this.sleep(BASE_RETRY_DELAY_MS * Math.pow(2, attempt));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw lastError ?? new Error('Voyage API request failed after retries');
|
|
131
|
+
}
|
|
132
|
+
/** Calculate retry delay with exponential backoff, respecting Retry-After header. */
|
|
133
|
+
getRetryDelay(attempt, response) {
|
|
134
|
+
const retryAfter = response.headers.get('retry-after');
|
|
135
|
+
if (retryAfter) {
|
|
136
|
+
const seconds = Number(retryAfter);
|
|
137
|
+
if (!Number.isNaN(seconds)) {
|
|
138
|
+
return seconds * 1000;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return BASE_RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
142
|
+
}
|
|
143
|
+
sleep(ms) {
|
|
144
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
145
|
+
}
|
|
146
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { SymbolKindSchema, CodeSymbolSchema, FileASTSchema, FileIndexSchema, SearchQuerySchema, SearchResultSchema, MemoryEntrySchema, DedupResultSchema, IndexMetadataSchema, RepoMapEntrySchema, } from './types.js';
|
|
2
|
-
export type { SymbolKind, CodeSymbol, FileAST, FileIndex, SearchQuery, SearchResult, MemoryEntry, DedupResult, IndexMetadata, RepoMapEntry, } from './types.js';
|
|
1
|
+
export { SymbolKindSchema, CodeSymbolSchema, FileASTSchema, FileIndexSchema, SearchQuerySchema, SearchResultSchema, MemoryEntrySchema, DedupResultSchema, IndexMetadataSchema, RepoMapEntrySchema, EmbeddingChunkSchema, EmbeddingProviderConfigSchema, VectorSearchResultSchema, VectorIndexConfigSchema, } from './types.js';
|
|
2
|
+
export type { SymbolKind, CodeSymbol, FileAST, FileIndex, SearchQuery, SearchResult, MemoryEntry, DedupResult, IndexMetadata, RepoMapEntry, EmbeddingChunk, EmbeddingProviderConfig, VectorSearchResult, VectorIndexConfig, } from './types.js';
|
|
3
3
|
export { SymbolExtractor } from './parser/symbol-extractor.js';
|
|
4
4
|
export type { LanguageExtractor } from './parser/symbol-extractor.js';
|
|
5
5
|
export { TypeScriptExtractor } from './parser/typescript-extractor.js';
|
|
@@ -10,10 +10,14 @@ export { MerkleTree, type MerkleNode } from './indexing/merkle-tree.js';
|
|
|
10
10
|
export { GitHashProvider } from './indexing/git-hash-provider.js';
|
|
11
11
|
export { ChangeDetector } from './indexing/change-detector.js';
|
|
12
12
|
export { IncrementalIndexer } from './indexing/incremental-indexer.js';
|
|
13
|
+
export { VectorIndexer } from './indexing/vector-indexer.js';
|
|
13
14
|
export { CodeTokenizer } from './search/tokenizer.js';
|
|
14
15
|
export { InvertedIndex } from './search/inverted-index.js';
|
|
15
16
|
export { BM25 } from './search/bm25.js';
|
|
16
17
|
export { SearchEngine } from './search/search-engine.js';
|
|
18
|
+
export { HybridSearchEngine } from './search/hybrid-search.js';
|
|
19
|
+
export { classifyQuery } from './search/query-classifier.js';
|
|
20
|
+
export { minMaxNormalize } from './search/score-normalizer.js';
|
|
17
21
|
export { DependencyGraph } from './repo-map/dependency-graph.js';
|
|
18
22
|
export { PageRank } from './repo-map/pagerank.js';
|
|
19
23
|
export { RepoMapGenerator } from './repo-map/repo-map-generator.js';
|
|
@@ -22,5 +26,13 @@ export { SimHash } from './memory/simhash.js';
|
|
|
22
26
|
export { DedupPipeline } from './memory/dedup-pipeline.js';
|
|
23
27
|
export type { MemoryStore } from './memory/memory-store.js';
|
|
24
28
|
export { InMemoryStore } from './memory/memory-store.js';
|
|
29
|
+
export type { EmbeddingProvider } from './embedding/embedding-provider.js';
|
|
30
|
+
export { VoyageCodeProvider } from './embedding/voyage-provider.js';
|
|
31
|
+
export { Chunker } from './embedding/chunker.js';
|
|
32
|
+
export type { VectorStore } from './vector/vector-store.js';
|
|
33
|
+
export { InMemoryVectorStore } from './vector/hnsw-store.js';
|
|
34
|
+
export type { RerankerProvider, RerankDocument, RerankResult, RerankerConfig } from './reranking/reranker-provider.js';
|
|
35
|
+
export { CohereReranker } from './reranking/cohere-reranker.js';
|
|
36
|
+
export { VoyageReranker } from './reranking/voyage-reranker.js';
|
|
25
37
|
export { codeIntelligencePlugin } from './plugin/code-intelligence-plugin.js';
|
|
26
38
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,6BAA6B,EAC7B,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,UAAU,EACV,UAAU,EACV,OAAO,EACP,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAG1D,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAG9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AAGnE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAGxD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAGhD,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAG5D,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtH,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAG/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAA"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Core types and schemas
|
|
2
|
-
export { SymbolKindSchema, CodeSymbolSchema, FileASTSchema, FileIndexSchema, SearchQuerySchema, SearchResultSchema, MemoryEntrySchema, DedupResultSchema, IndexMetadataSchema, RepoMapEntrySchema, } from './types.js';
|
|
2
|
+
export { SymbolKindSchema, CodeSymbolSchema, FileASTSchema, FileIndexSchema, SearchQuerySchema, SearchResultSchema, MemoryEntrySchema, DedupResultSchema, IndexMetadataSchema, RepoMapEntrySchema, EmbeddingChunkSchema, EmbeddingProviderConfigSchema, VectorSearchResultSchema, VectorIndexConfigSchema, } from './types.js';
|
|
3
3
|
// Parser
|
|
4
4
|
export { SymbolExtractor } from './parser/symbol-extractor.js';
|
|
5
5
|
export { TypeScriptExtractor } from './parser/typescript-extractor.js';
|
|
@@ -11,11 +11,15 @@ export { MerkleTree } from './indexing/merkle-tree.js';
|
|
|
11
11
|
export { GitHashProvider } from './indexing/git-hash-provider.js';
|
|
12
12
|
export { ChangeDetector } from './indexing/change-detector.js';
|
|
13
13
|
export { IncrementalIndexer } from './indexing/incremental-indexer.js';
|
|
14
|
+
export { VectorIndexer } from './indexing/vector-indexer.js';
|
|
14
15
|
// Search
|
|
15
16
|
export { CodeTokenizer } from './search/tokenizer.js';
|
|
16
17
|
export { InvertedIndex } from './search/inverted-index.js';
|
|
17
18
|
export { BM25 } from './search/bm25.js';
|
|
18
19
|
export { SearchEngine } from './search/search-engine.js';
|
|
20
|
+
export { HybridSearchEngine } from './search/hybrid-search.js';
|
|
21
|
+
export { classifyQuery } from './search/query-classifier.js';
|
|
22
|
+
export { minMaxNormalize } from './search/score-normalizer.js';
|
|
19
23
|
// Repo Map
|
|
20
24
|
export { DependencyGraph } from './repo-map/dependency-graph.js';
|
|
21
25
|
export { PageRank } from './repo-map/pagerank.js';
|
|
@@ -25,5 +29,10 @@ export { xxhash64 } from './memory/xxhash.js';
|
|
|
25
29
|
export { SimHash } from './memory/simhash.js';
|
|
26
30
|
export { DedupPipeline } from './memory/dedup-pipeline.js';
|
|
27
31
|
export { InMemoryStore } from './memory/memory-store.js';
|
|
32
|
+
export { VoyageCodeProvider } from './embedding/voyage-provider.js';
|
|
33
|
+
export { Chunker } from './embedding/chunker.js';
|
|
34
|
+
export { InMemoryVectorStore } from './vector/hnsw-store.js';
|
|
35
|
+
export { CohereReranker } from './reranking/cohere-reranker.js';
|
|
36
|
+
export { VoyageReranker } from './reranking/voyage-reranker.js';
|
|
28
37
|
// Plugin
|
|
29
38
|
export { codeIntelligencePlugin } from './plugin/code-intelligence-plugin.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vector-indexing.test.d.ts","sourceRoot":"","sources":["../../../../src/indexing/__tests__/vector-indexing.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { VectorIndexer } from '../vector-indexer.js';
|
|
3
|
+
import { IncrementalIndexer } from '../incremental-indexer.js';
|
|
4
|
+
import { SymbolExtractor } from '../../parser/symbol-extractor.js';
|
|
5
|
+
import { InMemoryVectorStore } from '../../vector/hnsw-store.js';
|
|
6
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
7
|
+
const mockProvider = {
|
|
8
|
+
model: 'mock-model',
|
|
9
|
+
dimensions: 4,
|
|
10
|
+
async embed(texts) {
|
|
11
|
+
return texts.map(t => [t.length / 100, 0.5, 0.3, 0.1]);
|
|
12
|
+
},
|
|
13
|
+
async embedQuery(text) {
|
|
14
|
+
return [text.length / 100, 0.5, 0.3, 0.1];
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
function makeSymbol(name, kind, filePath, extra) {
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
kind: kind,
|
|
21
|
+
filePath,
|
|
22
|
+
line: 0,
|
|
23
|
+
exported: true,
|
|
24
|
+
...extra,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function makeFileAST(filePath, language, symbols) {
|
|
28
|
+
return { filePath, language, symbols, imports: [], exports: [] };
|
|
29
|
+
}
|
|
30
|
+
// ── VectorIndexer ────────────────────────────────────────────────────
|
|
31
|
+
describe('VectorIndexer', () => {
|
|
32
|
+
it('indexes files: chunks, embeds, and inserts into store', async () => {
|
|
33
|
+
const store = new InMemoryVectorStore();
|
|
34
|
+
const indexer = new VectorIndexer({
|
|
35
|
+
enabled: true,
|
|
36
|
+
embeddingProvider: mockProvider,
|
|
37
|
+
vectorStore: store,
|
|
38
|
+
});
|
|
39
|
+
const asts = [
|
|
40
|
+
makeFileAST('src/utils.ts', 'typescript', [
|
|
41
|
+
makeSymbol('add', 'function', 'src/utils.ts', {
|
|
42
|
+
line: 0,
|
|
43
|
+
endLine: 3,
|
|
44
|
+
signature: 'function add(a: number, b: number): number',
|
|
45
|
+
}),
|
|
46
|
+
makeSymbol('subtract', 'function', 'src/utils.ts', {
|
|
47
|
+
line: 5,
|
|
48
|
+
endLine: 8,
|
|
49
|
+
signature: 'function subtract(a: number, b: number): number',
|
|
50
|
+
}),
|
|
51
|
+
]),
|
|
52
|
+
];
|
|
53
|
+
const fileContents = new Map([
|
|
54
|
+
['src/utils.ts', 'function add(a: number, b: number): number {\n return a + b\n}\n\nfunction subtract(a: number, b: number): number {\n return a - b\n}\n'],
|
|
55
|
+
]);
|
|
56
|
+
const result = await indexer.indexFiles(asts, fileContents);
|
|
57
|
+
expect(result.chunked).toBe(2);
|
|
58
|
+
expect(result.embedded).toBe(2);
|
|
59
|
+
expect(store.size()).toBe(2);
|
|
60
|
+
});
|
|
61
|
+
it('handles disabled config (no-ops)', async () => {
|
|
62
|
+
const store = new InMemoryVectorStore();
|
|
63
|
+
const indexer = new VectorIndexer({
|
|
64
|
+
enabled: false,
|
|
65
|
+
embeddingProvider: mockProvider,
|
|
66
|
+
vectorStore: store,
|
|
67
|
+
});
|
|
68
|
+
const asts = [
|
|
69
|
+
makeFileAST('src/utils.ts', 'typescript', [
|
|
70
|
+
makeSymbol('add', 'function', 'src/utils.ts', { line: 0, endLine: 3 }),
|
|
71
|
+
]),
|
|
72
|
+
];
|
|
73
|
+
const fileContents = new Map([['src/utils.ts', 'function add() { return 1 }']]);
|
|
74
|
+
const indexResult = await indexer.indexFiles(asts, fileContents);
|
|
75
|
+
expect(indexResult.chunked).toBe(0);
|
|
76
|
+
expect(indexResult.embedded).toBe(0);
|
|
77
|
+
expect(store.size()).toBe(0);
|
|
78
|
+
// removeFiles is also a no-op
|
|
79
|
+
await indexer.removeFiles(['src/utils.ts']);
|
|
80
|
+
expect(store.size()).toBe(0);
|
|
81
|
+
// updateFiles is also a no-op
|
|
82
|
+
const updateResult = await indexer.updateFiles(asts, fileContents);
|
|
83
|
+
expect(updateResult.added).toBe(0);
|
|
84
|
+
expect(updateResult.removed).toBe(0);
|
|
85
|
+
expect(updateResult.unchanged).toBe(0);
|
|
86
|
+
});
|
|
87
|
+
it('removes files by path', async () => {
|
|
88
|
+
const store = new InMemoryVectorStore();
|
|
89
|
+
const indexer = new VectorIndexer({
|
|
90
|
+
enabled: true,
|
|
91
|
+
embeddingProvider: mockProvider,
|
|
92
|
+
vectorStore: store,
|
|
93
|
+
});
|
|
94
|
+
// Index two files
|
|
95
|
+
const asts = [
|
|
96
|
+
makeFileAST('src/a.ts', 'typescript', [
|
|
97
|
+
makeSymbol('foo', 'function', 'src/a.ts', { line: 0, endLine: 2 }),
|
|
98
|
+
]),
|
|
99
|
+
makeFileAST('src/b.ts', 'typescript', [
|
|
100
|
+
makeSymbol('bar', 'function', 'src/b.ts', { line: 0, endLine: 2 }),
|
|
101
|
+
]),
|
|
102
|
+
];
|
|
103
|
+
const fileContents = new Map([
|
|
104
|
+
['src/a.ts', 'function foo() {}\n'],
|
|
105
|
+
['src/b.ts', 'function bar() {}\n'],
|
|
106
|
+
]);
|
|
107
|
+
await indexer.indexFiles(asts, fileContents);
|
|
108
|
+
expect(store.size()).toBe(2);
|
|
109
|
+
// Remove one file
|
|
110
|
+
await indexer.removeFiles(['src/a.ts']);
|
|
111
|
+
expect(store.size()).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
it('updates modified files (only re-embeds changed chunks)', async () => {
|
|
114
|
+
const store = new InMemoryVectorStore();
|
|
115
|
+
const embedSpy = vi.fn(mockProvider.embed.bind(mockProvider));
|
|
116
|
+
const spyProvider = {
|
|
117
|
+
...mockProvider,
|
|
118
|
+
embed: embedSpy,
|
|
119
|
+
};
|
|
120
|
+
const indexer = new VectorIndexer({
|
|
121
|
+
enabled: true,
|
|
122
|
+
embeddingProvider: spyProvider,
|
|
123
|
+
vectorStore: store,
|
|
124
|
+
batchSize: 128,
|
|
125
|
+
});
|
|
126
|
+
// Initial index with two symbols
|
|
127
|
+
const initialAsts = [
|
|
128
|
+
makeFileAST('src/math.ts', 'typescript', [
|
|
129
|
+
makeSymbol('add', 'function', 'src/math.ts', {
|
|
130
|
+
line: 0,
|
|
131
|
+
endLine: 2,
|
|
132
|
+
signature: 'function add(a: number, b: number): number',
|
|
133
|
+
}),
|
|
134
|
+
makeSymbol('multiply', 'function', 'src/math.ts', {
|
|
135
|
+
line: 4,
|
|
136
|
+
endLine: 6,
|
|
137
|
+
signature: 'function multiply(a: number, b: number): number',
|
|
138
|
+
}),
|
|
139
|
+
]),
|
|
140
|
+
];
|
|
141
|
+
const initialContents = new Map([
|
|
142
|
+
['src/math.ts', 'function add(a: number, b: number): number {\n return a + b\n}\n\nfunction multiply(a: number, b: number): number {\n return a * b\n}\n'],
|
|
143
|
+
]);
|
|
144
|
+
await indexer.indexFiles(initialAsts, initialContents);
|
|
145
|
+
expect(store.size()).toBe(2);
|
|
146
|
+
const initialEmbedCalls = embedSpy.mock.calls.length;
|
|
147
|
+
// Update: only change `add`, keep `multiply` the same
|
|
148
|
+
const updatedAsts = [
|
|
149
|
+
makeFileAST('src/math.ts', 'typescript', [
|
|
150
|
+
makeSymbol('add', 'function', 'src/math.ts', {
|
|
151
|
+
line: 0,
|
|
152
|
+
endLine: 3,
|
|
153
|
+
signature: 'function add(a: number, b: number): number',
|
|
154
|
+
}),
|
|
155
|
+
makeSymbol('multiply', 'function', 'src/math.ts', {
|
|
156
|
+
line: 5,
|
|
157
|
+
endLine: 7,
|
|
158
|
+
signature: 'function multiply(a: number, b: number): number',
|
|
159
|
+
}),
|
|
160
|
+
]),
|
|
161
|
+
];
|
|
162
|
+
const updatedContents = new Map([
|
|
163
|
+
['src/math.ts', 'function add(a: number, b: number): number {\n // Enhanced add\n return a + b\n}\n\nfunction multiply(a: number, b: number): number {\n return a * b\n}\n'],
|
|
164
|
+
]);
|
|
165
|
+
const result = await indexer.updateFiles(updatedAsts, updatedContents);
|
|
166
|
+
// `add` content changed, `multiply` content unchanged
|
|
167
|
+
expect(result.unchanged).toBeGreaterThanOrEqual(0); // multiply may or may not have changed depending on chunk IDs
|
|
168
|
+
expect(result.added).toBeGreaterThanOrEqual(1); // at least `add` was re-embedded
|
|
169
|
+
expect(store.size()).toBe(2);
|
|
170
|
+
});
|
|
171
|
+
it('emits progress events', async () => {
|
|
172
|
+
const store = new InMemoryVectorStore();
|
|
173
|
+
const indexer = new VectorIndexer({
|
|
174
|
+
enabled: true,
|
|
175
|
+
embeddingProvider: mockProvider,
|
|
176
|
+
vectorStore: store,
|
|
177
|
+
});
|
|
178
|
+
const asts = [
|
|
179
|
+
makeFileAST('src/a.ts', 'typescript', [
|
|
180
|
+
makeSymbol('foo', 'function', 'src/a.ts', { line: 0, endLine: 2 }),
|
|
181
|
+
]),
|
|
182
|
+
];
|
|
183
|
+
const fileContents = new Map([['src/a.ts', 'function foo() { return 1 }']]);
|
|
184
|
+
const progress = [];
|
|
185
|
+
await indexer.indexFiles(asts, fileContents, (p) => {
|
|
186
|
+
progress.push({ ...p });
|
|
187
|
+
});
|
|
188
|
+
// Should have progress events for chunking, embedding, and indexing phases
|
|
189
|
+
const phases = progress.map(p => p.phase);
|
|
190
|
+
expect(phases).toContain('chunking');
|
|
191
|
+
expect(phases).toContain('embedding');
|
|
192
|
+
expect(phases).toContain('indexing');
|
|
193
|
+
});
|
|
194
|
+
it('handles batch splitting', async () => {
|
|
195
|
+
const store = new InMemoryVectorStore();
|
|
196
|
+
const embedSpy = vi.fn(mockProvider.embed.bind(mockProvider));
|
|
197
|
+
const spyProvider = {
|
|
198
|
+
...mockProvider,
|
|
199
|
+
embed: embedSpy,
|
|
200
|
+
};
|
|
201
|
+
const indexer = new VectorIndexer({
|
|
202
|
+
enabled: true,
|
|
203
|
+
embeddingProvider: spyProvider,
|
|
204
|
+
vectorStore: store,
|
|
205
|
+
batchSize: 2, // Small batch to test splitting
|
|
206
|
+
maxConcurrentBatches: 1,
|
|
207
|
+
});
|
|
208
|
+
// Create 5 symbols => 5 chunks => should be split into 3 batches (2+2+1)
|
|
209
|
+
const symbols = Array.from({ length: 5 }, (_, i) => makeSymbol(`fn${i}`, 'function', 'src/many.ts', { line: i * 3, endLine: i * 3 + 2 }));
|
|
210
|
+
const asts = [
|
|
211
|
+
makeFileAST('src/many.ts', 'typescript', symbols),
|
|
212
|
+
];
|
|
213
|
+
const lines = Array.from({ length: 15 }, (_, i) => `line ${i}`);
|
|
214
|
+
const fileContents = new Map([['src/many.ts', lines.join('\n')]]);
|
|
215
|
+
await indexer.indexFiles(asts, fileContents);
|
|
216
|
+
// 5 chunks / batchSize 2 = 3 embed calls
|
|
217
|
+
expect(embedSpy).toHaveBeenCalledTimes(3);
|
|
218
|
+
expect(store.size()).toBe(5);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
// ── IncrementalIndexer + VectorIndexer Integration ───────────────────
|
|
222
|
+
describe('IncrementalIndexer with VectorIndexer', () => {
|
|
223
|
+
it('added files get vector-indexed', async () => {
|
|
224
|
+
const extractor = new SymbolExtractor();
|
|
225
|
+
const store = new InMemoryVectorStore();
|
|
226
|
+
const vectorIndexer = new VectorIndexer({
|
|
227
|
+
enabled: true,
|
|
228
|
+
embeddingProvider: mockProvider,
|
|
229
|
+
vectorStore: store,
|
|
230
|
+
});
|
|
231
|
+
const indexer = new IncrementalIndexer(extractor);
|
|
232
|
+
indexer.setVectorIndexer(vectorIndexer);
|
|
233
|
+
const files = new Map([
|
|
234
|
+
['src/main.ts', 'export function main() { return 1 }'],
|
|
235
|
+
['src/utils.ts', 'export function helper() { return 42 }'],
|
|
236
|
+
]);
|
|
237
|
+
const result = await indexer.index(files);
|
|
238
|
+
expect(result.changes.added).toHaveLength(2);
|
|
239
|
+
// Vector store should have chunks for the added files
|
|
240
|
+
expect(store.size()).toBeGreaterThan(0);
|
|
241
|
+
});
|
|
242
|
+
it('deleted files get removed from vector store', async () => {
|
|
243
|
+
const extractor = new SymbolExtractor();
|
|
244
|
+
const store = new InMemoryVectorStore();
|
|
245
|
+
const vectorIndexer = new VectorIndexer({
|
|
246
|
+
enabled: true,
|
|
247
|
+
embeddingProvider: mockProvider,
|
|
248
|
+
vectorStore: store,
|
|
249
|
+
});
|
|
250
|
+
const indexer = new IncrementalIndexer(extractor);
|
|
251
|
+
indexer.setVectorIndexer(vectorIndexer);
|
|
252
|
+
// First index: two files
|
|
253
|
+
const files1 = new Map([
|
|
254
|
+
['src/a.ts', 'export function foo() { return 1 }'],
|
|
255
|
+
['src/b.ts', 'export function bar() { return 2 }'],
|
|
256
|
+
]);
|
|
257
|
+
await indexer.index(files1);
|
|
258
|
+
const sizeAfterFirstIndex = store.size();
|
|
259
|
+
expect(sizeAfterFirstIndex).toBeGreaterThan(0);
|
|
260
|
+
// Second index: remove src/b.ts
|
|
261
|
+
const files2 = new Map([
|
|
262
|
+
['src/a.ts', 'export function foo() { return 1 }'],
|
|
263
|
+
]);
|
|
264
|
+
const result = await indexer.index(files2);
|
|
265
|
+
expect(result.changes.deleted).toEqual(['src/b.ts']);
|
|
266
|
+
// Store should have fewer chunks
|
|
267
|
+
expect(store.size()).toBeLessThan(sizeAfterFirstIndex);
|
|
268
|
+
});
|
|
269
|
+
it('works exactly as before without vector indexer', async () => {
|
|
270
|
+
const extractor = new SymbolExtractor();
|
|
271
|
+
const indexer = new IncrementalIndexer(extractor);
|
|
272
|
+
// No setVectorIndexer call — should work fine
|
|
273
|
+
const files = new Map([
|
|
274
|
+
['src/main.ts', 'export function main() {}'],
|
|
275
|
+
['src/utils.ts', 'export const helper = () => 42'],
|
|
276
|
+
]);
|
|
277
|
+
const result = await indexer.index(files);
|
|
278
|
+
expect(result.changes.added).toHaveLength(2);
|
|
279
|
+
expect(result.changes.modified).toHaveLength(0);
|
|
280
|
+
expect(result.indexed).toHaveLength(2);
|
|
281
|
+
expect(result.metadata.totalFiles).toBe(2);
|
|
282
|
+
// Second pass with modification
|
|
283
|
+
const files2 = new Map([
|
|
284
|
+
['src/main.ts', 'export function main() {}'],
|
|
285
|
+
['src/utils.ts', 'export const helper = () => 99'],
|
|
286
|
+
]);
|
|
287
|
+
const result2 = await indexer.index(files2);
|
|
288
|
+
expect(result2.changes.modified).toEqual(['src/utils.ts']);
|
|
289
|
+
expect(result2.indexed).toHaveLength(1);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FileIndex, IndexMetadata, CodeSymbol } from '../types.js';
|
|
2
2
|
import { type ChangeSet } from './change-detector.js';
|
|
3
3
|
import { SymbolExtractor } from '../parser/symbol-extractor.js';
|
|
4
|
+
import type { VectorIndexer } from './vector-indexer.js';
|
|
4
5
|
export interface IndexerOptions {
|
|
5
6
|
indexDir?: string;
|
|
6
7
|
filePatterns?: string[];
|
|
@@ -16,7 +17,10 @@ export declare class IncrementalIndexer {
|
|
|
16
17
|
private indexDir;
|
|
17
18
|
private previousTree;
|
|
18
19
|
private fileIndex;
|
|
20
|
+
private vectorIndexer;
|
|
19
21
|
constructor(extractor: SymbolExtractor, options?: IndexerOptions);
|
|
22
|
+
/** Set an optional VectorIndexer for dense vector indexing. */
|
|
23
|
+
setVectorIndexer(indexer: VectorIndexer): void;
|
|
20
24
|
/**
|
|
21
25
|
* Index files, returning only the changed ones.
|
|
22
26
|
* @param files Map of filePath -> content
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"incremental-indexer.d.ts","sourceRoot":"","sources":["../../../src/indexing/incremental-indexer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"incremental-indexer.d.ts","sourceRoot":"","sources":["../../../src/indexing/incremental-indexer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAW,MAAM,aAAa,CAAA;AAEhF,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAErE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AAED;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,aAAa,CAA2B;gBAEpC,SAAS,EAAE,eAAe,EAAE,OAAO,GAAE,cAAmB;IAKpE,+DAA+D;IAC/D,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAI9C;;;OAGG;IACG,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;QAC/C,OAAO,EAAE,SAAS,CAAA;QAClB,OAAO,EAAE,SAAS,EAAE,CAAA;QACpB,QAAQ,EAAE,aAAa,CAAA;KACxB,CAAC;IA0FF,0BAA0B;IACpB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3C,4BAA4B;IACtB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA8B9C,+BAA+B;IAC/B,aAAa,IAAI,UAAU,EAAE;IAQ7B,kCAAkC;IAClC,YAAY,IAAI,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;CAGvC"}
|
|
@@ -14,10 +14,15 @@ export class IncrementalIndexer {
|
|
|
14
14
|
indexDir;
|
|
15
15
|
previousTree;
|
|
16
16
|
fileIndex = new Map();
|
|
17
|
+
vectorIndexer;
|
|
17
18
|
constructor(extractor, options = {}) {
|
|
18
19
|
this.extractor = extractor;
|
|
19
20
|
this.indexDir = options.indexDir ?? '.agentfactory/code-index';
|
|
20
21
|
}
|
|
22
|
+
/** Set an optional VectorIndexer for dense vector indexing. */
|
|
23
|
+
setVectorIndexer(indexer) {
|
|
24
|
+
this.vectorIndexer = indexer;
|
|
25
|
+
}
|
|
21
26
|
/**
|
|
22
27
|
* Index files, returning only the changed ones.
|
|
23
28
|
* @param files Map of filePath -> content
|
|
@@ -41,6 +46,10 @@ export class IncrementalIndexer {
|
|
|
41
46
|
}
|
|
42
47
|
// Index added and modified files
|
|
43
48
|
const indexed = [];
|
|
49
|
+
const addedAsts = [];
|
|
50
|
+
const addedContents = new Map();
|
|
51
|
+
const modifiedAsts = [];
|
|
52
|
+
const modifiedContents = new Map();
|
|
44
53
|
const toIndex = [...changes.added, ...changes.modified];
|
|
45
54
|
for (const path of toIndex) {
|
|
46
55
|
const content = files.get(path);
|
|
@@ -56,6 +65,27 @@ export class IncrementalIndexer {
|
|
|
56
65
|
};
|
|
57
66
|
this.fileIndex.set(path, fileIdx);
|
|
58
67
|
indexed.push(fileIdx);
|
|
68
|
+
// Collect ASTs/contents for vector indexing
|
|
69
|
+
if (changes.added.includes(path)) {
|
|
70
|
+
addedAsts.push(ast);
|
|
71
|
+
addedContents.set(path, content);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
modifiedAsts.push(ast);
|
|
75
|
+
modifiedContents.set(path, content);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Vector indexing (optional)
|
|
79
|
+
if (this.vectorIndexer) {
|
|
80
|
+
if (changes.deleted.length > 0) {
|
|
81
|
+
await this.vectorIndexer.removeFiles(changes.deleted);
|
|
82
|
+
}
|
|
83
|
+
if (addedAsts.length > 0) {
|
|
84
|
+
await this.vectorIndexer.indexFiles(addedAsts, addedContents);
|
|
85
|
+
}
|
|
86
|
+
if (modifiedAsts.length > 0) {
|
|
87
|
+
await this.vectorIndexer.updateFiles(modifiedAsts, modifiedContents);
|
|
88
|
+
}
|
|
59
89
|
}
|
|
60
90
|
this.previousTree = newTree;
|
|
61
91
|
// Collect all symbols for metadata
|
|
@@ -87,6 +117,11 @@ export class IncrementalIndexer {
|
|
|
87
117
|
rootHash: this.previousTree?.getRootHash() ?? '',
|
|
88
118
|
};
|
|
89
119
|
await writeFile(join(dir, 'index.json'), JSON.stringify(data, null, 2));
|
|
120
|
+
// Save vector store if available
|
|
121
|
+
if (this.vectorIndexer) {
|
|
122
|
+
const vectorDir = join(dir, 'vectors');
|
|
123
|
+
await this.vectorIndexer.getStore().save(vectorDir);
|
|
124
|
+
}
|
|
90
125
|
}
|
|
91
126
|
/** Load index from disk. */
|
|
92
127
|
async load(basePath) {
|
|
@@ -101,6 +136,16 @@ export class IncrementalIndexer {
|
|
|
101
136
|
fileHashes.set(path, fi.gitHash);
|
|
102
137
|
}
|
|
103
138
|
this.previousTree = MerkleTree.fromHashes(fileHashes);
|
|
139
|
+
// Load vector store if available
|
|
140
|
+
if (this.vectorIndexer) {
|
|
141
|
+
const vectorDir = join(basePath, this.indexDir, 'vectors');
|
|
142
|
+
try {
|
|
143
|
+
await this.vectorIndexer.getStore().load(vectorDir);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Vector store may not exist yet — that's fine
|
|
147
|
+
}
|
|
148
|
+
}
|
|
104
149
|
return true;
|
|
105
150
|
}
|
|
106
151
|
catch {
|