/** * V3 CLI Deep Commands & Infrastructure Tests * 100+ test cases covering all commands, init system, parser, output, and suggest modules */ import { describe, it, expect, beforeEach, vi } from 'vitest'; // ============================================================================ // SECTION 1: Command Definitions (38+ commands) // ============================================================================ // Mock MCP client before importing commands vi.mock('../src/mcp-client.js', () => ({ callMCPTool: vi.fn(async () => ({})), MCPClientError: class MCPClientError extends Error { constructor(message: string) { super(message); this.name = 'MCPClientError'; } }, })); // Mock fs/promises for commands that use it vi.mock('fs/promises', () => ({ default: { readFile: vi.fn(async () => '{}'), writeFile: vi.fn(async () => undefined), mkdir: vi.fn(async () => undefined), access: vi.fn(async () => undefined), readdir: vi.fn(async () => []), stat: vi.fn(async () => ({ isDirectory: () => true, isFile: () => true })), unlink: vi.fn(async () => undefined), rm: vi.fn(async () => undefined), cp: vi.fn(async () => undefined), }, readFile: vi.fn(async () => '{}'), writeFile: vi.fn(async () => undefined), mkdir: vi.fn(async () => undefined), access: vi.fn(async () => undefined), readdir: vi.fn(async () => []), stat: vi.fn(async () => ({ isDirectory: () => true, isFile: () => true })), unlink: vi.fn(async () => undefined), rm: vi.fn(async () => undefined), cp: vi.fn(async () => undefined), })); // Mock child_process vi.mock('child_process', () => ({ execSync: vi.fn(() => Buffer.from('')), execFileSync: vi.fn(() => ''), exec: vi.fn(), execFile: vi.fn(), spawn: vi.fn(() => ({ on: vi.fn(), stdout: { on: vi.fn() }, stderr: { on: vi.fn() }, pid: 12345, })), })); import { agentCommand } from '../src/commands/agent.js'; import { swarmCommand } from '../src/commands/swarm.js'; import { memoryCommand } from '../src/commands/memory.js'; import { configCommand } from '../src/commands/config.js'; import { initCommand } from '../src/commands/init.js'; import { statusCommand } from '../src/commands/status.js'; import { taskCommand } from '../src/commands/task.js'; import { sessionCommand } from '../src/commands/session.js'; import { mcpCommand } from '../src/commands/mcp.js'; import { hooksCommand } from '../src/commands/hooks.js'; import { daemonCommand } from '../src/commands/daemon.js'; import { doctorCommand } from '../src/commands/doctor.js'; import { embeddingsCommand } from '../src/commands/embeddings.js'; import { neuralCommand } from '../src/commands/neural.js'; import { performanceCommand } from '../src/commands/performance.js'; import { securityCommand } from '../src/commands/security.js'; import { hiveMindCommand } from '../src/commands/hive-mind.js'; import { completionsCommand } from '../src/commands/completions.js'; import { migrateCommand } from '../src/commands/migrate.js'; import { workflowCommand } from '../src/commands/workflow.js'; import { claimsCommand } from '../src/commands/claims.js'; import { deploymentCommand } from '../src/commands/deployment.js'; import { providersCommand } from '../src/commands/providers.js'; import { pluginsCommand } from '../src/commands/plugins.js'; import { processCommand } from '../src/commands/process.js'; import { startCommand } from '../src/commands/start.js'; import { analyzeCommand } from '../src/commands/analyze.js'; import { routeCommand } from '../src/commands/route.js'; import { progressCommand } from '../src/commands/progress.js'; import { issuesCommand } from '../src/commands/issues.js'; import { guidanceCommand } from '../src/commands/guidance.js'; import { applianceCommand } from '../src/commands/appliance.js'; import updateCommand from '../src/commands/update.js'; import { ruvectorCommand } from '../src/commands/ruvector/index.js'; import type { Command } from '../src/types.js'; // ============================================================================ // Helper: validate a command structure // ============================================================================ function expectValidCommand(cmd: Command, expectedName: string) { expect(cmd).toBeDefined(); expect(cmd.name).toBe(expectedName); expect(typeof cmd.description).toBe('string'); expect(cmd.description.length).toBeGreaterThan(0); } function expectHasSubcommands(cmd: Command, minCount: number) { expect(cmd.subcommands).toBeDefined(); expect(Array.isArray(cmd.subcommands)).toBe(true); expect(cmd.subcommands!.length).toBeGreaterThanOrEqual(minCount); } function expectSubcommandNames(cmd: Command, names: string[]) { const subNames = cmd.subcommands!.map((s) => s.name); for (const name of names) { expect(subNames).toContain(name); } } // ============================================================================ // 1. Command Structure Tests // ============================================================================ describe('Command Definitions', () => { describe('agent command', () => { it('should have correct name and description', () => { expectValidCommand(agentCommand, 'agent'); }); it('should have 8 subcommands', () => { expectHasSubcommands(agentCommand, 8); expectSubcommandNames(agentCommand, [ 'spawn', 'list', 'status', 'stop', 'metrics', 'pool', 'health', 'logs', ]); }); it('should have an action or subcommands with actions', () => { const hasAction = typeof agentCommand.action === 'function'; const hasSubActions = agentCommand.subcommands?.some((s) => typeof s.action === 'function'); expect(hasAction || hasSubActions).toBe(true); }); it('each subcommand should have name and description', () => { for (const sub of agentCommand.subcommands!) { expect(typeof sub.name).toBe('string'); expect(typeof sub.description).toBe('string'); } }); }); describe('swarm command', () => { it('should have correct name', () => { expectValidCommand(swarmCommand, 'swarm'); }); it('should have subcommands including init, status, deploy, scale', () => { expectHasSubcommands(swarmCommand, 4); expectSubcommandNames(swarmCommand, ['init', 'status']); }); }); describe('memory command', () => { it('should have correct name', () => { expectValidCommand(memoryCommand, 'memory'); }); it('should have subcommands for store, retrieve, search, list', () => { expectHasSubcommands(memoryCommand, 4); expectSubcommandNames(memoryCommand, ['store', 'retrieve', 'search', 'list']); }); }); describe('config command', () => { it('should have correct name', () => { expectValidCommand(configCommand, 'config'); }); it('should have subcommands including init, get, set', () => { expectHasSubcommands(configCommand, 3); expectSubcommandNames(configCommand, ['init', 'get', 'set']); }); }); describe('init command', () => { it('should have correct name', () => { expectValidCommand(initCommand, 'init'); }); it('should have options or subcommands', () => { const hasOptions = initCommand.options && initCommand.options.length > 0; const hasSubs = initCommand.subcommands && initCommand.subcommands.length > 0; expect(hasOptions || hasSubs).toBe(true); }); }); describe('status command', () => { it('should have correct name', () => { expectValidCommand(statusCommand, 'status'); }); }); describe('task command', () => { it('should have correct name', () => { expectValidCommand(taskCommand, 'task'); }); it('should have subcommands', () => { expectHasSubcommands(taskCommand, 3); }); }); describe('session command', () => { it('should have correct name', () => { expectValidCommand(sessionCommand, 'session'); }); it('should have subcommands for start, end, restore, list', () => { expectHasSubcommands(sessionCommand, 4); }); }); describe('mcp command', () => { it('should have correct name', () => { expectValidCommand(mcpCommand, 'mcp'); }); it('should have subcommands', () => { expectHasSubcommands(mcpCommand, 3); }); }); describe('hooks command', () => { it('should have correct name', () => { expectValidCommand(hooksCommand, 'hooks'); }); it('should have many subcommands (17+)', () => { expectHasSubcommands(hooksCommand, 10); }); }); describe('daemon command', () => { it('should have correct name', () => { expectValidCommand(daemonCommand, 'daemon'); }); it('should have subcommands for start, stop, status', () => { expectHasSubcommands(daemonCommand, 3); expectSubcommandNames(daemonCommand, ['start', 'stop', 'status']); }); }); describe('doctor command', () => { it('should have correct name', () => { expectValidCommand(doctorCommand, 'doctor'); }); }); describe('embeddings command', () => { it('should have correct name', () => { expectValidCommand(embeddingsCommand, 'embeddings'); }); it('should have subcommands', () => { expectHasSubcommands(embeddingsCommand, 3); }); }); describe('neural command', () => { it('should have correct name', () => { expectValidCommand(neuralCommand, 'neural'); }); it('should have subcommands', () => { expectHasSubcommands(neuralCommand, 3); }); }); describe('performance command', () => { it('should have correct name', () => { expectValidCommand(performanceCommand, 'performance'); }); it('should have subcommands', () => { expectHasSubcommands(performanceCommand, 3); }); }); describe('security command', () => { it('should have correct name', () => { expectValidCommand(securityCommand, 'security'); }); it('should have subcommands', () => { expectHasSubcommands(securityCommand, 3); }); }); describe('hive-mind command', () => { it('should have correct name', () => { expectValidCommand(hiveMindCommand, 'hive-mind'); }); it('should have subcommands', () => { expectHasSubcommands(hiveMindCommand, 3); }); }); describe('completions command', () => { it('should have correct name', () => { expectValidCommand(completionsCommand, 'completions'); }); it('should have subcommands for bash, zsh, fish, powershell', () => { expectHasSubcommands(completionsCommand, 4); expectSubcommandNames(completionsCommand, ['bash', 'zsh', 'fish', 'powershell']); }); }); describe('migrate command', () => { it('should have correct name', () => { expectValidCommand(migrateCommand, 'migrate'); }); it('should have subcommands', () => { expectHasSubcommands(migrateCommand, 3); }); }); describe('workflow command', () => { it('should have correct name', () => { expectValidCommand(workflowCommand, 'workflow'); }); it('should have subcommands', () => { expectHasSubcommands(workflowCommand, 3); }); }); describe('claims command', () => { it('should have correct name', () => { expectValidCommand(claimsCommand, 'claims'); }); it('should have subcommands', () => { expectHasSubcommands(claimsCommand, 3); }); }); describe('deployment command', () => { it('should have correct name', () => { expectValidCommand(deploymentCommand, 'deployment'); }); it('should have subcommands', () => { expectHasSubcommands(deploymentCommand, 3); }); }); describe('providers command', () => { it('should have correct name', () => { expectValidCommand(providersCommand, 'providers'); }); it('should have subcommands', () => { expectHasSubcommands(providersCommand, 3); }); }); describe('plugins command', () => { it('should have correct name', () => { expectValidCommand(pluginsCommand, 'plugins'); }); it('should have subcommands', () => { expectHasSubcommands(pluginsCommand, 3); }); }); describe('process command', () => { it('should have correct name', () => { expectValidCommand(processCommand, 'process'); }); }); describe('start command', () => { it('should have correct name', () => { expectValidCommand(startCommand, 'start'); }); }); describe('analyze command', () => { it('should have correct name', () => { expectValidCommand(analyzeCommand, 'analyze'); }); it('should have subcommands', () => { expectHasSubcommands(analyzeCommand, 2); }); }); describe('route command', () => { it('should have correct name', () => { expectValidCommand(routeCommand, 'route'); }); }); describe('progress command', () => { it('should have correct name', () => { expectValidCommand(progressCommand, 'progress'); }); }); describe('issues command', () => { it('should have correct name', () => { expectValidCommand(issuesCommand, 'issues'); }); }); describe('update command', () => { it('should have correct name', () => { expectValidCommand(updateCommand, 'update'); }); }); describe('guidance command', () => { it('should have correct name', () => { expectValidCommand(guidanceCommand, 'guidance'); }); it('should have subcommands', () => { expectHasSubcommands(guidanceCommand, 3); }); }); describe('appliance command', () => { it('should have correct name', () => { expectValidCommand(applianceCommand, 'appliance'); }); it('should have subcommands including build, inspect, verify', () => { expectHasSubcommands(applianceCommand, 3); }); }); describe('ruvector command', () => { it('should have correct name', () => { expectValidCommand(ruvectorCommand, 'ruvector'); }); }); }); // All commands array for bulk tests const allCommands: { cmd: Command; name: string }[] = [ { cmd: agentCommand, name: 'agent' }, { cmd: swarmCommand, name: 'swarm' }, { cmd: memoryCommand, name: 'memory' }, { cmd: configCommand, name: 'config' }, { cmd: initCommand, name: 'init' }, { cmd: statusCommand, name: 'status' }, { cmd: taskCommand, name: 'task' }, { cmd: sessionCommand, name: 'session' }, { cmd: mcpCommand, name: 'mcp' }, { cmd: hooksCommand, name: 'hooks' }, { cmd: daemonCommand, name: 'daemon' }, { cmd: doctorCommand, name: 'doctor' }, { cmd: embeddingsCommand, name: 'embeddings' }, { cmd: neuralCommand, name: 'neural' }, { cmd: performanceCommand, name: 'performance' }, { cmd: securityCommand, name: 'security' }, { cmd: hiveMindCommand, name: 'hive-mind' }, { cmd: completionsCommand, name: 'completions' }, { cmd: migrateCommand, name: 'migrate' }, { cmd: workflowCommand, name: 'workflow' }, { cmd: claimsCommand, name: 'claims' }, { cmd: deploymentCommand, name: 'deployment' }, { cmd: providersCommand, name: 'providers' }, { cmd: pluginsCommand, name: 'plugins' }, { cmd: processCommand, name: 'process' }, { cmd: startCommand, name: 'start' }, { cmd: analyzeCommand, name: 'analyze' }, { cmd: routeCommand, name: 'route' }, { cmd: progressCommand, name: 'progress' }, { cmd: issuesCommand, name: 'issues' }, { cmd: updateCommand, name: 'update' }, { cmd: guidanceCommand, name: 'guidance' }, { cmd: applianceCommand, name: 'appliance' }, { cmd: ruvectorCommand, name: 'ruvector' }, ]; describe('Bulk Command Structure Validation', () => { it.each(allCommands)('$name: should have non-empty description', ({ cmd }) => { expect(cmd.description).toBeTruthy(); expect(cmd.description.length).toBeGreaterThan(5); }); it.each(allCommands)('$name: description should not contain placeholder text', ({ cmd }) => { expect(cmd.description).not.toContain('TODO'); expect(cmd.description).not.toContain('FIXME'); expect(cmd.description).not.toContain('placeholder'); }); it.each(allCommands)('$name: subcommands (if any) should have unique names', ({ cmd }) => { if (cmd.subcommands && cmd.subcommands.length > 0) { const names = cmd.subcommands.map((s) => s.name); const uniqueNames = new Set(names); expect(uniqueNames.size).toBe(names.length); } }); it.each(allCommands)('$name: options (if any) should have valid types', ({ cmd }) => { if (cmd.options) { for (const opt of cmd.options) { expect(['string', 'boolean', 'number', 'array']).toContain(opt.type); } } }); it.each(allCommands)( '$name: subcommand descriptions should be non-empty strings', ({ cmd }) => { if (cmd.subcommands) { for (const sub of cmd.subcommands) { expect(typeof sub.description).toBe('string'); expect(sub.description.length).toBeGreaterThan(0); } } }, ); }); // ============================================================================ // SECTION 2: Command Registry Tests // ============================================================================ describe('Command Registry (commands/index.ts)', () => { it('should export all core commands synchronously', async () => { const indexModule = await import('../src/commands/index.js'); expect(indexModule.agentCommand).toBeDefined(); expect(indexModule.swarmCommand).toBeDefined(); expect(indexModule.memoryCommand).toBeDefined(); expect(indexModule.initCommand).toBeDefined(); expect(indexModule.statusCommand).toBeDefined(); expect(indexModule.taskCommand).toBeDefined(); expect(indexModule.sessionCommand).toBeDefined(); expect(indexModule.mcpCommand).toBeDefined(); expect(indexModule.hooksCommand).toBeDefined(); // Advanced commands are lazy-loaded (PERF-03) — verify async getters exist expect(typeof indexModule.getCommandAsync).toBe('function'); expect(typeof indexModule.loadAllCommands).toBe('function'); }); it('should export commandsByCategory with all categories', async () => { const indexModule = await import('../src/commands/index.js'); expect(indexModule.commandsByCategory).toBeDefined(); const categories = Object.keys(indexModule.commandsByCategory); expect(categories.length).toBeGreaterThanOrEqual(3); }); it('should export commands array with many entries', async () => { const indexModule = await import('../src/commands/index.js'); expect(Array.isArray(indexModule.commands)).toBe(true); expect(indexModule.commands.length).toBeGreaterThanOrEqual(10); }); it('should export loadAllCommands as async function', async () => { const indexModule = await import('../src/commands/index.js'); expect(typeof indexModule.loadAllCommands).toBe('function'); }); it('should export getCommandAsync as async function', async () => { const indexModule = await import('../src/commands/index.js'); expect(typeof indexModule.getCommandAsync).toBe('function'); }); }); // ============================================================================ // SECTION 3: Parser Tests // ============================================================================ import { CommandParser, commandParser } from '../src/parser.js'; import type { ParsedFlags } from '../src/types.js'; describe('CommandParser', () => { let parser: CommandParser; beforeEach(() => { parser = new CommandParser({ allowUnknownFlags: true }); }); describe('basic parsing', () => { it('should parse empty args', () => { const result = parser.parse([]); expect(result.command).toEqual([]); expect(result.positional).toEqual([]); expect(result.flags._).toEqual([]); }); it('should parse a single positional arg as positional (not registered command)', () => { const result = parser.parse(['foo']); expect(result.positional).toContain('foo'); }); it('should parse long boolean flag --help', () => { const result = parser.parse(['--help']); expect(result.flags.help).toBe(true); }); it('should parse long boolean flag --verbose', () => { const result = parser.parse(['--verbose']); expect(result.flags.verbose).toBe(true); }); it('should parse --flag=value syntax', () => { const result = parser.parse(['--format=json']); expect(result.flags.format).toBe('json'); }); it('should parse --flag value syntax for non-boolean flags', () => { const result = parser.parse(['--config', '/path/to/config']); expect(result.flags.config).toBe('/path/to/config'); }); it('should parse boolean negation --no-color', () => { const result = parser.parse(['--no-color']); expect(result.flags.color).toBe(false); }); it('should parse boolean negation --no-interactive', () => { const result = parser.parse(['--no-interactive']); expect(result.flags.interactive).toBe(false); }); }); describe('short flags', () => { it('should resolve short flag -h to help', () => { const result = parser.parse(['-h']); expect(result.flags.help).toBe(true); }); it('should resolve short flag -v to verbose', () => { const result = parser.parse(['-v']); expect(result.flags.verbose).toBe(true); }); it('should resolve short flag -Q to quiet', () => { const result = parser.parse(['-Q']); expect(result.flags.quiet).toBe(true); }); it('should parse combined short flags -hv', () => { const result = parser.parse(['-hv']); expect(result.flags.help).toBe(true); expect(result.flags.verbose).toBe(true); }); it('should parse short flag with value -c /path', () => { const result = parser.parse(['-c', '/path/config.json']); expect(result.flags.config).toBe('/path/config.json'); }); it('should parse long flag --format with value', () => { // Short flag -f removed from global options — collides with 50+ subcommands (#1425) const result = parser.parse(['--format', 'json']); expect(result.flags.format).toBe('json'); }); }); describe('value type coercion', () => { it('should coerce numeric string to number', () => { const result = parser.parse(['--port=3000']); expect(result.flags.port).toBe(3000); }); it('should coerce "true" string to boolean true', () => { const result = parser.parse(['--debug=true']); expect(result.flags.debug).toBe(true); }); it('should coerce "false" string to boolean false', () => { const result = parser.parse(['--debug=false']); expect(result.flags.debug).toBe(false); }); it('should keep non-numeric strings as strings', () => { const result = parser.parse(['--name=my-project']); expect(result.flags.name).toBe('my-project'); }); it('should handle empty string value', () => { const result = parser.parse(['--name=']); expect(result.flags.name).toBe(''); }); }); describe('kebab-case to camelCase normalization', () => { it('should convert --no-color to noColor flag (negation)', () => { // --no-color is boolean negation => flags.color = false const result = parser.parse(['--no-color']); expect(result.flags.color).toBe(false); }); it('should convert --some-flag to someFlag', () => { const result = parser.parse(['--some-flag=value']); expect(result.flags.someFlag).toBe('value'); }); it('should convert --my-long-option to myLongOption', () => { const result = parser.parse(['--my-long-option=test']); expect(result.flags.myLongOption).toBe('test'); }); }); describe('end-of-flags marker --', () => { it('should stop parsing flags after --', () => { const result = parser.parse(['--verbose', '--', '--not-a-flag']); expect(result.flags.verbose).toBe(true); expect(result.positional).toContain('--not-a-flag'); }); }); describe('command recognition', () => { it('should recognize registered command', () => { parser.registerCommand(agentCommand); const result = parser.parse(['agent']); expect(result.command).toEqual(['agent']); }); it('should recognize command with subcommand', () => { parser.registerCommand(agentCommand); const result = parser.parse(['agent', 'spawn']); expect(result.command).toEqual(['agent', 'spawn']); }); it('should recognize command with subcommand and flags', () => { parser.registerCommand(agentCommand); const result = parser.parse(['agent', 'spawn', '--type', 'coder']); expect(result.command).toEqual(['agent', 'spawn']); expect(result.flags.type).toBe('coder'); }); it('should handle unknown subcommand as positional', () => { parser.registerCommand(agentCommand); const result = parser.parse(['agent', 'nonexistent']); // 'agent' is recognized, 'nonexistent' won't match a subcommand expect(result.command).toEqual(['agent']); }); }); describe('defaults', () => { it('should apply global defaults for help', () => { const result = parser.parse([]); expect(result.flags.help).toBe(false); }); it('should apply global defaults for verbose', () => { const result = parser.parse([]); expect(result.flags.verbose).toBe(false); }); it('should apply global defaults for format', () => { const result = parser.parse([]); expect(result.flags.format).toBe('text'); }); it('should apply global defaults for interactive', () => { const result = parser.parse([]); expect(result.flags.interactive).toBe(true); }); it('should not override explicit values with defaults', () => { const result = parser.parse(['--verbose']); expect(result.flags.verbose).toBe(true); }); }); describe('validation', () => { it('should return no errors for valid flags', () => { const flags: ParsedFlags = { _: [], help: false, verbose: true, format: 'json' }; const errors = parser.validateFlags(flags); expect(errors).toEqual([]); }); it('should validate choices for format flag', () => { const flags: ParsedFlags = { _: [], format: 'invalid-format' }; const errors = parser.validateFlags(flags); expect(errors.some((e) => e.includes('format'))).toBe(true); }); it('should report required options as missing', () => { const cmdWithRequired: Command = { name: 'test', description: 'test', options: [ { name: 'required-opt', description: 'Required option', type: 'string', required: true, }, ], }; const flags: ParsedFlags = { _: [] }; const errors = parser.validateFlags(flags, cmdWithRequired); expect(errors.length).toBeGreaterThan(0); expect(errors[0]).toContain('required-opt'); }); }); describe('getAllCommands', () => { it('should return unique commands (no alias duplicates)', () => { parser.registerCommand(agentCommand); parser.registerCommand(swarmCommand); const all = parser.getAllCommands(); const names = all.map((c) => c.name); const unique = new Set(names); expect(unique.size).toBe(names.length); }); }); describe('getGlobalOptions', () => { it('should return global options array', () => { const opts = parser.getGlobalOptions(); expect(Array.isArray(opts)).toBe(true); expect(opts.length).toBeGreaterThanOrEqual(5); }); it('should include help, version, verbose, quiet, config, format', () => { const opts = parser.getGlobalOptions(); const names = opts.map((o) => o.name); expect(names).toContain('help'); expect(names).toContain('version'); expect(names).toContain('verbose'); expect(names).toContain('quiet'); expect(names).toContain('config'); expect(names).toContain('format'); }); }); describe('singleton export', () => { it('commandParser singleton should be an instance of CommandParser', () => { expect(commandParser).toBeInstanceOf(CommandParser); }); it('commandParser should have allowUnknownFlags enabled', () => { // It should not report errors for unknown flags const flags: ParsedFlags = { _: [], someRandomFlag: 'value' }; const errors = commandParser.validateFlags(flags); // With allowUnknownFlags, no error for unknown flags const unknownErrors = errors.filter((e) => e.includes('Unknown option')); expect(unknownErrors).toEqual([]); }); }); }); // ============================================================================ // SECTION 4: Output Formatter Tests // ============================================================================ import { OutputFormatter, Progress, Spinner, output } from '../src/output.js'; describe('OutputFormatter', () => { let formatter: OutputFormatter; beforeEach(() => { formatter = new OutputFormatter({ color: false }); }); describe('color methods (color disabled)', () => { it('bold() should return text without ANSI codes when color disabled', () => { const result = formatter.bold('hello'); expect(result).toBe('hello'); }); it('dim() should return text without ANSI codes when color disabled', () => { const result = formatter.dim('hello'); expect(result).toBe('hello'); }); it('success() should return text without ANSI codes when color disabled', () => { const result = formatter.success('done'); expect(result).toBe('done'); }); it('error() should return text without ANSI codes when color disabled', () => { const result = formatter.error('fail'); expect(result).toBe('fail'); }); it('warning() should return text without ANSI codes when color disabled', () => { const result = formatter.warning('warn'); expect(result).toBe('warn'); }); it('info() should return text without ANSI codes when color disabled', () => { const result = formatter.info('info'); expect(result).toBe('info'); }); it('highlight() should return text without ANSI codes when color disabled', () => { const result = formatter.highlight('hi'); expect(result).toBe('hi'); }); }); describe('color methods (color enabled)', () => { let colorFormatter: OutputFormatter; beforeEach(() => { colorFormatter = new OutputFormatter({ color: true }); }); it('bold() should wrap text with ANSI bold codes', () => { const result = colorFormatter.bold('hello'); expect(result).toContain('\x1b[1m'); expect(result).toContain('hello'); expect(result).toContain('\x1b[0m'); }); it('error() should wrap text with red ANSI codes', () => { const result = colorFormatter.error('fail'); expect(result).toContain('\x1b[31m'); expect(result).toContain('fail'); }); it('success() should wrap text with green ANSI codes', () => { const result = colorFormatter.success('pass'); expect(result).toContain('\x1b[32m'); }); it('warning() should wrap text with yellow ANSI codes', () => { const result = colorFormatter.warning('caution'); expect(result).toContain('\x1b[33m'); }); it('info() should wrap text with blue ANSI codes', () => { const result = colorFormatter.info('note'); expect(result).toContain('\x1b[34m'); }); }); describe('verbosity', () => { it('should default to normal verbosity', () => { expect(formatter.getVerbosity()).toBe('normal'); }); it('should set verbosity', () => { formatter.setVerbosity('quiet'); expect(formatter.getVerbosity()).toBe('quiet'); expect(formatter.isQuiet()).toBe(true); }); it('isVerbose should return true for verbose and debug', () => { formatter.setVerbosity('verbose'); expect(formatter.isVerbose()).toBe(true); formatter.setVerbosity('debug'); expect(formatter.isVerbose()).toBe(true); }); it('isDebug should return true only for debug', () => { formatter.setVerbosity('verbose'); expect(formatter.isDebug()).toBe(false); formatter.setVerbosity('debug'); expect(formatter.isDebug()).toBe(true); }); it('isQuiet should return true only for quiet', () => { formatter.setVerbosity('normal'); expect(formatter.isQuiet()).toBe(false); formatter.setVerbosity('quiet'); expect(formatter.isQuiet()).toBe(true); }); }); describe('table formatting', () => { it('should render a basic table with borders', () => { const result = formatter.table({ columns: [ { key: 'name', header: 'Name' }, { key: 'value', header: 'Value' }, ], data: [ { name: 'a', value: '1' }, { name: 'b', value: '2' }, ], }); expect(result).toContain('Name'); expect(result).toContain('Value'); expect(result).toContain('a'); expect(result).toContain('b'); expect(result).toContain('|'); expect(result).toContain('+'); }); it('should render a table without borders', () => { const result = formatter.table({ columns: [{ key: 'x', header: 'X' }], data: [{ x: 'hello' }], border: false, }); expect(result).not.toContain('+'); expect(result).toContain('hello'); }); it('should render a table without header', () => { const result = formatter.table({ columns: [{ key: 'x', header: 'X' }], data: [{ x: 'val' }], header: false, }); expect(result).toContain('val'); }); it('should apply column formatter', () => { const result = formatter.table({ columns: [ { key: 'x', header: 'X', format: (v) => `[${v}]`, }, ], data: [{ x: 'test' }], }); expect(result).toContain('[test]'); }); it('should handle empty data', () => { const result = formatter.table({ columns: [{ key: 'x', header: 'X' }], data: [], }); expect(result).toContain('X'); }); it('should handle null/undefined values', () => { const result = formatter.table({ columns: [{ key: 'x', header: 'X' }], data: [{ x: null }, { x: undefined }], }); expect(result).toBeDefined(); }); }); describe('list formatting', () => { it('should format a bulleted list', () => { const result = formatter.list(['item1', 'item2', 'item3']); expect(result).toContain('- item1'); expect(result).toContain('- item2'); expect(result).toContain('- item3'); }); it('should use custom bullet', () => { const result = formatter.list(['a', 'b'], '*'); expect(result).toContain('* a'); expect(result).toContain('* b'); }); it('should format a numbered list', () => { const result = formatter.numberedList(['first', 'second', 'third']); expect(result).toContain('1. first'); expect(result).toContain('2. second'); expect(result).toContain('3. third'); }); }); describe('box formatting', () => { it('should render a box around content', () => { const result = formatter.box('Hello World'); expect(result).toContain('+'); expect(result).toContain('|'); expect(result).toContain('Hello World'); }); it('should render a box with title', () => { const result = formatter.box('Content', 'Title'); expect(result).toContain('Title'); expect(result).toContain('Content'); }); it('should handle multiline content', () => { const result = formatter.box('Line 1\nLine 2\nLine 3'); expect(result).toContain('Line 1'); expect(result).toContain('Line 2'); expect(result).toContain('Line 3'); }); }); describe('JSON output', () => { it('should format JSON with indentation by default', () => { const result = formatter.json({ key: 'value' }); expect(result).toContain('"key"'); expect(result).toContain('"value"'); expect(result).toContain('\n'); }); it('should format compact JSON when pretty is false', () => { const result = formatter.json({ key: 'value' }, false); expect(result).not.toContain('\n'); expect(result).toBe('{"key":"value"}'); }); }); describe('progress bar', () => { it('should render progress bar at 0%', () => { const result = formatter.progressBar(0, 100); expect(result).toContain('0.0%'); }); it('should render progress bar at 50%', () => { const result = formatter.progressBar(50, 100); expect(result).toContain('50.0%'); }); it('should render progress bar at 100%', () => { const result = formatter.progressBar(100, 100); expect(result).toContain('100.0%'); }); it('should clamp above 100%', () => { const result = formatter.progressBar(150, 100); expect(result).toContain('100.0%'); }); }); describe('color toggle', () => { it('should toggle color on and off', () => { formatter.setColorEnabled(true); expect(formatter.isColorEnabled()).toBe(true); formatter.setColorEnabled(false); expect(formatter.isColorEnabled()).toBe(false); }); }); describe('singleton export', () => { it('output should be an instance of OutputFormatter', () => { expect(output).toBeInstanceOf(OutputFormatter); }); }); }); // ============================================================================ // SECTION 5: Suggest Module Tests // ============================================================================ import { levenshteinDistance, similarityScore, findSimilar, formatSuggestion, suggestCommand, getTypoCorrection, COMMON_TYPOS, } from '../src/suggest.js'; describe('Suggest Module', () => { describe('levenshteinDistance', () => { it('should return 0 for identical strings', () => { expect(levenshteinDistance('hello', 'hello')).toBe(0); }); it('should return length of b for empty a', () => { expect(levenshteinDistance('', 'abc')).toBe(3); }); it('should return length of a for empty b', () => { expect(levenshteinDistance('abc', '')).toBe(3); }); it('should return 0 for two empty strings', () => { expect(levenshteinDistance('', '')).toBe(0); }); it('should compute correct distance for single char difference', () => { expect(levenshteinDistance('cat', 'bat')).toBe(1); }); it('should compute correct distance for insertion', () => { expect(levenshteinDistance('cat', 'cats')).toBe(1); }); it('should compute correct distance for deletion', () => { expect(levenshteinDistance('cats', 'cat')).toBe(1); }); it('should compute correct distance for completely different strings', () => { expect(levenshteinDistance('abc', 'xyz')).toBe(3); }); it('should handle longer strings', () => { const d = levenshteinDistance('sitting', 'kitten'); expect(d).toBe(3); }); }); describe('similarityScore', () => { it('should return 1 for identical strings', () => { expect(similarityScore('hello', 'hello')).toBe(1); }); it('should return 1 for two empty strings', () => { expect(similarityScore('', '')).toBe(1); }); it('should return 0 for completely different single-char strings', () => { expect(similarityScore('a', 'b')).toBe(0); }); it('should return a value between 0 and 1', () => { const score = similarityScore('agent', 'agnet'); expect(score).toBeGreaterThan(0); expect(score).toBeLessThanOrEqual(1); }); it('should be case-insensitive', () => { const score = similarityScore('Agent', 'agent'); expect(score).toBe(1); }); }); describe('findSimilar', () => { const candidates = ['agent', 'swarm', 'memory', 'config', 'hooks', 'init', 'status']; it('should find similar commands for typo', () => { const results = findSimilar('agnet', candidates); expect(results).toContain('agent'); }); it('should find similar commands for prefix', () => { const results = findSimilar('con', candidates); expect(results).toContain('config'); }); it('should return empty for completely unrelated input', () => { const results = findSimilar('zzzzzzzzzzzzz', candidates); expect(results).toEqual([]); }); it('should limit results to maxSuggestions', () => { const results = findSimilar('a', candidates, { maxSuggestions: 2 }); expect(results.length).toBeLessThanOrEqual(2); }); it('should respect minSimilarity threshold', () => { const results = findSimilar('xyz', candidates, { minSimilarity: 0.9 }); expect(results).toEqual([]); }); }); describe('formatSuggestion', () => { it('should return empty string for no suggestions', () => { expect(formatSuggestion('bad', [])).toBe(''); }); it('should format single suggestion', () => { const result = formatSuggestion('agnet', ['agent']); expect(result).toContain('Did you mean'); expect(result).toContain('agent'); }); it('should format multiple suggestions', () => { const result = formatSuggestion('con', ['config', 'completions']); expect(result).toContain('Did you mean'); expect(result).toContain('config'); expect(result).toContain('completions'); }); it('should use context-appropriate prefix for subcommand', () => { const result = formatSuggestion('spwn', ['spawn'], 'subcommand'); expect(result).toContain('Available subcommands'); }); it('should use context-appropriate prefix for value', () => { const result = formatSuggestion('jsonn', ['json', 'table'], 'value'); expect(result).toContain('Valid values'); }); }); describe('COMMON_TYPOS', () => { it('should map common agent typos', () => { expect(COMMON_TYPOS['agnet']).toBe('agent'); expect(COMMON_TYPOS['agen']).toBe('agent'); }); it('should map common memory typos', () => { expect(COMMON_TYPOS['memroy']).toBe('memory'); expect(COMMON_TYPOS['mem']).toBe('memory'); expect(COMMON_TYPOS['memmory']).toBe('memory'); }); it('should map common swarm typos', () => { expect(COMMON_TYPOS['swarrm']).toBe('swarm'); expect(COMMON_TYPOS['swarn']).toBe('swarm'); }); it('should map common hive-mind typos', () => { expect(COMMON_TYPOS['hive']).toBe('hive-mind'); expect(COMMON_TYPOS['hivemind']).toBe('hive-mind'); expect(COMMON_TYPOS['hive_mind']).toBe('hive-mind'); }); it('should map status typos', () => { expect(COMMON_TYPOS['staus']).toBe('status'); expect(COMMON_TYPOS['stauts']).toBe('status'); }); it('should map daemon typos', () => { expect(COMMON_TYPOS['deamon']).toBe('daemon'); }); it('should map workflow typos', () => { expect(COMMON_TYPOS['wf']).toBe('workflow'); expect(COMMON_TYPOS['wokflow']).toBe('workflow'); }); }); describe('getTypoCorrection', () => { it('should return correction for known typo', () => { expect(getTypoCorrection('agnet')).toBe('agent'); }); it('should return undefined for unknown input', () => { expect(getTypoCorrection('qwerty')).toBeUndefined(); }); it('should be case-insensitive', () => { expect(getTypoCorrection('AGNET')).toBe('agent'); }); }); describe('suggestCommand', () => { const commands = ['agent', 'swarm', 'memory', 'config', 'hooks', 'init', 'status', 'daemon']; it('should suggest correction for common typo', () => { const result = suggestCommand('agnet', commands); expect(result.correction).toBe('agent'); expect(result.suggestions).toContain('agent'); }); it('should suggest similar commands for unknown input', () => { const result = suggestCommand('swar', commands); expect(result.suggestions.length).toBeGreaterThan(0); expect(result.suggestions).toContain('swarm'); }); it('should provide helpful message when no suggestions found', () => { const result = suggestCommand('zzzzzzzzzzzzz', commands); expect(result.suggestions).toEqual([]); expect(result.message).toContain('--help'); }); it('should provide "Did you mean" message for single suggestion', () => { const result = suggestCommand('agnet', commands); expect(result.message).toContain('Did you mean'); }); }); }); // ============================================================================ // SECTION 6: Init System Tests // ============================================================================ import { generateSettings, generateSettingsJson } from '../src/init/settings-generator.js'; import { generateClaudeMd, generateMinimalClaudeMd, CLAUDE_MD_TEMPLATES } from '../src/init/claudemd-generator.js'; import { DEFAULT_INIT_OPTIONS, MINIMAL_INIT_OPTIONS, FULL_INIT_OPTIONS, detectPlatform } from '../src/init/types.js'; import type { InitOptions, ClaudeMdTemplate } from '../src/init/types.js'; describe('Init System', () => { describe('DEFAULT_INIT_OPTIONS', () => { it('should have all components enabled by default', () => { expect(DEFAULT_INIT_OPTIONS.components.settings).toBe(true); expect(DEFAULT_INIT_OPTIONS.components.skills).toBe(true); expect(DEFAULT_INIT_OPTIONS.components.runtime).toBe(true); expect(DEFAULT_INIT_OPTIONS.components.claudeMd).toBe(true); }); it('should have hierarchical-mesh topology', () => { expect(DEFAULT_INIT_OPTIONS.runtime.topology).toBe('hierarchical-mesh'); }); it('should have HNSW enabled', () => { expect(DEFAULT_INIT_OPTIONS.runtime.enableHNSW).toBe(true); }); it('should have neural enabled', () => { expect(DEFAULT_INIT_OPTIONS.runtime.enableNeural).toBe(true); }); it('should have hybrid memory backend', () => { expect(DEFAULT_INIT_OPTIONS.runtime.memoryBackend).toBe('hybrid'); }); it('should have force=false', () => { expect(DEFAULT_INIT_OPTIONS.force).toBe(false); }); it('should have interactive=true', () => { expect(DEFAULT_INIT_OPTIONS.interactive).toBe(true); }); }); describe('MINIMAL_INIT_OPTIONS', () => { it('should have fewer components enabled', () => { expect(MINIMAL_INIT_OPTIONS.components.commands).toBe(false); expect(MINIMAL_INIT_OPTIONS.components.agents).toBe(false); expect(MINIMAL_INIT_OPTIONS.components.helpers).toBe(false); expect(MINIMAL_INIT_OPTIONS.components.statusline).toBe(false); }); it('should have simpler topology', () => { expect(MINIMAL_INIT_OPTIONS.runtime.topology).toBe('mesh'); }); it('should have HNSW disabled', () => { expect(MINIMAL_INIT_OPTIONS.runtime.enableHNSW).toBe(false); }); it('should have fewer max agents', () => { expect(MINIMAL_INIT_OPTIONS.runtime.maxAgents).toBeLessThan( DEFAULT_INIT_OPTIONS.runtime.maxAgents, ); }); }); describe('FULL_INIT_OPTIONS', () => { it('should have all components enabled', () => { const comps = FULL_INIT_OPTIONS.components; expect(comps.settings).toBe(true); expect(comps.skills).toBe(true); expect(comps.commands).toBe(true); expect(comps.agents).toBe(true); expect(comps.helpers).toBe(true); expect(comps.statusline).toBe(true); expect(comps.mcp).toBe(true); expect(comps.runtime).toBe(true); expect(comps.claudeMd).toBe(true); }); it('should have dualMode skills enabled', () => { expect(FULL_INIT_OPTIONS.skills.dualMode).toBe(true); }); it('should have all MCP servers enabled', () => { expect(FULL_INIT_OPTIONS.mcp.claudeFlow).toBe(true); expect(FULL_INIT_OPTIONS.mcp.ruvSwarm).toBe(true); expect(FULL_INIT_OPTIONS.mcp.flowNexus).toBe(true); }); }); describe('detectPlatform', () => { it('should return valid platform info', () => { const platform = detectPlatform(); expect(platform).toBeDefined(); expect(['windows', 'darwin', 'linux']).toContain(platform.os); expect(typeof platform.arch).toBe('string'); expect(typeof platform.nodeVersion).toBe('string'); expect(typeof platform.shell).toBe('string'); expect(typeof platform.homeDir).toBe('string'); expect(typeof platform.configDir).toBe('string'); }); it('should detect node version starting with v', () => { const platform = detectPlatform(); expect(platform.nodeVersion).toMatch(/^v\d+/); }); }); describe('generateSettings', () => { it('should return an object', () => { const settings = generateSettings(DEFAULT_INIT_OPTIONS); expect(typeof settings).toBe('object'); expect(settings).not.toBeNull(); }); it('should include hooks when settings component is enabled', () => { const settings = generateSettings(DEFAULT_INIT_OPTIONS) as Record; expect(settings.hooks).toBeDefined(); }); it('should include permissions', () => { const settings = generateSettings(DEFAULT_INIT_OPTIONS) as Record; expect(settings.permissions).toBeDefined(); const perms = settings.permissions as Record; expect(perms.allow).toBeDefined(); expect(perms.deny).toBeDefined(); }); it('should NOT include attribution by default (opt-in per #1670)', () => { // #1670 — attribution (Co-Authored-By trailer) is now opt-in to avoid // silently injecting a third-party co-author into user commits. const settings = generateSettings(DEFAULT_INIT_OPTIONS) as Record; expect(settings.attribution).toBeUndefined(); }); it('should include attribution when opted in', () => { const settings = generateSettings({ ...DEFAULT_INIT_OPTIONS, attribution: true }) as Record; expect(settings.attribution).toBeDefined(); const attribution = settings.attribution as Record; expect(attribution.commit).toContain('Co-Authored-By:'); expect(attribution.pr).toContain('Generated with'); }); it('should include env with agent teams enabled', () => { const settings = generateSettings(DEFAULT_INIT_OPTIONS) as Record; expect(settings.env).toBeDefined(); const env = settings.env as Record; expect(env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS).toBe('1'); }); it('should include claudeFlow v3 settings', () => { const settings = generateSettings(DEFAULT_INIT_OPTIONS) as Record; expect(settings.claudeFlow).toBeDefined(); const cf = settings.claudeFlow as Record; expect(cf.version).toBe('3.0.0'); expect(cf.enabled).toBe(true); }); it('should deny reading .env files', () => { const settings = generateSettings(DEFAULT_INIT_OPTIONS) as Record; const perms = settings.permissions as { deny: string[] }; expect(perms.deny).toContain('Read(./.env)'); }); }); describe('generateSettingsJson', () => { it('should return valid JSON string', () => { const json = generateSettingsJson(DEFAULT_INIT_OPTIONS); expect(typeof json).toBe('string'); const parsed = JSON.parse(json); expect(typeof parsed).toBe('object'); }); }); describe('generateClaudeMd', () => { it('should return non-empty string', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS); expect(typeof md).toBe('string'); expect(md.length).toBeGreaterThan(100); }); // The CLAUDE.md generator was deliberately rewritten to a terser, more // imperative schema (header "# Ruflo — Claude Code Configuration", section // titles like "## Rules" / "## Swarm & Routing"). These tests were // originally written against the older verbose schema; updated below to // pin the *current* contract so future drift is caught. it('should contain the Ruflo header', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS); expect(md).toContain('# Ruflo'); }); it('should contain a Rules section (behavioral rules)', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS); expect(md).toContain('## Rules'); }); it('should mention required project subdirectories (file organization)', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS); // /src, /tests, /docs, /config, /scripts are all in the Rules block expect(md).toContain('/src'); expect(md).toContain('/tests'); expect(md).toContain('/docs'); }); it('should describe agent comms (project architecture coordination)', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS); expect(md).toContain('Agent Comms'); }); it('should describe anti-drift swarm topology', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'standard'); expect(md).toContain('anti-drift'); }); it('standard template should include swarm config', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'standard'); expect(md).toContain('Swarm'); }); it('full template should include hooks reference', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'full'); // hooksRef() emits a section about hooks expect(md.toLowerCase()).toContain('hook'); }); it('full template should include intelligence/SONA reference', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'full'); // intelligenceSystem() mentions SONA / RuVector / HNSW expect(md.toLowerCase()).toMatch(/sona|ruvector|hnsw|intelligence/); }); it('security template should include security rules', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'security'); expect(md).toContain('Security'); }); it('performance template should include performance section', () => { const md = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'performance'); expect(md).toContain('Performance'); }); it('minimal template should be shorter than full template', () => { const minimal = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'minimal'); const full = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'full'); expect(minimal.length).toBeLessThan(full.length); }); }); describe('generateMinimalClaudeMd', () => { it('should produce same result as generateClaudeMd with minimal template', () => { const a = generateMinimalClaudeMd(DEFAULT_INIT_OPTIONS); const b = generateClaudeMd(DEFAULT_INIT_OPTIONS, 'minimal'); expect(a).toBe(b); }); }); describe('CLAUDE_MD_TEMPLATES', () => { it('should have 6 templates', () => { expect(CLAUDE_MD_TEMPLATES.length).toBe(6); }); it('should include all template names', () => { const names = CLAUDE_MD_TEMPLATES.map((t) => t.name); expect(names).toContain('minimal'); expect(names).toContain('standard'); expect(names).toContain('full'); expect(names).toContain('security'); expect(names).toContain('performance'); expect(names).toContain('solo'); }); it('each template should have a description', () => { for (const tmpl of CLAUDE_MD_TEMPLATES) { expect(typeof tmpl.description).toBe('string'); expect(tmpl.description.length).toBeGreaterThan(10); } }); }); }); // ============================================================================ // SECTION 7: Error Type Tests // ============================================================================ import { CLIError, ValidationError, ConfigError, CommandNotFoundError, } from '../src/types.js'; describe('Error Types', () => { describe('CLIError', () => { it('should have name, message, code, and exitCode', () => { const err = new CLIError('test error', 'TEST_CODE', 2); expect(err.name).toBe('CLIError'); expect(err.message).toBe('test error'); expect(err.code).toBe('TEST_CODE'); expect(err.exitCode).toBe(2); }); it('should default exitCode to 1', () => { const err = new CLIError('err', 'CODE'); expect(err.exitCode).toBe(1); }); it('should accept details', () => { const err = new CLIError('err', 'CODE', 1, { extra: 'info' }); expect(err.details).toEqual({ extra: 'info' }); }); it('should be an instance of Error', () => { const err = new CLIError('err', 'CODE'); expect(err).toBeInstanceOf(Error); }); }); describe('ValidationError', () => { it('should have VALIDATION_ERROR code', () => { const err = new ValidationError('invalid input'); expect(err.code).toBe('VALIDATION_ERROR'); expect(err.name).toBe('ValidationError'); }); it('should be an instance of CLIError', () => { const err = new ValidationError('invalid'); expect(err).toBeInstanceOf(CLIError); }); }); describe('ConfigError', () => { it('should have CONFIG_ERROR code', () => { const err = new ConfigError('bad config'); expect(err.code).toBe('CONFIG_ERROR'); expect(err.name).toBe('ConfigError'); }); }); describe('CommandNotFoundError', () => { it('should include command name in message', () => { const err = new CommandNotFoundError('foobar'); expect(err.message).toContain('foobar'); expect(err.code).toBe('COMMAND_NOT_FOUND'); expect(err.exitCode).toBe(127); }); }); });