giga-code
Version:
A personal AI CLI assistant powered by Grok for local development.
292 lines • 9.89 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IndexingService = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const events_1 = require("events");
const rag_context_service_1 = require("./rag-context-service");
const rag_config_1 = require("../utils/rag-config");
class IndexingService extends events_1.EventEmitter {
constructor(projectPath = process.cwd()) {
super();
this.fileWatcher = null;
this.isWatching = false;
this.jobQueue = [];
this.currentJob = null;
this.jobIdCounter = 1;
this.lastFullIndexTime = null;
// Debounce file changes to avoid excessive indexing
this.pendingChanges = new Set();
this.changeDebounceTimer = null;
this.DEBOUNCE_DELAY = 2000; // 2 seconds
this.projectPath = projectPath;
this.ragService = new rag_context_service_1.RAGContextService(projectPath);
}
async initialize() {
try {
await this.ragService.initialize();
this.emit('initialized');
}
catch (error) {
this.emit('error', error);
throw error;
}
}
async startWatching() {
if (this.isWatching) {
return;
}
const config = rag_config_1.RAGConfigManager.loadConfig(this.projectPath);
if (!config.enabled) {
return;
}
try {
await this.initialize();
// Watch for file changes
this.fileWatcher = fs.watch(this.projectPath, { recursive: true }, (eventType, filename) => {
if (filename && this.shouldWatchFile(filename)) {
this.handleFileChange(eventType, filename);
}
});
this.isWatching = true;
this.emit('watching-started');
// Check if we need to do an initial full index
const indexInfo = await this.ragService.getIndexInfo();
if (indexInfo.count === 0) {
this.scheduleFullIndex();
}
}
catch (error) {
this.emit('error', error);
throw error;
}
}
async stopWatching() {
if (this.fileWatcher) {
this.fileWatcher.close();
this.fileWatcher = null;
}
if (this.changeDebounceTimer) {
clearTimeout(this.changeDebounceTimer);
this.changeDebounceTimer = null;
}
this.isWatching = false;
this.emit('watching-stopped');
}
shouldWatchFile(filename) {
const config = rag_config_1.RAGConfigManager.loadConfig(this.projectPath);
const fullPath = path.join(this.projectPath, filename);
// Skip directories
try {
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
return false;
}
}
catch {
return false; // File doesn't exist or can't be accessed
}
// Check include patterns
const includeMatch = config.includePatterns.some(pattern => {
const regex = this.globToRegex(pattern);
return regex.test(filename) || regex.test(fullPath);
});
if (!includeMatch) {
return false;
}
// Check exclude patterns
const excludeMatch = config.excludePatterns.some(pattern => {
const regex = this.globToRegex(pattern);
return regex.test(filename) || regex.test(fullPath);
});
return !excludeMatch;
}
globToRegex(pattern) {
const regexPattern = pattern
.replace(/\*\*/g, '.*')
.replace(/\*/g, '[^/]*')
.replace(/\?/g, '[^/]');
return new RegExp(`^${regexPattern}$`);
}
handleFileChange(eventType, filename) {
const fullPath = path.join(this.projectPath, filename);
// Skip if file doesn't exist (was deleted)
if (!fs.existsSync(fullPath)) {
return;
}
this.pendingChanges.add(fullPath);
// Reset debounce timer
if (this.changeDebounceTimer) {
clearTimeout(this.changeDebounceTimer);
}
this.changeDebounceTimer = setTimeout(() => {
this.processFileChanges();
}, this.DEBOUNCE_DELAY);
}
async processFileChanges() {
if (this.pendingChanges.size === 0) {
return;
}
const changedFiles = Array.from(this.pendingChanges);
this.pendingChanges.clear();
this.scheduleIncrementalIndex(changedFiles);
}
scheduleFullIndex() {
const jobId = `full-${this.jobIdCounter++}`;
const job = {
id: jobId,
type: 'full',
status: 'pending'
};
this.jobQueue.push(job);
this.processJobQueue();
return jobId;
}
scheduleIncrementalIndex(filePaths) {
const jobId = `incremental-${this.jobIdCounter++}`;
const job = {
id: jobId,
type: 'incremental',
filePaths,
status: 'pending'
};
this.jobQueue.push(job);
this.processJobQueue();
return jobId;
}
scheduleFileIndex(filePath) {
const jobId = `file-${this.jobIdCounter++}`;
const job = {
id: jobId,
type: 'file',
filePaths: [filePath],
status: 'pending'
};
this.jobQueue.push(job);
this.processJobQueue();
return jobId;
}
async processJobQueue() {
if (this.currentJob || this.jobQueue.length === 0) {
return;
}
const job = this.jobQueue.shift();
this.currentJob = job;
job.status = 'running';
job.startTime = new Date();
this.emit('job-started', job);
try {
await this.executeJob(job);
job.status = 'completed';
job.endTime = new Date();
this.emit('job-completed', job);
if (job.type === 'full') {
this.lastFullIndexTime = job.endTime;
}
}
catch (error) {
job.status = 'failed';
job.endTime = new Date();
job.error = error.message;
this.emit('job-failed', job);
}
finally {
this.currentJob = null;
// Process next job in queue
if (this.jobQueue.length > 0) {
setImmediate(() => this.processJobQueue());
}
}
}
async executeJob(job) {
const startTime = Date.now();
switch (job.type) {
case 'full':
await this.ragService.indexProject();
break;
case 'incremental':
case 'file':
if (job.filePaths && job.filePaths.length > 0) {
// For now, we'll do a simple re-index of changed files
// In the future, we could implement more sophisticated incremental updates
await this.ragService.indexProject();
}
break;
}
const endTime = Date.now();
const indexInfo = await this.ragService.getIndexInfo();
job.stats = {
filesProcessed: job.filePaths?.length || 0,
chunksCreated: indexInfo.count,
duration: endTime - startTime
};
}
getCurrentJob() {
return this.currentJob;
}
getJobQueue() {
return [...this.jobQueue];
}
getJobHistory() {
// In a real implementation, you might want to persist this
return [];
}
async getIndexingStatus() {
const indexInfo = await this.ragService.getIndexInfo();
return {
isWatching: this.isWatching,
currentJob: this.currentJob,
queueLength: this.jobQueue.length,
lastFullIndex: this.lastFullIndexTime,
indexInfo
};
}
async clearIndex() {
// Stop current job if running
if (this.currentJob) {
}
// Clear job queue
this.jobQueue = [];
this.currentJob = null;
// Clear the actual index
await this.ragService.clearIndex();
this.lastFullIndexTime = null;
this.emit('index-cleared');
}
// Static method to create and start a background indexing service
static async createAndStart(projectPath = process.cwd()) {
const service = new IndexingService(projectPath);
try {
await service.startWatching();
return service;
}
catch (error) {
return service;
}
}
}
exports.IndexingService = IndexingService;
//# sourceMappingURL=indexing-service.js.map