feat: enhanced multi-agent plan generation for Ralph wizard

- Add plan-orchestrator.ts with parallel subagent spawning
- Phase 1: 4 specialist subagents analyze in parallel (requirements, architecture, testing, risks)
- Phase 2: Synthesis merges and deduplicates outputs
- Phase 3: Verification subagent assigns priorities and identifies gaps
- New /api/generate-plan-detailed endpoint with SSE progress updates
- Auto-regenerate plan when switching between Standard/Enhanced modes
- Show quality score, warnings, and gaps from verification
- Rename "Detailed" to "Enhanced" button with tooltip

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-27 18:16:19 +01:00
co-authored by Claude Opus 4.5
parent b3f808b5f5
commit de45239fb7
7 changed files with 923 additions and 20 deletions
+20 -1
View File
@@ -38,7 +38,7 @@ Claudeman is a Claude Code session manager with a web interface and autonomous R
When adding new features, always ask: "Will this maintain responsiveness with 20 sessions and 50 agent windows?"
**Version**: 0.1399 (must match `package.json`)
**Version**: 0.1400 (must match `package.json`)
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
@@ -359,6 +359,7 @@ claudeman reset # Reset all state
| `src/ai-plan-checker.ts` | Spawns Claude to detect plan mode prompts for auto-accept |
| `src/ralph-tracker.ts` | Detects `<promise>PHRASE</promise>`, todos, loop status |
| `src/ralph-config.ts` | Parses `.claude/ralph-loop.local.md` and CLAUDE.md for Ralph config |
| `src/plan-orchestrator.ts` | Multi-agent plan generation with parallel analysis + verification |
| `src/run-summary.ts` | Tracks session events for "what happened while away" summaries |
**Spawn Protocol (Autonomous Agents):**
@@ -854,6 +855,24 @@ The `RalphTracker` class (`src/ralph-tracker.ts`) detects Ralph patterns in Clau
3. Optionally generates a task plan (`@fix_plan.md`)
4. Sends the initial prompt with iteration protocol
**Plan Generation Modes** (Step 2 of wizard):
| Mode | Description | API Endpoint |
|------|-------------|--------------|
| **Brief** | High-level milestones only | `/api/generate-plan` |
| **Standard** | Balanced implementation steps | `/api/generate-plan` |
| **Enhanced** | Multi-agent orchestration with verification | `/api/generate-plan-detailed` |
**Enhanced Plan Generation** (`src/plan-orchestrator.ts`): When "Enhanced" mode is selected, the plan is generated using parallel subagent orchestration:
1. **Phase 1 - Parallel Analysis**: Spawns 4 specialist subagents simultaneously:
- Requirements Analyst → Extracts explicit/implicit requirements
- Architecture Planner → Identifies modules, interfaces, types
- TDD Specialist → Designs test-first approach, edge cases
- Risk Analyst → Identifies failure points, dependencies, blockers
2. **Phase 2 - Synthesis**: Merges outputs, deduplicates, orders by dependency
3. **Phase 3 - Verification**: Review subagent validates plan, assigns P0/P1/P2 priorities, identifies gaps
The enhanced mode takes longer (~60-90s) but produces more thorough plans with quality scores. Switching between modes auto-regenerates the plan.
**Respawn for Ralph Loops**: Disabled by default (checkbox unchecked). When enabled, the respawn controller uses Ralph-specific prompts:
- **Update Prompt**: Instructs Claude to document progress to CLAUDE.md, update planning files (`@fix_plan.md`), mark completed tasks, and write a summary before `/clear`
- **Kickstart Prompt**: After `/init`, tells Claude it's in a Ralph Wiggum loop and to continue work by reading `@fix_plan.md` and CLAUDE.md notes, then resume on uncompleted tasks
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claudeman",
"version": "0.1399",
"version": "0.1400",
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
"type": "module",
"main": "dist/index.js",
+636
View File
@@ -0,0 +1,636 @@
/**
* @fileoverview Enhanced plan generation using subagent orchestration.
*
* This module implements a multi-phase plan generation system that leverages
* Claude's subagent capabilities for parallel analysis and verification.
*
* Architecture:
* 1. Phase 1 (Parallel Analysis): Spawn 4 specialist subagents simultaneously
* 2. Phase 2 (Synthesis): Merge and deduplicate outputs
* 3. Phase 3 (Verification): Final review and priority assignment
*
* @see https://code.claude.com/docs/en/sub-agents
* @module plan-orchestrator
*/
import { Session } from './session.js';
import { ScreenManager } from './screen-manager.js';
// ============================================================================
// Types
// ============================================================================
export interface PlanItem {
content: string;
priority: 'P0' | 'P1' | 'P2' | null;
source?: string;
rationale?: string;
phase?: number;
}
export interface SubagentResult {
agentType: 'requirements' | 'architecture' | 'testing' | 'risks';
items: Array<{
category: string;
content: string;
rationale?: string;
}>;
success: boolean;
error?: string;
durationMs: number;
}
export interface SynthesisResult {
items: PlanItem[];
stats: {
totalFromSubagents: number;
afterDedup: number;
sourceBreakdown: Record<string, number>;
};
}
export interface VerificationResult {
validatedPlan: PlanItem[];
gaps: string[];
warnings: string[];
qualityScore: number;
}
export interface DetailedPlanResult {
success: boolean;
items?: PlanItem[];
costUsd?: number;
metadata?: {
subagentResults: SubagentResult[];
synthesisStats: SynthesisResult['stats'];
verificationGaps: string[];
verificationWarnings: string[];
qualityScore: number;
totalDurationMs: number;
};
error?: string;
}
export type ProgressCallback = (phase: string, detail: string) => void;
// ============================================================================
// Constants
// ============================================================================
const SUBAGENT_TIMEOUT_MS = 45000; // 45 seconds per subagent
const VERIFICATION_TIMEOUT_MS = 60000; // 60 seconds for verification
const MODEL_ANALYSIS = 'haiku'; // Fast model for parallel analysis
const MODEL_VERIFICATION = 'sonnet'; // Better reasoning for verification
// ============================================================================
// Subagent Prompts
// ============================================================================
const REQUIREMENTS_ANALYST_PROMPT = `You are a Requirements Analyst specializing in extracting all requirements from task descriptions.
## YOUR TASK
Analyze the following task and extract ALL requirements (explicit and implicit):
## TASK DESCRIPTION
{TASK}
## INSTRUCTIONS
1. Identify explicit requirements (directly stated)
2. Infer implicit requirements (unstated but necessary)
3. Note any assumptions that should be validated
4. Consider non-functional requirements (performance, security, usability)
## OUTPUT FORMAT
Return ONLY a JSON array:
[
{"category": "functional|non-functional|constraint|assumption", "content": "requirement description", "rationale": "why this is needed"}
]
Generate 8-15 items. Be thorough - missing requirements cause project failures.`;
const ARCHITECTURE_PLANNER_PROMPT = `You are an Architecture Planner specializing in software component design.
## YOUR TASK
Design the architecture for implementing this task:
## TASK DESCRIPTION
{TASK}
## INSTRUCTIONS
1. Identify all modules/components needed
2. Define interfaces between components
3. Specify data structures and types
4. Note configuration and setup requirements
5. Consider separation of concerns
## OUTPUT FORMAT
Return ONLY a JSON array:
[
{"category": "module|interface|type|config|infrastructure", "content": "component description", "rationale": "why needed"}
]
Generate 10-20 items. Think about the complete system architecture.`;
const TESTING_SPECIALIST_PROMPT = `You are a TDD Specialist designing a comprehensive test strategy.
## YOUR TASK
Design test coverage for this task:
## TASK DESCRIPTION
{TASK}
## INSTRUCTIONS
Following Test-Driven Development methodology:
1. Design unit tests for each component
2. Plan integration tests for feature interactions
3. Identify edge cases and boundary conditions
4. Consider error scenarios and failure modes
5. Plan verification steps
## OUTPUT FORMAT
Return ONLY a JSON array:
[
{"category": "unit|integration|edge-case|error|verification", "content": "test description", "rationale": "what it validates"}
]
Generate 12-25 items. Tests should be written BEFORE implementation.`;
const RISK_ANALYST_PROMPT = `You are a Risk Analyst identifying potential issues and blockers.
## YOUR TASK
Identify risks and edge cases for this task:
## TASK DESCRIPTION
{TASK}
## INSTRUCTIONS
1. Identify potential failure points
2. Note edge cases that could cause bugs
3. Consider security vulnerabilities
4. Flag performance concerns
5. Identify dependencies that could block progress
## OUTPUT FORMAT
Return ONLY a JSON array:
[
{"category": "failure|edge-case|security|performance|dependency", "content": "risk description", "rationale": "mitigation approach"}
]
Generate 8-15 items. Being proactive about risks prevents surprises.`;
const VERIFICATION_PROMPT = `You are a Plan Verification Expert reviewing an implementation plan for completeness and quality.
## ORIGINAL TASK
{TASK}
## SYNTHESIZED PLAN (from multiple analysis subagents)
{PLAN}
## YOUR MISSION
Review this plan and:
1. Assign priorities (P0=critical/blocking, P1=required, P2=enhancement)
2. Identify any gaps or missing steps
3. Check logical ordering (tests before implementation, setup before coding)
4. Flag potential issues or warnings
5. Calculate an overall quality score (0.0-1.0)
## PRIORITY GUIDELINES
- P0: Foundation tasks, type definitions, project setup, blocking dependencies
- P1: Core implementation, tests, main features, error handling
- P2: Polish, optimization, documentation, nice-to-have features
## OUTPUT FORMAT
Return ONLY a JSON object:
{
"validatedPlan": [
{"content": "step description", "priority": "P0|P1|P2", "rationale": "why this priority"}
],
"gaps": ["missing requirement 1", "missing test coverage for X"],
"warnings": ["consider Y before Z", "potential issue with..."],
"qualityScore": 0.85
}
Be critical but constructive. A thorough review catches issues early.`;
// ============================================================================
// Main Orchestrator Class
// ============================================================================
export class PlanOrchestrator {
private screenManager: ScreenManager;
private workingDir: string;
constructor(screenManager: ScreenManager, workingDir: string = process.cwd()) {
this.screenManager = screenManager;
this.workingDir = workingDir;
}
/**
* Generate a detailed implementation plan using subagent orchestration.
*
* Phases:
* 1. Spawn 4 specialist subagents in parallel for analysis
* 2. Synthesize their outputs into a unified plan
* 3. Run verification subagent for quality assurance
*/
async generateDetailedPlan(
taskDescription: string,
onProgress?: ProgressCallback
): Promise<DetailedPlanResult> {
const startTime = Date.now();
let totalCost = 0;
try {
// Phase 1: Parallel Analysis
onProgress?.('parallel-analysis', 'Spawning analysis subagents...');
const subagentResults = await this.runParallelAnalysis(taskDescription, onProgress);
totalCost += subagentResults.reduce((sum, r) => sum + (r.success ? 0.002 : 0), 0); // Estimate
// Check if we got enough results to continue
const successfulResults = subagentResults.filter(r => r.success);
if (successfulResults.length < 2) {
return {
success: false,
error: `Only ${successfulResults.length} subagents succeeded. Falling back to standard generation.`,
};
}
// Phase 2: Synthesis
onProgress?.('synthesis', 'Synthesizing subagent outputs...');
const synthesisResult = this.synthesizeResults(subagentResults);
// Phase 3: Verification
onProgress?.('verification', 'Running verification subagent...');
const verificationResult = await this.runVerification(
taskDescription,
synthesisResult.items,
onProgress
);
totalCost += 0.01; // Verification cost estimate
const totalDurationMs = Date.now() - startTime;
return {
success: true,
items: verificationResult.validatedPlan,
costUsd: totalCost,
metadata: {
subagentResults,
synthesisStats: synthesisResult.stats,
verificationGaps: verificationResult.gaps,
verificationWarnings: verificationResult.warnings,
qualityScore: verificationResult.qualityScore,
totalDurationMs,
},
};
} catch (err) {
return {
success: false,
error: err instanceof Error ? err.message : String(err),
};
}
}
/**
* Run all 4 analysis subagents in parallel.
*/
private async runParallelAnalysis(
taskDescription: string,
onProgress?: ProgressCallback
): Promise<SubagentResult[]> {
const subagents: Array<{
type: SubagentResult['agentType'];
prompt: string;
}> = [
{ type: 'requirements', prompt: REQUIREMENTS_ANALYST_PROMPT.replace('{TASK}', taskDescription) },
{ type: 'architecture', prompt: ARCHITECTURE_PLANNER_PROMPT.replace('{TASK}', taskDescription) },
{ type: 'testing', prompt: TESTING_SPECIALIST_PROMPT.replace('{TASK}', taskDescription) },
{ type: 'risks', prompt: RISK_ANALYST_PROMPT.replace('{TASK}', taskDescription) },
];
// Run all subagents in parallel
const promises = subagents.map(({ type, prompt }) =>
this.runSubagent(type, prompt, onProgress)
);
return Promise.all(promises);
}
/**
* Run a single analysis subagent.
*/
private async runSubagent(
agentType: SubagentResult['agentType'],
prompt: string,
onProgress?: ProgressCallback
): Promise<SubagentResult> {
const startTime = Date.now();
const session = new Session({
workingDir: this.workingDir,
screenManager: this.screenManager,
useScreen: false,
mode: 'claude',
});
try {
onProgress?.('subagent', `Running ${agentType} analysis...`);
const { result } = await Promise.race([
session.runPrompt(prompt, { model: MODEL_ANALYSIS }),
this.timeout(SUBAGENT_TIMEOUT_MS),
]);
// Parse JSON from result
const jsonMatch = result.match(/\[[\s\S]*\]/);
if (!jsonMatch) {
return {
agentType,
items: [],
success: false,
error: 'No JSON array found in response',
durationMs: Date.now() - startTime,
};
}
const parsed = JSON.parse(jsonMatch[0]);
if (!Array.isArray(parsed)) {
return {
agentType,
items: [],
success: false,
error: 'Response is not an array',
durationMs: Date.now() - startTime,
};
}
const items = parsed.map((item: unknown) => {
if (typeof item !== 'object' || item === null) {
return { category: 'unknown', content: String(item) };
}
const obj = item as Record<string, unknown>;
return {
category: String(obj.category || 'general'),
content: String(obj.content || ''),
rationale: obj.rationale ? String(obj.rationale) : undefined,
};
});
onProgress?.('subagent', `${agentType} complete (${items.length} items)`);
return {
agentType,
items,
success: true,
durationMs: Date.now() - startTime,
};
} catch (err) {
return {
agentType,
items: [],
success: false,
error: err instanceof Error ? err.message : String(err),
durationMs: Date.now() - startTime,
};
} finally {
try {
await session.stop();
} catch {
// Ignore cleanup errors
}
}
}
/**
* Synthesize results from all subagents into a unified plan.
*/
private synthesizeResults(subagentResults: SubagentResult[]): SynthesisResult {
const allItems: PlanItem[] = [];
const sourceBreakdown: Record<string, number> = {};
// Collect items from all successful subagents
for (const result of subagentResults) {
if (!result.success) continue;
sourceBreakdown[result.agentType] = result.items.length;
for (const item of result.items) {
allItems.push({
content: item.content,
priority: null, // Will be assigned by verification
source: result.agentType,
rationale: item.rationale,
phase: this.determinePhase(item.content, result.agentType),
});
}
}
const totalFromSubagents = allItems.length;
// Deduplicate similar items
const deduped = this.deduplicateItems(allItems);
// Sort by phase
deduped.sort((a, b) => (a.phase || 4) - (b.phase || 4));
return {
items: deduped,
stats: {
totalFromSubagents,
afterDedup: deduped.length,
sourceBreakdown,
},
};
}
/**
* Determine the implementation phase for an item based on keywords.
*/
private determinePhase(content: string, source: string): number {
const lower = content.toLowerCase();
// Phase 1: Foundation
if (
lower.includes('create') && (lower.includes('project') || lower.includes('directory')) ||
lower.includes('setup') ||
lower.includes('initialize') ||
lower.includes('configure') ||
lower.includes('install') ||
lower.includes('define') && (lower.includes('interface') || lower.includes('type'))
) {
return 1;
}
// Phase 2: Tests (before implementation)
if (
source === 'testing' ||
lower.includes('test') ||
lower.includes('verify') && !lower.includes('final')
) {
return 2;
}
// Phase 3: Implementation
if (
lower.includes('implement') ||
lower.includes('build') ||
lower.includes('add') ||
lower.includes('create') && !lower.includes('test')
) {
return 3;
}
// Phase 4: Integration & Verification
if (
lower.includes('integrate') ||
lower.includes('connect') ||
lower.includes('final') ||
lower.includes('run') && lower.includes('suite')
) {
return 4;
}
return 3; // Default to implementation phase
}
/**
* Deduplicate similar items using fuzzy matching.
*/
private deduplicateItems(items: PlanItem[]): PlanItem[] {
const result: PlanItem[] = [];
for (const item of items) {
const isDuplicate = result.some(existing =>
this.isSimilar(existing.content, item.content)
);
if (!isDuplicate) {
result.push(item);
}
}
return result;
}
/**
* Check if two strings are similar (>60% word overlap).
*/
private isSimilar(a: string, b: string): boolean {
const wordsA = new Set(a.toLowerCase().split(/\s+/).filter(w => w.length > 3));
const wordsB = new Set(b.toLowerCase().split(/\s+/).filter(w => w.length > 3));
if (wordsA.size === 0 || wordsB.size === 0) return false;
let overlap = 0;
for (const word of wordsA) {
if (wordsB.has(word)) overlap++;
}
const similarity = overlap / Math.min(wordsA.size, wordsB.size);
return similarity > 0.6;
}
/**
* Run the verification subagent to validate and prioritize the plan.
*/
private async runVerification(
taskDescription: string,
synthesizedItems: PlanItem[],
onProgress?: ProgressCallback
): Promise<VerificationResult> {
const session = new Session({
workingDir: this.workingDir,
screenManager: this.screenManager,
useScreen: false,
mode: 'claude',
});
try {
// Format plan for verification
const planText = synthesizedItems
.map((item, idx) => `${idx + 1}. [Phase ${item.phase}] ${item.content}`)
.join('\n');
const prompt = VERIFICATION_PROMPT
.replace('{TASK}', taskDescription)
.replace('{PLAN}', planText);
onProgress?.('verification', 'Validating plan quality...');
const { result } = await Promise.race([
session.runPrompt(prompt, { model: MODEL_VERIFICATION }),
this.timeout(VERIFICATION_TIMEOUT_MS),
]);
// Parse JSON from result
const jsonMatch = result.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
// Fallback: return items with default priorities
return this.fallbackVerification(synthesizedItems);
}
const parsed = JSON.parse(jsonMatch[0]);
const validatedPlan: PlanItem[] = (parsed.validatedPlan || []).map((item: unknown) => {
if (typeof item !== 'object' || item === null) {
return { content: String(item), priority: 'P1' as const };
}
const obj = item as Record<string, unknown>;
let priority: PlanItem['priority'] = null;
if (obj.priority === 'P0' || obj.priority === 'P1' || obj.priority === 'P2') {
priority = obj.priority;
}
return {
content: String(obj.content || ''),
priority,
rationale: obj.rationale ? String(obj.rationale) : undefined,
};
});
onProgress?.('verification', `Verification complete (quality: ${Math.round((parsed.qualityScore || 0.8) * 100)}%)`);
return {
validatedPlan,
gaps: Array.isArray(parsed.gaps) ? parsed.gaps.map(String) : [],
warnings: Array.isArray(parsed.warnings) ? parsed.warnings.map(String) : [],
qualityScore: typeof parsed.qualityScore === 'number' ? parsed.qualityScore : 0.8,
};
} catch (err) {
console.error('[PlanOrchestrator] Verification failed:', err);
return this.fallbackVerification(synthesizedItems);
} finally {
try {
await session.stop();
} catch {
// Ignore cleanup errors
}
}
}
/**
* Fallback verification when the verification subagent fails.
*/
private fallbackVerification(items: PlanItem[]): VerificationResult {
return {
validatedPlan: items.map(item => ({
...item,
priority: item.phase === 1 ? 'P0' as const :
item.phase === 4 ? 'P2' as const : 'P1' as const,
})),
gaps: [],
warnings: ['Verification subagent failed - using heuristic priorities'],
qualityScore: 0.7,
};
}
/**
* Create a timeout promise.
*/
private timeout(ms: number): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms);
});
}
}
+170 -15
View File
@@ -1880,6 +1880,34 @@ class ClaudemanApp {
}
}, 5 * 60 * 1000); // 5 minutes
});
// Plan generation progress events (for detailed mode with subagents)
this.eventSource.addEventListener('plan:progress', (e) => {
const data = JSON.parse(e.data);
console.log('[Plan Progress]', data);
// Update UI if we have a progress handler registered
if (this._planProgressHandler) {
this._planProgressHandler({ type: 'plan:progress', data });
}
// Also update the loading display directly for better feedback
const titleEl = document.getElementById('planLoadingTitle');
const hintEl = document.getElementById('planLoadingHint');
if (titleEl && data.phase) {
const phaseLabels = {
'parallel-analysis': 'Running parallel analysis...',
'subagent': data.detail || 'Subagent working...',
'synthesis': 'Synthesizing results...',
'verification': 'Running verification...',
};
titleEl.textContent = phaseLabels[data.phase] || data.phase;
}
if (hintEl && data.detail) {
hintEl.textContent = data.detail;
}
});
}
setConnectionStatus(status) {
@@ -2856,6 +2884,7 @@ class ClaudemanApp {
async generatePlan() {
const config = this.ralphWizardConfig;
const isDetailed = config.planDetailLevel === 'detailed';
// Stop any existing generation first
this.stopPlanGeneration();
@@ -2869,8 +2898,8 @@ class ClaudemanApp {
document.getElementById('planEditor')?.classList.add('hidden');
document.getElementById('planGenerationLoading')?.classList.remove('hidden');
// Animated progress phases
const phases = [
// Different phases for detailed vs standard generation
const standardPhases = [
{ time: 0, title: 'Starting Opus 4.5...', hint: 'Initializing deep reasoning model' },
{ time: 3, title: 'Analyzing task requirements...', hint: 'Understanding the scope and complexity' },
{ time: 8, title: 'Identifying components...', hint: 'Breaking down into modules and features' },
@@ -2882,6 +2911,23 @@ class ClaudemanApp {
{ time: 90, title: 'Still working...', hint: 'Complex tasks take longer - hang tight!' },
];
const detailedPhases = [
{ time: 0, title: 'Spawning analysis subagents...', hint: 'Starting 4 specialist agents in parallel' },
{ time: 3, title: 'Requirements Analyst working...', hint: 'Extracting explicit and implicit requirements' },
{ time: 6, title: 'Architecture Planner working...', hint: 'Designing modules and interfaces' },
{ time: 9, title: 'TDD Specialist working...', hint: 'Planning test-first implementation' },
{ time: 12, title: 'Risk Analyst working...', hint: 'Identifying edge cases and blockers' },
{ time: 20, title: 'Subagents completing...', hint: 'Collecting analysis results' },
{ time: 30, title: 'Synthesizing results...', hint: 'Merging and deduplicating items' },
{ time: 40, title: 'Starting verification...', hint: 'Running quality assurance subagent' },
{ time: 50, title: 'Assigning priorities...', hint: 'Determining P0/P1/P2 for each task' },
{ time: 60, title: 'Checking for gaps...', hint: 'Identifying missing requirements' },
{ time: 75, title: 'Finalizing plan...', hint: 'Preparing validated task list' },
{ time: 90, title: 'Still working...', hint: 'Complex tasks take longer - hang tight!' },
];
const phases = isDetailed ? detailedPhases : standardPhases;
// Start elapsed time and phase display
this.planLoadingStartTime = Date.now();
const timeEl = document.getElementById('planLoadingTime');
@@ -2908,14 +2954,42 @@ class ClaudemanApp {
}
}, 1000);
// Listen for real-time progress updates from detailed generation
const handlePlanProgress = (event) => {
if (event.type === 'plan:progress' && event.data) {
const titleEl = document.getElementById('planLoadingTitle');
const hintEl = document.getElementById('planLoadingHint');
if (titleEl && event.data.phase) {
const phaseLabels = {
'parallel-analysis': 'Running parallel analysis...',
'subagent': event.data.detail || 'Subagent working...',
'synthesis': 'Synthesizing results...',
'verification': 'Running verification...',
};
titleEl.textContent = phaseLabels[event.data.phase] || event.data.phase;
}
if (hintEl && event.data.detail) {
hintEl.textContent = event.data.detail;
}
}
};
// Add SSE listener for detailed mode progress
if (isDetailed) {
this._planProgressHandler = handlePlanProgress;
}
try {
const res = await fetch('/api/generate-plan', {
// Use different endpoint for detailed mode
const endpoint = isDetailed ? '/api/generate-plan-detailed' : '/api/generate-plan';
const body = isDetailed
? { taskDescription: config.taskDescription }
: { taskDescription: config.taskDescription, detailLevel: config.planDetailLevel };
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
taskDescription: config.taskDescription,
detailLevel: config.planDetailLevel,
}),
body: JSON.stringify(body),
signal: this.planGenerationAbortController?.signal,
});
@@ -2927,6 +3001,9 @@ class ClaudemanApp {
this.planLoadingTimer = null;
}
// Remove progress handler
this._planProgressHandler = null;
if (!data.success) {
this.showPlanError(data.error || 'Failed to generate plan');
return;
@@ -2937,12 +3014,20 @@ class ClaudemanApp {
return;
}
// Show "Done!" briefly before transitioning
const titleEl = document.getElementById('planLoadingTitle');
const hintEl = document.getElementById('planLoadingHint');
// Show "Done!" with quality info for detailed mode
const doneTitle = document.getElementById('planLoadingTitle');
const doneHint = document.getElementById('planLoadingHint');
const spinnerEl = document.querySelector('.plan-spinner');
if (titleEl) titleEl.textContent = 'Done!';
if (hintEl) hintEl.textContent = `Generated ${data.data.items.length} steps`;
if (doneTitle) doneTitle.textContent = 'Done!';
if (doneHint) {
if (isDetailed && data.data.metadata?.qualityScore) {
const quality = Math.round(data.data.metadata.qualityScore * 100);
doneHint.textContent = `Generated ${data.data.items.length} steps (Quality: ${quality}%)`;
} else {
doneHint.textContent = `Generated ${data.data.items.length} steps`;
}
}
if (spinnerEl) spinnerEl.style.display = 'none';
// Brief pause to show "Done!" before showing editor
@@ -2958,6 +3043,11 @@ class ClaudemanApp {
config.skipPlanGeneration = false;
config.planCost = data.data.costUsd || 0;
// Store metadata for detailed mode
if (isDetailed && data.data.metadata) {
config.planMetadata = data.data.metadata;
}
// Show editor and update detail buttons
this.renderPlanEditor();
this.updateDetailLevelButtons();
@@ -2969,6 +3059,9 @@ class ClaudemanApp {
this.planLoadingTimer = null;
}
// Remove progress handler
this._planProgressHandler = null;
// Ignore abort errors (user cancelled, e.g., clicked "Use Existing Plan")
if (err.name === 'AbortError') {
console.log('Plan generation aborted by user');
@@ -2981,14 +3074,39 @@ class ClaudemanApp {
}
setPlanDetail(level) {
const previousLevel = this.ralphWizardConfig.planDetailLevel;
this.ralphWizardConfig.planDetailLevel = level;
this.updateDetailLevelButtons();
// If plan was already generated and level changed, automatically regenerate
if (this.ralphWizardConfig.planGenerated && previousLevel !== level) {
const modeLabel = level === 'detailed' ? 'Enhanced (Multi-Agent)' : level === 'brief' ? 'Brief' : 'Standard';
console.log(`[Ralph Wizard] Plan mode changed to ${modeLabel}, regenerating...`);
// Clear current plan and regenerate
this.ralphWizardConfig.generatedPlan = null;
this.ralphWizardConfig.planGenerated = false;
this.ralphWizardConfig.planMetadata = null;
// Trigger regeneration with visual feedback
this.generatePlan();
}
}
updateDetailLevelButtons() {
const level = this.ralphWizardConfig.planDetailLevel;
document.querySelectorAll('.plan-detail-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.detail === level);
const isActive = btn.dataset.detail === level;
btn.classList.toggle('active', isActive);
// Add visual indicator for enhanced mode
if (btn.dataset.detail === 'detailed') {
btn.title = 'Enhanced: Uses 4 parallel subagents + verification (slower but more thorough)';
} else if (btn.dataset.detail === 'standard') {
btn.title = 'Standard: Single-pass generation with Opus 4.5';
} else {
btn.title = 'Brief: High-level milestones only';
}
});
}
@@ -3015,11 +3133,48 @@ class ClaudemanApp {
list.innerHTML = '';
const items = this.ralphWizardConfig.generatedPlan || [];
const cost = this.ralphWizardConfig.planCost || 0;
const metadata = this.ralphWizardConfig.planMetadata;
// Update header with item count and cost
// Update header with item count, cost, and quality score if available
const statsEl = document.getElementById('planStats');
if (statsEl) {
statsEl.textContent = `${items.length} steps · $${cost.toFixed(3)}`;
let statsText = `${items.length} steps · $${cost.toFixed(3)}`;
// Show quality score and source info for detailed mode
if (metadata) {
const quality = Math.round(metadata.qualityScore * 100);
const qualityClass = quality >= 80 ? 'quality-high' : quality >= 60 ? 'quality-medium' : 'quality-low';
statsText = `<span class="${qualityClass}">${quality}% quality</span> · ${items.length} steps · $${cost.toFixed(3)}`;
// Add subagent breakdown if available
if (metadata.synthesisStats?.sourceBreakdown) {
const sources = metadata.synthesisStats.sourceBreakdown;
const sourceList = Object.entries(sources)
.map(([src, count]) => `${src}: ${count}`)
.join(', ');
statsText += ` · Sources: ${sourceList}`;
}
}
statsEl.innerHTML = statsText;
}
// Show warnings/gaps if present from verification
const warningsEl = document.getElementById('planWarnings');
if (warningsEl) {
if (metadata?.verificationWarnings?.length > 0 || metadata?.verificationGaps?.length > 0) {
let warningsHtml = '';
if (metadata.verificationGaps?.length > 0) {
warningsHtml += `<div class="plan-gaps"><strong>Gaps identified:</strong> ${metadata.verificationGaps.join('; ')}</div>`;
}
if (metadata.verificationWarnings?.length > 0) {
warningsHtml += `<div class="plan-warnings"><strong>Warnings:</strong> ${metadata.verificationWarnings.join('; ')}</div>`;
}
warningsEl.innerHTML = warningsHtml;
warningsEl.classList.remove('hidden');
} else {
warningsEl.classList.add('hidden');
}
}
items.forEach((item, index) => {
+4 -3
View File
@@ -1033,14 +1033,15 @@
<div class="plan-option-group">
<label>Detail:</label>
<div class="plan-detail-btns">
<button type="button" class="plan-detail-btn" data-detail="brief" onclick="app.setPlanDetail('brief')">Brief</button>
<button type="button" class="plan-detail-btn active" data-detail="standard" onclick="app.setPlanDetail('standard')">Standard</button>
<button type="button" class="plan-detail-btn" data-detail="detailed" onclick="app.setPlanDetail('detailed')">Detailed</button>
<button type="button" class="plan-detail-btn" data-detail="brief" onclick="app.setPlanDetail('brief')" title="High-level milestones only">Brief</button>
<button type="button" class="plan-detail-btn active" data-detail="standard" onclick="app.setPlanDetail('standard')" title="Single-pass generation with Opus 4.5">Standard</button>
<button type="button" class="plan-detail-btn" data-detail="detailed" onclick="app.setPlanDetail('detailed')" title="Enhanced: 4 parallel subagents + verification (slower but more thorough)">Enhanced</button>
</div>
</div>
<button class="btn-toolbar btn-sm" onclick="app.regeneratePlan()">Regenerate</button>
</div>
</div>
<div id="planWarnings" class="hidden"></div>
<div id="planItemsList" class="plan-items-list"></div>
<div class="plan-editor-footer">
<button class="btn-toolbar btn-sm" onclick="app.addPlanItem()">+ Add Step</button>
+43
View File
@@ -5718,6 +5718,49 @@ kbd {
text-align: left;
}
/* Quality score indicators for detailed plan mode */
.quality-high {
color: #4ade80;
font-weight: 500;
}
.quality-medium {
color: #fbbf24;
font-weight: 500;
}
.quality-low {
color: #f87171;
font-weight: 500;
}
.mode-changed {
color: var(--accent);
font-weight: 500;
}
/* Plan warnings and gaps display */
#planWarnings {
margin-bottom: 0.75rem;
padding: 0.5rem;
background: rgba(251, 191, 36, 0.1);
border-radius: 4px;
font-size: 0.8rem;
}
#planWarnings.hidden {
display: none;
}
.plan-gaps {
color: #f87171;
margin-bottom: 0.25rem;
}
.plan-warnings {
color: #fbbf24;
}
/* Textarea with AI Assist button */
.textarea-with-assist {
position: relative;
+49
View File
@@ -34,6 +34,7 @@ import { TranscriptWatcher } from '../transcript-watcher.js';
import { v4 as uuidv4 } from 'uuid';
import { createRequire } from 'node:module';
import { RunSummaryTracker } from '../run-summary.js';
import { PlanOrchestrator, type DetailedPlanResult } from '../plan-orchestrator.js';
// Load version from package.json
const require = createRequire(import.meta.url);
@@ -2387,6 +2388,54 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
}
});
// Generate detailed implementation plan using subagent orchestration
// This spawns multiple specialist subagents in parallel for thorough analysis
this.app.post('/api/generate-plan-detailed', async (req): Promise<ApiResponse> => {
const { taskDescription } = req.body as { taskDescription: string };
if (!taskDescription || typeof taskDescription !== 'string') {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Task description is required');
}
if (taskDescription.length > 10000) {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Task description too long (max 10000 chars)');
}
const orchestrator = new PlanOrchestrator(this.screenManager, process.cwd());
// Track progress for SSE updates
const progressUpdates: Array<{ phase: string; detail: string; timestamp: number }> = [];
const onProgress = (phase: string, detail: string) => {
const update = { phase, detail, timestamp: Date.now() };
progressUpdates.push(update);
// Broadcast progress to connected clients
this.broadcast('plan:progress', update);
};
try {
const result: DetailedPlanResult = await orchestrator.generateDetailedPlan(
taskDescription,
onProgress
);
if (!result.success) {
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, result.error || 'Plan generation failed');
}
return {
success: true,
data: {
items: result.items,
costUsd: result.costUsd,
metadata: result.metadata,
progressLog: progressUpdates,
},
};
} catch (err) {
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'Detailed plan generation failed: ' + getErrorMessage(err));
}
});
// ============ App Settings Endpoints ============
const settingsPath = join(homedir(), '.claudeman', 'settings.json');