diff --git a/CLAUDE.md b/CLAUDE.md index 84338ca6..9c2b0127 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.1393 (must match `package.json`) +**Version**: 0.1394 (must match `package.json`) **Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty diff --git a/package.json b/package.json index 8b1e5c7b..60c45d4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1393", + "version": "0.1394", "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/session.ts b/src/session.ts index f8bfaa8d..e74194c7 100644 --- a/src/session.ts +++ b/src/session.ts @@ -1202,18 +1202,24 @@ export class Session extends EventEmitter { * the response. * * @param prompt - The prompt text to send to Claude + * @param options - Optional configuration + * @param options.model - Model to use ('opus', 'sonnet', or full model name). Defaults to default model. + * @param options.onProgress - Callback for progress updates (token count, status) * @returns Promise resolving to the result text and total cost in USD * @throws {Error} If a process is already running in this session * * @example * ```typescript * const session = new Session({ workingDir: '/project' }); - * const { result, cost } = await session.runPrompt('Explain this code'); + * const { result, cost } = await session.runPrompt('Explain this code', { model: 'opus' }); * console.log(`Response: ${result}`); * console.log(`Cost: $${cost.toFixed(4)}`); * ``` */ - async runPrompt(prompt: string): Promise<{ result: string; cost: number }> { + async runPrompt( + prompt: string, + options?: { model?: string; onProgress?: (info: { tokens?: number; status?: string }) => void } + ): Promise<{ result: string; cost: number }> { return new Promise((resolve, reject) => { if (this.ptyProcess) { reject(new Error('Session already has a running process')); @@ -1235,14 +1241,20 @@ export class Session extends EventEmitter { try { // Spawn claude in a real PTY - console.log('[Session] Spawning PTY for claude with prompt:', prompt.substring(0, 50)); + const model = options?.model; + console.log('[Session] Spawning PTY for claude with prompt:', prompt.substring(0, 50), model ? `(model: ${model})` : ''); - this.ptyProcess = pty.spawn('claude', [ + const args = [ '-p', '--dangerously-skip-permissions', '--output-format', 'stream-json', - prompt - ], { + ]; + if (model) { + args.push('--model', model); + } + args.push(prompt); + + this.ptyProcess = pty.spawn('claude', args, { name: 'xterm-256color', cols: 120, rows: 40, diff --git a/src/web/public/app.js b/src/web/public/app.js index 40f388ed..ea74e4a7 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -2635,34 +2635,67 @@ class ClaudemanApp { async generatePlan() { const config = this.ralphWizardConfig; - // Stop any existing timer + // Stop any existing timers if (this.planLoadingTimer) { clearInterval(this.planLoadingTimer); this.planLoadingTimer = null; } + if (this.planPhaseTimer) { + clearInterval(this.planPhaseTimer); + this.planPhaseTimer = null; + } // Show loading state document.getElementById('planGenerationError')?.classList.add('hidden'); document.getElementById('planEditor')?.classList.add('hidden'); document.getElementById('planGenerationLoading')?.classList.remove('hidden'); - // Start elapsed time display + // Animated progress phases + const phases = [ + { 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' }, + { time: 15, title: 'Planning TDD approach...', hint: 'Designing test-first implementation strategy' }, + { time: 25, title: 'Generating implementation steps...', hint: 'Creating detailed action items with tests' }, + { time: 40, title: 'Adding verification checkpoints...', hint: 'Ensuring each phase has validation' }, + { time: 55, title: 'Reviewing for completeness...', hint: 'Checking all requirements are covered' }, + { time: 70, title: 'Finalizing plan...', hint: 'Organizing and prioritizing steps' }, + { time: 90, title: 'Still working...', hint: 'Complex tasks take longer - hang tight!' }, + ]; + + // Start elapsed time and phase display this.planLoadingStartTime = Date.now(); const timeEl = document.getElementById('planLoadingTime'); - if (timeEl) timeEl.textContent = '0s'; + const titleEl = document.getElementById('planLoadingTitle'); + const hintEl = document.getElementById('planLoadingHint'); + if (timeEl) timeEl.textContent = '0s'; + if (titleEl) titleEl.textContent = phases[0].title; + if (hintEl) hintEl.textContent = phases[0].hint; + + let currentPhaseIndex = 0; this.planLoadingTimer = setInterval(() => { const elapsed = Math.floor((Date.now() - this.planLoadingStartTime) / 1000); if (timeEl) timeEl.textContent = `${elapsed}s`; + + // Update phase based on elapsed time + for (let i = phases.length - 1; i >= 0; i--) { + if (elapsed >= phases[i].time && i > currentPhaseIndex) { + currentPhaseIndex = i; + if (titleEl) titleEl.textContent = phases[i].title; + if (hintEl) hintEl.textContent = phases[i].hint; + break; + } + } }, 1000); // Determine max items based on detail level const detailConfig = { - brief: { maxItems: 5, includeTests: false }, - standard: { maxItems: 8, includeTests: true }, - detailed: { maxItems: 12, includeTests: true }, + brief: { maxItems: 10 }, + standard: { maxItems: 15 }, + detailed: { maxItems: 20 }, }; - const { maxItems, includeTests } = detailConfig[config.planDetailLevel] || detailConfig.standard; + const { maxItems } = detailConfig[config.planDetailLevel] || detailConfig.standard; try { const res = await fetch('/api/generate-plan', { @@ -2671,7 +2704,6 @@ class ClaudemanApp { body: JSON.stringify({ taskDescription: config.taskDescription, maxItems, - includeTests, detailLevel: config.planDetailLevel, }), }); @@ -2838,6 +2870,16 @@ class ClaudemanApp { } skipPlanGeneration() { + // Stop any running timers + if (this.planLoadingTimer) { + clearInterval(this.planLoadingTimer); + this.planLoadingTimer = null; + } + if (this.planPhaseTimer) { + clearInterval(this.planPhaseTimer); + this.planPhaseTimer = null; + } + this.ralphWizardConfig.skipPlanGeneration = true; this.ralphWizardConfig.planGenerated = false; this.ralphWizardConfig.generatedPlan = null; diff --git a/src/web/public/index.html b/src/web/public/index.html index 8566bc69..6f972826 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -1011,11 +1011,12 @@
- Generating implementation plan... + Starting Opus 4.5... 0s
-

Claude is breaking down your task into actionable steps with tests

+

Initializing deep reasoning model

+ diff --git a/src/web/public/styles.css b/src/web/public/styles.css index 9ef715b6..009a6e24 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -5344,7 +5344,7 @@ kbd { /* Wizard Pages */ .wizard-page { - min-height: 280px; + min-height: 180px; } .wizard-page.hidden { @@ -5482,7 +5482,15 @@ kbd { .plan-loading-hint { color: var(--text-dim); font-size: 0.85rem; - margin: 0; + margin: 0 0 1rem 0; +} + +.plan-skip-btn { + opacity: 0.6; +} + +.plan-skip-btn:hover { + opacity: 1; } .plan-error-msg { @@ -5744,11 +5752,11 @@ kbd { background: var(--bg-dark); border: 1px solid var(--border); border-radius: 4px; - padding: 0.75rem; + padding: 0.5rem 0.75rem; font-family: 'SF Mono', Monaco, 'Courier New', monospace; font-size: 0.75rem; color: var(--text-dim); - max-height: 200px; + max-height: 120px; overflow-y: auto; white-space: pre-wrap; word-break: break-word; diff --git a/src/web/server.ts b/src/web/server.ts index 1383eb89..3f88bae0 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -2056,6 +2056,7 @@ export class WebServer extends EventEmitter { interface GeneratePlanRequest { taskDescription: string; maxItems?: number; + detailLevel?: 'brief' | 'standard' | 'detailed'; } interface PlanItem { @@ -2064,7 +2065,11 @@ export class WebServer extends EventEmitter { } this.app.post('/api/generate-plan', async (req): Promise => { - const { taskDescription, maxItems = 8 } = req.body as GeneratePlanRequest; + const { + taskDescription, + maxItems = 12, + detailLevel = 'standard' + } = req.body as GeneratePlanRequest; if (!taskDescription || typeof taskDescription !== 'string') { return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Task description is required'); @@ -2074,26 +2079,86 @@ export class WebServer extends EventEmitter { return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Task description too long (max 10000 chars)'); } - const prompt = `Break down this task into ${maxItems} or fewer actionable implementation steps. + // Build sophisticated prompt based on Ralph Wiggum methodology + const detailConfig = { + brief: { minSteps: 5, maxSteps: maxItems, testDepth: 'basic' }, + standard: { minSteps: 8, maxSteps: maxItems, testDepth: 'thorough' }, + detailed: { minSteps: 12, maxSteps: Math.max(maxItems, 15), testDepth: 'comprehensive' }, + }; + const config = detailConfig[detailLevel] || detailConfig.standard; -TASK: + const prompt = `You are an expert software architect breaking down a task into a thorough implementation plan. + +## TASK TO IMPLEMENT ${taskDescription} -OUTPUT FORMAT: -Return ONLY a JSON array. Each item must have: -- content: specific, actionable step (verb phrase, 5-80 chars) -- priority: "P0" (critical path), "P1" (standard), "P2" (nice-to-have), or null +## YOUR MISSION +Create a detailed, actionable implementation plan following Test-Driven Development (TDD) methodology. +Think deeply about: +- What are all the components needed? +- What could go wrong? Add defensive steps. +- How will we verify each part works? +- What edge cases need handling? -Example output: -[{"content": "Create auth database schema", "priority": "P0"}, {"content": "Implement JWT token generation", "priority": "P1"}] +## PLAN STRUCTURE (${config.minSteps}-${config.maxSteps} steps) -RULES: -- Start each step with a verb (Create, Implement, Add, Write, Configure, etc.) -- Keep steps focused and achievable in one work session -- Order by logical dependency (do first things first) -- Use P0 sparingly for truly critical items`; +Your plan MUST include these phases in order: - // Create temporary session for the AI call +### Phase 1: Foundation & Setup +- Project structure, dependencies, configuration +- Database schemas, type definitions, interfaces + +### Phase 2: Core Implementation (TDD Cycle) +For EACH feature: +1. Write failing tests first (unit tests) +2. Implement the feature +3. Run tests, debug until passing +4. Refactor if needed + +### Phase 3: Integration & Edge Cases +- Integration tests for feature interactions +- Edge case handling (errors, boundaries, invalid input) +- Error messages and user feedback + +### Phase 4: Verification & Hardening +- Run full test suite +- Fix any failing tests +- Add missing test coverage +- Final verification that ALL requirements are met + +## OUTPUT FORMAT +Return ONLY a JSON array. Each item: +- content: specific action (verb phrase, 15-120 chars, be descriptive!) +- priority: "P0" (critical/blocking), "P1" (required), "P2" (enhancement) + +## EXAMPLE OUTPUT +[ + {"content": "Create project structure with src/, tests/, and config directories", "priority": "P0"}, + {"content": "Define TypeScript interfaces for User, Session, and AuthToken types", "priority": "P0"}, + {"content": "Write failing unit tests for password hashing (valid password, empty, too short)", "priority": "P0"}, + {"content": "Implement password hashing with bcrypt, configurable salt rounds", "priority": "P0"}, + {"content": "Run password tests and debug until all pass", "priority": "P0"}, + {"content": "Write failing tests for JWT token generation and validation", "priority": "P0"}, + {"content": "Implement JWT service with access/refresh token support", "priority": "P0"}, + {"content": "Run JWT tests and verify token expiration handling works", "priority": "P0"}, + {"content": "Write integration tests for login flow (valid creds, invalid, locked account)", "priority": "P1"}, + {"content": "Implement login endpoint with rate limiting and audit logging", "priority": "P1"}, + {"content": "Add error handling for network failures and database timeouts", "priority": "P1"}, + {"content": "Run full test suite and fix any failures", "priority": "P1"}, + {"content": "Verify all original requirements are implemented and tested", "priority": "P1"} +] + +## CRITICAL RULES +1. EVERY implementation step should have a corresponding test step BEFORE it +2. Include "Run tests and debug/fix" steps after implementation blocks +3. Be SPECIFIC - not "Add tests" but "Write tests for X covering Y and Z" +4. Think about what could fail and add defensive steps +5. End with verification that ALL original requirements are met +6. Use P0 for foundation and core features, P1 for required work, P2 for nice-to-have + +NOW: Generate the implementation plan for the task above. Think step by step.`; + + // Create temporary session for the AI call using Opus 4.5 for deep reasoning const session = new Session({ workingDir: process.cwd(), screenManager: this.screenManager, @@ -2101,8 +2166,11 @@ RULES: mode: 'claude', }); + // Use Opus 4.5 for plan generation (better reasoning) + const modelToUse = 'opus'; + try { - const { result, cost } = await session.runPrompt(prompt); + const { result, cost } = await session.runPrompt(prompt, { model: modelToUse }); // Parse JSON from result const jsonMatch = result.match(/\[[\s\S]*\]/); diff --git a/test-screenshots/01-settings-display-tab.png b/test-screenshots/01-settings-display-tab.png new file mode 100644 index 00000000..7869e224 Binary files /dev/null and b/test-screenshots/01-settings-display-tab.png differ diff --git a/test-screenshots/02-settings-notifications-tab.png b/test-screenshots/02-settings-notifications-tab.png new file mode 100644 index 00000000..27279247 Binary files /dev/null and b/test-screenshots/02-settings-notifications-tab.png differ diff --git a/test-screenshots/03-file-browser-panel.png b/test-screenshots/03-file-browser-panel.png new file mode 100644 index 00000000..5a799149 Binary files /dev/null and b/test-screenshots/03-file-browser-panel.png differ diff --git a/test-screenshots/04-file-browser-expanded.png b/test-screenshots/04-file-browser-expanded.png new file mode 100644 index 00000000..70367013 Binary files /dev/null and b/test-screenshots/04-file-browser-expanded.png differ diff --git a/test-screenshots/05-file-browser-filtered.png b/test-screenshots/05-file-browser-filtered.png new file mode 100644 index 00000000..8aa83131 Binary files /dev/null and b/test-screenshots/05-file-browser-filtered.png differ diff --git a/test-screenshots/07-file-browser-all-expanded.png b/test-screenshots/07-file-browser-all-expanded.png new file mode 100644 index 00000000..20712a5d Binary files /dev/null and b/test-screenshots/07-file-browser-all-expanded.png differ diff --git a/test-screenshots/08-final-state.png b/test-screenshots/08-final-state.png new file mode 100644 index 00000000..d0c57ff6 Binary files /dev/null and b/test-screenshots/08-final-state.png differ diff --git a/test-screenshots/error-state.png b/test-screenshots/error-state.png new file mode 100644 index 00000000..158f15c5 Binary files /dev/null and b/test-screenshots/error-state.png differ