mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 06:29:42 +02:00
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:
+170
-15
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user