@code-rag/cli 0.1.5 → 0.1.7
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.
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { type EmbeddingConfig, type EmbeddingProvider } from '@code-rag/core';
|
|
2
|
+
import { type ParsedFile, type CodeRAGConfig, type EmbeddingConfig, type EmbeddingProvider } from '@code-rag/core';
|
|
3
3
|
export declare function createSimpleEmbeddingProvider(embeddingConfig: EmbeddingConfig): EmbeddingProvider;
|
|
4
|
+
export declare class IndexLogger {
|
|
5
|
+
private spinner;
|
|
6
|
+
private logPath;
|
|
7
|
+
private progressPath;
|
|
8
|
+
private phase;
|
|
9
|
+
private counts;
|
|
10
|
+
private readonly quiet;
|
|
11
|
+
constructor(storagePath: string, quiet?: boolean);
|
|
12
|
+
init(): Promise<void>;
|
|
13
|
+
start(text: string): void;
|
|
14
|
+
info(text: string): Promise<void>;
|
|
15
|
+
succeed(text: string): Promise<void>;
|
|
16
|
+
warn(text: string): Promise<void>;
|
|
17
|
+
fail(text: string): Promise<void>;
|
|
18
|
+
setPhase(phase: string, counts?: Record<string, number>): Promise<void>;
|
|
19
|
+
updateCount(key: string, value: number): Promise<void>;
|
|
20
|
+
private log;
|
|
21
|
+
private writeProgress;
|
|
22
|
+
}
|
|
4
23
|
export declare function registerIndexCommand(program: Command): void;
|
|
24
|
+
/**
|
|
25
|
+
* Rebuild the root-level merged index from existing per-repo stores.
|
|
26
|
+
* Used when incremental indexing finds no changes but the unified index is missing
|
|
27
|
+
* (e.g., after upgrading CodeRAG or first run after multi-repo index was created).
|
|
28
|
+
*/
|
|
29
|
+
export declare function rebuildMergedIndex(storagePath: string, repoResults: Array<{
|
|
30
|
+
repoName: string;
|
|
31
|
+
repoPath: string;
|
|
32
|
+
repoStoragePath: string;
|
|
33
|
+
parsedFiles: ParsedFile[];
|
|
34
|
+
}>, config: CodeRAGConfig, logger: IndexLogger): Promise<void>;
|
|
@@ -33,7 +33,7 @@ export function createSimpleEmbeddingProvider(embeddingConfig) {
|
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
34
|
// IndexLogger — dual output: ora spinner (interactive) + file log
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
|
-
class IndexLogger {
|
|
36
|
+
export class IndexLogger {
|
|
37
37
|
spinner;
|
|
38
38
|
logPath;
|
|
39
39
|
progressPath;
|
|
@@ -968,6 +968,86 @@ export function registerIndexCommand(program) {
|
|
|
968
968
|
}
|
|
969
969
|
});
|
|
970
970
|
}
|
|
971
|
+
/**
|
|
972
|
+
* Rebuild the root-level merged index from existing per-repo stores.
|
|
973
|
+
* Used when incremental indexing finds no changes but the unified index is missing
|
|
974
|
+
* (e.g., after upgrading CodeRAG or first run after multi-repo index was created).
|
|
975
|
+
*/
|
|
976
|
+
export async function rebuildMergedIndex(storagePath, repoResults, config, logger) {
|
|
977
|
+
await logger.info('Rebuilding unified index from per-repo data...');
|
|
978
|
+
const mergedGraph = new DependencyGraph();
|
|
979
|
+
let totalMergedChunks = 0;
|
|
980
|
+
// Open root LanceDB store
|
|
981
|
+
const rootStore = new LanceDBStore(storagePath, config.embedding.dimensions);
|
|
982
|
+
await rootStore.connect();
|
|
983
|
+
for (const rr of repoResults) {
|
|
984
|
+
const repoGraphPath = join(rr.repoStoragePath, 'graph.json');
|
|
985
|
+
// Read all rows from per-repo LanceDB and copy to root store
|
|
986
|
+
const repoStore = new LanceDBStore(rr.repoStoragePath, config.embedding.dimensions);
|
|
987
|
+
await repoStore.connect();
|
|
988
|
+
try {
|
|
989
|
+
const internal = repoStore;
|
|
990
|
+
const table = internal.table;
|
|
991
|
+
if (table) {
|
|
992
|
+
const allRows = await table.query().toArray();
|
|
993
|
+
if (allRows.length > 0) {
|
|
994
|
+
const ids = allRows.map((r) => r.id);
|
|
995
|
+
// LanceDB returns Arrow-typed vectors (FixedSizeList with .isValid),
|
|
996
|
+
// not plain number[]. Convert to plain arrays for re-ingestion.
|
|
997
|
+
const embeddings = allRows.map((r) => Array.from(r.vector));
|
|
998
|
+
// Parse the metadata JSON string back to an object so upsert
|
|
999
|
+
// preserves all original fields (start_line, end_line, name, repo_name, etc.)
|
|
1000
|
+
// without double-serialization.
|
|
1001
|
+
const metadata = allRows.map((r) => {
|
|
1002
|
+
try {
|
|
1003
|
+
return JSON.parse(r.metadata);
|
|
1004
|
+
}
|
|
1005
|
+
catch {
|
|
1006
|
+
return {
|
|
1007
|
+
content: r.content,
|
|
1008
|
+
nl_summary: r.nl_summary,
|
|
1009
|
+
chunk_type: r.chunk_type,
|
|
1010
|
+
file_path: r.file_path,
|
|
1011
|
+
language: r.language,
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
const upsertResult = await rootStore.upsert(ids, embeddings, metadata);
|
|
1016
|
+
if (upsertResult.isOk()) {
|
|
1017
|
+
totalMergedChunks += allRows.length;
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
await logger.warn(`[${rr.repoName}] LanceDB upsert failed: ${upsertResult.error.message}`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
catch (mergeErr) {
|
|
1026
|
+
const mergeMsg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
|
|
1027
|
+
await logger.warn(`[${rr.repoName}] Failed to read LanceDB for merge: ${mergeMsg}`);
|
|
1028
|
+
}
|
|
1029
|
+
repoStore.close();
|
|
1030
|
+
// Merge graph
|
|
1031
|
+
try {
|
|
1032
|
+
const graphData = await readFile(repoGraphPath, 'utf-8');
|
|
1033
|
+
const repoGraph = DependencyGraph.fromJSON(JSON.parse(graphData));
|
|
1034
|
+
for (const node of repoGraph.getAllNodes())
|
|
1035
|
+
mergedGraph.addNode(node);
|
|
1036
|
+
for (const edge of repoGraph.getAllEdges())
|
|
1037
|
+
mergedGraph.addEdge(edge);
|
|
1038
|
+
}
|
|
1039
|
+
catch {
|
|
1040
|
+
// No graph for this repo — skip
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
// Build merged BM25 from the root LanceDB (which now has all chunks)
|
|
1044
|
+
const mergedBm25 = await rebuildBm25FromStore(rootStore, logger, '');
|
|
1045
|
+
await writeFile(join(storagePath, 'bm25-index.json'), mergedBm25.serialize(), 'utf-8');
|
|
1046
|
+
rootStore.close();
|
|
1047
|
+
// Write merged graph
|
|
1048
|
+
await writeFile(join(storagePath, 'graph.json'), JSON.stringify(mergedGraph.toJSON()), 'utf-8');
|
|
1049
|
+
await logger.succeed(`Unified index rebuilt: ${totalMergedChunks} chunks from ${repoResults.length} repos`);
|
|
1050
|
+
}
|
|
971
1051
|
/**
|
|
972
1052
|
* Multi-repo indexing: iterate configured repos, index each with separate
|
|
973
1053
|
* progress reporting and per-repo storage directories.
|
|
@@ -1113,9 +1193,25 @@ async function indexMultiRepo(config, storagePath, options, logger, startTime, e
|
|
|
1113
1193
|
}
|
|
1114
1194
|
await writeFile(rr.indexStatePath, JSON.stringify(rr.indexState.toJSON(), null, 2), 'utf-8');
|
|
1115
1195
|
}
|
|
1196
|
+
// Check if root merged index is missing or empty — rebuild from per-repo stores
|
|
1197
|
+
const rootBm25Path = join(storagePath, 'bm25-index.json');
|
|
1198
|
+
let needsRebuild = !existsSync(rootBm25Path);
|
|
1199
|
+
if (!needsRebuild) {
|
|
1200
|
+
try {
|
|
1201
|
+
const bm25Data = await readFile(rootBm25Path, 'utf-8');
|
|
1202
|
+
const parsed = JSON.parse(bm25Data);
|
|
1203
|
+
needsRebuild = !parsed.documentCount || parsed.documentCount === 0;
|
|
1204
|
+
}
|
|
1205
|
+
catch {
|
|
1206
|
+
needsRebuild = true;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
if (needsRebuild) {
|
|
1210
|
+
await rebuildMergedIndex(storagePath, repoResults, config, logger);
|
|
1211
|
+
}
|
|
1116
1212
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1117
1213
|
// eslint-disable-next-line no-console
|
|
1118
|
-
console.log(chalk.yellow('No chunks
|
|
1214
|
+
console.log(chalk.yellow('No new chunks. Index is up to date.'));
|
|
1119
1215
|
// eslint-disable-next-line no-console
|
|
1120
1216
|
console.log(` Time elapsed: ${chalk.cyan(elapsed + 's')}`);
|
|
1121
1217
|
return;
|
|
@@ -1198,6 +1294,12 @@ async function indexMultiRepo(config, storagePath, options, logger, startTime, e
|
|
|
1198
1294
|
// ── Phase 3: Embed & Store per repo ─────────────────────────────────
|
|
1199
1295
|
await logger.setPhase('embed');
|
|
1200
1296
|
const resolvedEmbeddingProvider = embeddingProvider ?? createSimpleEmbeddingProvider(config.embedding);
|
|
1297
|
+
// Accumulators for merged root-level index
|
|
1298
|
+
const mergedIds = [];
|
|
1299
|
+
const mergedEmbeddings = [];
|
|
1300
|
+
const mergedMetadata = [];
|
|
1301
|
+
const mergedBm25Chunks = [];
|
|
1302
|
+
const mergedGraph = new DependencyGraph();
|
|
1201
1303
|
for (const rr of repoResults) {
|
|
1202
1304
|
if (rr.chunks.length === 0)
|
|
1203
1305
|
continue;
|
|
@@ -1215,7 +1317,7 @@ async function indexMultiRepo(config, storagePath, options, logger, startTime, e
|
|
|
1215
1317
|
continue;
|
|
1216
1318
|
}
|
|
1217
1319
|
const embeddings = embedResult.value;
|
|
1218
|
-
// Store in LanceDB
|
|
1320
|
+
// Store in per-repo LanceDB
|
|
1219
1321
|
await logger.info(`[${rr.repoName}] Storing in LanceDB...`);
|
|
1220
1322
|
const store = new LanceDBStore(rr.repoStoragePath, config.embedding.dimensions);
|
|
1221
1323
|
await store.connect();
|
|
@@ -1238,7 +1340,12 @@ async function indexMultiRepo(config, storagePath, options, logger, startTime, e
|
|
|
1238
1340
|
await logger.fail(`[${rr.repoName}] Store failed: ${upsertResult.error.message}`);
|
|
1239
1341
|
continue;
|
|
1240
1342
|
}
|
|
1241
|
-
//
|
|
1343
|
+
// Accumulate for merged root index
|
|
1344
|
+
mergedIds.push(...ids);
|
|
1345
|
+
mergedEmbeddings.push(...embeddings);
|
|
1346
|
+
mergedMetadata.push(...metadata);
|
|
1347
|
+
mergedBm25Chunks.push(...enrichedChunks);
|
|
1348
|
+
// Per-repo BM25 index
|
|
1242
1349
|
const bm25Path = join(rr.repoStoragePath, 'bm25-index.json');
|
|
1243
1350
|
let bm25;
|
|
1244
1351
|
if (options.full) {
|
|
@@ -1269,12 +1376,17 @@ async function indexMultiRepo(config, storagePath, options, logger, startTime, e
|
|
|
1269
1376
|
}
|
|
1270
1377
|
bm25.addChunks(enrichedChunks);
|
|
1271
1378
|
await writeFile(bm25Path, bm25.serialize(), 'utf-8');
|
|
1272
|
-
//
|
|
1379
|
+
// Per-repo dependency graph
|
|
1273
1380
|
const graphBuilder = new GraphBuilder(rr.repoPath);
|
|
1274
1381
|
const graphResult = graphBuilder.buildFromFiles(rr.parsedFiles);
|
|
1275
1382
|
if (graphResult.isOk()) {
|
|
1276
1383
|
const graphPath = join(rr.repoStoragePath, 'graph.json');
|
|
1277
1384
|
const newGraph = graphResult.value;
|
|
1385
|
+
// Accumulate for merged graph
|
|
1386
|
+
for (const node of newGraph.getAllNodes())
|
|
1387
|
+
mergedGraph.addNode(node);
|
|
1388
|
+
for (const edge of newGraph.getAllEdges())
|
|
1389
|
+
mergedGraph.addEdge(edge);
|
|
1278
1390
|
if (options.full) {
|
|
1279
1391
|
await writeFile(graphPath, JSON.stringify(newGraph.toJSON()), 'utf-8');
|
|
1280
1392
|
}
|
|
@@ -1320,6 +1432,26 @@ async function indexMultiRepo(config, storagePath, options, logger, startTime, e
|
|
|
1320
1432
|
store.close();
|
|
1321
1433
|
await logger.succeed(`[${rr.repoName}] ${enrichedChunks.length} chunks indexed`);
|
|
1322
1434
|
}
|
|
1435
|
+
// ── Phase 4: Merge all repos into root-level unified index ─────────
|
|
1436
|
+
// This ensures search/MCP/viewer/benchmark can query all repos at once
|
|
1437
|
+
if (mergedIds.length > 0) {
|
|
1438
|
+
await logger.info('Merging all repos into unified index...');
|
|
1439
|
+
// Merged LanceDB at root storagePath
|
|
1440
|
+
const rootStore = new LanceDBStore(storagePath, config.embedding.dimensions);
|
|
1441
|
+
await rootStore.connect();
|
|
1442
|
+
const mergeResult = await rootStore.upsert(mergedIds, mergedEmbeddings, mergedMetadata);
|
|
1443
|
+
if (mergeResult.isErr()) {
|
|
1444
|
+
await logger.fail(`Merged store failed: ${mergeResult.error.message}`);
|
|
1445
|
+
}
|
|
1446
|
+
rootStore.close();
|
|
1447
|
+
// Merged BM25 index at root
|
|
1448
|
+
const rootBm25 = new BM25Index();
|
|
1449
|
+
rootBm25.addChunks(mergedBm25Chunks);
|
|
1450
|
+
await writeFile(join(storagePath, 'bm25-index.json'), rootBm25.serialize(), 'utf-8');
|
|
1451
|
+
// Merged graph at root
|
|
1452
|
+
await writeFile(join(storagePath, 'graph.json'), JSON.stringify(mergedGraph.toJSON()), 'utf-8');
|
|
1453
|
+
await logger.succeed(`Unified index: ${mergedIds.length} chunks across ${repos.length} repos`);
|
|
1454
|
+
}
|
|
1323
1455
|
// ── Final summary ───────────────────────────────────────────────────
|
|
1324
1456
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1325
1457
|
// eslint-disable-next-line no-console
|
|
@@ -93,3 +93,8 @@ export declare function runNonInteractive(rootDir: string, options: {
|
|
|
93
93
|
languages?: string;
|
|
94
94
|
multi?: boolean;
|
|
95
95
|
}): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Ensure .coderag/ is listed in .gitignore so database files are not committed.
|
|
98
|
+
* Creates .gitignore if it does not exist; appends entry if missing.
|
|
99
|
+
*/
|
|
100
|
+
export declare function ensureGitignore(rootDir: string): Promise<void>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { select, input, confirm } from '@inquirer/prompts';
|
|
3
3
|
import { stringify } from 'yaml';
|
|
4
|
-
import { writeFile, mkdir, access, readdir } from 'node:fs/promises';
|
|
4
|
+
import { writeFile, readFile, mkdir, access, readdir } from 'node:fs/promises';
|
|
5
5
|
import { join, basename } from 'node:path';
|
|
6
6
|
import { detectLanguages } from './init.js';
|
|
7
7
|
// --- Constants ---
|
|
@@ -419,6 +419,8 @@ export async function runWizard(rootDir) {
|
|
|
419
419
|
await mkdir(storageDir, { recursive: true });
|
|
420
420
|
// eslint-disable-next-line no-console
|
|
421
421
|
console.log(chalk.green(` Created ${storageDir}`));
|
|
422
|
+
// Step 7b: Ensure .coderag/ is in .gitignore
|
|
423
|
+
await ensureGitignore(rootDir);
|
|
422
424
|
// Step 8: Summary
|
|
423
425
|
// eslint-disable-next-line no-console
|
|
424
426
|
console.log(chalk.bold.green('\n CodeRAG initialized successfully!\n'));
|
|
@@ -501,12 +503,37 @@ export async function runNonInteractive(rootDir, options) {
|
|
|
501
503
|
await mkdir(storageDir, { recursive: true });
|
|
502
504
|
// eslint-disable-next-line no-console
|
|
503
505
|
console.log(chalk.green('Created'), storageDir);
|
|
506
|
+
// Ensure .coderag/ is in .gitignore
|
|
507
|
+
await ensureGitignore(rootDir);
|
|
504
508
|
// Done
|
|
505
509
|
// eslint-disable-next-line no-console
|
|
506
510
|
console.log(chalk.green('\nCodeRAG initialized successfully!'));
|
|
507
511
|
// eslint-disable-next-line no-console
|
|
508
512
|
console.log(chalk.dim('Run "coderag index" to index your codebase.'));
|
|
509
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Ensure .coderag/ is listed in .gitignore so database files are not committed.
|
|
516
|
+
* Creates .gitignore if it does not exist; appends entry if missing.
|
|
517
|
+
*/
|
|
518
|
+
export async function ensureGitignore(rootDir) {
|
|
519
|
+
const gitignorePath = join(rootDir, '.gitignore');
|
|
520
|
+
const entry = '.coderag/';
|
|
521
|
+
try {
|
|
522
|
+
const content = await readFile(gitignorePath, 'utf-8');
|
|
523
|
+
const lines = content.split('\n').map((l) => l.trim());
|
|
524
|
+
if (lines.includes(entry) || lines.includes('.coderag'))
|
|
525
|
+
return;
|
|
526
|
+
const newContent = content.endsWith('\n') ? `${content}${entry}\n` : `${content}\n${entry}\n`;
|
|
527
|
+
await writeFile(gitignorePath, newContent, 'utf-8');
|
|
528
|
+
// eslint-disable-next-line no-console
|
|
529
|
+
console.log(chalk.green(` Added ${entry} to .gitignore`));
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
await writeFile(gitignorePath, `${entry}\n`, 'utf-8');
|
|
533
|
+
// eslint-disable-next-line no-console
|
|
534
|
+
console.log(chalk.green(` Created .gitignore with ${entry}`));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
510
537
|
/**
|
|
511
538
|
* Pull an Ollama model via the API.
|
|
512
539
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-rag/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "CLI tool for CodeRAG — init, index, search, serve, and status commands for codebase context engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"commander": "^13.1.0",
|
|
49
49
|
"ora": "^8.2.0",
|
|
50
50
|
"yaml": "^2.7.0",
|
|
51
|
-
"@code-rag/api-server": "0.1.
|
|
52
|
-
"@code-rag/core": "0.1.
|
|
53
|
-
"@code-rag/mcp-server": "0.1.
|
|
51
|
+
"@code-rag/api-server": "0.1.7",
|
|
52
|
+
"@code-rag/core": "0.1.7",
|
|
53
|
+
"@code-rag/mcp-server": "0.1.7"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "^22.13.4",
|