refactor: simplify ralph wizard from 9 agents to 2

Remove execution layer that never actually controlled execution:
- execution-bridge.ts (model param was ignored)
- group-scheduler.ts (task-tool mode never used)
- model-selector.ts (recommendations were display-only)
- context-manager.ts (never invoked)
- execution-limits.ts

Remove redundant agent prompts (overlapping outputs):
- requirements-analyst, architecture-planner, risk-analyst
- testing-specialist, verification (merged into planner.ts)
- execution-optimizer (output was ignored)
- final-review (scores were cosmetic)

Simplify plan-orchestrator.ts from 2400 LOC to 520 LOC:
- Before: 9 agents, 6 phases, ~40-60 minutes
- After: 2 agents (research + planner), ~18 minutes

Remove /api/execution/* endpoints and related server code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-30 12:15:09 +01:00
co-authored by Claude Opus 4.5
parent 0020ae5a3e
commit a92826b66d
18 changed files with 329 additions and 5468 deletions
-270
View File
@@ -1,270 +0,0 @@
/**
* @fileoverview Tests for the Execution Bridge system.
*
* Tests the core functionality of:
* - ModelSelector
* - GroupScheduler
* - ExecutionBridge
*
* Uses mocks to avoid spawning real Claude sessions.
*
* Port: none (unit tests, no server)
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
ModelSelector,
resetModelSelector,
createDefaultModelConfig,
type ModelConfig,
type AgentType,
} from '../src/model-selector.js';
import {
GroupScheduler,
resetGroupScheduler,
type GroupTask,
} from '../src/group-scheduler.js';
import {
ExecutionBridge,
resetExecutionBridge,
type PlanItem,
} from '../src/execution-bridge.js';
describe('ModelSelector', () => {
let selector: ModelSelector;
beforeEach(() => {
resetModelSelector();
selector = new ModelSelector();
});
afterEach(() => {
resetModelSelector();
});
it('should use default model when no overrides', () => {
const selection = selector.selectModel('task-1', { agentType: 'explore' });
expect(selection.model).toBe('sonnet');
expect(selection.usedUserDefault).toBe(true);
});
it('should respect user default model', () => {
selector.updateConfig({ defaultModel: 'opus' });
const selection = selector.selectModel('task-1', { agentType: 'explore' });
expect(selection.model).toBe('opus');
expect(selection.usedUserDefault).toBe(true);
});
it('should use agent type override when set', () => {
selector.updateConfig({
agentTypeOverrides: { explore: 'haiku' },
});
const selection = selector.selectModel('task-1', { agentType: 'explore' });
expect(selection.model).toBe('haiku');
expect(selection.usedUserDefault).toBe(false);
});
it('should include optimizer recommendation when different', () => {
selector.updateConfig({ defaultModel: 'opus' });
const selection = selector.selectModel('task-1', {
agentType: 'explore',
recommendedModel: 'haiku',
});
expect(selection.model).toBe('opus');
expect(selection.optimizerRecommendation).toBe('haiku');
});
it('should select session mode for high token tasks', () => {
const mode = selector.selectExecutionMode({ estimatedTokens: 60000 });
expect(mode.mode).toBe('session');
});
it('should select task-tool mode for low token explore tasks', () => {
const mode = selector.selectExecutionMode({
estimatedTokens: 10000,
agentType: 'explore',
});
expect(mode.mode).toBe('task-tool');
});
it('should return correct cost multipliers', () => {
expect(selector.getModelCostMultiplier('opus')).toBe(5.0);
expect(selector.getModelCostMultiplier('sonnet')).toBe(1.0);
expect(selector.getModelCostMultiplier('haiku')).toBe(0.04);
});
});
describe('GroupScheduler', () => {
let scheduler: GroupScheduler;
beforeEach(() => {
resetGroupScheduler();
scheduler = new GroupScheduler();
});
afterEach(() => {
resetGroupScheduler();
});
it('should build schedule from plan items', () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1', parallelGroup: 0 },
{ id: 't2', title: 'Task 2', description: 'Desc 2', parallelGroup: 0 },
{ id: 't3', title: 'Task 3', description: 'Desc 3', parallelGroup: 1 },
];
const schedule = scheduler.buildSchedule(items);
expect(schedule.groups).toHaveLength(2);
expect(schedule.groups[0].tasks).toHaveLength(2);
expect(schedule.groups[1].tasks).toHaveLength(1);
expect(schedule.totalTasks).toBe(3);
});
it('should order groups by number', () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1', parallelGroup: 2 },
{ id: 't2', title: 'Task 2', description: 'Desc 2', parallelGroup: 0 },
{ id: 't3', title: 'Task 3', description: 'Desc 3', parallelGroup: 1 },
];
const schedule = scheduler.buildSchedule(items);
expect(schedule.groups[0].groupNumber).toBe(0);
expect(schedule.groups[1].groupNumber).toBe(1);
expect(schedule.groups[2].groupNumber).toBe(2);
});
it('should track group dependencies', () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1', parallelGroup: 0 },
{ id: 't2', title: 'Task 2', description: 'Desc 2', parallelGroup: 1, dependencies: ['t1'] },
];
const schedule = scheduler.buildSchedule(items);
expect(schedule.groups[1].dependsOnGroups).toContain(0);
});
it('should return first group as ready initially', () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1', parallelGroup: 0 },
{ id: 't2', title: 'Task 2', description: 'Desc 2', parallelGroup: 1 },
];
scheduler.buildSchedule(items);
const nextGroup = scheduler.getNextReadyGroup();
expect(nextGroup).not.toBeNull();
expect(nextGroup!.groupNumber).toBe(0);
});
it('should update task status', () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1', parallelGroup: 0 },
];
scheduler.buildSchedule(items);
scheduler.startGroup(0);
scheduler.updateTaskStatus('t1', 'completed');
const stats = scheduler.getStats();
expect(stats.completedTasks).toBe(1);
});
it('should determine execution mode based on task characteristics', () => {
const items: PlanItem[] = [
{
id: 't1',
title: 'High token task',
description: 'Desc',
parallelGroup: 0,
estimatedTokens: 60000,
},
];
const schedule = scheduler.buildSchedule(items);
expect(schedule.groups[0].executionMode).toBe('session');
});
});
describe('ExecutionBridge', () => {
let bridge: ExecutionBridge;
beforeEach(() => {
resetExecutionBridge();
resetGroupScheduler();
resetModelSelector();
bridge = new ExecutionBridge();
});
afterEach(() => {
bridge.reset();
resetExecutionBridge();
resetGroupScheduler();
resetModelSelector();
});
it('should have idle status initially', () => {
expect(bridge.status).toBe('idle');
});
it('should load a plan', () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1', parallelGroup: 0 },
{ id: 't2', title: 'Task 2', description: 'Desc 2', parallelGroup: 1 },
];
const schedule = bridge.loadPlan(items);
expect(schedule).not.toBeNull();
expect(schedule.totalTasks).toBe(2);
expect(bridge.status).toBe('idle');
});
it('should report progress', () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1' },
];
bridge.loadPlan(items);
const progress = bridge.getProgress();
expect(progress.totalTasks).toBe(1);
expect(progress.completedTasks).toBe(0);
expect(progress.status).toBe('idle');
});
it('should update model config', () => {
bridge.updateModelConfig({ defaultModel: 'opus' });
const config = bridge.getModelConfig();
expect(config.defaultModel).toBe('opus');
});
it('should reset properly', () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1' },
];
bridge.loadPlan(items);
bridge.reset();
expect(bridge.status).toBe('idle');
expect(bridge.getProgress().totalTasks).toBe(0);
});
it('should throw when starting without a spawner', async () => {
const items: PlanItem[] = [
{ id: 't1', title: 'Task 1', description: 'Desc 1' },
];
bridge.loadPlan(items);
await expect(bridge.start()).rejects.toThrow('No agent spawner configured');
});
it('should track execution history', () => {
const history = bridge.getHistory();
expect(Array.isArray(history)).toBe(true);
});
});