From de45239fb72b70b6bf0c885c7f4ff26d62c05672 Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 27 Jan 2026 18:16:19 +0100 Subject: [PATCH] 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 --- CLAUDE.md | 21 +- package.json | 2 +- src/plan-orchestrator.ts | 636 ++++++++++++++++++++++++++++++++++++++ src/web/public/app.js | 185 ++++++++++- src/web/public/index.html | 7 +- src/web/public/styles.css | 43 +++ src/web/server.ts | 49 +++ 7 files changed, 923 insertions(+), 20 deletions(-) create mode 100644 src/plan-orchestrator.ts diff --git a/CLAUDE.md b/CLAUDE.md index be191af3..f57623eb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 `PHRASE`, 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 diff --git a/package.json b/package.json index 50b0e97d..181db80d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/plan-orchestrator.ts b/src/plan-orchestrator.ts new file mode 100644 index 00000000..91047c6a --- /dev/null +++ b/src/plan-orchestrator.ts @@ -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; + }; +} + +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 { + 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 { + 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 { + 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; + 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 = {}; + + // 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 { + 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; + 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 { + return new Promise((_, reject) => { + setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms); + }); + } +} diff --git a/src/web/public/app.js b/src/web/public/app.js index c1b805bd..7982acb7 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -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 = `${quality}% quality · ${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 += `
Gaps identified: ${metadata.verificationGaps.join('; ')}
`; + } + if (metadata.verificationWarnings?.length > 0) { + warningsHtml += `
Warnings: ${metadata.verificationWarnings.join('; ')}
`; + } + warningsEl.innerHTML = warningsHtml; + warningsEl.classList.remove('hidden'); + } else { + warningsEl.classList.add('hidden'); + } } items.forEach((item, index) => { diff --git a/src/web/public/index.html b/src/web/public/index.html index d138f2b6..4db67e7f 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -1033,14 +1033,15 @@
- - - + + +
+