UNPKG

@knath2000/codebase-indexing-mcp

Version:

MCP server for codebase indexing with Voyage AI embeddings and Qdrant vector storage

325 lines โ€ข 12 kB
import { createHash } from 'crypto'; import { readdir, stat, readFile } from 'fs/promises'; import { join, resolve, basename } from 'path'; import { exec } from 'child_process'; import { promisify } from 'util'; import { EventEmitter } from 'events'; const execAsync = promisify(exec); /** * Enhanced Workspace Manager that provides superior multi-workspace handling * compared to Cursor's built-in capabilities */ export class WorkspaceManager extends EventEmitter { constructor() { super(); this.workspaces = new Map(); this.currentWorkspace = null; this.workspaceProfiles = new Map(); } /** * Detect and register current workspace with enhanced intelligence */ async detectCurrentWorkspace(workspacePath = process.cwd()) { const absolutePath = resolve(workspacePath); console.log(`๐Ÿ” Detecting workspace type for: ${absolutePath}`); // Check for VSCode multi-root workspace files const multiRootWorkspace = await this.detectMultiRootWorkspace(absolutePath); if (multiRootWorkspace) { console.log(`๐Ÿ“ Detected multi-root workspace: ${multiRootWorkspace.name}`); return this.registerWorkspace(multiRootWorkspace); } // Check for Git repository const gitWorkspace = await this.detectGitWorkspace(absolutePath); if (gitWorkspace) { console.log(`๐Ÿ”— Detected Git workspace: ${gitWorkspace.name}`); return this.registerWorkspace(gitWorkspace); } // Check for npm/package.json workspace const npmWorkspace = await this.detectNpmWorkspace(absolutePath); if (npmWorkspace) { console.log(`๐Ÿ“ฆ Detected npm workspace: ${npmWorkspace.name}`); return this.registerWorkspace(npmWorkspace); } // Fallback to directory-based workspace const directoryWorkspace = await this.createDirectoryWorkspace(absolutePath); console.log(`๐Ÿ“‚ Created directory workspace: ${directoryWorkspace.name}`); return this.registerWorkspace(directoryWorkspace); } /** * Detect VSCode multi-root workspace from .code-workspace files */ async detectMultiRootWorkspace(rootPath) { try { const files = await readdir(rootPath); const workspaceFiles = files.filter(f => f.endsWith('.code-workspace')); if (workspaceFiles.length === 0) return null; // Use the first .code-workspace file found const workspaceFile = workspaceFiles[0]; const workspaceFilePath = join(rootPath, workspaceFile); const content = await readFile(workspaceFilePath, 'utf-8'); const config = JSON.parse(content); if (!config.folders || !Array.isArray(config.folders)) return null; const folders = config.folders .map((folder) => { if (typeof folder === 'string') return resolve(rootPath, folder); if (folder.path) return resolve(rootPath, folder.path); return null; }) .filter(Boolean); const workspaceId = this.generateWorkspaceId('multi-root', rootPath, folders); const workspaceName = basename(workspaceFile, '.code-workspace'); return { id: workspaceId, name: workspaceName, rootPath, type: 'multi-root', folders, lastAccessed: new Date(), collectionName: `workspace_${workspaceId.substring(0, 12)}` }; } catch (error) { console.log(`โš ๏ธ Failed to detect multi-root workspace: ${error}`); return null; } } /** * Detect Git workspace with remote tracking */ async detectGitWorkspace(rootPath) { try { // Check if .git directory exists const gitDir = join(rootPath, '.git'); const gitStat = await stat(gitDir); if (!gitStat.isDirectory()) return null; // Get git remote origin let gitRemote; try { const { stdout } = await execAsync('git remote get-url origin', { cwd: rootPath }); gitRemote = stdout.trim(); } catch { // No remote configured } // Get repository name from remote or directory let repoName = basename(rootPath); if (gitRemote) { const match = gitRemote.match(/\/([^\/]+?)(?:\.git)?$/); if (match) repoName = match[1]; } const workspaceId = this.generateWorkspaceId('git', rootPath, [rootPath], gitRemote); const workspace = { id: workspaceId, name: repoName, rootPath, type: 'git', folders: [rootPath], lastAccessed: new Date(), collectionName: `workspace_${workspaceId.substring(0, 12)}` }; if (gitRemote) { workspace.gitRemote = gitRemote; } return workspace; } catch (error) { return null; } } /** * Detect npm workspace from package.json */ async detectNpmWorkspace(rootPath) { try { const packagePath = join(rootPath, 'package.json'); const packageStat = await stat(packagePath); if (!packageStat.isFile()) return null; const content = await readFile(packagePath, 'utf-8'); const pkg = JSON.parse(content); const packageName = pkg.name || basename(rootPath); const workspaceId = this.generateWorkspaceId('npm', rootPath, [rootPath], packageName); return { id: workspaceId, name: packageName, rootPath, type: 'npm', folders: [rootPath], packageName, lastAccessed: new Date(), collectionName: `workspace_${workspaceId.substring(0, 12)}` }; } catch (error) { return null; } } /** * Create basic directory workspace as fallback */ async createDirectoryWorkspace(rootPath) { const dirName = basename(rootPath); const workspaceId = this.generateWorkspaceId('single', rootPath, [rootPath]); return { id: workspaceId, name: dirName, rootPath, type: 'single', folders: [rootPath], lastAccessed: new Date(), collectionName: `workspace_${workspaceId.substring(0, 12)}` }; } /** * Generate cryptographic workspace ID for perfect isolation */ generateWorkspaceId(type, rootPath, folders, extra) { const hash = createHash('sha256'); hash.update(type); hash.update(rootPath); folders.forEach(folder => hash.update(folder)); if (extra) hash.update(extra); return hash.digest('hex'); } /** * Register workspace and set as current */ registerWorkspace(workspace) { this.workspaces.set(workspace.id, workspace); // Check if workspace has changed const wasCurrentWorkspace = this.currentWorkspace?.id === workspace.id; this.currentWorkspace = workspace; if (!wasCurrentWorkspace) { console.log(`๐Ÿ”„ Workspace changed to: ${workspace.name} (${workspace.type})`); console.log(`๐Ÿ“Š Collection: ${workspace.collectionName}`); console.log(`๐Ÿ“ Folders: ${workspace.folders.length} folder(s)`); workspace.folders.forEach((folder, i) => { console.log(` ${i + 1}. ${folder}`); }); this.emit('workspace-changed', workspace); } else { console.log(`โœ… Current workspace confirmed: ${workspace.name}`); } return workspace; } /** * Get current workspace info */ getCurrentWorkspace() { return this.currentWorkspace; } /** * Get workspace by ID */ getWorkspace(id) { return this.workspaces.get(id) || null; } /** * List all registered workspaces */ getAllWorkspaces() { return Array.from(this.workspaces.values()) .sort((a, b) => b.lastAccessed.getTime() - a.lastAccessed.getTime()); } /** * Switch to a different workspace */ async switchToWorkspace(workspaceId) { const workspace = this.workspaces.get(workspaceId); if (!workspace) { console.log(`โŒ Workspace not found: ${workspaceId}`); return null; } workspace.lastAccessed = new Date(); this.currentWorkspace = workspace; console.log(`๐Ÿ”„ Switched to workspace: ${workspace.name}`); this.emit('workspace-changed', workspace); return workspace; } /** * Create workspace profile for custom settings */ createWorkspaceProfile(workspace, customSettings) { const profile = { id: workspace.id, name: workspace.name, excludePatterns: customSettings.excludePatterns || [], supportedExtensions: customSettings.supportedExtensions || [], chunkSize: customSettings.chunkSize || 800, enableLLMReranking: customSettings.enableLLMReranking ?? true, customSettings: customSettings.customSettings || {} }; this.workspaceProfiles.set(workspace.id, profile); console.log(`โš™๏ธ Created workspace profile for: ${workspace.name}`); return profile; } /** * Get workspace profile with fallbacks */ getWorkspaceProfile(workspaceId) { return this.workspaceProfiles.get(workspaceId) || null; } /** * Clean up stale workspaces (not accessed in 30 days) */ async cleanupStaleWorkspaces() { const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const staleWorkspaces = []; for (const [id, workspace] of this.workspaces) { if (workspace.lastAccessed < thirtyDaysAgo) { staleWorkspaces.push(id); this.workspaces.delete(id); this.workspaceProfiles.delete(id); } } if (staleWorkspaces.length > 0) { console.log(`๐Ÿงน Cleaned up ${staleWorkspaces.length} stale workspace(s)`); } return staleWorkspaces; } /** * Get workspace statistics */ getWorkspaceStats() { const recentWorkspaces = this.getAllWorkspaces() .slice(0, 5) .map(w => w.name); return { totalWorkspaces: this.workspaces.size, currentWorkspace: this.currentWorkspace?.name || null, recentWorkspaces }; } /** * Export workspace configuration for backup/sync */ exportConfiguration() { return { workspaces: Array.from(this.workspaces.values()), profiles: Array.from(this.workspaceProfiles.values()) }; } /** * Import workspace configuration from backup/sync */ importConfiguration(config) { // Import workspaces config.workspaces.forEach(workspace => { this.workspaces.set(workspace.id, workspace); }); // Import profiles config.profiles.forEach(profile => { this.workspaceProfiles.set(profile.id, profile); }); console.log(`๐Ÿ“ฅ Imported ${config.workspaces.length} workspaces and ${config.profiles.length} profiles`); } } //# sourceMappingURL=workspace-manager.js.map