raggrep 0.1.0
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/LICENSE +22 -0
- package/README.md +15 -0
- package/dist/application/index.d.ts +7 -0
- package/dist/application/usecases/cleanupIndex.d.ts +54 -0
- package/dist/application/usecases/index.d.ts +9 -0
- package/dist/application/usecases/indexDirectory.d.ts +54 -0
- package/dist/application/usecases/searchIndex.d.ts +48 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +1596 -0
- package/dist/cli/main.js.map +22 -0
- package/dist/composition.d.ts +52 -0
- package/dist/domain/entities/chunk.d.ts +41 -0
- package/dist/domain/entities/config.d.ts +43 -0
- package/dist/domain/entities/fileIndex.d.ts +58 -0
- package/dist/domain/entities/fileSummary.d.ts +61 -0
- package/dist/domain/entities/index.d.ts +14 -0
- package/dist/domain/entities/searchResult.d.ts +36 -0
- package/dist/domain/index.d.ts +11 -0
- package/dist/domain/ports/embedding.d.ts +60 -0
- package/dist/domain/ports/filesystem.d.ts +78 -0
- package/dist/domain/ports/index.d.ts +10 -0
- package/dist/domain/ports/storage.d.ts +79 -0
- package/dist/domain/services/bm25.d.ts +82 -0
- package/dist/domain/services/bm25.test.d.ts +4 -0
- package/dist/domain/services/index.d.ts +8 -0
- package/dist/domain/services/keywords.d.ts +27 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +1378 -0
- package/dist/index.js.map +22 -0
- package/dist/indexer/index.d.ts +33 -0
- package/dist/infrastructure/embeddings/index.d.ts +4 -0
- package/dist/infrastructure/embeddings/transformersEmbedding.d.ts +34 -0
- package/dist/infrastructure/filesystem/index.d.ts +4 -0
- package/dist/infrastructure/filesystem/nodeFileSystem.d.ts +28 -0
- package/dist/infrastructure/index.d.ts +9 -0
- package/dist/infrastructure/storage/fileIndexStorage.d.ts +68 -0
- package/dist/infrastructure/storage/index.d.ts +4 -0
- package/dist/modules/registry.d.ts +3 -0
- package/dist/modules/semantic/index.d.ts +55 -0
- package/dist/modules/semantic/parseCode.d.ts +44 -0
- package/dist/modules/semantic/parseCode.test.d.ts +4 -0
- package/dist/search/index.d.ts +11 -0
- package/dist/types.d.ts +84 -0
- package/dist/utils/bm25.d.ts +9 -0
- package/dist/utils/config.d.ts +45 -0
- package/dist/utils/embeddings.d.ts +46 -0
- package/dist/utils/embeddings.test.d.ts +4 -0
- package/dist/utils/tieredIndex.d.ts +100 -0
- package/package.json +66 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1378 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
12
|
+
// src/domain/entities/searchResult.ts
|
|
13
|
+
var init_searchResult = () => {};
|
|
14
|
+
|
|
15
|
+
// src/domain/entities/config.ts
|
|
16
|
+
function createDefaultConfig() {
|
|
17
|
+
return {
|
|
18
|
+
version: "0.1.0",
|
|
19
|
+
indexDir: ".raggrep",
|
|
20
|
+
extensions: DEFAULT_EXTENSIONS,
|
|
21
|
+
ignorePaths: DEFAULT_IGNORE_PATHS,
|
|
22
|
+
modules: [
|
|
23
|
+
{
|
|
24
|
+
id: "semantic",
|
|
25
|
+
enabled: true,
|
|
26
|
+
options: {
|
|
27
|
+
embeddingModel: "all-MiniLM-L6-v2"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
var DEFAULT_IGNORE_PATHS, DEFAULT_EXTENSIONS;
|
|
34
|
+
var init_config = __esm(() => {
|
|
35
|
+
DEFAULT_IGNORE_PATHS = [
|
|
36
|
+
"node_modules",
|
|
37
|
+
".pnpm-store",
|
|
38
|
+
".yarn",
|
|
39
|
+
"vendor",
|
|
40
|
+
".git",
|
|
41
|
+
"dist",
|
|
42
|
+
"build",
|
|
43
|
+
"out",
|
|
44
|
+
".output",
|
|
45
|
+
"target",
|
|
46
|
+
".next",
|
|
47
|
+
".nuxt",
|
|
48
|
+
".svelte-kit",
|
|
49
|
+
".vercel",
|
|
50
|
+
".netlify",
|
|
51
|
+
".cache",
|
|
52
|
+
".turbo",
|
|
53
|
+
".parcel-cache",
|
|
54
|
+
".eslintcache",
|
|
55
|
+
"coverage",
|
|
56
|
+
".nyc_output",
|
|
57
|
+
"__pycache__",
|
|
58
|
+
".venv",
|
|
59
|
+
"venv",
|
|
60
|
+
".pytest_cache",
|
|
61
|
+
"*.egg-info",
|
|
62
|
+
".idea",
|
|
63
|
+
".raggrep"
|
|
64
|
+
];
|
|
65
|
+
DEFAULT_EXTENSIONS = [
|
|
66
|
+
".ts",
|
|
67
|
+
".tsx",
|
|
68
|
+
".js",
|
|
69
|
+
".jsx",
|
|
70
|
+
".py",
|
|
71
|
+
".go",
|
|
72
|
+
".rs",
|
|
73
|
+
".java",
|
|
74
|
+
".md"
|
|
75
|
+
];
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// src/domain/entities/index.ts
|
|
79
|
+
var init_entities = __esm(() => {
|
|
80
|
+
init_searchResult();
|
|
81
|
+
init_config();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// src/utils/embeddings.ts
|
|
85
|
+
import { pipeline, env } from "@xenova/transformers";
|
|
86
|
+
import * as path from "path";
|
|
87
|
+
import * as os from "os";
|
|
88
|
+
function configureEmbeddings(config) {
|
|
89
|
+
const newConfig = { ...currentConfig, ...config };
|
|
90
|
+
if (newConfig.model !== currentConfig.model) {
|
|
91
|
+
embeddingPipeline = null;
|
|
92
|
+
currentModelName = null;
|
|
93
|
+
}
|
|
94
|
+
currentConfig = newConfig;
|
|
95
|
+
}
|
|
96
|
+
async function initializePipeline() {
|
|
97
|
+
if (embeddingPipeline && currentModelName === currentConfig.model) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (isInitializing && initPromise) {
|
|
101
|
+
return initPromise;
|
|
102
|
+
}
|
|
103
|
+
isInitializing = true;
|
|
104
|
+
initPromise = (async () => {
|
|
105
|
+
const modelId = EMBEDDING_MODELS[currentConfig.model];
|
|
106
|
+
if (currentConfig.showProgress) {
|
|
107
|
+
console.log(`
|
|
108
|
+
Loading embedding model: ${currentConfig.model}`);
|
|
109
|
+
console.log(` Cache: ${CACHE_DIR}`);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
embeddingPipeline = await pipeline("feature-extraction", modelId, {
|
|
113
|
+
progress_callback: currentConfig.showProgress ? (progress) => {
|
|
114
|
+
if (progress.status === "progress" && progress.file) {
|
|
115
|
+
const pct = progress.progress ? Math.round(progress.progress) : 0;
|
|
116
|
+
process.stdout.write(`\r Downloading ${progress.file}: ${pct}% `);
|
|
117
|
+
} else if (progress.status === "done" && progress.file) {
|
|
118
|
+
process.stdout.write(`\r Downloaded ${progress.file}
|
|
119
|
+
`);
|
|
120
|
+
} else if (progress.status === "ready") {}
|
|
121
|
+
} : undefined
|
|
122
|
+
});
|
|
123
|
+
currentModelName = currentConfig.model;
|
|
124
|
+
if (currentConfig.showProgress) {
|
|
125
|
+
console.log(` Model ready.
|
|
126
|
+
`);
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
embeddingPipeline = null;
|
|
130
|
+
currentModelName = null;
|
|
131
|
+
throw new Error(`Failed to load embedding model: ${error}`);
|
|
132
|
+
} finally {
|
|
133
|
+
isInitializing = false;
|
|
134
|
+
initPromise = null;
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
return initPromise;
|
|
138
|
+
}
|
|
139
|
+
async function getEmbedding(text) {
|
|
140
|
+
await initializePipeline();
|
|
141
|
+
if (!embeddingPipeline) {
|
|
142
|
+
throw new Error("Embedding pipeline not initialized");
|
|
143
|
+
}
|
|
144
|
+
const output = await embeddingPipeline(text, {
|
|
145
|
+
pooling: "mean",
|
|
146
|
+
normalize: true
|
|
147
|
+
});
|
|
148
|
+
return Array.from(output.data);
|
|
149
|
+
}
|
|
150
|
+
async function getEmbeddings(texts) {
|
|
151
|
+
if (texts.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
await initializePipeline();
|
|
154
|
+
if (!embeddingPipeline) {
|
|
155
|
+
throw new Error("Embedding pipeline not initialized");
|
|
156
|
+
}
|
|
157
|
+
const results = [];
|
|
158
|
+
for (let i = 0;i < texts.length; i += BATCH_SIZE) {
|
|
159
|
+
const batch = texts.slice(i, i + BATCH_SIZE);
|
|
160
|
+
const outputs = await Promise.all(batch.map(async (text) => {
|
|
161
|
+
const output = await embeddingPipeline(text, {
|
|
162
|
+
pooling: "mean",
|
|
163
|
+
normalize: true
|
|
164
|
+
});
|
|
165
|
+
return Array.from(output.data);
|
|
166
|
+
}));
|
|
167
|
+
results.push(...outputs);
|
|
168
|
+
}
|
|
169
|
+
return results;
|
|
170
|
+
}
|
|
171
|
+
function cosineSimilarity(a, b) {
|
|
172
|
+
if (a.length !== b.length) {
|
|
173
|
+
throw new Error("Vectors must have the same length");
|
|
174
|
+
}
|
|
175
|
+
let dotProduct = 0;
|
|
176
|
+
let normA = 0;
|
|
177
|
+
let normB = 0;
|
|
178
|
+
for (let i = 0;i < a.length; i++) {
|
|
179
|
+
dotProduct += a[i] * b[i];
|
|
180
|
+
normA += a[i] * a[i];
|
|
181
|
+
normB += b[i] * b[i];
|
|
182
|
+
}
|
|
183
|
+
if (normA === 0 || normB === 0)
|
|
184
|
+
return 0;
|
|
185
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
186
|
+
}
|
|
187
|
+
function getEmbeddingConfig() {
|
|
188
|
+
return { ...currentConfig };
|
|
189
|
+
}
|
|
190
|
+
var CACHE_DIR, EMBEDDING_MODELS, embeddingPipeline = null, currentModelName = null, isInitializing = false, initPromise = null, DEFAULT_CONFIG, currentConfig, BATCH_SIZE = 32;
|
|
191
|
+
var init_embeddings = __esm(() => {
|
|
192
|
+
CACHE_DIR = path.join(os.homedir(), ".cache", "raggrep", "models");
|
|
193
|
+
env.cacheDir = CACHE_DIR;
|
|
194
|
+
env.allowLocalModels = true;
|
|
195
|
+
EMBEDDING_MODELS = {
|
|
196
|
+
"all-MiniLM-L6-v2": "Xenova/all-MiniLM-L6-v2",
|
|
197
|
+
"all-MiniLM-L12-v2": "Xenova/all-MiniLM-L12-v2",
|
|
198
|
+
"bge-small-en-v1.5": "Xenova/bge-small-en-v1.5",
|
|
199
|
+
"paraphrase-MiniLM-L3-v2": "Xenova/paraphrase-MiniLM-L3-v2"
|
|
200
|
+
};
|
|
201
|
+
DEFAULT_CONFIG = {
|
|
202
|
+
model: "all-MiniLM-L6-v2",
|
|
203
|
+
showProgress: true
|
|
204
|
+
};
|
|
205
|
+
currentConfig = { ...DEFAULT_CONFIG };
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// src/utils/config.ts
|
|
209
|
+
import * as path2 from "path";
|
|
210
|
+
import * as fs from "fs/promises";
|
|
211
|
+
function getRaggrepDir(rootDir, config = DEFAULT_CONFIG2) {
|
|
212
|
+
return path2.join(rootDir, config.indexDir);
|
|
213
|
+
}
|
|
214
|
+
function getModuleIndexPath(rootDir, moduleId, config = DEFAULT_CONFIG2) {
|
|
215
|
+
return path2.join(rootDir, config.indexDir, "index", moduleId);
|
|
216
|
+
}
|
|
217
|
+
function getModuleManifestPath(rootDir, moduleId, config = DEFAULT_CONFIG2) {
|
|
218
|
+
return path2.join(rootDir, config.indexDir, "index", moduleId, "manifest.json");
|
|
219
|
+
}
|
|
220
|
+
function getGlobalManifestPath(rootDir, config = DEFAULT_CONFIG2) {
|
|
221
|
+
return path2.join(rootDir, config.indexDir, "manifest.json");
|
|
222
|
+
}
|
|
223
|
+
function getConfigPath(rootDir, config = DEFAULT_CONFIG2) {
|
|
224
|
+
return path2.join(rootDir, config.indexDir, "config.json");
|
|
225
|
+
}
|
|
226
|
+
async function loadConfig(rootDir) {
|
|
227
|
+
const configPath = getConfigPath(rootDir, DEFAULT_CONFIG2);
|
|
228
|
+
try {
|
|
229
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
230
|
+
const savedConfig = JSON.parse(content);
|
|
231
|
+
return { ...DEFAULT_CONFIG2, ...savedConfig };
|
|
232
|
+
} catch {
|
|
233
|
+
return DEFAULT_CONFIG2;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function getModuleConfig(config, moduleId) {
|
|
237
|
+
return config.modules.find((m) => m.id === moduleId);
|
|
238
|
+
}
|
|
239
|
+
function getEmbeddingConfigFromModule(moduleConfig) {
|
|
240
|
+
const options = moduleConfig.options || {};
|
|
241
|
+
const modelName = options.embeddingModel || "all-MiniLM-L6-v2";
|
|
242
|
+
if (!(modelName in EMBEDDING_MODELS)) {
|
|
243
|
+
console.warn(`Unknown embedding model: ${modelName}, falling back to all-MiniLM-L6-v2`);
|
|
244
|
+
return { model: "all-MiniLM-L6-v2" };
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
model: modelName,
|
|
248
|
+
showProgress: options.showProgress !== false
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
var DEFAULT_CONFIG2;
|
|
252
|
+
var init_config2 = __esm(() => {
|
|
253
|
+
init_entities();
|
|
254
|
+
init_embeddings();
|
|
255
|
+
DEFAULT_CONFIG2 = createDefaultConfig();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// src/domain/services/bm25.ts
|
|
259
|
+
function tokenize(text) {
|
|
260
|
+
return text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1);
|
|
261
|
+
}
|
|
262
|
+
function termFrequency(term, tokens) {
|
|
263
|
+
return tokens.filter((t) => t === term).length;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
class BM25Index {
|
|
267
|
+
documents = new Map;
|
|
268
|
+
avgDocLength = 0;
|
|
269
|
+
documentFrequencies = new Map;
|
|
270
|
+
totalDocs = 0;
|
|
271
|
+
addDocuments(documents) {
|
|
272
|
+
let totalLength = this.avgDocLength * this.totalDocs;
|
|
273
|
+
for (const doc of documents) {
|
|
274
|
+
const tokens = doc.tokens ?? tokenize(doc.content);
|
|
275
|
+
this.documents.set(doc.id, { content: doc.content, tokens });
|
|
276
|
+
totalLength += tokens.length;
|
|
277
|
+
this.totalDocs++;
|
|
278
|
+
const uniqueTerms = new Set(tokens);
|
|
279
|
+
for (const term of uniqueTerms) {
|
|
280
|
+
const count = this.documentFrequencies.get(term) || 0;
|
|
281
|
+
this.documentFrequencies.set(term, count + 1);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
this.avgDocLength = this.totalDocs > 0 ? totalLength / this.totalDocs : 0;
|
|
285
|
+
}
|
|
286
|
+
idf(term) {
|
|
287
|
+
const docFreq = this.documentFrequencies.get(term) || 0;
|
|
288
|
+
if (docFreq === 0)
|
|
289
|
+
return 0;
|
|
290
|
+
return Math.log(1 + (this.totalDocs - docFreq + 0.5) / (docFreq + 0.5));
|
|
291
|
+
}
|
|
292
|
+
score(tokens, queryTerms) {
|
|
293
|
+
const docLength = tokens.length;
|
|
294
|
+
let score = 0;
|
|
295
|
+
for (const term of queryTerms) {
|
|
296
|
+
const tf = termFrequency(term, tokens);
|
|
297
|
+
if (tf === 0)
|
|
298
|
+
continue;
|
|
299
|
+
const idfScore = this.idf(term);
|
|
300
|
+
const numerator = tf * (BM25_K1 + 1);
|
|
301
|
+
const denominator = tf + BM25_K1 * (1 - BM25_B + BM25_B * (docLength / this.avgDocLength));
|
|
302
|
+
score += idfScore * (numerator / denominator);
|
|
303
|
+
}
|
|
304
|
+
return score;
|
|
305
|
+
}
|
|
306
|
+
search(query, topK = 10) {
|
|
307
|
+
const queryTerms = tokenize(query);
|
|
308
|
+
if (queryTerms.length === 0)
|
|
309
|
+
return [];
|
|
310
|
+
const results = [];
|
|
311
|
+
for (const [id, { tokens }] of this.documents) {
|
|
312
|
+
const score = this.score(tokens, queryTerms);
|
|
313
|
+
if (score > 0) {
|
|
314
|
+
results.push({ id, score });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
results.sort((a, b) => b.score - a.score);
|
|
318
|
+
return results.slice(0, topK);
|
|
319
|
+
}
|
|
320
|
+
get size() {
|
|
321
|
+
return this.totalDocs;
|
|
322
|
+
}
|
|
323
|
+
clear() {
|
|
324
|
+
this.documents.clear();
|
|
325
|
+
this.documentFrequencies.clear();
|
|
326
|
+
this.avgDocLength = 0;
|
|
327
|
+
this.totalDocs = 0;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function normalizeScore(score, midpoint = 5) {
|
|
331
|
+
return 1 / (1 + Math.exp(-score / midpoint + 1));
|
|
332
|
+
}
|
|
333
|
+
var BM25_K1 = 1.5, BM25_B = 0.75;
|
|
334
|
+
|
|
335
|
+
// src/utils/bm25.ts
|
|
336
|
+
var init_bm25 = () => {};
|
|
337
|
+
|
|
338
|
+
// src/modules/semantic/parseCode.ts
|
|
339
|
+
import * as ts from "typescript";
|
|
340
|
+
function parseCode(content, filepath) {
|
|
341
|
+
const ext = filepath.split(".").pop()?.toLowerCase();
|
|
342
|
+
if (["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs"].includes(ext || "")) {
|
|
343
|
+
return parseTypeScript(content, filepath);
|
|
344
|
+
}
|
|
345
|
+
return parseGenericCode(content);
|
|
346
|
+
}
|
|
347
|
+
function parseTypeScript(content, filepath) {
|
|
348
|
+
const chunks = [];
|
|
349
|
+
const lines = content.split(`
|
|
350
|
+
`);
|
|
351
|
+
const sourceFile = ts.createSourceFile(filepath, content, ts.ScriptTarget.Latest, true, filepath.endsWith(".tsx") || filepath.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
352
|
+
function getLineNumbers(node) {
|
|
353
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
354
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
355
|
+
return {
|
|
356
|
+
startLine: start.line + 1,
|
|
357
|
+
endLine: end.line + 1
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function getNodeText(node) {
|
|
361
|
+
return node.getText(sourceFile);
|
|
362
|
+
}
|
|
363
|
+
function isExported(node) {
|
|
364
|
+
if (!ts.canHaveModifiers(node))
|
|
365
|
+
return false;
|
|
366
|
+
const modifiers = ts.getModifiers(node);
|
|
367
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
368
|
+
}
|
|
369
|
+
function getJSDoc(node) {
|
|
370
|
+
const jsDocNodes = ts.getJSDocCommentsAndTags(node);
|
|
371
|
+
if (jsDocNodes.length === 0)
|
|
372
|
+
return;
|
|
373
|
+
return jsDocNodes.map((doc) => doc.getText(sourceFile)).join(`
|
|
374
|
+
`);
|
|
375
|
+
}
|
|
376
|
+
function getFunctionName(node) {
|
|
377
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
378
|
+
return node.name.text;
|
|
379
|
+
}
|
|
380
|
+
if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
381
|
+
return node.name.text;
|
|
382
|
+
}
|
|
383
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
384
|
+
return node.name.text;
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
function visit(node) {
|
|
389
|
+
const { startLine, endLine } = getLineNumbers(node);
|
|
390
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
391
|
+
chunks.push({
|
|
392
|
+
content: getNodeText(node),
|
|
393
|
+
startLine,
|
|
394
|
+
endLine,
|
|
395
|
+
type: "function",
|
|
396
|
+
name: node.name.text,
|
|
397
|
+
isExported: isExported(node),
|
|
398
|
+
jsDoc: getJSDoc(node)
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (ts.isVariableStatement(node)) {
|
|
403
|
+
for (const decl of node.declarationList.declarations) {
|
|
404
|
+
if (decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
|
|
405
|
+
const name = ts.isIdentifier(decl.name) ? decl.name.text : undefined;
|
|
406
|
+
chunks.push({
|
|
407
|
+
content: getNodeText(node),
|
|
408
|
+
startLine,
|
|
409
|
+
endLine,
|
|
410
|
+
type: "function",
|
|
411
|
+
name,
|
|
412
|
+
isExported: isExported(node),
|
|
413
|
+
jsDoc: getJSDoc(node)
|
|
414
|
+
});
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (ts.isClassDeclaration(node) && node.name) {
|
|
420
|
+
chunks.push({
|
|
421
|
+
content: getNodeText(node),
|
|
422
|
+
startLine,
|
|
423
|
+
endLine,
|
|
424
|
+
type: "class",
|
|
425
|
+
name: node.name.text,
|
|
426
|
+
isExported: isExported(node),
|
|
427
|
+
jsDoc: getJSDoc(node)
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
432
|
+
chunks.push({
|
|
433
|
+
content: getNodeText(node),
|
|
434
|
+
startLine,
|
|
435
|
+
endLine,
|
|
436
|
+
type: "interface",
|
|
437
|
+
name: node.name.text,
|
|
438
|
+
isExported: isExported(node),
|
|
439
|
+
jsDoc: getJSDoc(node)
|
|
440
|
+
});
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
444
|
+
chunks.push({
|
|
445
|
+
content: getNodeText(node),
|
|
446
|
+
startLine,
|
|
447
|
+
endLine,
|
|
448
|
+
type: "type",
|
|
449
|
+
name: node.name.text,
|
|
450
|
+
isExported: isExported(node),
|
|
451
|
+
jsDoc: getJSDoc(node)
|
|
452
|
+
});
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (ts.isEnumDeclaration(node)) {
|
|
456
|
+
chunks.push({
|
|
457
|
+
content: getNodeText(node),
|
|
458
|
+
startLine,
|
|
459
|
+
endLine,
|
|
460
|
+
type: "enum",
|
|
461
|
+
name: node.name.text,
|
|
462
|
+
isExported: isExported(node),
|
|
463
|
+
jsDoc: getJSDoc(node)
|
|
464
|
+
});
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (ts.isVariableStatement(node) && isExported(node)) {
|
|
468
|
+
for (const decl of node.declarationList.declarations) {
|
|
469
|
+
if (decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const name = ts.isIdentifier(decl.name) ? decl.name.text : undefined;
|
|
473
|
+
chunks.push({
|
|
474
|
+
content: getNodeText(node),
|
|
475
|
+
startLine,
|
|
476
|
+
endLine,
|
|
477
|
+
type: "variable",
|
|
478
|
+
name,
|
|
479
|
+
isExported: true,
|
|
480
|
+
jsDoc: getJSDoc(node)
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
ts.forEachChild(node, visit);
|
|
486
|
+
}
|
|
487
|
+
ts.forEachChild(sourceFile, visit);
|
|
488
|
+
if (chunks.length === 0) {
|
|
489
|
+
return parseGenericCode(content);
|
|
490
|
+
}
|
|
491
|
+
return chunks;
|
|
492
|
+
}
|
|
493
|
+
function parseGenericCode(content) {
|
|
494
|
+
const chunks = [];
|
|
495
|
+
const lines = content.split(`
|
|
496
|
+
`);
|
|
497
|
+
const CHUNK_SIZE = 30;
|
|
498
|
+
const OVERLAP = 5;
|
|
499
|
+
if (lines.length <= CHUNK_SIZE) {
|
|
500
|
+
return [
|
|
501
|
+
{
|
|
502
|
+
content,
|
|
503
|
+
startLine: 1,
|
|
504
|
+
endLine: lines.length,
|
|
505
|
+
type: "file"
|
|
506
|
+
}
|
|
507
|
+
];
|
|
508
|
+
}
|
|
509
|
+
for (let i = 0;i < lines.length; i += CHUNK_SIZE - OVERLAP) {
|
|
510
|
+
const endIdx = Math.min(i + CHUNK_SIZE, lines.length);
|
|
511
|
+
chunks.push({
|
|
512
|
+
content: lines.slice(i, endIdx).join(`
|
|
513
|
+
`),
|
|
514
|
+
startLine: i + 1,
|
|
515
|
+
endLine: endIdx,
|
|
516
|
+
type: "block"
|
|
517
|
+
});
|
|
518
|
+
if (endIdx >= lines.length)
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
return chunks;
|
|
522
|
+
}
|
|
523
|
+
function generateChunkId(filepath, startLine, endLine) {
|
|
524
|
+
const safePath = filepath.replace(/[/\\]/g, "-").replace(/\./g, "_");
|
|
525
|
+
return `${safePath}-${startLine}-${endLine}`;
|
|
526
|
+
}
|
|
527
|
+
var init_parseCode = () => {};
|
|
528
|
+
|
|
529
|
+
// src/domain/services/keywords.ts
|
|
530
|
+
function extractKeywords(content, name, maxKeywords = 50) {
|
|
531
|
+
const keywords = new Set;
|
|
532
|
+
if (name) {
|
|
533
|
+
keywords.add(name.toLowerCase());
|
|
534
|
+
const parts = name.split(/(?=[A-Z])/).map((p) => p.toLowerCase());
|
|
535
|
+
parts.forEach((p) => p.length > 2 && keywords.add(p));
|
|
536
|
+
}
|
|
537
|
+
const identifierRegex = /\b([a-zA-Z_][a-zA-Z0-9_]{2,})\b/g;
|
|
538
|
+
let match;
|
|
539
|
+
while ((match = identifierRegex.exec(content)) !== null) {
|
|
540
|
+
const word = match[1].toLowerCase();
|
|
541
|
+
if (!COMMON_KEYWORDS.has(word) && word.length > 2) {
|
|
542
|
+
keywords.add(word);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return Array.from(keywords).slice(0, maxKeywords);
|
|
546
|
+
}
|
|
547
|
+
function extractPathKeywords(filepath) {
|
|
548
|
+
return filepath.split(/[/\\.]/).filter((p) => p.length > 2 && !COMMON_KEYWORDS.has(p.toLowerCase())).map((p) => p.toLowerCase());
|
|
549
|
+
}
|
|
550
|
+
var COMMON_KEYWORDS;
|
|
551
|
+
var init_keywords = __esm(() => {
|
|
552
|
+
COMMON_KEYWORDS = new Set([
|
|
553
|
+
"const",
|
|
554
|
+
"let",
|
|
555
|
+
"var",
|
|
556
|
+
"function",
|
|
557
|
+
"class",
|
|
558
|
+
"interface",
|
|
559
|
+
"type",
|
|
560
|
+
"enum",
|
|
561
|
+
"export",
|
|
562
|
+
"import",
|
|
563
|
+
"from",
|
|
564
|
+
"return",
|
|
565
|
+
"async",
|
|
566
|
+
"await",
|
|
567
|
+
"new",
|
|
568
|
+
"this",
|
|
569
|
+
"true",
|
|
570
|
+
"false",
|
|
571
|
+
"null",
|
|
572
|
+
"undefined",
|
|
573
|
+
"if",
|
|
574
|
+
"else",
|
|
575
|
+
"for",
|
|
576
|
+
"while",
|
|
577
|
+
"switch",
|
|
578
|
+
"case",
|
|
579
|
+
"break",
|
|
580
|
+
"continue",
|
|
581
|
+
"try",
|
|
582
|
+
"catch",
|
|
583
|
+
"finally",
|
|
584
|
+
"throw",
|
|
585
|
+
"typeof",
|
|
586
|
+
"instanceof",
|
|
587
|
+
"void",
|
|
588
|
+
"delete",
|
|
589
|
+
"in",
|
|
590
|
+
"of",
|
|
591
|
+
"string",
|
|
592
|
+
"number",
|
|
593
|
+
"boolean",
|
|
594
|
+
"any",
|
|
595
|
+
"unknown",
|
|
596
|
+
"never",
|
|
597
|
+
"object",
|
|
598
|
+
"public",
|
|
599
|
+
"private",
|
|
600
|
+
"protected",
|
|
601
|
+
"static",
|
|
602
|
+
"readonly",
|
|
603
|
+
"abstract",
|
|
604
|
+
"implements",
|
|
605
|
+
"extends",
|
|
606
|
+
"super",
|
|
607
|
+
"get",
|
|
608
|
+
"set",
|
|
609
|
+
"constructor",
|
|
610
|
+
"the",
|
|
611
|
+
"and",
|
|
612
|
+
"for",
|
|
613
|
+
"not",
|
|
614
|
+
"with",
|
|
615
|
+
"are",
|
|
616
|
+
"was",
|
|
617
|
+
"has",
|
|
618
|
+
"have"
|
|
619
|
+
]);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// src/utils/tieredIndex.ts
|
|
623
|
+
import * as fs2 from "fs/promises";
|
|
624
|
+
import * as path3 from "path";
|
|
625
|
+
|
|
626
|
+
class SymbolicIndex {
|
|
627
|
+
meta = null;
|
|
628
|
+
fileSummaries = new Map;
|
|
629
|
+
bm25Index = null;
|
|
630
|
+
symbolicPath;
|
|
631
|
+
moduleId;
|
|
632
|
+
constructor(indexDir, moduleId) {
|
|
633
|
+
this.symbolicPath = path3.join(indexDir, "index", moduleId, "symbolic");
|
|
634
|
+
this.moduleId = moduleId;
|
|
635
|
+
}
|
|
636
|
+
async initialize() {
|
|
637
|
+
try {
|
|
638
|
+
await this.load();
|
|
639
|
+
} catch {
|
|
640
|
+
this.meta = {
|
|
641
|
+
version: "1.0.0",
|
|
642
|
+
lastUpdated: new Date().toISOString(),
|
|
643
|
+
moduleId: this.moduleId,
|
|
644
|
+
fileCount: 0,
|
|
645
|
+
bm25Data: {
|
|
646
|
+
avgDocLength: 0,
|
|
647
|
+
documentFrequencies: {},
|
|
648
|
+
totalDocs: 0
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
this.bm25Index = new BM25Index;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
addFile(summary) {
|
|
655
|
+
this.fileSummaries.set(summary.filepath, summary);
|
|
656
|
+
}
|
|
657
|
+
removeFile(filepath) {
|
|
658
|
+
return this.fileSummaries.delete(filepath);
|
|
659
|
+
}
|
|
660
|
+
buildBM25Index() {
|
|
661
|
+
this.bm25Index = new BM25Index;
|
|
662
|
+
for (const [filepath, summary] of this.fileSummaries) {
|
|
663
|
+
const content = [
|
|
664
|
+
...summary.keywords,
|
|
665
|
+
...summary.exports,
|
|
666
|
+
...extractPathKeywords(filepath)
|
|
667
|
+
].join(" ");
|
|
668
|
+
this.bm25Index.addDocuments([{ id: filepath, content }]);
|
|
669
|
+
}
|
|
670
|
+
if (this.meta) {
|
|
671
|
+
this.meta.fileCount = this.fileSummaries.size;
|
|
672
|
+
this.meta.bm25Data.totalDocs = this.fileSummaries.size;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
findCandidates(query, maxCandidates = 20) {
|
|
676
|
+
if (!this.bm25Index) {
|
|
677
|
+
return Array.from(this.fileSummaries.keys());
|
|
678
|
+
}
|
|
679
|
+
const results = this.bm25Index.search(query, maxCandidates);
|
|
680
|
+
return results.map((r) => r.id);
|
|
681
|
+
}
|
|
682
|
+
getAllFiles() {
|
|
683
|
+
return Array.from(this.fileSummaries.keys());
|
|
684
|
+
}
|
|
685
|
+
getFileSummary(filepath) {
|
|
686
|
+
return this.fileSummaries.get(filepath);
|
|
687
|
+
}
|
|
688
|
+
async save() {
|
|
689
|
+
if (!this.meta)
|
|
690
|
+
throw new Error("Index not initialized");
|
|
691
|
+
this.meta.lastUpdated = new Date().toISOString();
|
|
692
|
+
this.meta.fileCount = this.fileSummaries.size;
|
|
693
|
+
await fs2.mkdir(this.symbolicPath, { recursive: true });
|
|
694
|
+
const metaPath = path3.join(this.symbolicPath, "_meta.json");
|
|
695
|
+
await fs2.writeFile(metaPath, JSON.stringify(this.meta, null, 2));
|
|
696
|
+
for (const [filepath, summary] of this.fileSummaries) {
|
|
697
|
+
const summaryPath = this.getFileSummaryPath(filepath);
|
|
698
|
+
await fs2.mkdir(path3.dirname(summaryPath), { recursive: true });
|
|
699
|
+
await fs2.writeFile(summaryPath, JSON.stringify(summary, null, 2));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
async load() {
|
|
703
|
+
const metaPath = path3.join(this.symbolicPath, "_meta.json");
|
|
704
|
+
const metaContent = await fs2.readFile(metaPath, "utf-8");
|
|
705
|
+
this.meta = JSON.parse(metaContent);
|
|
706
|
+
this.fileSummaries.clear();
|
|
707
|
+
await this.loadFileSummariesRecursive(this.symbolicPath);
|
|
708
|
+
this.buildBM25Index();
|
|
709
|
+
}
|
|
710
|
+
async loadFileSummariesRecursive(dir) {
|
|
711
|
+
try {
|
|
712
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
713
|
+
for (const entry of entries) {
|
|
714
|
+
const fullPath = path3.join(dir, entry.name);
|
|
715
|
+
if (entry.isDirectory()) {
|
|
716
|
+
await this.loadFileSummariesRecursive(fullPath);
|
|
717
|
+
} else if (entry.name.endsWith(".json") && entry.name !== "_meta.json") {
|
|
718
|
+
try {
|
|
719
|
+
const content = await fs2.readFile(fullPath, "utf-8");
|
|
720
|
+
const summary = JSON.parse(content);
|
|
721
|
+
if (summary.filepath) {
|
|
722
|
+
this.fileSummaries.set(summary.filepath, summary);
|
|
723
|
+
}
|
|
724
|
+
} catch {}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
} catch {}
|
|
728
|
+
}
|
|
729
|
+
getFileSummaryPath(filepath) {
|
|
730
|
+
const jsonPath = filepath.replace(/\.[^.]+$/, ".json");
|
|
731
|
+
return path3.join(this.symbolicPath, jsonPath);
|
|
732
|
+
}
|
|
733
|
+
async deleteFileSummary(filepath) {
|
|
734
|
+
try {
|
|
735
|
+
await fs2.unlink(this.getFileSummaryPath(filepath));
|
|
736
|
+
} catch {}
|
|
737
|
+
this.fileSummaries.delete(filepath);
|
|
738
|
+
}
|
|
739
|
+
async exists() {
|
|
740
|
+
try {
|
|
741
|
+
const metaPath = path3.join(this.symbolicPath, "_meta.json");
|
|
742
|
+
await fs2.access(metaPath);
|
|
743
|
+
return true;
|
|
744
|
+
} catch {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
get size() {
|
|
749
|
+
return this.fileSummaries.size;
|
|
750
|
+
}
|
|
751
|
+
clear() {
|
|
752
|
+
this.fileSummaries.clear();
|
|
753
|
+
if (this.meta) {
|
|
754
|
+
this.meta.fileCount = 0;
|
|
755
|
+
this.meta.bm25Data = {
|
|
756
|
+
avgDocLength: 0,
|
|
757
|
+
documentFrequencies: {},
|
|
758
|
+
totalDocs: 0
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
this.bm25Index = new BM25Index;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
var init_tieredIndex = __esm(() => {
|
|
765
|
+
init_keywords();
|
|
766
|
+
init_keywords();
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// src/modules/semantic/index.ts
|
|
770
|
+
var exports_semantic = {};
|
|
771
|
+
__export(exports_semantic, {
|
|
772
|
+
SemanticModule: () => SemanticModule,
|
|
773
|
+
DEFAULT_TOP_K: () => DEFAULT_TOP_K,
|
|
774
|
+
DEFAULT_MIN_SCORE: () => DEFAULT_MIN_SCORE
|
|
775
|
+
});
|
|
776
|
+
import * as path4 from "path";
|
|
777
|
+
|
|
778
|
+
class SemanticModule {
|
|
779
|
+
id = "semantic";
|
|
780
|
+
name = "Semantic Search";
|
|
781
|
+
description = "Natural language code search using local text embeddings";
|
|
782
|
+
version = "1.0.0";
|
|
783
|
+
embeddingConfig = null;
|
|
784
|
+
symbolicIndex = null;
|
|
785
|
+
pendingSummaries = new Map;
|
|
786
|
+
rootDir = "";
|
|
787
|
+
async initialize(config) {
|
|
788
|
+
this.embeddingConfig = getEmbeddingConfigFromModule(config);
|
|
789
|
+
configureEmbeddings(this.embeddingConfig);
|
|
790
|
+
this.pendingSummaries.clear();
|
|
791
|
+
}
|
|
792
|
+
async indexFile(filepath, content, ctx) {
|
|
793
|
+
this.rootDir = ctx.rootDir;
|
|
794
|
+
const parsedChunks = parseCode(content, filepath);
|
|
795
|
+
if (parsedChunks.length === 0) {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
const chunkContents = parsedChunks.map((c) => c.content);
|
|
799
|
+
const embeddings = await getEmbeddings(chunkContents);
|
|
800
|
+
const chunks = parsedChunks.map((pc) => ({
|
|
801
|
+
id: generateChunkId(filepath, pc.startLine, pc.endLine),
|
|
802
|
+
content: pc.content,
|
|
803
|
+
startLine: pc.startLine,
|
|
804
|
+
endLine: pc.endLine,
|
|
805
|
+
type: pc.type,
|
|
806
|
+
name: pc.name,
|
|
807
|
+
isExported: pc.isExported,
|
|
808
|
+
jsDoc: pc.jsDoc
|
|
809
|
+
}));
|
|
810
|
+
const references = this.extractReferences(content, filepath);
|
|
811
|
+
const stats = await ctx.getFileStats(filepath);
|
|
812
|
+
const currentConfig2 = getEmbeddingConfig();
|
|
813
|
+
const moduleData = {
|
|
814
|
+
embeddings,
|
|
815
|
+
embeddingModel: currentConfig2.model
|
|
816
|
+
};
|
|
817
|
+
const chunkTypes = [...new Set(parsedChunks.map((pc) => pc.type))];
|
|
818
|
+
const exports = parsedChunks.filter((pc) => pc.isExported && pc.name).map((pc) => pc.name);
|
|
819
|
+
const allKeywords = new Set;
|
|
820
|
+
for (const pc of parsedChunks) {
|
|
821
|
+
const keywords = extractKeywords(pc.content, pc.name);
|
|
822
|
+
keywords.forEach((k) => allKeywords.add(k));
|
|
823
|
+
}
|
|
824
|
+
const fileSummary = {
|
|
825
|
+
filepath,
|
|
826
|
+
chunkCount: chunks.length,
|
|
827
|
+
chunkTypes,
|
|
828
|
+
keywords: Array.from(allKeywords),
|
|
829
|
+
exports,
|
|
830
|
+
lastModified: stats.lastModified
|
|
831
|
+
};
|
|
832
|
+
this.pendingSummaries.set(filepath, fileSummary);
|
|
833
|
+
return {
|
|
834
|
+
filepath,
|
|
835
|
+
lastModified: stats.lastModified,
|
|
836
|
+
chunks,
|
|
837
|
+
moduleData,
|
|
838
|
+
references
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
async finalize(ctx) {
|
|
842
|
+
const indexDir = getRaggrepDir(ctx.rootDir, ctx.config);
|
|
843
|
+
this.symbolicIndex = new SymbolicIndex(indexDir, this.id);
|
|
844
|
+
await this.symbolicIndex.initialize();
|
|
845
|
+
for (const [filepath, summary] of this.pendingSummaries) {
|
|
846
|
+
this.symbolicIndex.addFile(summary);
|
|
847
|
+
}
|
|
848
|
+
this.symbolicIndex.buildBM25Index();
|
|
849
|
+
await this.symbolicIndex.save();
|
|
850
|
+
console.log(` Symbolic index built with ${this.pendingSummaries.size} file summaries`);
|
|
851
|
+
this.pendingSummaries.clear();
|
|
852
|
+
}
|
|
853
|
+
async search(query, ctx, options = {}) {
|
|
854
|
+
const { topK = DEFAULT_TOP_K, minScore = DEFAULT_MIN_SCORE, filePatterns } = options;
|
|
855
|
+
const indexDir = getRaggrepDir(ctx.rootDir, ctx.config);
|
|
856
|
+
const symbolicIndex = new SymbolicIndex(indexDir, this.id);
|
|
857
|
+
let candidateFiles;
|
|
858
|
+
try {
|
|
859
|
+
await symbolicIndex.initialize();
|
|
860
|
+
const maxCandidates = topK * TIER1_CANDIDATE_MULTIPLIER;
|
|
861
|
+
candidateFiles = symbolicIndex.findCandidates(query, maxCandidates);
|
|
862
|
+
if (candidateFiles.length === 0) {
|
|
863
|
+
candidateFiles = symbolicIndex.getAllFiles();
|
|
864
|
+
}
|
|
865
|
+
} catch {
|
|
866
|
+
candidateFiles = await ctx.listIndexedFiles();
|
|
867
|
+
}
|
|
868
|
+
if (filePatterns && filePatterns.length > 0) {
|
|
869
|
+
candidateFiles = candidateFiles.filter((filepath) => {
|
|
870
|
+
return filePatterns.some((pattern) => {
|
|
871
|
+
if (pattern.startsWith("*.")) {
|
|
872
|
+
const ext = pattern.slice(1);
|
|
873
|
+
return filepath.endsWith(ext);
|
|
874
|
+
}
|
|
875
|
+
return filepath.includes(pattern);
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
const queryEmbedding = await getEmbedding(query);
|
|
880
|
+
const bm25Index = new BM25Index;
|
|
881
|
+
const allChunksData = [];
|
|
882
|
+
for (const filepath of candidateFiles) {
|
|
883
|
+
const fileIndex = await ctx.loadFileIndex(filepath);
|
|
884
|
+
if (!fileIndex)
|
|
885
|
+
continue;
|
|
886
|
+
const moduleData = fileIndex.moduleData;
|
|
887
|
+
if (!moduleData?.embeddings)
|
|
888
|
+
continue;
|
|
889
|
+
for (let i = 0;i < fileIndex.chunks.length; i++) {
|
|
890
|
+
const chunk = fileIndex.chunks[i];
|
|
891
|
+
const embedding = moduleData.embeddings[i];
|
|
892
|
+
if (!embedding)
|
|
893
|
+
continue;
|
|
894
|
+
allChunksData.push({
|
|
895
|
+
filepath: fileIndex.filepath,
|
|
896
|
+
chunk,
|
|
897
|
+
embedding
|
|
898
|
+
});
|
|
899
|
+
bm25Index.addDocuments([{ id: chunk.id, content: chunk.content }]);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const bm25Results = bm25Index.search(query, topK * 3);
|
|
903
|
+
const bm25Scores = new Map;
|
|
904
|
+
for (const result of bm25Results) {
|
|
905
|
+
bm25Scores.set(result.id, normalizeScore(result.score, 3));
|
|
906
|
+
}
|
|
907
|
+
const results = [];
|
|
908
|
+
for (const { filepath, chunk, embedding } of allChunksData) {
|
|
909
|
+
const semanticScore = cosineSimilarity(queryEmbedding, embedding);
|
|
910
|
+
const bm25Score = bm25Scores.get(chunk.id) || 0;
|
|
911
|
+
const hybridScore = SEMANTIC_WEIGHT * semanticScore + BM25_WEIGHT * bm25Score;
|
|
912
|
+
if (hybridScore >= minScore || bm25Score > 0.3) {
|
|
913
|
+
results.push({
|
|
914
|
+
filepath,
|
|
915
|
+
chunk,
|
|
916
|
+
score: hybridScore,
|
|
917
|
+
moduleId: this.id,
|
|
918
|
+
context: {
|
|
919
|
+
semanticScore,
|
|
920
|
+
bm25Score
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
results.sort((a, b) => b.score - a.score);
|
|
926
|
+
return results.slice(0, topK);
|
|
927
|
+
}
|
|
928
|
+
extractReferences(content, filepath) {
|
|
929
|
+
const references = [];
|
|
930
|
+
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
931
|
+
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
932
|
+
let match;
|
|
933
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
934
|
+
const importPath = match[1];
|
|
935
|
+
if (importPath.startsWith(".")) {
|
|
936
|
+
const dir = path4.dirname(filepath);
|
|
937
|
+
const resolved = path4.normalize(path4.join(dir, importPath));
|
|
938
|
+
references.push(resolved);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
942
|
+
const importPath = match[1];
|
|
943
|
+
if (importPath.startsWith(".")) {
|
|
944
|
+
const dir = path4.dirname(filepath);
|
|
945
|
+
const resolved = path4.normalize(path4.join(dir, importPath));
|
|
946
|
+
references.push(resolved);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return references;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
var DEFAULT_MIN_SCORE = 0.15, DEFAULT_TOP_K = 10, SEMANTIC_WEIGHT = 0.7, BM25_WEIGHT = 0.3, TIER1_CANDIDATE_MULTIPLIER = 3;
|
|
953
|
+
var init_semantic = __esm(() => {
|
|
954
|
+
init_embeddings();
|
|
955
|
+
init_bm25();
|
|
956
|
+
init_config2();
|
|
957
|
+
init_parseCode();
|
|
958
|
+
init_tieredIndex();
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
// src/indexer/index.ts
|
|
962
|
+
init_config2();
|
|
963
|
+
import { glob } from "glob";
|
|
964
|
+
import * as fs3 from "fs/promises";
|
|
965
|
+
import * as path5 from "path";
|
|
966
|
+
|
|
967
|
+
// src/modules/registry.ts
|
|
968
|
+
class ModuleRegistryImpl {
|
|
969
|
+
modules = new Map;
|
|
970
|
+
register(module) {
|
|
971
|
+
if (this.modules.has(module.id)) {
|
|
972
|
+
console.warn(`Module '${module.id}' is already registered, overwriting...`);
|
|
973
|
+
}
|
|
974
|
+
this.modules.set(module.id, module);
|
|
975
|
+
}
|
|
976
|
+
get(id) {
|
|
977
|
+
return this.modules.get(id);
|
|
978
|
+
}
|
|
979
|
+
list() {
|
|
980
|
+
return Array.from(this.modules.values());
|
|
981
|
+
}
|
|
982
|
+
getEnabled(config) {
|
|
983
|
+
const enabledIds = new Set(config.modules.filter((m) => m.enabled).map((m) => m.id));
|
|
984
|
+
return this.list().filter((m) => enabledIds.has(m.id));
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
var registry = new ModuleRegistryImpl;
|
|
988
|
+
async function registerBuiltInModules() {
|
|
989
|
+
const { SemanticModule: SemanticModule2 } = await Promise.resolve().then(() => (init_semantic(), exports_semantic));
|
|
990
|
+
registry.register(new SemanticModule2);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/indexer/index.ts
|
|
994
|
+
async function indexDirectory(rootDir, options = {}) {
|
|
995
|
+
const verbose = options.verbose ?? false;
|
|
996
|
+
rootDir = path5.resolve(rootDir);
|
|
997
|
+
console.log(`Indexing directory: ${rootDir}`);
|
|
998
|
+
const config = await loadConfig(rootDir);
|
|
999
|
+
await registerBuiltInModules();
|
|
1000
|
+
const enabledModules = registry.getEnabled(config);
|
|
1001
|
+
if (enabledModules.length === 0) {
|
|
1002
|
+
console.log("No modules enabled. Check your configuration.");
|
|
1003
|
+
return [];
|
|
1004
|
+
}
|
|
1005
|
+
console.log(`Enabled modules: ${enabledModules.map((m) => m.id).join(", ")}`);
|
|
1006
|
+
const files = await findFiles(rootDir, config);
|
|
1007
|
+
console.log(`Found ${files.length} files to index`);
|
|
1008
|
+
const results = [];
|
|
1009
|
+
for (const module of enabledModules) {
|
|
1010
|
+
console.log(`
|
|
1011
|
+
[${module.name}] Starting indexing...`);
|
|
1012
|
+
const moduleConfig = getModuleConfig(config, module.id);
|
|
1013
|
+
if (module.initialize && moduleConfig) {
|
|
1014
|
+
const configWithOverrides = { ...moduleConfig };
|
|
1015
|
+
if (options.model && module.id === "semantic") {
|
|
1016
|
+
configWithOverrides.options = {
|
|
1017
|
+
...configWithOverrides.options,
|
|
1018
|
+
embeddingModel: options.model
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
await module.initialize(configWithOverrides);
|
|
1022
|
+
}
|
|
1023
|
+
const result = await indexWithModule(rootDir, files, module, config, verbose);
|
|
1024
|
+
results.push(result);
|
|
1025
|
+
if (module.finalize) {
|
|
1026
|
+
console.log(`[${module.name}] Building secondary indexes...`);
|
|
1027
|
+
const ctx = {
|
|
1028
|
+
rootDir,
|
|
1029
|
+
config,
|
|
1030
|
+
readFile: async (filepath) => {
|
|
1031
|
+
const fullPath = path5.isAbsolute(filepath) ? filepath : path5.join(rootDir, filepath);
|
|
1032
|
+
return fs3.readFile(fullPath, "utf-8");
|
|
1033
|
+
},
|
|
1034
|
+
getFileStats: async (filepath) => {
|
|
1035
|
+
const fullPath = path5.isAbsolute(filepath) ? filepath : path5.join(rootDir, filepath);
|
|
1036
|
+
const stats = await fs3.stat(fullPath);
|
|
1037
|
+
return { lastModified: stats.mtime.toISOString() };
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
await module.finalize(ctx);
|
|
1041
|
+
}
|
|
1042
|
+
console.log(`[${module.name}] Complete: ${result.indexed} indexed, ${result.skipped} skipped, ${result.errors} errors`);
|
|
1043
|
+
}
|
|
1044
|
+
await updateGlobalManifest(rootDir, enabledModules, config);
|
|
1045
|
+
return results;
|
|
1046
|
+
}
|
|
1047
|
+
async function indexWithModule(rootDir, files, module, config, verbose) {
|
|
1048
|
+
const result = {
|
|
1049
|
+
moduleId: module.id,
|
|
1050
|
+
indexed: 0,
|
|
1051
|
+
skipped: 0,
|
|
1052
|
+
errors: 0
|
|
1053
|
+
};
|
|
1054
|
+
const manifest = await loadModuleManifest(rootDir, module.id, config);
|
|
1055
|
+
const ctx = {
|
|
1056
|
+
rootDir,
|
|
1057
|
+
config,
|
|
1058
|
+
readFile: async (filepath) => {
|
|
1059
|
+
const fullPath = path5.isAbsolute(filepath) ? filepath : path5.join(rootDir, filepath);
|
|
1060
|
+
return fs3.readFile(fullPath, "utf-8");
|
|
1061
|
+
},
|
|
1062
|
+
getFileStats: async (filepath) => {
|
|
1063
|
+
const fullPath = path5.isAbsolute(filepath) ? filepath : path5.join(rootDir, filepath);
|
|
1064
|
+
const stats = await fs3.stat(fullPath);
|
|
1065
|
+
return { lastModified: stats.mtime.toISOString() };
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
for (const filepath of files) {
|
|
1069
|
+
const relativePath = path5.relative(rootDir, filepath);
|
|
1070
|
+
try {
|
|
1071
|
+
const stats = await fs3.stat(filepath);
|
|
1072
|
+
const lastModified = stats.mtime.toISOString();
|
|
1073
|
+
const existingEntry = manifest.files[relativePath];
|
|
1074
|
+
if (existingEntry && existingEntry.lastModified === lastModified) {
|
|
1075
|
+
if (verbose) {
|
|
1076
|
+
console.log(` Skipped ${relativePath} (unchanged)`);
|
|
1077
|
+
}
|
|
1078
|
+
result.skipped++;
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
const content = await fs3.readFile(filepath, "utf-8");
|
|
1082
|
+
if (verbose) {
|
|
1083
|
+
console.log(` Processing ${relativePath}...`);
|
|
1084
|
+
}
|
|
1085
|
+
const fileIndex = await module.indexFile(relativePath, content, ctx);
|
|
1086
|
+
if (!fileIndex) {
|
|
1087
|
+
if (verbose) {
|
|
1088
|
+
console.log(` Skipped ${relativePath} (no chunks)`);
|
|
1089
|
+
}
|
|
1090
|
+
result.skipped++;
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
await writeFileIndex(rootDir, module.id, relativePath, fileIndex, config);
|
|
1094
|
+
manifest.files[relativePath] = {
|
|
1095
|
+
lastModified,
|
|
1096
|
+
chunkCount: fileIndex.chunks.length
|
|
1097
|
+
};
|
|
1098
|
+
result.indexed++;
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
console.error(` Error indexing ${relativePath}:`, error);
|
|
1101
|
+
result.errors++;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
manifest.lastUpdated = new Date().toISOString();
|
|
1105
|
+
await writeModuleManifest(rootDir, module.id, manifest, config);
|
|
1106
|
+
return result;
|
|
1107
|
+
}
|
|
1108
|
+
async function findFiles(rootDir, config) {
|
|
1109
|
+
const patterns = config.extensions.map((ext) => `**/*${ext}`);
|
|
1110
|
+
const ignorePatterns = config.ignorePaths.map((p) => `**/${p}/**`);
|
|
1111
|
+
const files = [];
|
|
1112
|
+
for (const pattern of patterns) {
|
|
1113
|
+
const matches = await glob(pattern, {
|
|
1114
|
+
cwd: rootDir,
|
|
1115
|
+
absolute: true,
|
|
1116
|
+
ignore: ignorePatterns
|
|
1117
|
+
});
|
|
1118
|
+
files.push(...matches);
|
|
1119
|
+
}
|
|
1120
|
+
return [...new Set(files)];
|
|
1121
|
+
}
|
|
1122
|
+
async function loadModuleManifest(rootDir, moduleId, config) {
|
|
1123
|
+
const manifestPath = getModuleManifestPath(rootDir, moduleId, config);
|
|
1124
|
+
try {
|
|
1125
|
+
const content = await fs3.readFile(manifestPath, "utf-8");
|
|
1126
|
+
return JSON.parse(content);
|
|
1127
|
+
} catch {
|
|
1128
|
+
return {
|
|
1129
|
+
moduleId,
|
|
1130
|
+
version: "1.0.0",
|
|
1131
|
+
lastUpdated: new Date().toISOString(),
|
|
1132
|
+
files: {}
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
async function writeModuleManifest(rootDir, moduleId, manifest, config) {
|
|
1137
|
+
const manifestPath = getModuleManifestPath(rootDir, moduleId, config);
|
|
1138
|
+
await fs3.mkdir(path5.dirname(manifestPath), { recursive: true });
|
|
1139
|
+
await fs3.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
1140
|
+
}
|
|
1141
|
+
async function writeFileIndex(rootDir, moduleId, filepath, fileIndex, config) {
|
|
1142
|
+
const indexPath = getModuleIndexPath(rootDir, moduleId, config);
|
|
1143
|
+
const indexFilePath = path5.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
|
|
1144
|
+
await fs3.mkdir(path5.dirname(indexFilePath), { recursive: true });
|
|
1145
|
+
await fs3.writeFile(indexFilePath, JSON.stringify(fileIndex, null, 2));
|
|
1146
|
+
}
|
|
1147
|
+
async function updateGlobalManifest(rootDir, modules, config) {
|
|
1148
|
+
const manifestPath = getGlobalManifestPath(rootDir, config);
|
|
1149
|
+
const manifest = {
|
|
1150
|
+
version: config.version,
|
|
1151
|
+
lastUpdated: new Date().toISOString(),
|
|
1152
|
+
modules: modules.map((m) => m.id)
|
|
1153
|
+
};
|
|
1154
|
+
await fs3.mkdir(path5.dirname(manifestPath), { recursive: true });
|
|
1155
|
+
await fs3.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
1156
|
+
}
|
|
1157
|
+
async function cleanupIndex(rootDir, options = {}) {
|
|
1158
|
+
const verbose = options.verbose ?? false;
|
|
1159
|
+
rootDir = path5.resolve(rootDir);
|
|
1160
|
+
console.log(`Cleaning up index in: ${rootDir}`);
|
|
1161
|
+
const config = await loadConfig(rootDir);
|
|
1162
|
+
await registerBuiltInModules();
|
|
1163
|
+
const enabledModules = registry.getEnabled(config);
|
|
1164
|
+
if (enabledModules.length === 0) {
|
|
1165
|
+
console.log("No modules enabled.");
|
|
1166
|
+
return [];
|
|
1167
|
+
}
|
|
1168
|
+
const results = [];
|
|
1169
|
+
for (const module of enabledModules) {
|
|
1170
|
+
console.log(`
|
|
1171
|
+
[${module.name}] Checking for stale entries...`);
|
|
1172
|
+
const result = await cleanupModuleIndex(rootDir, module.id, config, verbose);
|
|
1173
|
+
results.push(result);
|
|
1174
|
+
console.log(`[${module.name}] Removed ${result.removed} stale entries, kept ${result.kept} valid entries`);
|
|
1175
|
+
}
|
|
1176
|
+
return results;
|
|
1177
|
+
}
|
|
1178
|
+
async function cleanupModuleIndex(rootDir, moduleId, config, verbose) {
|
|
1179
|
+
const result = {
|
|
1180
|
+
moduleId,
|
|
1181
|
+
removed: 0,
|
|
1182
|
+
kept: 0
|
|
1183
|
+
};
|
|
1184
|
+
const manifest = await loadModuleManifest(rootDir, moduleId, config);
|
|
1185
|
+
const indexPath = getModuleIndexPath(rootDir, moduleId, config);
|
|
1186
|
+
const filesToRemove = [];
|
|
1187
|
+
const updatedFiles = {};
|
|
1188
|
+
for (const [filepath, entry] of Object.entries(manifest.files)) {
|
|
1189
|
+
const fullPath = path5.join(rootDir, filepath);
|
|
1190
|
+
try {
|
|
1191
|
+
await fs3.access(fullPath);
|
|
1192
|
+
updatedFiles[filepath] = entry;
|
|
1193
|
+
result.kept++;
|
|
1194
|
+
} catch {
|
|
1195
|
+
filesToRemove.push(filepath);
|
|
1196
|
+
result.removed++;
|
|
1197
|
+
if (verbose) {
|
|
1198
|
+
console.log(` Removing stale entry: ${filepath}`);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
for (const filepath of filesToRemove) {
|
|
1203
|
+
const indexFilePath = path5.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
|
|
1204
|
+
try {
|
|
1205
|
+
await fs3.unlink(indexFilePath);
|
|
1206
|
+
} catch {}
|
|
1207
|
+
}
|
|
1208
|
+
manifest.files = updatedFiles;
|
|
1209
|
+
manifest.lastUpdated = new Date().toISOString();
|
|
1210
|
+
await writeModuleManifest(rootDir, moduleId, manifest, config);
|
|
1211
|
+
await cleanupEmptyDirectories(indexPath);
|
|
1212
|
+
return result;
|
|
1213
|
+
}
|
|
1214
|
+
async function cleanupEmptyDirectories(dir) {
|
|
1215
|
+
try {
|
|
1216
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
1217
|
+
for (const entry of entries) {
|
|
1218
|
+
if (entry.isDirectory()) {
|
|
1219
|
+
const subDir = path5.join(dir, entry.name);
|
|
1220
|
+
await cleanupEmptyDirectories(subDir);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
const remainingEntries = await fs3.readdir(dir);
|
|
1224
|
+
if (remainingEntries.length === 0) {
|
|
1225
|
+
await fs3.rmdir(dir);
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
return false;
|
|
1229
|
+
} catch {
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/search/index.ts
|
|
1235
|
+
init_config2();
|
|
1236
|
+
import * as fs4 from "fs/promises";
|
|
1237
|
+
import * as path6 from "path";
|
|
1238
|
+
async function search(rootDir, query, options = {}) {
|
|
1239
|
+
rootDir = path6.resolve(rootDir);
|
|
1240
|
+
console.log(`Searching for: "${query}"`);
|
|
1241
|
+
const config = await loadConfig(rootDir);
|
|
1242
|
+
await registerBuiltInModules();
|
|
1243
|
+
const globalManifest = await loadGlobalManifest(rootDir, config);
|
|
1244
|
+
if (!globalManifest || globalManifest.modules.length === 0) {
|
|
1245
|
+
console.log('No index found. Run "bun run index" first.');
|
|
1246
|
+
return [];
|
|
1247
|
+
}
|
|
1248
|
+
const modulesToSearch = [];
|
|
1249
|
+
for (const moduleId of globalManifest.modules) {
|
|
1250
|
+
const module = registry.get(moduleId);
|
|
1251
|
+
const moduleConfig = getModuleConfig(config, moduleId);
|
|
1252
|
+
if (module && moduleConfig?.enabled) {
|
|
1253
|
+
if (module.initialize) {
|
|
1254
|
+
await module.initialize(moduleConfig);
|
|
1255
|
+
}
|
|
1256
|
+
modulesToSearch.push(module);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
if (modulesToSearch.length === 0) {
|
|
1260
|
+
console.log("No enabled modules with indexes found.");
|
|
1261
|
+
return [];
|
|
1262
|
+
}
|
|
1263
|
+
const allResults = [];
|
|
1264
|
+
for (const module of modulesToSearch) {
|
|
1265
|
+
const ctx = createSearchContext(rootDir, module.id, config);
|
|
1266
|
+
const moduleResults = await module.search(query, ctx, options);
|
|
1267
|
+
allResults.push(...moduleResults);
|
|
1268
|
+
}
|
|
1269
|
+
allResults.sort((a, b) => b.score - a.score);
|
|
1270
|
+
const topK = options.topK ?? 10;
|
|
1271
|
+
return allResults.slice(0, topK);
|
|
1272
|
+
}
|
|
1273
|
+
function createSearchContext(rootDir, moduleId, config) {
|
|
1274
|
+
const indexPath = getModuleIndexPath(rootDir, moduleId, config);
|
|
1275
|
+
return {
|
|
1276
|
+
rootDir,
|
|
1277
|
+
config,
|
|
1278
|
+
loadFileIndex: async (filepath) => {
|
|
1279
|
+
const hasExtension = /\.[^./]+$/.test(filepath);
|
|
1280
|
+
const indexFilePath = hasExtension ? path6.join(indexPath, filepath.replace(/\.[^.]+$/, ".json")) : path6.join(indexPath, filepath + ".json");
|
|
1281
|
+
try {
|
|
1282
|
+
const content = await fs4.readFile(indexFilePath, "utf-8");
|
|
1283
|
+
return JSON.parse(content);
|
|
1284
|
+
} catch {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
listIndexedFiles: async () => {
|
|
1289
|
+
const files = [];
|
|
1290
|
+
await traverseDirectory(indexPath, files, indexPath);
|
|
1291
|
+
return files.filter((f) => f.endsWith(".json") && !f.endsWith("manifest.json")).map((f) => {
|
|
1292
|
+
const relative3 = path6.relative(indexPath, f);
|
|
1293
|
+
return relative3.replace(/\.json$/, "");
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
async function traverseDirectory(dir, files, basePath) {
|
|
1299
|
+
try {
|
|
1300
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
1301
|
+
for (const entry of entries) {
|
|
1302
|
+
const fullPath = path6.join(dir, entry.name);
|
|
1303
|
+
if (entry.isDirectory()) {
|
|
1304
|
+
await traverseDirectory(fullPath, files, basePath);
|
|
1305
|
+
} else if (entry.isFile()) {
|
|
1306
|
+
files.push(fullPath);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
} catch {}
|
|
1310
|
+
}
|
|
1311
|
+
async function loadGlobalManifest(rootDir, config) {
|
|
1312
|
+
const manifestPath = getGlobalManifestPath(rootDir, config);
|
|
1313
|
+
try {
|
|
1314
|
+
const content = await fs4.readFile(manifestPath, "utf-8");
|
|
1315
|
+
return JSON.parse(content);
|
|
1316
|
+
} catch {
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
function formatSearchResults(results) {
|
|
1321
|
+
if (results.length === 0) {
|
|
1322
|
+
return "No results found.";
|
|
1323
|
+
}
|
|
1324
|
+
let output = `Found ${results.length} results:
|
|
1325
|
+
|
|
1326
|
+
`;
|
|
1327
|
+
for (let i = 0;i < results.length; i++) {
|
|
1328
|
+
const result = results[i];
|
|
1329
|
+
const { chunk } = result;
|
|
1330
|
+
const location = `${result.filepath}:${chunk.startLine}-${chunk.endLine}`;
|
|
1331
|
+
const nameInfo = chunk.name ? ` (${chunk.name})` : "";
|
|
1332
|
+
output += `${i + 1}. ${location}${nameInfo}
|
|
1333
|
+
`;
|
|
1334
|
+
output += ` Score: ${(result.score * 100).toFixed(1)}% | Type: ${chunk.type}`;
|
|
1335
|
+
if (chunk.isExported) {
|
|
1336
|
+
output += " | exported";
|
|
1337
|
+
}
|
|
1338
|
+
output += `
|
|
1339
|
+
`;
|
|
1340
|
+
const lines = chunk.content.split(`
|
|
1341
|
+
`).slice(0, 3);
|
|
1342
|
+
for (const line of lines) {
|
|
1343
|
+
const trimmedLine = line.substring(0, 80);
|
|
1344
|
+
output += ` ${trimmedLine}${line.length > 80 ? "..." : ""}
|
|
1345
|
+
`;
|
|
1346
|
+
}
|
|
1347
|
+
output += `
|
|
1348
|
+
`;
|
|
1349
|
+
}
|
|
1350
|
+
return output;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/index.ts
|
|
1354
|
+
async function index(directory, options = {}) {
|
|
1355
|
+
return indexDirectory(directory, options);
|
|
1356
|
+
}
|
|
1357
|
+
async function search2(directory, query, options = {}) {
|
|
1358
|
+
return search(directory, query, options);
|
|
1359
|
+
}
|
|
1360
|
+
async function cleanup(directory, options = {}) {
|
|
1361
|
+
return cleanupIndex(directory, options);
|
|
1362
|
+
}
|
|
1363
|
+
var raggrep = {
|
|
1364
|
+
index,
|
|
1365
|
+
search: search2,
|
|
1366
|
+
cleanup,
|
|
1367
|
+
formatSearchResults
|
|
1368
|
+
};
|
|
1369
|
+
var src_default = raggrep;
|
|
1370
|
+
export {
|
|
1371
|
+
search2 as search,
|
|
1372
|
+
index,
|
|
1373
|
+
formatSearchResults,
|
|
1374
|
+
src_default as default,
|
|
1375
|
+
cleanup
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1378
|
+
//# debugId=3A2C4D6166478FB764756E2164756E21
|