claude-flow-novice
Version:
Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.
133 lines (132 loc) • 4.69 kB
JavaScript
/**
* RuVectorIndex - Core API for semantic codebase search
*/ import { glob } from 'glob';
import * as path from 'path';
import * as fs from 'fs/promises';
import { createEmbeddingProvider } from '../embeddings/index.js';
import { createFileProcessor } from './file-processor.js';
export class RuVectorIndex {
embeddingProvider;
fileProcessor;
db = null;
workingDir;
storagePath;
include;
exclude;
batchSize;
verbose;
constructor(options = {}, workingDir = process.cwd()){
this.workingDir = workingDir;
this.storagePath = options.storagePath ?? './data/codebase.db';
this.include = options.indexing?.include ?? [
'**/*.ts',
'**/*.js',
'**/*.md'
];
this.exclude = options.indexing?.exclude ?? [
'node_modules/**',
'dist/**',
'.git/**'
];
this.batchSize = options.indexing?.batchSize ?? 50;
this.verbose = options.verbose ?? false;
this.embeddingProvider = createEmbeddingProvider({
provider: options.embedding?.provider ?? 'openai',
model: options.embedding?.model,
apiKey: options.embedding?.apiKey,
baseUrl: options.embedding?.baseUrl
});
this.fileProcessor = createFileProcessor(options.indexing?.maxFileSize);
}
async getDb() {
if (this.db) return this.db;
const { VectorDB } = await import('@ruvector/core');
const storagePath = path.resolve(this.workingDir, this.storagePath);
await fs.mkdir(path.dirname(storagePath), {
recursive: true
});
this.db = new VectorDB({
dimensions: 1536,
storagePath
});
return this.db;
}
log(msg) {
if (this.verbose) console.log(`[ruvector] ${msg}`);
}
async indexDirectory(directory = '.') {
const absoluteDir = path.resolve(this.workingDir, directory);
this.log(`Indexing: ${absoluteDir}`);
const patterns = this.include.map((p)=>path.join(absoluteDir, p));
const files = await glob(patterns, {
ignore: this.exclude,
nodir: true,
absolute: true
});
this.log(`Found ${files.length} files`);
const stats = {
totalFiles: files.length,
indexedFiles: 0,
failedFiles: 0,
lastIndexedAt: new Date()
};
const db = await this.getDb();
for(let i = 0; i < files.length; i += this.batchSize){
const batch = files.slice(i, i + this.batchSize);
const processed = [];
for (const file of batch){
const result = await this.fileProcessor.process(file);
if (result) processed.push(result);
else stats.failedFiles++;
}
if (processed.length === 0) continue;
try {
const texts = processed.map((p)=>p.content);
const embeddings = await this.embeddingProvider.embedBatch(texts);
const items = processed.map((p, idx)=>({
id: path.relative(this.workingDir, p.path),
vector: new Float32Array(embeddings[idx].embedding)
}));
await db.upsert(items);
stats.indexedFiles += processed.length;
this.log(`Indexed ${stats.indexedFiles}/${stats.totalFiles}`);
} catch (error) {
this.log(`Batch failed: ${error.message}`);
stats.failedFiles += processed.length;
}
}
return stats;
}
async search(query, options = {}) {
const limit = options.limit ?? 10;
const { embedding } = await this.embeddingProvider.embed(query);
const db = await this.getDb();
const results = await db.search({
vector: new Float32Array(embedding),
k: limit
});
return results.map((r)=>({
path: r.id,
score: r.score
}));
}
async getStats() {
const db = await this.getDb();
const count = await db.count();
return {
totalFiles: count,
indexedFiles: count,
failedFiles: 0,
lastIndexedAt: null
};
}
async clear() {
const storagePath = path.resolve(this.workingDir, this.storagePath);
try {
await fs.unlink(storagePath);
this.db = null;
} catch {}
}
}
export { createFileProcessor } from './file-processor.js';
//# sourceMappingURL=index.js.map