feat: verify Phase 8 platform architecture + integration tests (P8-019) (#185)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #185.
This commit is contained in:
2026-03-16 03:43:42 +00:00
committed by jason.woltje
parent a989b5e549
commit 39ef2ff123
5 changed files with 726 additions and 22 deletions

View File

@@ -0,0 +1,348 @@
/**
* Integration tests for TUI command parsing + registry (P8-019)
*
* Covers:
* - parseSlashCommand() + commandRegistry.find() round-trip for all aliases
* - /help, /stop, /cost, /status resolve to 'local' execution
* - Unknown commands return null from find()
* - Alias resolution: /h → help, /m → model, /n → new, etc.
* - filterCommands prefix filtering
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { parseSlashCommand } from './parse.js';
import { CommandRegistry } from './registry.js';
import type { CommandDef } from '@mosaic/types';
// ─── Parse + Registry Round-trip ─────────────────────────────────────────────
describe('parseSlashCommand + CommandRegistry — integration', () => {
let registry: CommandRegistry;
// Gateway-style commands to simulate a live manifest
const gatewayCommands: CommandDef[] = [
{
name: 'model',
description: 'Switch the active model',
aliases: ['m'],
args: [{ name: 'model-name', type: 'string', optional: false, description: 'Model name' }],
scope: 'core',
execution: 'socket',
available: true,
},
{
name: 'thinking',
description: 'Set thinking level',
aliases: ['t'],
args: [
{
name: 'level',
type: 'enum',
optional: false,
values: ['none', 'low', 'medium', 'high', 'auto'],
description: 'Thinking level',
},
],
scope: 'core',
execution: 'socket',
available: true,
},
{
name: 'new',
description: 'Start a new conversation',
aliases: ['n'],
scope: 'core',
execution: 'socket',
available: true,
},
{
name: 'agent',
description: 'Switch or list available agents',
aliases: ['a'],
args: [{ name: 'args', type: 'string', optional: true, description: 'list or <agent-id>' }],
scope: 'agent',
execution: 'socket',
available: true,
},
{
name: 'preferences',
description: 'View or set user preferences',
aliases: ['pref'],
args: [
{
name: 'action',
type: 'enum',
optional: true,
values: ['show', 'set', 'reset'],
description: 'Action',
},
],
scope: 'core',
execution: 'rest',
available: true,
},
{
name: 'gc',
description: 'Trigger garbage collection sweep',
aliases: [],
scope: 'core',
execution: 'socket',
available: true,
},
{
name: 'mission',
description: 'View or set active mission',
aliases: [],
args: [{ name: 'args', type: 'string', optional: true, description: 'status | set <id>' }],
scope: 'agent',
execution: 'socket',
available: true,
},
];
beforeEach(() => {
registry = new CommandRegistry();
registry.updateManifest({ version: 1, commands: gatewayCommands, skills: [] });
});
// ── parseSlashCommand tests ──
it('returns null for non-slash input', () => {
expect(parseSlashCommand('hello world')).toBeNull();
expect(parseSlashCommand('')).toBeNull();
expect(parseSlashCommand('model')).toBeNull();
});
it('parses "/model claude-3-opus" → command=model args=claude-3-opus', () => {
const parsed = parseSlashCommand('/model claude-3-opus');
expect(parsed).not.toBeNull();
expect(parsed!.command).toBe('model');
expect(parsed!.args).toBe('claude-3-opus');
expect(parsed!.raw).toBe('/model claude-3-opus');
});
it('parses "/gc" with no args → command=gc args=null', () => {
const parsed = parseSlashCommand('/gc');
expect(parsed).not.toBeNull();
expect(parsed!.command).toBe('gc');
expect(parsed!.args).toBeNull();
});
it('parses "/system you are a helpful assistant" → args contains full text', () => {
const parsed = parseSlashCommand('/system you are a helpful assistant');
expect(parsed!.command).toBe('system');
expect(parsed!.args).toBe('you are a helpful assistant');
});
it('parses "/help" → command=help args=null', () => {
const parsed = parseSlashCommand('/help');
expect(parsed!.command).toBe('help');
expect(parsed!.args).toBeNull();
});
// ── Round-trip: parse then find ──
it('round-trip: /m → resolves to "model" command via alias', () => {
const parsed = parseSlashCommand('/m claude-3-haiku');
expect(parsed).not.toBeNull();
const cmd = registry.find(parsed!.command);
expect(cmd).not.toBeNull();
// /m → model (alias map in registry)
expect(cmd!.name === 'model' || cmd!.aliases.includes('m')).toBe(true);
});
it('round-trip: /h → resolves to "help" (local command)', () => {
const parsed = parseSlashCommand('/h');
expect(parsed).not.toBeNull();
const cmd = registry.find(parsed!.command);
expect(cmd).not.toBeNull();
expect(cmd!.name === 'help' || cmd!.aliases.includes('h')).toBe(true);
});
it('round-trip: /n → resolves to "new" via gateway manifest', () => {
const parsed = parseSlashCommand('/n');
expect(parsed).not.toBeNull();
const cmd = registry.find(parsed!.command);
expect(cmd).not.toBeNull();
expect(cmd!.name === 'new' || cmd!.aliases.includes('n')).toBe(true);
});
it('round-trip: /a → resolves to "agent" via gateway manifest', () => {
const parsed = parseSlashCommand('/a list');
expect(parsed).not.toBeNull();
const cmd = registry.find(parsed!.command);
expect(cmd).not.toBeNull();
expect(cmd!.name === 'agent' || cmd!.aliases.includes('a')).toBe(true);
});
it('round-trip: /pref → resolves to "preferences" via alias', () => {
const parsed = parseSlashCommand('/pref show');
expect(parsed).not.toBeNull();
const cmd = registry.find(parsed!.command);
expect(cmd).not.toBeNull();
expect(cmd!.name === 'preferences' || cmd!.aliases.includes('pref')).toBe(true);
});
it('round-trip: /t → resolves to "thinking" via alias', () => {
const parsed = parseSlashCommand('/t high');
expect(parsed).not.toBeNull();
const cmd = registry.find(parsed!.command);
expect(cmd).not.toBeNull();
expect(cmd!.name === 'thinking' || cmd!.aliases.includes('t')).toBe(true);
});
// ── Local commands resolve to 'local' execution ──
it('/help resolves to local execution', () => {
const cmd = registry.find('help');
expect(cmd).not.toBeNull();
expect(cmd!.execution).toBe('local');
});
it('/stop resolves to local execution', () => {
const cmd = registry.find('stop');
expect(cmd).not.toBeNull();
expect(cmd!.execution).toBe('local');
});
it('/cost resolves to local execution', () => {
const cmd = registry.find('cost');
expect(cmd).not.toBeNull();
expect(cmd!.execution).toBe('local');
});
it('/status resolves to local execution (TUI local override)', () => {
const cmd = registry.find('status');
expect(cmd).not.toBeNull();
// status is 'local' in the TUI registry (local takes precedence over gateway)
expect(cmd!.execution).toBe('local');
});
// ── Unknown commands return null ──
it('find() returns null for unknown command', () => {
expect(registry.find('nonexistent')).toBeNull();
expect(registry.find('xyz')).toBeNull();
expect(registry.find('')).toBeNull();
});
it('find() returns null when no gateway manifest and command not local', () => {
const emptyRegistry = new CommandRegistry();
expect(emptyRegistry.find('model')).toBeNull();
expect(emptyRegistry.find('gc')).toBeNull();
});
// ── getAll returns combined local + gateway ──
it('getAll() includes both local and gateway commands', () => {
const all = registry.getAll();
const names = all.map((c) => c.name);
// Local commands
expect(names).toContain('help');
expect(names).toContain('stop');
expect(names).toContain('cost');
expect(names).toContain('status');
// Gateway commands
expect(names).toContain('model');
expect(names).toContain('gc');
});
it('getLocalCommands() returns only local commands', () => {
const local = registry.getLocalCommands();
expect(local.every((c) => c.execution === 'local')).toBe(true);
expect(local.some((c) => c.name === 'help')).toBe(true);
expect(local.some((c) => c.name === 'stop')).toBe(true);
});
});
// ─── filterCommands (autocomplete) ────────────────────────────────────────────
describe('filterCommands (from CommandAutocomplete)', () => {
// Import inline since filterCommands is not exported — replicate the logic here
function filterCommands(commands: CommandDef[], query: string): CommandDef[] {
if (!query) return commands;
const q = query.toLowerCase();
return commands.filter(
(c) =>
c.name.includes(q) ||
c.aliases.some((a) => a.includes(q)) ||
c.description.toLowerCase().includes(q),
);
}
const commands: CommandDef[] = [
{
name: 'model',
description: 'Switch the active model',
aliases: ['m'],
scope: 'core',
execution: 'socket',
available: true,
},
{
name: 'mission',
description: 'View or set active mission',
aliases: [],
scope: 'agent',
execution: 'socket',
available: true,
},
{
name: 'help',
description: 'Show available commands',
aliases: ['h'],
scope: 'core',
execution: 'local',
available: true,
},
{
name: 'gc',
description: 'Trigger garbage collection sweep',
aliases: [],
scope: 'core',
execution: 'socket',
available: true,
},
];
it('returns all commands when query is empty', () => {
expect(filterCommands(commands, '')).toHaveLength(commands.length);
});
it('filters by name prefix "mi" → mission only (not model, as "mi" not in model name or aliases)', () => {
const result = filterCommands(commands, 'mi');
const names = result.map((c) => c.name);
expect(names).toContain('mission');
expect(names).not.toContain('gc');
});
it('filters by name prefix "mo" → model only', () => {
const result = filterCommands(commands, 'mo');
const names = result.map((c) => c.name);
expect(names).toContain('model');
expect(names).not.toContain('mission');
expect(names).not.toContain('gc');
});
it('filters by exact name "gc" → gc only', () => {
const result = filterCommands(commands, 'gc');
expect(result).toHaveLength(1);
expect(result[0]!.name).toBe('gc');
});
it('filters by alias "h" → help', () => {
const result = filterCommands(commands, 'h');
const names = result.map((c) => c.name);
expect(names).toContain('help');
});
it('filters by description keyword "switch" → model', () => {
const result = filterCommands(commands, 'switch');
const names = result.map((c) => c.name);
expect(names).toContain('model');
});
it('returns empty array when no commands match', () => {
const result = filterCommands(commands, 'zzznotfound');
expect(result).toHaveLength(0);
});
});