UNPKG

@wildcard-ai/deepcontext

Version:

Advanced codebase indexing and semantic search MCP server

352 lines โ€ข 13.6 kB
/** * Namespace Manager Service * Handles namespace generation, indexed codebase management, and persistence */ import * as fs from 'fs/promises'; import * as path from 'path'; import * as crypto from 'crypto'; import { Logger } from '../utils/Logger.js'; export class NamespaceManagerService { codebaseOperations; logger; indexedCodebases = new Map(); constructor(codebaseOperations, loggerName = 'NamespaceManagerService') { this.codebaseOperations = codebaseOperations; this.logger = new Logger(loggerName); } /** * Generate a namespace from a codebase path */ generateNamespace(codebasePath) { const normalized = path.resolve(codebasePath) + (process.env.WILDCARD_API_KEY ?? ''); const hash = crypto.createHash('md5').update(normalized).digest('hex'); return `mcp_${hash.substring(0, 16)}`; } /** * Register a new indexed codebase */ async registerCodebase(codebasePath, totalChunks, indexedAt) { const resolvedPath = path.resolve(codebasePath); const namespace = this.generateNamespace(resolvedPath); const indexedCodebase = { path: resolvedPath, namespace, totalChunks, indexedAt: (indexedAt || new Date()).toISOString() }; this.indexedCodebases.set(resolvedPath, indexedCodebase); await this.saveIndexedCodebases(); this.logger.info(`๐Ÿ“ Registered codebase: ${resolvedPath} -> ${namespace} (${totalChunks} chunks)`); return namespace; } /** * Register a failed indexing attempt */ async registerFailedIndexing(codebasePath, failureReason, indexedAt) { const resolvedPath = path.resolve(codebasePath); const namespace = this.generateNamespace(resolvedPath); const failedCodebase = { path: resolvedPath, namespace, totalChunks: 0, indexedAt: (indexedAt || new Date()).toISOString(), failed: true, failureReason }; this.indexedCodebases.set(resolvedPath, failedCodebase); await this.saveIndexedCodebases(); this.logger.warn(`โš ๏ธ Registered failed indexing: ${resolvedPath} - ${failureReason}`); return namespace; } /** * Get namespace for a codebase path */ getNamespaceForCodebase(codebasePath) { const resolvedPath = path.resolve(codebasePath); const indexed = this.indexedCodebases.get(resolvedPath); return indexed ? indexed.namespace : null; } /** * Get indexed codebase information */ getIndexedCodebase(codebasePath) { const resolvedPath = path.resolve(codebasePath); return this.indexedCodebases.get(resolvedPath) || null; } /** * Check if a codebase is indexed */ isCodebaseIndexed(codebasePath) { const resolvedPath = path.resolve(codebasePath); return this.indexedCodebases.has(resolvedPath); } /** * Get all indexed codebases */ async getAllIndexedCodebases() { // Ensure registry is loaded before returning data if (this.indexedCodebases.size === 0) { await this.loadIndexedCodebases(); } return new Map(this.indexedCodebases); } /** * Get list of indexed codebases as array */ getIndexedCodebasesList() { return Array.from(this.indexedCodebases.values()); } /** * Find codebase path by namespace */ getCodebaseByNamespace(namespace) { for (const [path, indexed] of this.indexedCodebases.entries()) { if (indexed.namespace === namespace) { return path; } } return null; } /** * Get the first available indexed codebase (for default operations) */ getFirstIndexedCodebase() { const firstEntry = Array.from(this.indexedCodebases.entries())[0]; return firstEntry ? { path: firstEntry[0], indexed: firstEntry[1] } : null; } /** * Update chunk count for an indexed codebase */ async updateChunkCount(codebasePath, totalChunks) { const resolvedPath = path.resolve(codebasePath); const indexed = this.indexedCodebases.get(resolvedPath); if (indexed) { indexed.totalChunks = totalChunks; indexed.indexedAt = new Date().toISOString(); await this.saveIndexedCodebases(); this.logger.debug(`๐Ÿ“Š Updated chunk count for ${resolvedPath}: ${totalChunks} chunks`); } } /** * Clear/remove indexed codebases */ async clearIndexedCodebases(codebasePath) { const namespacesCleared = []; try { if (codebasePath) { // Clear specific codebase const resolvedPath = path.resolve(codebasePath); const indexed = this.indexedCodebases.get(resolvedPath); if (indexed) { // Clear the vector store namespace await this.codebaseOperations.clearNamespace(indexed.namespace); namespacesCleared.push(indexed.namespace); // Remove from tracking this.indexedCodebases.delete(resolvedPath); this.logger.info(`๐Ÿ—‘๏ธ Cleared codebase: ${resolvedPath} (${indexed.namespace})`); } else { return { success: false, message: `Codebase not found: ${codebasePath}`, namespacesCleared: [] }; } } else { // Clear all codebases for (const indexed of this.indexedCodebases.values()) { await this.codebaseOperations.clearNamespace(indexed.namespace); namespacesCleared.push(indexed.namespace); } this.indexedCodebases.clear(); this.logger.info(`๐Ÿ—‘๏ธ Cleared all indexed codebases (${namespacesCleared.length} namespaces)`); } // Save the updated state await this.saveIndexedCodebases(); return { success: true, message: codebasePath ? `Cleared codebase: ${codebasePath}` : `Cleared all ${namespacesCleared.length} indexed codebases`, namespacesCleared }; } catch (error) { this.logger.error('Failed to clear indexed codebases:', error); return { success: false, message: `Failed to clear: ${error instanceof Error ? error.message : String(error)}`, namespacesCleared }; } } /** * Get namespace information with metadata */ getNamespaceInfo(codebasePath) { const indexed = this.getIndexedCodebase(codebasePath); if (!indexed) return null; return { namespace: indexed.namespace, codebasePath: indexed.path, totalChunks: indexed.totalChunks, indexedAt: new Date(indexed.indexedAt) }; } /** * Get all namespace information */ getAllNamespaceInfo() { return Array.from(this.indexedCodebases.values()).map(indexed => ({ namespace: indexed.namespace, codebasePath: indexed.path, totalChunks: indexed.totalChunks, indexedAt: new Date(indexed.indexedAt) })); } /** * Persistence methods */ getIndexedCodebasesPath() { const dataDir = process.env.CODEX_CONTEXT_DATA_DIR || path.join(process.env.HOME || '~', '.codex-context'); return path.join(dataDir, 'indexed-codebases.json'); } async saveIndexedCodebases() { try { const dataPath = this.getIndexedCodebasesPath(); const dir = path.dirname(dataPath); await fs.mkdir(dir, { recursive: true }); const data = Array.from(this.indexedCodebases.entries()); await fs.writeFile(dataPath, JSON.stringify(data, null, 2)); this.logger.debug(`๐Ÿ’พ Saved ${this.indexedCodebases.size} indexed codebases to disk`); } catch (error) { this.logger.warn('Failed to save indexed codebases:', error); } } async loadIndexedCodebases() { try { const dataPath = this.getIndexedCodebasesPath(); console.error(`[DEBUG] Loading registry from: ${dataPath}`); const content = await fs.readFile(dataPath, 'utf-8'); const data = JSON.parse(content); this.indexedCodebases = new Map(data); console.error(`[DEBUG] Successfully loaded ${this.indexedCodebases.size} indexed codebases`); this.logger.info(`๐Ÿ“‚ Loaded ${this.indexedCodebases.size} indexed codebases from disk`); } catch (error) { // No existing data, start fresh console.error(`[DEBUG] Failed to load registry: ${error instanceof Error ? error.message : String(error)}`); this.indexedCodebases = new Map(); this.logger.info('๐Ÿ“‚ Starting with empty indexed codebases (no existing data)'); } } /** * Initialize the service by loading existing data */ async initialize() { console.error(`[DEBUG] NamespaceManagerService.initialize() called`); await this.loadIndexedCodebases(); console.error(`[DEBUG] NamespaceManagerService initialized with ${this.indexedCodebases.size} indexed codebases`); this.logger.info(`๐Ÿš€ NamespaceManagerService initialized with ${this.indexedCodebases.size} indexed codebases`); } /** * Refresh the registry by reloading from disk (for background indexing updates) */ async refreshRegistry() { console.error(`[DEBUG] Refreshing registry from disk`); await this.loadIndexedCodebases(); console.error(`[DEBUG] Registry refreshed with ${this.indexedCodebases.size} indexed codebases`); } /** * Get service statistics */ getStatistics() { const codebases = Array.from(this.indexedCodebases.values()); const totalChunks = codebases.reduce((sum, cb) => sum + cb.totalChunks, 0); const namespaces = codebases.map(cb => cb.namespace); let oldestIndexed; let newestIndexed; if (codebases.length > 0) { const dates = codebases.map(cb => new Date(cb.indexedAt)); oldestIndexed = new Date(Math.min(...dates.map(d => d.getTime()))); newestIndexed = new Date(Math.max(...dates.map(d => d.getTime()))); } return { totalCodebases: codebases.length, totalChunks, namespaces, oldestIndexed, newestIndexed }; } /** * Get indexing status for codebases (compatible with IndexingOrchestrator interface) */ async getIndexingStatus(codebasePath) { // Refresh registry to pick up any background indexing updates await this.refreshRegistry(); const indexedList = Array.from(this.indexedCodebases.values()); let currentCodebase; let incrementalStats; if (codebasePath) { try { const normalizedPath = path.resolve(codebasePath); // Check if path exists const fs = await import('fs/promises'); await fs.access(normalizedPath); currentCodebase = this.indexedCodebases.get(normalizedPath); } catch (error) { return { indexedCodebases: indexedList, indexed: false, fileCount: 0 }; } if (currentCodebase) { incrementalStats = { indexingMethod: 'full', lastIndexed: currentCodebase.indexedAt }; } } const indexed = codebasePath ? (!!currentCodebase && !currentCodebase.failed) : indexedList.filter(cb => !cb.failed).length > 0; const fileCount = currentCodebase?.totalChunks || indexedList.reduce((sum, cb) => sum + cb.totalChunks, 0); return { indexedCodebases: indexedList, currentCodebase, incrementalStats, indexed, fileCount }; } /** * Validate namespace format */ isValidNamespace(namespace) { return /^mcp_[a-f0-9]{8}$/.test(namespace); } /** * Cleanup orphaned namespaces (namespaces not in our registry) */ async cleanupOrphanedNamespaces(allNamespaces) { const knownNamespaces = new Set(Array.from(this.indexedCodebases.values()).map(cb => cb.namespace)); const orphaned = allNamespaces.filter(ns => this.isValidNamespace(ns) && !knownNamespaces.has(ns)); let cleaned = 0; for (const orphanedNs of orphaned) { try { await this.codebaseOperations.clearNamespace(orphanedNs); cleaned++; this.logger.info(`๐Ÿงน Cleaned orphaned namespace: ${orphanedNs}`); } catch (error) { this.logger.warn(`Failed to clean orphaned namespace ${orphanedNs}:`, error); } } return { orphaned, cleaned }; } } //# sourceMappingURL=NamespaceManagerService.js.map