UNPKG

cortexweaver

Version:

CortexWeaver is a command-line interface (CLI) tool that orchestrates a swarm of specialized AI agents, powered by Claude Code and Gemini CLI, to assist in software development. It transforms a high-level project plan (plan.md) into a series of coordinate

414 lines (407 loc) โ€ข 19.7 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.CLIValidators = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const config_1 = require("../config"); const orchestrator_1 = require("../orchestrator"); const session_1 = require("../session"); const workspace_1 = require("../workspace"); const auth_manager_1 = require("../auth-manager"); const cli_utils_1 = require("../cli-utils"); /** * CLI Validators and Status operations * Contains project validation, status checking, and monitoring functionality */ class CLIValidators { async status(projectRoot = process.cwd()) { if (!cli_utils_1.CLIUtils.validateProject(projectRoot)) { return 'Error: Not a CortexWeaver project. Run "cortex-weaver init" first.'; } console.log('๐Ÿ“Š Gathering project status...'); try { // Load configuration for real-time status const configService = new config_1.ConfigService(projectRoot); let taskStatus = null; let orchestratorStatus = 'Not Running'; let activeTasks = 0; let completedTasks = 0; let failedTasks = 0; let impasseTasks = 0; let runningTasks = 0; try { // Try to get real-time task information const projectConfig = configService.loadProjectConfig(); const envVars = configService.loadEnvironmentVariables(); if (envVars.NEO4J_PASSWORD && envVars.CLAUDE_API_KEY) { Object.assign(process.env, envVars); const neo4jUri = process.env.NEO4J_URI || 'bolt://localhost:7687'; const neo4jUsername = process.env.NEO4J_USERNAME || 'neo4j'; const neo4jPassword = configService.getRequiredEnvVar('NEO4J_PASSWORD'); const orchestratorConfig = { neo4j: { uri: neo4jUri, username: neo4jUsername, password: neo4jPassword }, claude: { apiKey: configService.getRequiredEnvVar('CLAUDE_API_KEY'), defaultModel: projectConfig.models.claude, budgetLimit: projectConfig.budget.maxCost } }; const orchestrator = new orchestrator_1.Orchestrator(orchestratorConfig); await orchestrator.initialize(projectRoot); orchestratorStatus = orchestrator.getStatus(); // Get all tasks from all projects const canvas = orchestrator.canvas; const projects = await canvas.getAllProjects(); let allTasks = []; for (const project of projects) { const projectTasks = await canvas.getTasksByProject(project.id); allTasks = allTasks.concat(projectTasks); } // Count tasks by status activeTasks = allTasks.filter(t => t.status === 'pending').length; runningTasks = allTasks.filter(t => t.status === 'running').length; completedTasks = allTasks.filter(t => t.status === 'completed').length; failedTasks = allTasks.filter(t => t.status === 'failed').length; impasseTasks = allTasks.filter(t => t.status === 'impasse').length; taskStatus = allTasks; await orchestrator.shutdown(); } } catch (error) { // If we can't connect to get real-time status, continue with basic status console.log('โš ๏ธ Could not retrieve real-time task status'); } // Check contracts directory structure const contractsPath = path.join(projectRoot, 'contracts'); const contractsFiles = { readme: fs.existsSync(path.join(contractsPath, 'README.md')), openapi: fs.existsSync(path.join(contractsPath, 'api', 'openapi.yaml')), schemas: fs.readdirSync(path.join(contractsPath, 'schemas', 'models')).length > 0, properties: fs.readdirSync(path.join(contractsPath, 'properties', 'invariants')).length > 0, examples: fs.readdirSync(path.join(contractsPath, 'examples', 'requests')).length > 0 }; const contractsStatus = Object.entries(contractsFiles) .map(([key, exists]) => `${exists ? 'โœ…' : 'โŒ'} ${key}`) .join('\n'); // Check session status const sessionManager = new session_1.SessionManager(); const activeSessions = await sessionManager.listActiveTmuxSessions(); let statusReport = ` ๐ŸŽฏ CortexWeaver Project Status - Enhanced View ${'='.repeat(50)} ๐Ÿ“ Project Information: Root: ${projectRoot} Configuration: ${path.join(projectRoot, '.cortexweaver', 'config.json')} Plan File: ${path.join(projectRoot, 'plan.md')} Contracts Directory: ${contractsPath} ๐Ÿค– Orchestrator Status: ${orchestratorStatus} ๐Ÿ“Š Active Sessions: ${activeSessions.length} ๐Ÿ“‹ Task Management Status: ๐Ÿ”„ RUNNING: ${runningTasks} โœ… COMPLETED: ${completedTasks} โŒ FAILED: ${failedTasks} ๐Ÿšง IMPASSE: ${impasseTasks} โณ PENDING: ${activeTasks} ๐Ÿ“Š TOTAL: ${runningTasks + completedTasks + failedTasks + impasseTasks + activeTasks} ๐Ÿ“‹ Specification-Driven Development Status: ${contractsStatus} `; // Add detailed task information if available if (taskStatus && taskStatus.length > 0) { statusReport += `\n๐Ÿ” Recent Task Activity:\n`; // Show recent tasks (last 5) const recentTasks = taskStatus .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .slice(0, 5); for (const task of recentTasks) { const statusIcon = this.getTaskStatusIcon(task.status); statusReport += ` ${statusIcon} ${task.title} (${task.status})\n`; } } // Add session information if any active if (activeSessions.length > 0) { statusReport += `\n๐Ÿ”— Active Sessions:\n`; activeSessions.forEach(session => { statusReport += ` ๐Ÿ“บ ${session}\n`; }); } statusReport += ` ๐Ÿ’ก Next Steps: 1. ${taskStatus ? 'Monitor running tasks with: cortex-weaver logs <task-id>' : 'Define formal contracts in /contracts directory'} 2. ${taskStatus ? 'Check failed tasks and retry with: cortex-weaver retry <task-id>' : 'Use SDD workflow: Spec Writer โ†’ Formalizer โ†’ Architect โ†’ Coder โ†’ Testers'} 3. ${taskStatus ? 'View all agent personas with: cortex-weaver list-agents' : 'Run: cortex-weaver start'} ๐Ÿ“Š Real-time monitoring active โ€ข Last updated: ${new Date().toISOString()} `; return statusReport.trim(); } catch (error) { throw new Error(`Failed to get project status: ${error.message}`); } } async start(projectRoot = process.cwd()) { // 1. Validate CortexWeaver project if (!cli_utils_1.CLIUtils.validateProject(projectRoot)) { throw new Error('Not a CortexWeaver project. Run "cortex-weaver init" first.'); } console.log('๐Ÿ” Validating project setup...'); // 2. Initialize AuthManager and validate authentication console.log('๐Ÿ” Validating authentication...'); const authManager = new auth_manager_1.AuthManager(projectRoot); await authManager.discoverAuthentication(); const authStatus = await authManager.getAuthStatus(); if (!authStatus.claudeAuth.isAuthenticated) { console.log('โŒ Claude authentication not configured'); console.log('Run "cortex-weaver auth configure" to set up authentication'); throw new Error('Claude authentication required. Run "cortex-weaver auth configure" first.'); } console.log(`โœ… Using Claude ${authStatus.claudeAuth.method} authentication`); // Get authentication credentials const claudeCredentials = await authManager.getClaudeCredentials(); // Load configuration and environment variables const configService = new config_1.ConfigService(projectRoot); const projectConfig = configService.loadProjectConfig(); // Load environment variables from .env file const envVars = configService.loadEnvironmentVariables(); // Override process.env with loaded variables for validation Object.assign(process.env, envVars); // 3. Get authentication credentials (API key or session token) const claudeConfig = {}; if (claudeCredentials?.apiKey) { claudeConfig.apiKey = claudeCredentials.apiKey; } else if (claudeCredentials?.sessionToken) { claudeConfig.sessionToken = claudeCredentials.sessionToken; } else { throw new Error('No valid Claude credentials found'); } // 4. Validate required environment variables try { const neo4jUri = process.env.NEO4J_URI || 'bolt://localhost:7687'; const neo4jUsername = process.env.NEO4J_USERNAME || 'neo4j'; const neo4jPassword = configService.getRequiredEnvVar('NEO4J_PASSWORD'); // 5. Create Orchestrator configuration const orchestratorConfig = { neo4j: { uri: neo4jUri, username: neo4jUsername, password: neo4jPassword }, claude: { ...claudeConfig, defaultModel: projectConfig.models.claude, budgetLimit: projectConfig.budget.maxCost } }; console.log('๐Ÿš€ Initializing Orchestrator...'); // 6. Initialize Orchestrator with project root const orchestrator = new orchestrator_1.Orchestrator(orchestratorConfig); // Set up signal handlers for graceful shutdown const shutdownHandler = async (signal) => { console.log(`\nโš ๏ธ Received ${signal}. Shutting down gracefully...`); try { await orchestrator.shutdown(); console.log('โœ… Orchestrator shutdown completed'); process.exit(0); } catch (error) { console.error('โŒ Error during shutdown:', error.message); process.exit(1); } }; process.on('SIGINT', () => shutdownHandler('SIGINT')); process.on('SIGTERM', () => shutdownHandler('SIGTERM')); try { // 7. Initialize Orchestrator with project path await orchestrator.initialize(projectRoot); console.log('โœ… Orchestrator initialized successfully'); // 8. Budget validation before starting const tokenUsage = orchestrator.getTokenUsage(); if (tokenUsage.estimatedCost > projectConfig.budget.maxCost) { console.log(`โš ๏ธ Current usage ($${tokenUsage.estimatedCost.toFixed(2)}) approaches budget limit ($${projectConfig.budget.maxCost})`); console.log('๐Ÿ“Š Budget monitoring will continue during orchestration'); } console.log('๐ŸŽฏ Starting orchestration loop...'); console.log('๐Ÿ“Š Real-time status monitoring enabled'); console.log('๐Ÿ’ก Use Ctrl+C to stop gracefully'); // 9. Start orchestration loop with proper error handling await orchestrator.start(); // 10. Provide final status const finalStatus = orchestrator.getStatus(); const finalUsage = orchestrator.getTokenUsage(); console.log(`\n๐Ÿ Orchestration completed with status: ${finalStatus}`); console.log(`๐Ÿ“Š Final token usage: ${finalUsage.totalTokens} tokens ($${finalUsage.estimatedCost.toFixed(2)})`); } catch (error) { console.error('โŒ Orchestration error:', error.message); await orchestrator.shutdown(); throw error; } } catch (error) { if (error.message.includes('Required environment variable')) { throw new Error(`Missing required configuration: ${error.message}`); } throw error; } } async attach(sessionId) { const sessionManager = new session_1.SessionManager(); try { return await sessionManager.attachToSession(sessionId); } catch (error) { throw new Error('Session not found'); } } async merge(projectRoot = process.cwd(), taskId) { // Validate CortexWeaver project if (!cli_utils_1.CLIUtils.validateProject(projectRoot)) { throw new Error('Not a CortexWeaver project. Run "cortex-weaver init" first.'); } console.log(`๐Ÿ”€ Merging task ${taskId} to main branch...`); const workspaceManager = new workspace_1.WorkspaceManager(projectRoot); try { // Check if worktree exists const worktrees = await workspaceManager.listWorktrees(); const targetWorktree = worktrees.find(w => w.id === taskId); if (!targetWorktree) { throw new Error(`Worktree for task ${taskId} not found`); } // Check if worktree is clean const status = await workspaceManager.getWorktreeStatus(taskId); if (!status.clean) { console.log('โš ๏ธ Uncommitted changes found. Committing them first...'); await workspaceManager.commitChanges(taskId, `Auto-commit before merge for task ${taskId}`); } // Merge to main branch await workspaceManager.mergeToBranch(taskId, 'main'); // Remove the worktree after successful merge await workspaceManager.removeWorktree(taskId); console.log(`โœ… Successfully merged task ${taskId} to main branch`); } catch (error) { console.error(`โŒ Failed to merge task ${taskId}:`, error.message); throw error; } } async cleanup(projectRoot = process.cwd()) { if (!cli_utils_1.CLIUtils.validateProject(projectRoot)) { return 'Error: Not a CortexWeaver project. Run "cortex-weaver init" first.'; } console.log('๐Ÿงน Starting cleanup process...'); const sessionManager = new session_1.SessionManager(); const workspaceManager = new workspace_1.WorkspaceManager(projectRoot); let cleanedSessions = 0; let cleanedWorktrees = 0; let errors = []; try { // Clean up dead tmux sessions console.log('๐Ÿ” Cleaning up dead sessions...'); await sessionManager.cleanupDeadSessions(); // Get all active sessions and check for orphaned ones const activeSessions = await sessionManager.listActiveTmuxSessions(); const sessionList = sessionManager.listSessions(); for (const session of sessionList) { if (!activeSessions.includes(session.sessionId)) { try { await sessionManager.killSession(session.sessionId); cleanedSessions++; } catch (error) { errors.push(`Failed to clean session ${session.sessionId}: ${error.message}`); } } } // Clean up orphaned worktrees console.log('๐Ÿ” Cleaning up orphaned worktrees...'); const worktrees = await workspaceManager.listWorktrees(); for (const worktree of worktrees) { // Check if worktree directory exists but has no active session const hasActiveSession = activeSessions.some(s => s.includes(worktree.id)); if (!hasActiveSession) { try { // Check if worktree has uncommitted changes const status = await workspaceManager.getWorktreeStatus(worktree.id); if (!status.clean) { console.log(`โš ๏ธ Worktree ${worktree.id} has uncommitted changes - skipping cleanup`); continue; } await workspaceManager.removeWorktree(worktree.id); cleanedWorktrees++; } catch (error) { errors.push(`Failed to clean worktree ${worktree.id}: ${error.message}`); } } } } catch (error) { errors.push(`General cleanup error: ${error.message}`); } // Report results const report = ` ๐Ÿงน Cleanup completed! ๐Ÿ“Š Summary: - Sessions cleaned: ${cleanedSessions} - Worktrees cleaned: ${cleanedWorktrees} - Errors encountered: ${errors.length} ${errors.length > 0 ? `โš ๏ธ Errors:\n${errors.map(e => ` - ${e}`).join('\n')}` : ''} `; if (errors.length > 0) { errors.forEach(error => console.warn(`โš ๏ธ ${error}`)); } console.log(report); return report.trim(); } getTaskStatusIcon(status) { const iconMap = { 'pending': 'โณ', 'running': '๐Ÿ”„', 'completed': 'โœ…', 'failed': 'โŒ', 'impasse': '๐Ÿšง', 'error': 'โš ๏ธ' }; return iconMap[status] || 'โ“'; } } exports.CLIValidators = CLIValidators; //# sourceMappingURL=validators.js.map