claude-code-collective
Version:
Sub-agent collective framework for Claude Code with TDD validation, TaskMaster Task ID integration, hub-spoke coordination, and deterministic handoffs
411 lines (336 loc) • 11.8 kB
JavaScript
const CollectiveCommandParser = require('./command-parser');
const CommandAutocomplete = require('./command-autocomplete');
const CommandHistoryManager = require('./command-history');
const CommandHelpSystem = require('./command-help');
const EventEmitter = require('events');
const path = require('path');
class CommandSystem extends EventEmitter {
constructor(options = {}) {
super();
this.options = {
historyFile: options.historyFile || path.join(process.cwd(), '.claude-collective', 'command-history.json'),
maxHistorySize: options.maxHistorySize || 1000,
enableMetrics: options.enableMetrics !== false,
enableAutocomplete: options.enableAutocomplete !== false,
performanceThreshold: options.performanceThreshold || 100, // ms
...options
};
// Initialize components
this.parser = new CollectiveCommandParser();
this.history = new CommandHistoryManager(this.options.historyFile);
this.autocomplete = new CommandAutocomplete(this.parser);
this.help = new CommandHelpSystem();
// Performance tracking
this.metrics = {
totalCommands: 0,
successfulCommands: 0,
failedCommands: 0,
averageExecutionTime: 0,
slowCommands: []
};
this.setupEventListeners();
// System initialized
}
setupEventListeners() {
// Listen to system events only - metrics handled directly in executeCommand
this.on('system:ready', () => {
// System ready
});
}
async executeCommand(input, context = {}) {
const startTime = Date.now();
try {
// Validate input
if (typeof input !== 'string') {
throw new Error('Invalid command input');
}
const trimmedInput = input.trim();
if (!trimmedInput) {
throw new Error('Empty command');
}
// Executing command
// Parse and execute command
const result = await this.parser.parse(trimmedInput);
const executionTime = Math.max(1, Date.now() - startTime); // Ensure at least 1ms
// Add to history
await this.history.addCommand(trimmedInput, result, executionTime);
// Update metrics
this.updateCommandMetrics(result, executionTime);
// Check performance
if (executionTime > this.options.performanceThreshold) {
console.warn(`⚠️ Slow command execution: ${executionTime}ms for "${trimmedInput}"`);
this.metrics.slowCommands.push({
command: trimmedInput,
executionTime,
timestamp: new Date().toISOString()
});
}
// Emit success event
this.emit('command:completed', {
input: trimmedInput,
result,
executionTime,
context
});
return {
...result,
executionTime,
timestamp: new Date().toISOString()
};
} catch (error) {
const executionTime = Math.max(1, Date.now() - startTime); // Ensure at least 1ms
console.error(`❌ Command execution failed: ${error.message}`);
const errorResult = {
success: false,
error: error.message,
executionTime,
timestamp: new Date().toISOString()
};
// Add error to history
await this.history.addCommand(input, errorResult, executionTime);
// Emit error event
this.emit('command:error', {
input,
error: error.message,
executionTime,
context
});
return errorResult;
}
}
// Autocomplete functionality
getSuggestions(partial, context = {}) {
if (!this.options.enableAutocomplete) {
return [];
}
// Add recent command history to context
const enhancedContext = {
...context,
recentCommands: this.history.getRecentCommands(5),
systemState: this.getSystemState()
};
return this.autocomplete.getSuggestions(partial, enhancedContext);
}
// Help system integration
getHelp(query = '') {
return this.help.getHelp(query);
}
getInteractiveHelp(userInput, context = {}) {
return this.help.getInteractiveHelp(userInput, context);
}
getErrorHelp(error, command) {
return this.help.getErrorHelp(error, command);
}
// History management
getCommandHistory(limit = 10, filter = {}) {
return this.history.getHistory(limit, filter);
}
searchHistory(query, limit = 10) {
return this.history.searchHistory(query, limit);
}
getHistoryStatistics() {
return this.history.getStatistics();
}
// System state and metrics
getSystemState() {
return {
totalCommands: this.metrics.totalCommands,
successRate: this.getSuccessRate(),
averageExecutionTime: this.metrics.averageExecutionTime,
hasRecentErrors: this.hasRecentErrors(),
hasSlowCommands: this.metrics.slowCommands.length > 0
};
}
getMetrics() {
const historyStats = this.history.getStatistics();
return {
system: this.metrics,
history: historyStats,
performance: {
averageExecutionTime: this.metrics.averageExecutionTime,
slowCommandsCount: this.metrics.slowCommands.length,
performanceThreshold: this.options.performanceThreshold
},
usage: {
totalCommands: this.metrics.totalCommands,
successRate: this.getSuccessRate(),
naturalLanguageUsage: historyStats.naturalLanguageUsageRate
}
};
}
// Utility methods
updateCommandMetrics(result, executionTime) {
this.metrics.totalCommands++;
if (result.success) {
this.metrics.successfulCommands++;
} else {
this.metrics.failedCommands++;
}
// Update average execution time
const totalTime = this.metrics.averageExecutionTime * (this.metrics.totalCommands - 1) + executionTime;
this.metrics.averageExecutionTime = Math.round(totalTime / this.metrics.totalCommands);
}
// Event-based metrics removed - using direct metrics in executeCommand
getSuccessRate() {
if (this.metrics.totalCommands === 0) return 1;
return this.metrics.successfulCommands / this.metrics.totalCommands;
}
hasRecentErrors() {
const recentHistory = this.history.getHistory(5);
return recentHistory.some(entry => !entry.result.success);
}
// Command validation
validateCommand(input) {
if (!input || typeof input !== 'string') {
return { valid: false, error: 'Command must be a non-empty string' };
}
const trimmed = input.trim();
if (!trimmed) {
return { valid: false, error: 'Command cannot be empty' };
}
// Check for potentially dangerous commands (basic security)
const dangerousPatterns = [
/rm\s+-rf/,
/sudo\s+rm/,
/\.\.\/\.\.\//,
/system\(\"/,
/exec\(/
];
for (const pattern of dangerousPatterns) {
if (pattern.test(trimmed)) {
return { valid: false, error: 'Potentially dangerous command detected' };
}
}
return { valid: true };
}
// Command preprocessing
preprocessCommand(input) {
// Normalize whitespace
let processed = input.trim().replace(/\s+/g, ' ');
// Handle common typos and variations
const corrections = {
'/collecitve': '/collective',
'/agnet': '/agent',
'/gaet': '/gate',
'stauts': 'status',
'lsit': 'list'
};
for (const [typo, correction] of Object.entries(corrections)) {
processed = processed.replace(new RegExp(typo, 'gi'), correction);
}
return processed;
}
// Batch command execution
async executeBatch(commands, options = {}) {
const results = [];
const { continueOnError = false, maxConcurrency = 1 } = options;
if (maxConcurrency === 1) {
// Sequential execution
for (const command of commands) {
try {
const result = await this.executeCommand(command);
results.push(result);
if (!result.success && !continueOnError) {
break;
}
} catch (error) {
results.push({ success: false, error: error.message, command });
if (!continueOnError) {
break;
}
}
}
} else {
// Parallel execution (limited concurrency)
const chunks = this.chunkArray(commands, maxConcurrency);
for (const chunk of chunks) {
const chunkResults = await Promise.allSettled(
chunk.map(command => this.executeCommand(command))
);
for (const result of chunkResults) {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
results.push({ success: false, error: result.reason.message });
}
}
}
}
return {
total: commands.length,
successful: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length,
results
};
}
chunkArray(array, chunkSize) {
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
}
// Export functionality
async exportData(format = 'json', options = {}) {
const data = {
timestamp: new Date().toISOString(),
metrics: this.getMetrics(),
history: this.getCommandHistory(options.historyLimit || 100),
systemState: this.getSystemState()
};
if (format === 'json') {
return JSON.stringify(data, null, 2);
} else if (format === 'csv') {
// CSV export focusing on command history
return this.history.exportHistory('csv');
} else if (format === 'markdown') {
let md = '# Command System Export\n\n';
md += `**Generated:** ${data.timestamp}\n\n`;
md += '## System Metrics\n';
md += `- Total Commands: ${data.metrics.system.totalCommands}\n`;
md += `- Success Rate: ${(data.metrics.usage.successRate * 100).toFixed(1)}%\n`;
md += `- Average Execution Time: ${data.metrics.system.averageExecutionTime}ms\n`;
md += `- Natural Language Usage: ${(data.metrics.usage.naturalLanguageUsage * 100).toFixed(1)}%\n\n`;
md += '## Recent Commands\n\n';
data.history.forEach((entry, index) => {
const status = entry.result.success ? '✅' : '❌';
md += `${index + 1}. ${status} \`${entry.command}\`\n`;
});
return md;
}
throw new Error(`Unsupported export format: ${format}`);
}
// System maintenance
async performMaintenance() {
// Performing system maintenance
// Clean old history
const removedEntries = await this.history.clearOldHistory(30);
// Removed old history entries
// Clear caches
this.autocomplete.clearCache();
// Cleared autocomplete cache
// Reset slow commands tracking if too many accumulated
if (this.metrics.slowCommands.length > 100) {
this.metrics.slowCommands = this.metrics.slowCommands.slice(-20);
// Trimmed slow commands tracking
}
// Emit maintenance complete event
this.emit('maintenance:complete', {
removedHistoryEntries: removedEntries,
timestamp: new Date().toISOString()
});
// Command system maintenance complete
}
// Graceful shutdown
async shutdown() {
// Shutting down command system
// Save any pending history
await this.history.saveHistory();
// Clear autocomplete cache and timers
this.autocomplete.clearCache();
// Clear all listeners
this.removeAllListeners();
// Command system shutdown complete
}
}
module.exports = CommandSystem;