@knath2000/codebase-indexing-mcp
Version:
MCP server for codebase indexing with Voyage AI embeddings and Qdrant vector storage
325 lines โข 12 kB
JavaScript
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