UNPKG

claude-code-collective

Version:

Sub-agent collective framework for Claude Code with TDD validation, TaskMaster Task ID integration, hub-spoke coordination, and deterministic handoffs

572 lines (455 loc) 20.8 kB
const CommandSystem = require('../lib/command-system'); const CollectiveCommandParser = require('../lib/command-parser'); const CommandAutocomplete = require('../lib/command-autocomplete'); const CommandHistoryManager = require('../lib/command-history'); const CommandHelpSystem = require('../lib/command-help'); const fs = require('fs-extra'); const path = require('path'); describe('Phase 5 - Command System Implementation', () => { let commandSystem; let tempDir; beforeEach(async () => { // Create temporary directory for test history tempDir = path.join(__dirname, 'temp', `test-${Date.now()}`); await fs.ensureDir(tempDir); commandSystem = new CommandSystem({ historyFile: path.join(tempDir, 'test-history.json'), enableMetrics: true, enableAutocomplete: true }); }); afterEach(async () => { // Clean up if (commandSystem) { await commandSystem.shutdown(); } if (tempDir && await fs.pathExists(tempDir)) { await fs.remove(tempDir); } }); describe('CollectiveCommandParser', () => { let parser; beforeEach(() => { parser = new CollectiveCommandParser(); }); test('should parse basic collective commands', async () => { const result = await parser.parse('/collective status'); expect(result.success).toBe(true); expect(result.namespace).toBe('collective'); expect(result.command).toBe('status'); expect(result.result.action).toBe('status'); }); test('should parse agent commands with arguments', async () => { const result = await parser.parse('/agent spawn testing integration'); expect(result.success).toBe(true); expect(result.namespace).toBe('agent'); expect(result.command).toBe('spawn'); expect(result.result.action).toBe('spawn'); expect(result.result.type).toBe('testing'); expect(result.result.specialization).toBe('integration'); }); test('should parse gate commands with flags', async () => { const result = await parser.parse('/gate validate implementation --strict'); expect(result.success).toBe(true); expect(result.namespace).toBe('gate'); expect(result.command).toBe('validate'); expect(result.result.strict).toBe(true); }); test('should reject natural language commands', async () => { const testCases = [ 'show status', 'list agents', 'validate gates', 'spawn testing agent' ]; for (const input of testCases) { const result = await parser.parse(input); expect(result.success).toBe(false); expect(result.error).toContain('Invalid command format'); } }); test('should handle command aliases', async () => { const testCases = [ '/c status', '/a list', '/g validate', '/status', '/route test task' ]; for (const command of testCases) { const result = await parser.parse(command); expect(result.success).toBe(true); } }); test('should provide suggestions for invalid commands', async () => { const result = await parser.parse('/collective stauts'); // typo expect(result.success).toBe(false); expect(result.suggestion).toContain('Did you mean'); }); test('should handle complex command arguments', async () => { const result = await parser.parse('/collective route "create a user authentication component with validation"'); expect(result.success).toBe(true); expect(result.result.request).toContain('authentication component'); }); }); describe('CommandAutocomplete', () => { let parser; let autocomplete; beforeEach(() => { parser = new CollectiveCommandParser(); autocomplete = new CommandAutocomplete(parser); }); afterEach(() => { if (autocomplete) { autocomplete.clearCache(); } }); test('should provide namespace suggestions', () => { const suggestions = autocomplete.getSuggestions('/col'); expect(suggestions.length).toBeGreaterThan(0); expect(suggestions[0].text).toBe('/collective '); expect(suggestions[0].type).toBe('namespace'); }); test('should provide command suggestions', () => { const suggestions = autocomplete.getSuggestions('/collective st'); expect(suggestions.length).toBeGreaterThan(0); expect(suggestions.some(s => s.text.includes('status'))).toBe(true); }); test('should provide argument suggestions', () => { const suggestions = autocomplete.getSuggestions('/agent spawn test'); expect(suggestions.length).toBeGreaterThan(0); expect(suggestions.some(s => s.text === 'testing')).toBe(true); }); test('should provide natural language suggestions', () => { const suggestions = autocomplete.getSuggestions('show stat'); expect(suggestions.length).toBeGreaterThan(0); expect(suggestions.some(s => s.type === 'natural')).toBe(true); }); test('should provide flag suggestions', () => { const suggestions = autocomplete.getSuggestions('/collective status --verb'); expect(suggestions.length).toBeGreaterThan(0); expect(suggestions.some(s => s.text === '--verbose')).toBe(true); }); test('should rank suggestions by relevance', () => { const suggestions = autocomplete.getSuggestions('/agent'); expect(suggestions).toBeTruthy(); expect(suggestions[0].priority).toBeGreaterThanOrEqual(suggestions[1]?.priority || 0); }); }); describe('CommandHistoryManager', () => { let history; let tempHistoryFile; beforeEach(async () => { tempHistoryFile = path.join(tempDir, 'history.json'); history = new CommandHistoryManager(tempHistoryFile); }); test('should add commands to history', async () => { const command = '/collective status'; const result = { success: true, namespace: 'collective', command: 'status' }; await history.addCommand(command, result, 150); const historyEntries = history.getHistory(5); expect(historyEntries.length).toBe(1); expect(historyEntries[0].command).toBe(command); expect(historyEntries[0].metadata.executionTime).toBe(150); }); test('should search command history', async () => { await history.addCommand('/collective status', { success: true }, 100); await history.addCommand('/agent list', { success: true }, 80); await history.addCommand('/gate validate', { success: true }, 200); const results = history.searchHistory('status'); expect(results.length).toBe(1); expect(results[0].command).toBe('/collective status'); }); test('should generate statistics', async () => { await history.addCommand('/collective status', { success: true }, 100); await history.addCommand('/agent list', { success: false, error: 'Test error' }, 80); await history.addCommand('/collective status', { success: true }, 120); const stats = history.getStatistics(); expect(stats.total).toBe(3); expect(stats.successful).toBe(2); expect(stats.failed).toBe(1); expect(stats.successRate).toBeCloseTo(0.67, 1); }); test('should export history in different formats', async () => { await history.addCommand('/collective status', { success: true }, 100); const jsonExport = history.exportHistory('json'); expect(jsonExport).toContain('collective status'); expect(() => JSON.parse(jsonExport)).not.toThrow(); const csvExport = history.exportHistory('csv'); expect(csvExport).toContain('Timestamp,Command'); expect(csvExport).toContain('collective status'); const mdExport = history.exportHistory('markdown'); expect(mdExport).toContain('# Command History Export'); expect(mdExport).toContain('collective status'); }); test('should persist history to disk', async () => { history.addCommand('/collective status', { success: true }, 100); await history.saveHistory(); expect(await fs.pathExists(tempHistoryFile)).toBe(true); const savedData = await fs.readJson(tempHistoryFile); expect(savedData.history.length).toBe(1); expect(savedData.history[0].command).toBe('/collective status'); }); }); describe('CommandHelpSystem', () => { let help; beforeEach(() => { help = new CommandHelpSystem(); }); test('should provide general help', () => { const generalHelp = help.getHelp(); expect(generalHelp).toContain('Claude Code Sub-Agent Collective'); expect(generalHelp).toContain('/collective'); expect(generalHelp).toContain('/agent'); expect(generalHelp).toContain('/gate'); }); test('should provide namespace-specific help', () => { const collectiveHelp = help.getHelp('collective'); expect(collectiveHelp).toContain('/collective'); expect(collectiveHelp).toContain('status'); expect(collectiveHelp).toContain('route'); expect(collectiveHelp).toContain('agents'); }); test('should provide command-specific help', () => { const commandHelp = help.getHelp('collective route'); expect(commandHelp).toContain('/collective route'); expect(commandHelp).toContain('Route request to appropriate agent'); expect(commandHelp).toContain('Examples'); }); test('should handle unknown namespaces', () => { const unknownHelp = help.getHelp('unknown'); expect(unknownHelp).toContain('Unknown namespace'); expect(unknownHelp).toContain('Available namespaces'); }); test('should handle unknown commands', () => { const unknownCommand = help.getHelp('collective unknown'); expect(unknownCommand).toContain('Unknown command'); expect(unknownCommand).toContain('Available commands'); }); test('should provide interactive help', () => { const interactiveHelp = help.getInteractiveHelp('how do I use /collective route'); expect(interactiveHelp).toContain('/collective route'); expect(interactiveHelp).toBeTruthy(); }); test('should provide error-specific help', () => { const errorHelp = help.getErrorHelp('Unknown command: /collective stauts', '/collective stauts'); expect(errorHelp).toContain('Command Error Help'); expect(errorHelp).toContain('Unknown command'); expect(errorHelp).toContain('Suggestion'); }); }); describe('CommandSystem Integration', () => { test('should execute commands successfully', async () => { const result = await commandSystem.executeCommand('/collective status'); expect(result.success).toBe(true); expect(result.executionTime).toBeGreaterThan(0); expect(result.timestamp).toBeTruthy(); }); test('should reject natural language commands', async () => { const result = await commandSystem.executeCommand('show system status'); expect(result.success).toBe(false); expect(result.error).toContain('Invalid command format'); }); test('should provide autocomplete suggestions', () => { const suggestions = commandSystem.getSuggestions('/coll'); expect(suggestions.length).toBeGreaterThan(0); expect(suggestions[0].text).toContain('/collective'); }); test('should maintain command history', async () => { await commandSystem.executeCommand('/collective status'); await commandSystem.executeCommand('/agent list'); const history = commandSystem.getCommandHistory(5); expect(history.length).toBe(2); expect(history[0].command).toBe('/agent list'); expect(history[1].command).toBe('/collective status'); }); test('should track performance metrics', async () => { await commandSystem.executeCommand('/collective status'); await commandSystem.executeCommand('/agent list'); const metrics = commandSystem.getMetrics(); expect(metrics.system.totalCommands).toBe(2); expect(metrics.system.successfulCommands).toBe(2); expect(metrics.usage.successRate).toBe(1); }); test('should handle command validation', () => { expect(commandSystem.validateCommand('').valid).toBe(false); expect(commandSystem.validateCommand(null).valid).toBe(false); expect(commandSystem.validateCommand('/collective status').valid).toBe(true); }); test('should preprocess commands', () => { expect(commandSystem.preprocessCommand(' /collective status ')).toBe('/collective status'); expect(commandSystem.preprocessCommand('/collecitve status')).toBe('/collective status'); }); test('should execute batch commands', async () => { const commands = ['/collective status', '/agent list', '/gate status']; const results = await commandSystem.executeBatch(commands); expect(results.total).toBe(3); expect(results.successful).toBe(3); expect(results.failed).toBe(0); expect(results.results.length).toBe(3); }); test('should export system data', async () => { await commandSystem.executeCommand('/collective status'); const jsonExport = await commandSystem.exportData('json'); expect(jsonExport).toContain('timestamp'); expect(jsonExport).toContain('metrics'); expect(() => JSON.parse(jsonExport)).not.toThrow(); const mdExport = await commandSystem.exportData('markdown'); expect(mdExport).toContain('# Command System Export'); expect(mdExport).toContain('System Metrics'); }); test('should perform system maintenance', async () => { const maintenancePromise = new Promise((resolve) => { commandSystem.once('maintenance:complete', resolve); }); await commandSystem.performMaintenance(); const event = await maintenancePromise; expect(event.timestamp).toBeTruthy(); }); test('should handle errors gracefully', async () => { const result = await commandSystem.executeCommand('/invalid command format'); expect(result.success).toBe(false); expect(result.error).toBeTruthy(); expect(result.executionTime).toBeGreaterThan(0); }); test('should measure performance', async () => { const startTime = Date.now(); const result = await commandSystem.executeCommand('/collective status'); const endTime = Date.now(); expect(result.executionTime).toBeGreaterThan(0); expect(result.executionTime).toBeLessThanOrEqual(Math.max(1, endTime - startTime)); }); }); describe('Command System Edge Cases', () => { test('should handle empty commands', async () => { // Mock console.error to verify it's called but not display the message const originalConsoleError = console.error; const errorMessages = []; console.error = (...args) => { errorMessages.push(args.join(' ')); }; const result = await commandSystem.executeCommand(''); expect(result.success).toBe(false); expect(result.error).toContain('Empty command'); // Verify error was logged expect(errorMessages.length).toBe(1); expect(errorMessages[0]).toContain('Command execution failed: Empty command'); // Restore console.error console.error = originalConsoleError; }); test('should handle very long commands', async () => { const longCommand = '/collective route ' + 'a'.repeat(1000); const result = await commandSystem.executeCommand(longCommand); expect(result.success).toBe(true); }); test('should handle special characters', async () => { const result = await commandSystem.executeCommand('/collective route "test with special chars: !@#$%^&*()"'); expect(result.success).toBe(true); }); test('should handle concurrent commands', async () => { const promises = [ commandSystem.executeCommand('/collective status'), commandSystem.executeCommand('/agent list'), commandSystem.executeCommand('/gate status') ]; const results = await Promise.all(promises); expect(results.every(r => r.success)).toBe(true); }); test('should handle command with quotes', async () => { const result = await commandSystem.executeCommand('/gate bypass testing-gate "Emergency deployment for critical bug fix"'); expect(result.success).toBe(true); expect(result.result.reason).toBe('Emergency deployment for critical bug fix'); }); }); describe('Performance Requirements', () => { test('should execute simple commands under 100ms', async () => { const result = await commandSystem.executeCommand('/collective status'); expect(result.executionTime).toBeLessThan(100); }); test('should handle autocomplete under 50ms', () => { const start = Date.now(); commandSystem.getSuggestions('/collective st'); const duration = Date.now() - start; expect(duration).toBeLessThan(50); }); test('should maintain performance with large history', async () => { // Add many commands to history for (let i = 0; i < 100; i++) { await commandSystem.executeCommand('/collective status'); } const start = Date.now(); const result = await commandSystem.executeCommand('/collective status'); const duration = Date.now() - start; expect(duration).toBeLessThan(200); }); }); describe('Natural Language Processing', () => { test('should reject natural language commands', async () => { const queries = [ 'show me the status', 'what is the system status', 'how is everything', 'check system health', 'display current state' ]; for (const query of queries) { const result = await commandSystem.executeCommand(query); expect(result.success).toBe(false); expect(result.error).toContain('Invalid command format'); } }); test('should suggest proper command format', async () => { const result = await commandSystem.executeCommand('show status'); expect(result.success).toBe(false); expect(result.error).toContain('Commands must start with /'); }); }); }); // Test coverage and validation describe('Phase 5 Validation Criteria', () => { test('should meet all Phase 5 requirements', () => { // Command System Success expect(CollectiveCommandParser).toBeDefined(); expect(CommandAutocomplete).toBeDefined(); expect(CommandHistoryManager).toBeDefined(); expect(CommandHelpSystem).toBeDefined(); expect(CommandSystem).toBeDefined(); // User Experience const parser = new CollectiveCommandParser(); expect(parser.parseNaturalLanguage).toBeDefined(); expect(parser.getSuggestion).toBeDefined(); // Integration Success const commandSystem = new CommandSystem(); expect(commandSystem.executeCommand).toBeDefined(); expect(commandSystem.getSuggestions).toBeDefined(); expect(commandSystem.getHelp).toBeDefined(); }); test('should support all required command namespaces', () => { const parser = new CollectiveCommandParser(); // Collective commands expect(parser.commands.has('collective:status')).toBe(true); expect(parser.commands.has('collective:route')).toBe(true); expect(parser.commands.has('collective:agents')).toBe(true); expect(parser.commands.has('collective:metrics')).toBe(true); expect(parser.commands.has('collective:validate')).toBe(true); // Agent commands expect(parser.commands.has('agent:list')).toBe(true); expect(parser.commands.has('agent:spawn')).toBe(true); expect(parser.commands.has('agent:status')).toBe(true); expect(parser.commands.has('agent:route')).toBe(true); // Gate commands expect(parser.commands.has('gate:status')).toBe(true); expect(parser.commands.has('gate:validate')).toBe(true); expect(parser.commands.has('gate:bypass')).toBe(true); expect(parser.commands.has('gate:history')).toBe(true); }); test('should support required aliases', () => { const parser = new CollectiveCommandParser(); expect(parser.aliases.has('/c')).toBe(true); expect(parser.aliases.has('/a')).toBe(true); expect(parser.aliases.has('/g')).toBe(true); expect(parser.aliases.has('/status')).toBe(true); expect(parser.aliases.has('/route')).toBe(true); expect(parser.aliases.has('/spawn')).toBe(true); }); });