UNPKG

remcode

Version:

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

316 lines (315 loc) 11.3 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.loadConfig = loadConfig; exports.saveConfig = saveConfig; exports.getConfigValue = getConfigValue; exports.validateConfig = validateConfig; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const yaml = __importStar(require("yaml")); const dotenv = __importStar(require("dotenv")); const logger_1 = require("./logger"); const logger = (0, logger_1.getLogger)('Config'); // Load environment variables dotenv.config(); /** * Default configuration */ const defaultConfig = { ignore: ['node_modules', 'dist', 'build', '.git', '*.min.js'], analysis: { depth: 2, quality: { enabled: true, complexityThreshold: 10 }, dependencies: { enabled: true, includeExternal: true } }, vectorization: { chunking: { moduleLevelSize: 750, functionLevelSize: 150, overlapFactor: 0.2 }, embedding: { model: 'graphcodebert', fallbackModel: 'codebert', batchSize: 16 }, storage: { provider: 'pinecone', indexes: { moduleName: '{name}-module', functionName: '{name}-function' }, pinecone: { environment: 'gcp-starter', namespace: 'default' } } }, server: { port: 3000, host: 'localhost' } }; /** * Load configuration from a file or use default configuration */ function loadConfig(configPath) { logger.debug(`Loading configuration${configPath ? ` from ${configPath}` : ''}`); // Final config will combine: default config + file config + env vars let fileConfig = {}; // Step 1: Find config file if not specified if (!configPath) { // Look for config files in the current directory const possibleConfigPaths = [ '.remcode.yml', '.remcode.yaml', '.remcode.json', 'remcode.config.js' ]; for (const possiblePath of possibleConfigPaths) { if (fs.existsSync(possiblePath)) { configPath = possiblePath; logger.debug(`Found config file: ${possiblePath}`); break; } } } // Step 2: Load config from file if it exists if (configPath && fs.existsSync(configPath)) { try { const ext = path.extname(configPath).toLowerCase(); if (ext === '.json') { const content = fs.readFileSync(configPath, 'utf8'); fileConfig = JSON.parse(content); logger.debug('Loaded configuration from JSON file'); } else if (ext === '.yml' || ext === '.yaml') { const content = fs.readFileSync(configPath, 'utf8'); fileConfig = yaml.parse(content); logger.debug('Loaded configuration from YAML file'); } else if (ext === '.js') { // Note: This works for CommonJS modules, but not for ES modules fileConfig = require(path.resolve(process.cwd(), configPath)); logger.debug('Loaded configuration from JS file'); } else { logger.warn(`Unsupported config file format: ${ext}`); } } catch (error) { logger.warn(`Failed to load config file: ${error instanceof Error ? error.message : 'Unknown error'}`); } } else if (configPath) { logger.warn(`Config file not found: ${configPath}`); } else { logger.info('No config file found, using default configuration'); } // Step 3: Merge with environment variables const envConfig = loadEnvironmentConfig(); // Step 4: Merge everything, with environment variables taking precedence const mergedConfig = deepMerge(defaultConfig, fileConfig); const finalConfig = deepMerge(mergedConfig, envConfig); logger.debug('Configuration loaded successfully'); return finalConfig; } /** * Load configuration from environment variables */ function loadEnvironmentConfig() { const envConfig = {}; // Process Pinecone-related environment variables if (process.env.REMCODE_PINECONE_API_KEY || process.env.REMCODE_PINECONE_ENVIRONMENT || process.env.REMCODE_PINECONE_NAMESPACE) { // Create a complete vectorization object to avoid TypeScript errors envConfig.vectorization = { chunking: { moduleLevelSize: 750, functionLevelSize: 150, overlapFactor: 0.2 }, embedding: { model: 'graphcodebert', fallbackModel: 'codebert', batchSize: 16 }, storage: { provider: 'pinecone', indexes: { moduleName: '{name}-module', functionName: '{name}-function' }, pinecone: {} } }; // Now we can safely assign to these properties if (process.env.REMCODE_PINECONE_API_KEY) { envConfig.vectorization.storage.pinecone = { ...envConfig.vectorization.storage.pinecone, apiKey: process.env.REMCODE_PINECONE_API_KEY }; } if (process.env.REMCODE_PINECONE_ENVIRONMENT) { envConfig.vectorization.storage.pinecone = { ...envConfig.vectorization.storage.pinecone, environment: process.env.REMCODE_PINECONE_ENVIRONMENT }; } if (process.env.REMCODE_PINECONE_NAMESPACE) { envConfig.vectorization.storage.pinecone = { ...envConfig.vectorization.storage.pinecone, namespace: process.env.REMCODE_PINECONE_NAMESPACE }; } } // Process GitHub-related environment variables if (process.env.GITHUB_TOKEN || process.env.REMCODE_GITHUB_TOKEN) { envConfig.github = { token: process.env.GITHUB_TOKEN || process.env.REMCODE_GITHUB_TOKEN }; } // Process server-related environment variables if (process.env.REMCODE_SERVER_PORT || process.env.REMCODE_SERVER_HOST) { envConfig.server = { port: process.env.REMCODE_SERVER_PORT ? parseInt(process.env.REMCODE_SERVER_PORT, 10) : 3000, host: process.env.REMCODE_SERVER_HOST || 'localhost' }; } if (Object.keys(envConfig).length > 0) { logger.debug('Loaded configuration from environment variables'); } return envConfig; } /** * Save configuration to a file */ function saveConfig(config, configPath) { logger.debug(`Saving configuration to ${configPath}`); try { // Ensure directory exists const dir = path.dirname(configPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const ext = path.extname(configPath).toLowerCase(); let content; if (ext === '.json') { content = JSON.stringify(config, null, 2); } else if (ext === '.yml' || ext === '.yaml') { content = yaml.stringify(config); } else { throw new Error(`Unsupported config file format: ${ext}`); } fs.writeFileSync(configPath, content, 'utf8'); logger.info(`Configuration saved to ${configPath}`); } catch (error) { const errorMessage = `Failed to save config file: ${error instanceof Error ? error.message : 'Unknown error'}`; logger.error(errorMessage); throw new Error(errorMessage); } } /** * Get a specific configuration value by path * Example: getConfigValue(config, 'vectorization.storage.provider') */ function getConfigValue(config, path, defaultValue) { const parts = path.split('.'); let current = config; for (const part of parts) { if (current === undefined || current === null) { return defaultValue; } current = current[part]; } return current !== undefined ? current : defaultValue; } /** * Validate configuration against schema */ function validateConfig(config) { const errors = []; // Check required fields if (!config.vectorization) { errors.push('Missing vectorization configuration'); } else { if (!config.vectorization.storage) { errors.push('Missing vectorization.storage configuration'); } else if (!config.vectorization.storage.provider) { errors.push('Missing vectorization.storage.provider configuration'); } else if (config.vectorization.storage.provider === 'pinecone') { if (!config.vectorization.storage.pinecone?.apiKey && !process.env.REMCODE_PINECONE_API_KEY && !process.env.PINECONE_API_KEY) { errors.push('Missing Pinecone API key. Set it in config or as REMCODE_PINECONE_API_KEY environment variable'); } } } return { valid: errors.length === 0, errors }; } /** * Deep merge objects */ function deepMerge(target, source) { if (!source) return target; if (typeof target !== 'object' || typeof source !== 'object') return source; const output = { ...target }; Object.keys(source).forEach(key => { const value = source[key]; if (typeof value === 'object' && value !== null && !Array.isArray(value) && typeof output[key] === 'object' && output[key] !== null && !Array.isArray(output[key])) { output[key] = deepMerge(output[key], value); } else { output[key] = value; } }); return output; }