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
JavaScript
;
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