mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
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:
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user