UNPKG

remcode

Version:

Turn your AI assistant into a codebase expert. Intelligent code analysis, semantic search, and software engineering guidance through MCP integration.

439 lines (438 loc) 17 kB
"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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.RemcodeConfigManager = exports.ProcessingStatus = exports.VectorProvider = exports.EmbeddingModel = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const semver = __importStar(require("semver")); const logger_1 = require("../utils/logger"); const logger = (0, logger_1.getLogger)('RemcodeConfigManager'); /** * Available embedding models */ /** * Available embedding models (Inference API compatible) */ var EmbeddingModel; (function (EmbeddingModel) { EmbeddingModel["CODEBERT"] = "microsoft/codebert-base"; EmbeddingModel["BGE_BASE"] = "BAAI/bge-base-en-v1.5"; EmbeddingModel["BGE_SMALL"] = "BAAI/bge-small-en-v1.5"; EmbeddingModel["MINILM"] = "sentence-transformers/all-MiniLM-L12-v2"; // Legacy models (may not be available via Inference API) EmbeddingModel["GRAPHCODEBERT"] = "microsoft/graphcodebert-base"; EmbeddingModel["UNIXCODER"] = "microsoft/unixcoder-base"; EmbeddingModel["CODELLAMA"] = "codellama/CodeLlama-7b-hf"; })(EmbeddingModel || (exports.EmbeddingModel = EmbeddingModel = {})); /** * Vector DB providers */ var VectorProvider; (function (VectorProvider) { VectorProvider["PINECONE"] = "pinecone"; VectorProvider["QDRANT"] = "qdrant"; VectorProvider["MILVUS"] = "milvus"; VectorProvider["OPENSEARCH"] = "opensearch"; })(VectorProvider || (exports.VectorProvider = VectorProvider = {})); /** * Processing status values */ var ProcessingStatus; (function (ProcessingStatus) { ProcessingStatus["PENDING"] = "pending"; ProcessingStatus["PROCESSING"] = "processing"; ProcessingStatus["COMPLETED"] = "completed"; ProcessingStatus["FAILED"] = "failed"; })(ProcessingStatus || (exports.ProcessingStatus = ProcessingStatus = {})); /** * Class to manage Remcode configuration */ class RemcodeConfigManager { /** * Constructor * @param repoPath Path to the repository */ constructor(repoPath = process.cwd()) { this.currentVersion = '0.2.0'; this.repoPath = repoPath; this.configPath = path.join(this.repoPath, '.remcode'); logger.debug(`RemcodeConfigManager initialized for path: ${repoPath}`); } /** * Create initial configuration file * @param owner Repository owner * @param repo Repository name * @param options Additional configuration options * @returns The created configuration */ async createInitialConfig(owner, repo, options = {}) { logger.info('Creating initial .remcode configuration'); try { // Check if config already exists if (fs.existsSync(this.configPath)) { logger.warn('.remcode configuration already exists, updating instead'); return this.updateConfig(options); } // Build initial configuration const config = this.buildInitialConfig(owner, repo, options); // Validate configuration const validation = this.validateConfig(config); if (!validation.valid) { throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`); } // Write configuration to file fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2)); logger.info('Initial configuration created successfully'); return config; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Failed to create initial configuration: ${errorMessage}`); throw new Error(`Failed to create .remcode configuration: ${errorMessage}`); } } /** * Read configuration from file * @returns The current configuration */ readConfig() { try { if (!fs.existsSync(this.configPath)) { throw new Error('Configuration file does not exist'); } const configContent = fs.readFileSync(this.configPath, 'utf8'); const config = JSON.parse(configContent); return config; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Failed to read configuration: ${errorMessage}`); throw new Error(`Failed to read .remcode configuration: ${errorMessage}`); } } /** * Update configuration with new values * @param updates Configuration updates * @returns The updated configuration */ updateConfig(updates = {}) { try { // Read current configuration or create default if it doesn't exist let config; try { config = this.readConfig(); } catch (error) { // If config doesn't exist, create a default one logger.warn('Configuration file does not exist, creating default'); const owner = updates.repository?.owner || 'unknown'; const repo = updates.repository?.name || 'remcode-repo'; config = this.buildInitialConfig(owner, repo, {}); } // Apply updates using deep merge const updatedConfig = this.deepMerge(config, updates); // Update version and last modified updatedConfig.lastModified = new Date().toISOString(); // Check if we need to upgrade the config format if (semver.lt(config.version, this.currentVersion)) { logger.info(`Upgrading config from ${config.version} to ${this.currentVersion}`); this.upgradeConfig(updatedConfig); } // Validate configuration const validation = this.validateConfig(updatedConfig); if (!validation.valid) { throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`); } // Write updated configuration to file fs.writeFileSync(this.configPath, JSON.stringify(updatedConfig, null, 2)); logger.info('Configuration updated successfully'); return updatedConfig; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Failed to update configuration: ${errorMessage}`); throw new Error(`Failed to update .remcode configuration: ${errorMessage}`); } } /** * Update processing status and statistics * @param status New processing status * @param stats Updated statistics * @param commit Last processed commit hash * @returns The updated configuration */ updateProcessingStatus(status, stats, commit) { try { const config = this.readConfig(); // Update processing information config.processing.status = status; config.processing.lastUpdate = new Date().toISOString(); if (commit) { config.processing.lastCommit = commit; } // Update statistics if provided if (stats) { config.statistics = { ...config.statistics, ...stats, lastUpdated: new Date().toISOString() }; } // Write updated configuration to file fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2)); logger.info(`Processing status updated to ${status}`); return config; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Failed to update processing status: ${errorMessage}`); throw new Error(`Failed to update processing status: ${errorMessage}`); } } /** * Validate configuration * @param config Configuration to validate * @returns Validation result */ validateConfig(config) { const errors = []; const warnings = []; // Required fields if (!config.version) errors.push('Missing version'); if (!config.repository) errors.push('Missing repository configuration'); if (!config.processing) errors.push('Missing processing configuration'); if (!config.vectorization) errors.push('Missing vectorization configuration'); if (!config.statistics) errors.push('Missing statistics configuration'); // Repository validation if (config.repository) { if (!config.repository.name) errors.push('Missing repository name'); if (!config.repository.owner) errors.push('Missing repository owner'); } // Vectorization validation if (config.vectorization) { // Check if provider is valid const validProviders = Object.values(VectorProvider); if (!validProviders.includes(config.vectorization.provider)) { errors.push(`Invalid vector provider: ${config.vectorization.provider}`); } // Check embedding model const validModels = Object.values(EmbeddingModel); if (!validModels.includes(config.vectorization.embeddingModel)) { warnings.push(`Unknown embedding model: ${config.vectorization.embeddingModel}`); } // Check dimension if (config.vectorization.embeddingDimension <= 0) { errors.push('Embedding dimension must be a positive number'); } } return { valid: errors.length === 0, errors, warnings }; } /** * Build initial configuration * @param owner Repository owner * @param repo Repository name * @param options Additional configuration options * @returns Initial configuration */ buildInitialConfig(owner, repo, options) { const defaultBranch = options.repository?.defaultBranch || 'main'; // Create base configuration const baseConfig = { version: this.currentVersion, initialized: new Date().toISOString(), repository: { name: repo, owner, url: `https://github.com/${owner}/${repo}`, defaultBranch }, processing: { lastCommit: '', lastUpdate: '', status: ProcessingStatus.PENDING }, vectorization: { provider: VectorProvider.PINECONE, indexName: `remcode-${repo.toLowerCase().replace(/[^a-z0-9]/g, '-')}`, namespace: 'main', embeddingModel: EmbeddingModel.CODEBERT, embeddingModelName: 'CodeBERT-Base', embeddingDimension: 768, chunkSize: 1000, modelHealthy: false, lastModelCheck: new Date().toISOString(), availableModels: [] }, statistics: { filesProcessed: 0, chunksCreated: 0, vectorsStored: 0, lastUpdated: new Date().toISOString() }, advanced: { ignorePaths: [ 'node_modules/**', '.git/**', 'dist/**', 'build/**', '**/*.min.js', '**/*.map' ], includeExtensions: [ '.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.go', '.rb', '.php', '.html', '.css', '.md', '.json', '.yaml', '.yml' ], maxFileSize: 1000000, // 1MB useCache: true }, lastModified: new Date().toISOString() }; // Merge with provided options return this.deepMerge(baseConfig, options); } /** * Deep merge two objects * @param target Target object * @param source Source object * @returns Merged object */ deepMerge(target, source) { const output = { ...target }; if (isObject(target) && isObject(source)) { Object.keys(source).forEach(key => { const sourceValue = source[key]; if (sourceValue !== undefined) { if (isObject(sourceValue)) { if (!(key in target)) { output[key] = sourceValue; } else { output[key] = this.deepMerge(target[key], sourceValue); } } else { output[key] = sourceValue; } } }); } return output; } /** * Upgrade configuration to the latest version * @param config Configuration to upgrade */ upgradeConfig(config) { // Store the original version for logging const originalVersion = config.version; // Upgrade from 0.1.0 to 0.2.0 if (semver.satisfies(config.version, '0.1.0')) { // Add advanced section if it doesn't exist if (!config.advanced) { config.advanced = { ignorePaths: [ 'node_modules/**', '.git/**', 'dist/**', 'build/**', '**/*.min.js', '**/*.map' ], includeExtensions: [ '.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.go', '.rb', '.php', '.html', '.css', '.md', '.json', '.yaml', '.yml' ], maxFileSize: 1000000, useCache: true }; } // Add new fields to vectorization if (!config.vectorization.chunkSize) { config.vectorization.chunkSize = 1000; } // Update version config.version = '0.2.0'; } // Set to current version config.version = this.currentVersion; logger.info(`Configuration upgraded from ${originalVersion} to ${config.version}`); } /** * Check if configuration exists * @returns True if configuration exists */ configExists() { return fs.existsSync(this.configPath); } /** * Delete configuration file * @returns True if configuration was deleted */ deleteConfig() { try { if (this.configExists()) { fs.unlinkSync(this.configPath); logger.info('Configuration file deleted'); return true; } return false; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Failed to delete configuration: ${errorMessage}`); return false; } } } exports.RemcodeConfigManager = RemcodeConfigManager; /** * Helper function to check if a value is an object * @param item Item to check * @returns True if the item is an object */ function isObject(item) { return item && typeof item === 'object' && !Array.isArray(item); }