claude-kanban 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +74 -26
- package/dist/bin/cli.js.map +1 -1
- package/dist/server/index.js +74 -26
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/cli.ts","../../src/server/index.ts","../../src/server/services/executor.ts","../../src/server/services/project.ts","../../src/server/services/prd.ts","../../src/server/services/progress.ts","../../src/server/services/roadmap.ts","../../src/server/services/templates.ts","../../src/server/services/ai.ts","../../src/server/utils/port.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport open from 'open';\nimport { existsSync } from 'fs';\nimport { execSync } from 'child_process';\nimport { createInterface } from 'readline';\nimport { join } from 'path';\nimport { createServer } from '../server/index.js';\nimport { initializeProject, isProjectInitialized } from '../server/services/project.js';\nimport { findAvailablePort } from '../server/utils/port.js';\n\n/**\n * Check if directory is a git repository\n */\nfunction isGitRepo(dir: string): boolean {\n return existsSync(join(dir, '.git'));\n}\n\n/**\n * Check if git repo has a remote configured\n */\nfunction hasGitRemote(dir: string): boolean {\n try {\n const result = execSync('git remote -v', { cwd: dir, stdio: 'pipe' }).toString();\n return result.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Detect the type of git remote (github, gitlab, bitbucket, other)\n */\nfunction detectRemoteType(dir: string): 'github' | 'gitlab' | 'bitbucket' | 'other' | null {\n try {\n const result = execSync('git remote get-url origin', { cwd: dir, stdio: 'pipe' }).toString().toLowerCase();\n if (result.includes('github.com')) return 'github';\n if (result.includes('gitlab.com') || result.includes('gitlab')) return 'gitlab';\n if (result.includes('bitbucket.org') || result.includes('bitbucket')) return 'bitbucket';\n if (result.trim()) return 'other';\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Initialize git in directory\n */\nfunction initializeGit(dir: string): void {\n execSync('git init', { cwd: dir, stdio: 'pipe' });\n // Create initial commit if there are files\n try {\n execSync('git add -A', { cwd: dir, stdio: 'pipe' });\n execSync('git commit -m \"Initial commit\"', { cwd: dir, stdio: 'pipe' });\n } catch {\n // Ignore if nothing to commit\n }\n}\n\n/**\n * Prompt user for yes/no confirmation\n */\nasync function promptYesNo(question: string): Promise<boolean> {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.toLowerCase().startsWith('y'));\n });\n });\n}\n\nconst VERSION = '0.1.0';\n\nconst banner = `\n${chalk.cyan('╔═══════════════════════════════════════╗')}\n${chalk.cyan('║')} ${chalk.bold.white('Claude Kanban')} ${chalk.gray(`v${VERSION}`)} ${chalk.cyan('║')}\n${chalk.cyan('║')} ${chalk.gray('Visual AI-powered development')} ${chalk.cyan('║')}\n${chalk.cyan('╚═══════════════════════════════════════╝')}\n`;\n\nasync function main() {\n const program = new Command();\n\n program\n .name('claude-kanban')\n .description('Visual Kanban board for AI-powered development with Claude')\n .version(VERSION)\n .option('-p, --port <number>', 'Port to run server on', '4242')\n .option('-n, --no-open', 'Do not auto-open browser')\n .option('--init', 'Re-initialize project files')\n .option('--reset', 'Reset all tasks (keeps config)')\n .action(async (options) => {\n console.log(banner);\n\n const cwd = process.cwd();\n\n // Check if directory is a git repository\n if (!isGitRepo(cwd)) {\n console.log(chalk.yellow('\\n⚠ This directory is not a git repository.'));\n console.log(chalk.gray('Git is required for task isolation and parallel execution.\\n'));\n\n const shouldInit = await promptYesNo(chalk.white('Would you like to initialize git now? (y/n): '));\n\n if (shouldInit) {\n try {\n console.log(chalk.gray('Initializing git repository...'));\n initializeGit(cwd);\n console.log(chalk.green('✓ Git repository initialized'));\n } catch (error) {\n console.error(chalk.red('Failed to initialize git:'), error);\n process.exit(1);\n }\n } else {\n console.log(chalk.red('\\nGit is required to run Claude Kanban.'));\n console.log(chalk.gray('Please run \"git init\" manually and try again.'));\n process.exit(1);\n }\n } else {\n // Check for remote and show info\n const remoteType = detectRemoteType(cwd);\n if (remoteType) {\n console.log(chalk.gray(`Git remote detected: ${remoteType}`));\n } else if (!hasGitRemote(cwd)) {\n console.log(chalk.gray('Git repository (local only, no remote)'));\n }\n }\n\n // Check if project needs initialization\n const initialized = await isProjectInitialized(cwd);\n\n if (!initialized || options.init) {\n console.log(chalk.yellow('Initializing project...'));\n try {\n await initializeProject(cwd, options.reset);\n console.log(chalk.green('✓ Project initialized successfully'));\n } catch (error) {\n console.error(chalk.red('Failed to initialize project:'), error);\n process.exit(1);\n }\n } else {\n console.log(chalk.gray('Found existing configuration'));\n }\n\n // Find available port\n let port = parseInt(options.port, 10);\n try {\n port = await findAvailablePort(port);\n } catch {\n console.error(chalk.red(`Port ${port} is in use and no alternatives found`));\n process.exit(1);\n }\n\n // Start server\n console.log(chalk.gray(`Starting server on port ${port}...`));\n\n try {\n const server = await createServer(cwd, port);\n\n const url = `http://localhost:${port}`;\n console.log(chalk.green(`\\n✓ Server running at ${chalk.bold(url)}\\n`));\n\n // Open browser\n if (options.open !== false) {\n console.log(chalk.gray('Opening browser...'));\n await open(url);\n }\n\n // Handle graceful shutdown\n let isShuttingDown = false;\n const shutdown = () => {\n if (isShuttingDown) {\n console.log(chalk.red('\\nForce exiting...'));\n process.exit(1);\n }\n isShuttingDown = true;\n console.log(chalk.yellow('\\nShutting down...'));\n\n // Call cleanup function if available\n if ((server as any).cleanup) {\n (server as any).cleanup();\n }\n\n // Force exit after 3 seconds if server doesn't close\n const forceExitTimeout = setTimeout(() => {\n console.log(chalk.red('Forcing exit after timeout'));\n process.exit(0);\n }, 3000);\n\n server.close(() => {\n clearTimeout(forceExitTimeout);\n console.log(chalk.green('✓ Server stopped'));\n process.exit(0);\n });\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n // Keep process running\n console.log(chalk.gray('Press Ctrl+C to stop\\n'));\n\n } catch (error) {\n console.error(chalk.red('Failed to start server:'), error);\n process.exit(1);\n }\n });\n\n await program.parseAsync();\n}\n\nmain().catch((error) => {\n console.error(chalk.red('Fatal error:'), error);\n process.exit(1);\n});\n","import express from 'express';\nimport { createServer as createHttpServer, Server } from 'http';\nimport { Server as SocketIOServer } from 'socket.io';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { existsSync } from 'fs';\n\nimport { TaskExecutor } from './services/executor.js';\nimport { RoadmapService } from './services/roadmap.js';\nimport * as prdService from './services/prd.js';\nimport * as progressService from './services/progress.js';\nimport * as projectService from './services/project.js';\nimport * as templateService from './services/templates.js';\nimport { generateTaskFromPrompt } from './services/ai.js';\nimport type { CreateTaskRequest, UpdateTaskRequest, RoadmapGenerateRequest } from '../types.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport async function createServer(projectPath: string, port: number): Promise<Server> {\n const app = express();\n const httpServer = createHttpServer(app);\n const io = new SocketIOServer(httpServer, {\n cors: { origin: '*' },\n });\n\n // Middleware\n app.use(express.json());\n\n // Initialize executor\n const executor = new TaskExecutor(projectPath);\n\n // Initialize roadmap service\n const roadmapService = new RoadmapService(projectPath);\n\n // Forward executor events to WebSocket\n executor.on('task:started', (data) => io.emit('task:started', data));\n executor.on('task:output', (data) => io.emit('task:output', data));\n executor.on('task:completed', (data) => io.emit('task:completed', data));\n executor.on('task:failed', (data) => io.emit('task:failed', data));\n executor.on('task:cancelled', (data) => io.emit('task:cancelled', data));\n executor.on('afk:status', (data) => io.emit('afk:status', data));\n\n // Planning events\n executor.on('planning:started', (data) => io.emit('planning:started', data));\n executor.on('planning:output', (data) => io.emit('planning:output', data));\n executor.on('planning:completed', (data) => io.emit('planning:completed', data));\n executor.on('planning:failed', (data) => io.emit('planning:failed', data));\n executor.on('planning:cancelled', (data) => io.emit('planning:cancelled', data));\n\n // Roadmap events\n roadmapService.on('roadmap:started', (data) => io.emit('roadmap:started', data));\n roadmapService.on('roadmap:progress', (data) => io.emit('roadmap:progress', data));\n roadmapService.on('roadmap:completed', (data) => io.emit('roadmap:completed', data));\n roadmapService.on('roadmap:failed', (data) => io.emit('roadmap:failed', data));\n\n // ===== API Routes =====\n\n // Get all tasks\n app.get('/api/tasks', (_req, res) => {\n try {\n const tasks = prdService.getAllTasks(projectPath);\n res.json({ tasks });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Create task\n app.post('/api/tasks', (req, res) => {\n try {\n const request = req.body as CreateTaskRequest;\n if (!request.title || !request.description) {\n res.status(400).json({ error: 'Title and description are required' });\n return;\n }\n const task = prdService.createTask(projectPath, request);\n io.emit('task:created', task);\n res.status(201).json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Generate task with AI\n app.post('/api/tasks/generate', async (req, res) => {\n try {\n const { prompt } = req.body as { prompt: string };\n if (!prompt) {\n res.status(400).json({ error: 'Prompt is required' });\n return;\n }\n const taskRequest = await generateTaskFromPrompt(projectPath, prompt);\n res.json({ task: taskRequest });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get single task\n app.get('/api/tasks/:id', (req, res) => {\n try {\n const task = prdService.getTaskById(projectPath, req.params.id);\n if (!task) {\n res.status(404).json({ error: 'Task not found' });\n return;\n }\n res.json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Update task\n app.put('/api/tasks/:id', (req, res) => {\n try {\n const updates = req.body as UpdateTaskRequest;\n const task = prdService.updateTask(projectPath, req.params.id, updates);\n if (!task) {\n res.status(404).json({ error: 'Task not found' });\n return;\n }\n io.emit('task:updated', task);\n res.json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Delete task\n app.delete('/api/tasks/:id', (req, res) => {\n try {\n const deleted = prdService.deleteTask(projectPath, req.params.id);\n if (!deleted) {\n res.status(404).json({ error: 'Task not found' });\n return;\n }\n io.emit('task:deleted', { id: req.params.id });\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Run task\n app.post('/api/tasks/:id/run', async (req, res) => {\n try {\n await executor.runTask(req.params.id);\n res.json({ success: true });\n } catch (error) {\n res.status(400).json({ error: String(error) });\n }\n });\n\n // Cancel task (only one can run at a time)\n app.post('/api/tasks/:id/cancel', (req, res) => {\n try {\n // Verify the requested task is actually the one running\n const runningId = executor.getRunningTaskId();\n if (runningId !== req.params.id) {\n res.status(404).json({ error: 'Task not running' });\n return;\n }\n const cancelled = executor.cancelTask();\n if (!cancelled) {\n res.status(404).json({ error: 'Task not running' });\n return;\n }\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Retry task (move back to ready and optionally run)\n app.post('/api/tasks/:id/retry', async (req, res) => {\n try {\n const task = prdService.updateTask(projectPath, req.params.id, {\n status: 'ready',\n passes: false,\n });\n if (!task) {\n res.status(404).json({ error: 'Task not found' });\n return;\n }\n io.emit('task:updated', task);\n\n // Optionally auto-run\n if (req.body.autoRun) {\n await executor.runTask(req.params.id);\n }\n\n res.json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get task output (for running tasks)\n // Get task logs from file (persisted)\n app.get('/api/tasks/:id/logs', (req, res) => {\n try {\n const logs = executor.getTaskLog(req.params.id);\n if (logs === null) {\n res.json({ logs: '' }); // No logs yet\n return;\n }\n res.json({ logs });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n app.get('/api/tasks/:id/output', (req, res) => {\n try {\n const output = executor.getTaskOutput(req.params.id);\n if (!output) {\n res.status(404).json({ error: 'Task not running or not found' });\n return;\n }\n res.json({ output });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get progress log\n app.get('/api/progress', (req, res) => {\n try {\n const lines = parseInt(String(req.query.lines)) || 100;\n const content = progressService.getRecentProgress(projectPath, lines);\n res.json({ content });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get config\n app.get('/api/config', (_req, res) => {\n try {\n const config = projectService.getConfig(projectPath);\n res.json({ config });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Update config\n app.put('/api/config', (req, res) => {\n try {\n const currentConfig = projectService.getConfig(projectPath);\n const updatedConfig = { ...currentConfig, ...req.body };\n projectService.saveConfig(projectPath, updatedConfig);\n res.json({ config: updatedConfig });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get templates\n app.get('/api/templates', (_req, res) => {\n try {\n const templates = templateService.getAllTemplates();\n res.json({ templates });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get template by ID\n app.get('/api/templates/:id', (req, res) => {\n try {\n const template = templateService.getTemplateById(req.params.id);\n if (!template) {\n res.status(404).json({ error: 'Template not found' });\n return;\n }\n res.json({ template });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // AFK Mode endpoints (sequential only - one task at a time)\n app.post('/api/afk/start', (req, res) => {\n try {\n const { maxIterations } = req.body as { maxIterations?: number };\n executor.startAFKMode(maxIterations || 10);\n res.json({ success: true, status: executor.getAFKStatus() });\n } catch (error) {\n res.status(400).json({ error: String(error) });\n }\n });\n\n app.post('/api/afk/stop', (_req, res) => {\n try {\n executor.stopAFKMode();\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n app.get('/api/afk/status', (_req, res) => {\n try {\n const status = executor.getAFKStatus();\n res.json({ status });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // ===== Planning Endpoints =====\n\n // Start planning session\n app.post('/api/plan', async (req, res) => {\n try {\n const { goal } = req.body as { goal: string };\n if (!goal) {\n res.status(400).json({ error: 'Goal is required' });\n return;\n }\n await executor.runPlanningSession(goal);\n res.json({ success: true });\n } catch (error) {\n res.status(400).json({ error: String(error) });\n }\n });\n\n // Cancel planning session\n app.post('/api/plan/cancel', (_req, res) => {\n try {\n const cancelled = executor.cancelPlanning();\n if (!cancelled) {\n res.status(404).json({ error: 'No planning session running' });\n return;\n }\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get planning status\n app.get('/api/plan/status', (_req, res) => {\n try {\n const planning = executor.isPlanning();\n const goal = executor.getPlanningGoal();\n res.json({ planning, goal: goal || null });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get running task info (only one can run at a time)\n app.get('/api/running', (_req, res) => {\n try {\n const taskId = executor.getRunningTaskId();\n res.json({ running: taskId ? [taskId] : [], count: taskId ? 1 : 0 });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get task counts\n app.get('/api/stats', (_req, res) => {\n try {\n const counts = prdService.getTaskCounts(projectPath);\n const running = executor.isRunning() ? 1 : 0;\n const afk = executor.getAFKStatus();\n const planning = executor.isPlanning();\n res.json({ counts, running, afk, planning });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // ===== Roadmap Endpoints =====\n\n // Get existing roadmap\n app.get('/api/roadmap', (_req, res) => {\n try {\n const roadmap = roadmapService.getRoadmap();\n res.json({ roadmap });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Generate new roadmap\n app.post('/api/roadmap/generate', async (req, res) => {\n try {\n const request = req.body as RoadmapGenerateRequest;\n if (roadmapService.isRunning()) {\n res.status(400).json({ error: 'Roadmap generation already in progress' });\n return;\n }\n // Don't await - let it run in background and send events\n roadmapService.generate(request).catch(console.error);\n res.json({ success: true, message: 'Roadmap generation started' });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Cancel roadmap generation\n app.post('/api/roadmap/cancel', (_req, res) => {\n try {\n if (!roadmapService.isRunning()) {\n res.status(400).json({ error: 'No roadmap generation in progress' });\n return;\n }\n roadmapService.cancel();\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Add roadmap feature to kanban\n app.post('/api/roadmap/features/:id/add-to-kanban', (req, res) => {\n try {\n const roadmap = roadmapService.getRoadmap();\n if (!roadmap) {\n res.status(404).json({ error: 'No roadmap found' });\n return;\n }\n\n const feature = roadmap.features.find(f => f.id === req.params.id);\n if (!feature) {\n res.status(404).json({ error: 'Feature not found' });\n return;\n }\n\n if (feature.addedToKanban) {\n res.status(400).json({ error: 'Feature already added to kanban' });\n return;\n }\n\n // Convert MoSCoW priority to task priority\n const priorityMap: Record<string, 'critical' | 'high' | 'medium' | 'low'> = {\n must: 'critical',\n should: 'high',\n could: 'medium',\n wont: 'low',\n };\n\n // Create task from feature\n const task = prdService.createTask(projectPath, {\n title: feature.title,\n description: feature.description,\n category: feature.category,\n priority: priorityMap[feature.priority] || 'medium',\n steps: feature.steps || [],\n status: 'draft',\n });\n\n // Mark feature as added\n roadmapService.markFeatureAdded(req.params.id);\n\n io.emit('task:created', task);\n res.json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Delete roadmap feature\n app.delete('/api/roadmap/features/:id', (req, res) => {\n try {\n const roadmap = roadmapService.deleteFeature(req.params.id);\n if (!roadmap) {\n res.status(404).json({ error: 'Roadmap or feature not found' });\n return;\n }\n io.emit('roadmap:updated', roadmap);\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get roadmap generation status\n app.get('/api/roadmap/status', (_req, res) => {\n try {\n res.json({ generating: roadmapService.isRunning() });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // ===== Static Files =====\n\n // Serve the client files\n const clientPath = join(__dirname, '..', 'client');\n\n // Check if we're in development or production\n if (existsSync(clientPath)) {\n app.use(express.static(clientPath));\n }\n\n // Serve index.html for client-side routing\n app.get('*', (_req, res) => {\n // Serve inline HTML with the full client application\n res.send(getClientHTML());\n });\n\n // ===== WebSocket =====\n\n io.on('connection', (socket) => {\n console.log('Client connected');\n\n // Get logs for running task (only one can run at a time)\n const runningId = executor.getRunningTaskId();\n const runningIds = runningId ? [runningId] : [];\n const taskLogs: Record<string, string> = {};\n\n // Get logs for running task\n if (runningId) {\n const logs = executor.getTaskLog(runningId);\n if (logs) {\n taskLogs[runningId] = logs;\n }\n }\n\n // Send current state with logs\n socket.emit('init', {\n tasks: prdService.getAllTasks(projectPath),\n running: runningIds,\n afk: executor.getAFKStatus(),\n taskLogs, // Include logs for running task\n planning: executor.isPlanning(),\n planningGoal: executor.getPlanningGoal() || null,\n planningOutput: executor.getPlanningOutput() || [],\n });\n\n // Handle client requesting logs for a specific task\n socket.on('get-logs', (taskId: string) => {\n const logs = executor.getTaskLog(taskId);\n socket.emit('task-logs', { taskId, logs: logs || '' });\n });\n\n socket.on('disconnect', () => {\n console.log('Client disconnected');\n });\n });\n\n // ===== Start Server =====\n\n return new Promise((resolve, reject) => {\n httpServer.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n reject(new Error(`Port ${port} is already in use. Try a different port with --port`));\n } else {\n reject(error);\n }\n });\n\n httpServer.listen(port, () => {\n // Attach cleanup function to server for graceful shutdown\n (httpServer as any).cleanup = () => {\n console.log('Cleaning up executor...');\n executor.cancelAll();\n io.close();\n };\n resolve(httpServer);\n });\n });\n}\n\nfunction getClientHTML(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Claude Kanban</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=DM+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <script src=\"/socket.io/socket.io.js\"></script>\n <script>\n tailwind.config = {\n theme: {\n extend: {\n fontFamily: {\n display: ['DM Sans', 'system-ui', 'sans-serif'],\n sans: ['DM Sans', 'system-ui', 'sans-serif'],\n mono: ['IBM Plex Mono', 'monospace'],\n },\n colors: {\n canvas: {\n DEFAULT: '#ffffff',\n 50: '#fafafa',\n 100: '#f5f5f5',\n 200: '#e5e5e5',\n 300: '#d4d4d4',\n 400: '#a3a3a3',\n 500: '#737373',\n 600: '#525252',\n 700: '#404040',\n 800: '#262626',\n 900: '#171717',\n },\n accent: {\n DEFAULT: '#f97316',\n light: '#fb923c',\n dark: '#ea580c',\n muted: 'rgba(249, 115, 22, 0.1)',\n },\n status: {\n draft: '#a3a3a3',\n ready: '#3b82f6',\n running: '#f97316',\n success: '#22c55e',\n failed: '#ef4444',\n }\n },\n },\n },\n };\n </script>\n <style>\n /* ═══════════════════════════════════════════════════════════════\n CLEAN LIGHT THEME - Inspired by vibe-kanban\n ═══════════════════════════════════════════════════════════════ */\n\n :root {\n --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);\n --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);\n }\n\n * { box-sizing: border-box; }\n\n html {\n background: #fafafa;\n color: #171717;\n }\n\n body {\n font-family: 'DM Sans', system-ui, sans-serif;\n background: #fafafa;\n min-height: 100vh;\n overflow-x: hidden;\n }\n\n /* Clean scrollbar */\n ::-webkit-scrollbar { width: 6px; height: 6px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb {\n background: rgba(0, 0, 0, 0.15);\n border-radius: 3px;\n }\n ::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.25); }\n\n /* ─── Typography ─── */\n .font-display { font-family: 'DM Sans', system-ui, sans-serif; }\n .font-mono { font-family: 'IBM Plex Mono', monospace; }\n\n /* ─── Card System ─── */\n .card {\n background: #ffffff;\n border: 1px solid #e5e5e5;\n border-radius: 8px;\n transition: all 0.2s var(--ease-out-expo);\n }\n .card:hover {\n border-color: #d4d4d4;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n }\n\n /* ─── Task Card - Minimal like vibe-kanban ─── */\n .task-card {\n cursor: pointer;\n padding: 12px 14px;\n }\n .task-card:hover {\n background: #fafafa;\n }\n .task-card.selected {\n border-color: #f97316;\n background: rgba(249, 115, 22, 0.03);\n }\n\n /* ─── Drag & Drop ─── */\n .dragging {\n opacity: 0.9;\n transform: scale(1.02) rotate(0.5deg);\n box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);\n z-index: 1000;\n }\n .drag-over {\n background: rgba(249, 115, 22, 0.05);\n border: 2px dashed #f97316;\n border-radius: 8px;\n }\n\n /* ─── Column Headers - Minimal ─── */\n .column-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 4px;\n margin-bottom: 8px;\n border-bottom: 1px solid #f0f0f0;\n }\n .status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n .status-dot-draft { background: #a3a3a3; }\n .status-dot-ready { background: #3b82f6; }\n .status-dot-in_progress { background: #f97316; }\n .status-dot-completed { background: #22c55e; }\n .status-dot-failed { background: #ef4444; }\n\n /* ─── Buttons ─── */\n .btn {\n font-weight: 500;\n border-radius: 6px;\n transition: all 0.15s ease;\n cursor: pointer;\n border: none;\n }\n .btn:active { transform: scale(0.98); }\n\n .btn-primary {\n background: #f97316;\n color: white;\n }\n .btn-primary:hover {\n background: #ea580c;\n }\n\n .btn-ghost {\n background: transparent;\n border: 1px solid #e5e5e5;\n color: #525252;\n }\n .btn-ghost:hover {\n background: #f5f5f5;\n border-color: #d4d4d4;\n }\n\n .btn-danger {\n background: #fef2f2;\n color: #ef4444;\n border: 1px solid #fecaca;\n }\n .btn-danger:hover {\n background: #fee2e2;\n }\n\n /* ─── Side Panel (pushes content, not overlay) ─── */\n .side-panel {\n width: 420px;\n flex-shrink: 0;\n background: #ffffff;\n border-left: 1px solid #e5e5e5;\n display: flex;\n flex-direction: column;\n height: calc(100vh - 57px);\n overflow: hidden;\n }\n .main-content {\n flex: 1;\n min-width: 0;\n transition: all 0.2s var(--ease-out-expo);\n }\n .side-panel-header {\n padding: 12px 16px;\n border-bottom: 1px solid #e5e5e5;\n flex-shrink: 0;\n }\n .side-panel-body {\n flex: 1;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n min-height: 0;\n }\n .side-panel-tabs {\n display: flex;\n border-bottom: 1px solid #e5e5e5;\n padding: 0 20px;\n flex-shrink: 0;\n }\n .side-panel-tab {\n padding: 10px 14px;\n font-size: 13px;\n color: #737373;\n cursor: pointer;\n border-bottom: 2px solid transparent;\n transition: all 0.15s ease;\n }\n .side-panel-tab:hover { color: #171717; }\n .side-panel-tab.active {\n color: #171717;\n border-bottom-color: #f97316;\n }\n\n /* ─── Terminal/Log Panel ─── */\n .log-container {\n background: #1a1a1a;\n color: #e5e5e5;\n font-family: 'IBM Plex Mono', monospace;\n font-size: 12px;\n line-height: 1.6;\n padding: 16px;\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n min-height: 0;\n }\n .log-line {\n padding: 2px 0;\n word-wrap: break-word;\n white-space: pre-wrap;\n }\n .log-line:hover {\n background: rgba(255, 255, 255, 0.05);\n }\n\n /* ANSI colors */\n .ansi-red { color: #f87171; }\n .ansi-green { color: #86efac; }\n .ansi-yellow { color: #fde047; }\n .ansi-blue { color: #93c5fd; }\n .ansi-magenta { color: #e879f9; }\n .ansi-cyan { color: #67e8f9; }\n .ansi-bold { font-weight: 600; }\n .log-path { color: #93c5fd; }\n .log-error { color: #fca5a5; }\n .log-success { color: #86efac; }\n\n /* ─── Tab System ─── */\n .tab {\n padding: 10px 16px;\n color: #737373;\n cursor: pointer;\n border-bottom: 2px solid transparent;\n transition: all 0.15s ease;\n }\n .tab:hover { color: #171717; }\n .tab.active {\n color: #171717;\n border-bottom-color: #f97316;\n }\n\n /* ─── Modal ─── */\n .modal-backdrop {\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n }\n .modal-content {\n animation: modal-enter 0.2s var(--ease-out-expo);\n }\n @keyframes modal-enter {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n }\n\n /* ─── Form Inputs ─── */\n .input {\n background: #ffffff;\n border: 1px solid #e5e5e5;\n border-radius: 6px;\n padding: 10px 12px;\n color: #171717;\n transition: all 0.15s ease;\n font-size: 14px;\n }\n .input::placeholder { color: #a3a3a3; }\n .input:focus {\n outline: none;\n border-color: #f97316;\n box-shadow: 0 0 0 3px rgba(249, 115, 22, 0.1);\n }\n\n /* ─── Toast Notifications ─── */\n .toast-container {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .toast {\n padding: 12px 16px;\n border-radius: 8px;\n background: #ffffff;\n border: 1px solid #e5e5e5;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n animation: toast-enter 0.3s var(--ease-out-expo);\n display: flex;\n align-items: center;\n gap: 8px;\n color: #171717;\n }\n @keyframes toast-enter {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .toast-success { border-left: 3px solid #22c55e; }\n .toast-error { border-left: 3px solid #ef4444; }\n .toast-info { border-left: 3px solid #3b82f6; }\n .toast-warning { border-left: 3px solid #f97316; }\n\n /* ─── Status Badges ─── */\n .status-badge {\n font-size: 12px;\n font-weight: 500;\n padding: 4px 10px;\n border-radius: 12px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n .status-badge-draft { background: #f5f5f5; color: #737373; }\n .status-badge-ready { background: #eff6ff; color: #3b82f6; }\n .status-badge-in_progress { background: #fff7ed; color: #f97316; }\n .status-badge-completed { background: #f0fdf4; color: #22c55e; }\n .status-badge-failed { background: #fef2f2; color: #ef4444; }\n\n /* ─── Priority Badges ─── */\n .badge {\n font-size: 11px;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: 4px;\n }\n .badge-critical { background: #fef2f2; color: #ef4444; }\n .badge-high { background: #fff7ed; color: #f97316; }\n .badge-medium { background: #eff6ff; color: #3b82f6; }\n .badge-low { background: #f5f5f5; color: #737373; }\n\n /* ─── Stats Pills ─── */\n .stat-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n background: #f5f5f5;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 500;\n color: #525252;\n }\n .stat-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n }\n\n /* ─── Column Container ─── */\n .column-container {\n background: #ffffff;\n border: 1px solid #e5e5e5;\n border-radius: 12px;\n padding: 12px;\n min-height: 500px;\n }\n\n /* ─── Column Drop Zone ─── */\n .column-zone {\n min-height: 400px;\n padding: 4px;\n transition: all 0.2s ease;\n }\n\n /* ─── Chat Input ─── */\n .chat-input-container {\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 16px 24px;\n background: #ffffff;\n border-top: 1px solid #e5e5e5;\n z-index: 50;\n }\n .chat-input {\n display: flex;\n align-items: center;\n gap: 12px;\n background: #f5f5f5;\n border: 1px solid #e5e5e5;\n border-radius: 8px;\n padding: 10px 16px;\n }\n .chat-input input {\n flex: 1;\n background: transparent;\n border: none;\n outline: none;\n font-size: 14px;\n color: #171717;\n }\n .chat-input input::placeholder { color: #a3a3a3; }\n .chat-input button {\n background: #f97316;\n color: white;\n border: none;\n border-radius: 6px;\n padding: 8px 16px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s;\n }\n .chat-input button:hover { background: #ea580c; }\n\n /* ─── Details Grid ─── */\n .details-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 16px;\n padding: 16px 20px;\n border-bottom: 1px solid #e5e5e5;\n flex-shrink: 0;\n }\n .details-item {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .details-label {\n font-size: 11px;\n font-weight: 500;\n color: #737373;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .details-value {\n font-size: 13px;\n color: #171717;\n }\n\n /* ─── Page animations ─── */\n .fade-in {\n animation: fade-in 0.3s ease;\n }\n @keyframes fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n </style>\n</head>\n<body class=\"grain\">\n <div id=\"app\"></div>\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <script type=\"module\">\n${getClientJS()}\n </script>\n</body>\n</html>`;\n}\n\nfunction getClientJS(): string {\n return `\n// State\nlet state = {\n tasks: [],\n running: [],\n afk: { running: false, currentIteration: 0, maxIterations: 0, tasksCompleted: 0 },\n templates: [],\n config: null,\n selectedTask: null,\n taskOutput: {},\n taskStartTime: {},\n activeTab: null,\n showModal: null,\n aiPrompt: '',\n aiGenerating: false,\n editingTask: null,\n searchQuery: '',\n logSearch: '',\n showLineNumbers: false,\n autoScroll: true,\n logFullscreen: false,\n closedTabs: new Set(),\n sidePanel: null, // task id for side panel\n // Planning state\n planning: false,\n planningGoal: '',\n planningOutput: [],\n sidePanelTab: 'logs', // 'logs' or 'details'\n darkMode: localStorage.getItem('darkMode') === 'true', // Add dark mode state\n // View state (board or roadmap)\n currentView: 'board', // 'board' or 'roadmap'\n // Roadmap state\n roadmap: null,\n roadmapGenerating: false,\n roadmapProgress: null,\n roadmapError: null,\n roadmapSelectedFeature: null,\n roadmapEnableCompetitors: false,\n roadmapCustomPrompt: '',\n};\n\n// Toast notifications\nfunction showToast(message, type = 'info') {\n const container = document.getElementById('toast-container');\n const toast = document.createElement('div');\n toast.className = 'toast toast-' + type;\n const icons = { success: '✓', error: '✕', info: 'ℹ', warning: '⚠' };\n toast.innerHTML = '<span>' + (icons[type] || '') + '</span><span>' + escapeHtml(message) + '</span>';\n container.appendChild(toast);\n setTimeout(() => toast.remove(), 4000);\n}\n\n// ANSI color parser\nfunction parseAnsi(text) {\n const ansiRegex = /\\\\x1B\\\\[([0-9;]*)m/g;\n let result = '';\n let lastIndex = 0;\n let currentClasses = [];\n\n text.replace(ansiRegex, (match, codes, offset) => {\n result += escapeHtml(text.slice(lastIndex, offset));\n lastIndex = offset + match.length;\n\n codes.split(';').forEach(code => {\n const c = parseInt(code);\n if (c === 0) currentClasses = [];\n else if (c === 1) currentClasses.push('ansi-bold');\n else if (c === 2) currentClasses.push('ansi-dim');\n else if (c === 3) currentClasses.push('ansi-italic');\n else if (c === 4) currentClasses.push('ansi-underline');\n else if (c === 30) currentClasses.push('ansi-black');\n else if (c === 31) currentClasses.push('ansi-red');\n else if (c === 32) currentClasses.push('ansi-green');\n else if (c === 33) currentClasses.push('ansi-yellow');\n else if (c === 34) currentClasses.push('ansi-blue');\n else if (c === 35) currentClasses.push('ansi-magenta');\n else if (c === 36) currentClasses.push('ansi-cyan');\n else if (c === 37) currentClasses.push('ansi-white');\n else if (c === 90) currentClasses.push('ansi-bright-black');\n else if (c === 91) currentClasses.push('ansi-bright-red');\n else if (c === 92) currentClasses.push('ansi-bright-green');\n else if (c === 93) currentClasses.push('ansi-bright-yellow');\n else if (c === 94) currentClasses.push('ansi-bright-blue');\n else if (c === 95) currentClasses.push('ansi-bright-magenta');\n else if (c === 96) currentClasses.push('ansi-bright-cyan');\n else if (c === 97) currentClasses.push('ansi-bright-white');\n });\n\n if (currentClasses.length > 0) {\n result += '<span class=\"' + currentClasses.join(' ') + '\">';\n }\n });\n\n result += escapeHtml(text.slice(lastIndex));\n return result;\n}\n\n// Syntax highlight log lines\nfunction highlightLog(text) {\n let highlighted = parseAnsi(text);\n // Highlight file paths\n highlighted = highlighted.replace(/([\\\\/\\\\w.-]+\\\\.(ts|js|tsx|jsx|json|md|css|html))/g, '<span class=\"log-path\">$1</span>');\n // Highlight errors\n highlighted = highlighted.replace(/(error|Error|ERROR|failed|Failed|FAILED)/g, '<span class=\"log-error\">$1</span>');\n // Highlight success\n highlighted = highlighted.replace(/(success|Success|SUCCESS|complete|Complete|COMPLETE|passed|Passed|PASSED)/g, '<span class=\"log-success\">$1</span>');\n // Highlight warnings\n highlighted = highlighted.replace(/(warning|Warning|WARNING|warn|Warn|WARN)/g, '<span class=\"log-warning\">$1</span>');\n return highlighted;\n}\n\n// Format elapsed time\nfunction formatElapsed(startTime) {\n if (!startTime) return '';\n const elapsed = Date.now() - new Date(startTime).getTime();\n const seconds = Math.floor(elapsed / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm';\n if (minutes > 0) return minutes + 'm ' + (seconds % 60) + 's';\n return seconds + 's';\n}\n\n// Socket connection\nconst socket = io();\n\nsocket.on('init', (data) => {\n state.tasks = data.tasks;\n state.running = data.running;\n state.afk = data.afk;\n\n // Planning state\n state.planning = data.planning || false;\n state.planningGoal = data.planningGoal || '';\n state.planningOutput = (data.planningOutput || []).map(text => ({\n text: text,\n timestamp: new Date().toISOString()\n }));\n\n // Load persisted logs for running tasks\n if (data.taskLogs) {\n for (const [taskId, logs] of Object.entries(data.taskLogs)) {\n if (logs) {\n // Parse logs into lines for the taskOutput format\n state.taskOutput[taskId] = String(logs).split('\\\\n').filter(l => l).map(text => ({\n text: text + '\\\\n',\n timestamp: new Date().toISOString()\n }));\n }\n }\n }\n\n render();\n});\n\n// Handle logs response from server\nsocket.on('task-logs', ({ taskId, logs }) => {\n if (logs) {\n state.taskOutput[taskId] = logs.split('\\\\n').filter(l => l).map(text => ({\n text: text + '\\\\n',\n timestamp: new Date().toISOString()\n }));\n render();\n if (state.autoScroll) {\n requestAnimationFrame(() => scrollSidePanelLog());\n }\n }\n});\n\nsocket.on('task:created', (task) => {\n state.tasks.push(task);\n showToast('Task created: ' + task.title, 'success');\n render();\n});\n\nsocket.on('task:updated', (task) => {\n const idx = state.tasks.findIndex(t => t.id === task.id);\n if (idx >= 0) state.tasks[idx] = task;\n render();\n});\n\nsocket.on('task:deleted', ({ id }) => {\n state.tasks = state.tasks.filter(t => t.id !== id);\n render();\n});\n\nsocket.on('task:started', ({ taskId, timestamp }) => {\n if (!state.running.includes(taskId)) state.running.push(taskId);\n const task = state.tasks.find(t => t.id === taskId);\n if (task) task.status = 'in_progress';\n state.taskOutput[taskId] = [];\n state.taskStartTime[taskId] = timestamp || new Date().toISOString();\n state.activeTab = taskId;\n state.closedTabs.delete(taskId);\n showToast('Task started: ' + (task?.title || taskId), 'info');\n render();\n});\n\nsocket.on('task:output', ({ taskId, line }) => {\n if (!state.taskOutput[taskId]) state.taskOutput[taskId] = [];\n state.taskOutput[taskId].push({ text: line, timestamp: new Date().toISOString() });\n render();\n // Auto-scroll after DOM update\n if (state.autoScroll) {\n requestAnimationFrame(() => {\n scrollLogToBottom();\n scrollSidePanelLog();\n });\n }\n});\n\nsocket.on('task:completed', ({ taskId }) => {\n state.running = state.running.filter(id => id !== taskId);\n const task = state.tasks.find(t => t.id === taskId);\n if (task) {\n task.status = 'completed';\n task.passes = true;\n }\n showToast('Task completed: ' + (task?.title || taskId), 'success');\n render();\n});\n\nsocket.on('task:failed', ({ taskId }) => {\n state.running = state.running.filter(id => id !== taskId);\n const task = state.tasks.find(t => t.id === taskId);\n if (task) { task.status = 'failed'; task.passes = false; }\n showToast('Task failed: ' + (task?.title || taskId), 'error');\n render();\n});\n\nsocket.on('task:cancelled', ({ taskId }) => {\n state.running = state.running.filter(id => id !== taskId);\n const task = state.tasks.find(t => t.id === taskId);\n if (task) task.status = 'ready';\n showToast('Task cancelled', 'warning');\n render();\n});\n\nsocket.on('afk:status', (status) => {\n state.afk = status;\n render();\n});\n\n// Planning socket handlers\nsocket.on('planning:started', ({ goal, timestamp }) => {\n state.planning = true;\n state.planningGoal = goal;\n state.planningOutput = [];\n state.showModal = 'planning';\n showToast('Planning started: ' + goal.substring(0, 50) + (goal.length > 50 ? '...' : ''), 'info');\n render();\n});\n\nsocket.on('planning:output', ({ line }) => {\n state.planningOutput.push({\n text: line,\n timestamp: new Date().toISOString()\n });\n render();\n});\n\nsocket.on('planning:completed', () => {\n state.planning = false;\n showToast('Planning complete! New tasks added to board.', 'success');\n // Refresh tasks from server\n fetch('/api/tasks').then(r => r.json()).then(data => {\n state.tasks = data.tasks;\n state.showModal = null;\n render();\n });\n});\n\nsocket.on('planning:failed', ({ error }) => {\n state.planning = false;\n showToast('Planning failed: ' + error, 'error');\n render();\n});\n\nsocket.on('planning:cancelled', () => {\n state.planning = false;\n state.showModal = null;\n showToast('Planning cancelled', 'warning');\n render();\n});\n\n// Roadmap events\nsocket.on('roadmap:started', () => {\n state.roadmapGenerating = true;\n state.roadmapProgress = { phase: 'analyzing', message: 'Starting roadmap generation...' };\n state.roadmapError = null;\n render();\n});\n\nsocket.on('roadmap:progress', ({ phase, message }) => {\n state.roadmapProgress = { phase, message };\n render();\n});\n\nsocket.on('roadmap:completed', ({ roadmap }) => {\n state.roadmap = roadmap;\n state.roadmapGenerating = false;\n state.roadmapProgress = null;\n state.showModal = null;\n showToast('Roadmap generated successfully!', 'success');\n render();\n});\n\nsocket.on('roadmap:failed', ({ error }) => {\n state.roadmapGenerating = false;\n state.roadmapProgress = null;\n state.roadmapError = error;\n showToast('Roadmap generation failed: ' + error, 'error');\n render();\n});\n\nsocket.on('roadmap:updated', (roadmap) => {\n state.roadmap = roadmap;\n render();\n});\n\n// Load templates\nfetch('/api/templates').then(r => r.json()).then(data => {\n state.templates = data.templates;\n});\n\n// Load config\nfetch('/api/config').then(r => r.json()).then(data => {\n state.config = data.config;\n});\n\n// API calls\nasync function createTask(task) {\n const res = await fetch('/api/tasks', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(task)\n });\n return res.json();\n}\n\nasync function updateTask(id, updates) {\n const res = await fetch('/api/tasks/' + id, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(updates)\n });\n return res.json();\n}\n\nasync function deleteTask(id) {\n await fetch('/api/tasks/' + id, { method: 'DELETE' });\n}\n\nasync function runTask(id) {\n await fetch('/api/tasks/' + id + '/run', { method: 'POST' });\n}\n\nasync function cancelTask(id) {\n await fetch('/api/tasks/' + id + '/cancel', { method: 'POST' });\n}\n\nasync function retryTask(id) {\n await fetch('/api/tasks/' + id + '/retry', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ autoRun: false })\n });\n}\n\nasync function generateTask(prompt) {\n state.aiGenerating = true;\n render();\n try {\n const res = await fetch('/api/tasks/generate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ prompt })\n });\n const data = await res.json();\n state.aiGenerating = false;\n return data.task;\n } catch (e) {\n state.aiGenerating = false;\n throw e;\n }\n}\n\nasync function startAFK(maxIterations) {\n await fetch('/api/afk/start', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ maxIterations })\n });\n}\n\nasync function stopAFK() {\n await fetch('/api/afk/stop', { method: 'POST' });\n}\n\nasync function startPlanning(goal) {\n await fetch('/api/plan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ goal })\n });\n}\n\nasync function cancelPlanning() {\n await fetch('/api/plan/cancel', { method: 'POST' });\n}\n\n// Roadmap API functions\nasync function loadRoadmap() {\n const res = await fetch('/api/roadmap');\n const data = await res.json();\n state.roadmap = data.roadmap;\n render();\n}\n\nasync function generateRoadmap() {\n state.roadmapGenerating = true;\n state.roadmapError = null;\n render();\n try {\n await fetch('/api/roadmap/generate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n enableCompetitorResearch: state.roadmapEnableCompetitors,\n customPrompt: state.roadmapCustomPrompt || undefined\n })\n });\n } catch (e) {\n state.roadmapGenerating = false;\n state.roadmapError = e.message;\n render();\n }\n}\n\nasync function cancelRoadmap() {\n await fetch('/api/roadmap/cancel', { method: 'POST' });\n state.roadmapGenerating = false;\n state.roadmapProgress = null;\n render();\n}\n\nasync function addFeatureToKanban(featureId) {\n try {\n const res = await fetch('/api/roadmap/features/' + featureId + '/add-to-kanban', {\n method: 'POST'\n });\n const data = await res.json();\n if (data.task) {\n showToast('Feature added to kanban!', 'success');\n // Reload roadmap to update addedToKanban status\n await loadRoadmap();\n }\n } catch (e) {\n showToast('Failed to add feature: ' + e.message, 'error');\n }\n}\n\nasync function deleteRoadmapFeature(featureId) {\n try {\n await fetch('/api/roadmap/features/' + featureId, { method: 'DELETE' });\n showToast('Feature removed', 'info');\n } catch (e) {\n showToast('Failed to remove feature: ' + e.message, 'error');\n }\n}\n\n// Load roadmap on init\nloadRoadmap();\n\n// Enhanced Drag and drop\nlet draggedTask = null;\nlet draggedElement = null;\n\nfunction handleDragStart(e, taskId) {\n draggedTask = taskId;\n draggedElement = e.target;\n e.target.classList.add('dragging');\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', taskId);\n\n // Add visual feedback to valid drop zones\n setTimeout(() => {\n document.querySelectorAll('.column-drop-zone').forEach(zone => {\n const status = zone.dataset.status;\n if (status !== 'in_progress') {\n zone.classList.add('border-dashed', 'border-2', 'border-blue-400/30');\n }\n });\n }, 0);\n}\n\nfunction handleDragEnd(e) {\n e.target.classList.remove('dragging');\n document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));\n document.querySelectorAll('.column-drop-zone').forEach(zone => {\n zone.classList.remove('border-dashed', 'border-2', 'border-blue-400/30');\n });\n draggedTask = null;\n draggedElement = null;\n}\n\nfunction handleDragOver(e) {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n const zone = e.currentTarget;\n if (!zone.classList.contains('drag-over') && zone.dataset.status !== 'in_progress') {\n zone.classList.add('drag-over');\n }\n}\n\nfunction handleDragLeave(e) {\n // Only remove if actually leaving the zone\n const rect = e.currentTarget.getBoundingClientRect();\n const x = e.clientX;\n const y = e.clientY;\n if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {\n e.currentTarget.classList.remove('drag-over');\n }\n}\n\nfunction handleDrop(e, newStatus) {\n e.preventDefault();\n e.currentTarget.classList.remove('drag-over');\n if (draggedTask && newStatus !== 'in_progress') {\n const task = state.tasks.find(t => t.id === draggedTask);\n if (task && task.status !== newStatus) {\n updateTask(draggedTask, { status: newStatus });\n showToast('Moved to ' + newStatus.replace('_', ' '), 'info');\n }\n }\n draggedTask = null;\n}\n\nfunction scrollLogToBottom() {\n const logEl = document.getElementById('log-content');\n if (logEl) logEl.scrollTop = logEl.scrollHeight;\n}\n\nfunction scrollSidePanelLog() {\n const logEl = document.getElementById('side-panel-log');\n if (logEl) logEl.scrollTop = logEl.scrollHeight;\n}\n\nfunction copyLogToClipboard() {\n const output = state.taskOutput[state.activeTab] || [];\n const text = output.map(l => l.text || l).join('');\n navigator.clipboard.writeText(text).then(() => {\n showToast('Log copied to clipboard', 'success');\n });\n}\n\nfunction clearLog(taskId) {\n state.taskOutput[taskId] = [];\n render();\n}\n\nfunction closeLogTab(taskId) {\n state.closedTabs.add(taskId);\n if (state.activeTab === taskId) {\n const remaining = Object.keys(state.taskOutput).filter(id => !state.closedTabs.has(id));\n state.activeTab = remaining[0] || null;\n }\n render();\n}\n\nfunction toggleLogFullscreen() {\n state.logFullscreen = !state.logFullscreen;\n render();\n}\n\nconst categoryIcons = {\n functional: '⚙️',\n ui: '🎨',\n bug: '🐛',\n enhancement: '✨',\n testing: '🧪',\n refactor: '🔧'\n};\n\n// Render functions\nfunction renderCard(task) {\n const isRunning = state.running.includes(task.id);\n const isSelected = state.sidePanel === task.id;\n\n return \\`\n <div class=\"card task-card mb-2 group \\${isRunning ? 'running' : ''} \\${isSelected ? 'selected' : ''}\"\n onclick=\"openSidePanel('\\${task.id}')\"\n draggable=\"\\${!isRunning}\"\n ondragstart=\"handleDragStart(event, '\\${task.id}')\"\n ondragend=\"handleDragEnd(event)\">\n <div class=\"flex justify-between items-start gap-2\">\n <h3 class=\"font-medium text-canvas-800 text-sm leading-snug\">\\${escapeHtml(task.title)}</h3>\n <button onclick=\"event.stopPropagation(); showTaskMenu('\\${task.id}')\"\n class=\"text-canvas-400 hover:text-canvas-600 p-1 -mr-1 opacity-0 group-hover:opacity-100\">\n ⋯\n </button>\n </div>\n <p class=\"text-xs text-canvas-500 mt-1 line-clamp-2\">\\${escapeHtml(task.description.substring(0, 80))}\\${task.description.length > 80 ? '...' : ''}</p>\n \\${isRunning ? \\`\n <div class=\"flex items-center gap-2 mt-2 text-xs text-status-running\">\n <span class=\"w-1.5 h-1.5 bg-status-running rounded-full animate-pulse\"></span>\n Running...\n </div>\n \\` : ''}\n </div>\n \\`;\n}\n\nfunction openSidePanel(taskId) {\n state.sidePanel = taskId;\n state.activeTab = taskId;\n state.closedTabs.delete(taskId);\n\n // Auto-switch to logs tab if task is running\n if (state.running.includes(taskId)) {\n state.sidePanelTab = 'logs';\n }\n\n // Request logs from server if not already loaded\n if (!state.taskOutput[taskId] || state.taskOutput[taskId].length === 0) {\n socket.emit('get-logs', taskId);\n }\n\n render();\n\n // Scroll to bottom after render\n if (state.autoScroll) {\n requestAnimationFrame(() => scrollSidePanelLog());\n }\n}\n\nfunction closeSidePanel() {\n state.sidePanel = null;\n render();\n}\n\nfunction showTaskMenu(taskId) {\n // For now just open side panel, could add dropdown menu later\n openSidePanel(taskId);\n}\n\n// Roadmap rendering functions\nfunction renderRoadmap() {\n if (state.roadmapGenerating) {\n return \\`\n <div class=\"flex-1 flex items-center justify-center\">\n <div class=\"text-center\">\n <div class=\"animate-spin rounded-full h-12 w-12 border-b-2 border-accent mx-auto mb-4\"></div>\n <h3 class=\"text-lg font-medium text-canvas-800 mb-2\">Generating Roadmap...</h3>\n <p class=\"text-canvas-500\">\\${state.roadmapProgress?.message || 'Please wait...'}</p>\n <button onclick=\"cancelRoadmap()\" class=\"btn btn-ghost mt-4 text-sm\">Cancel</button>\n </div>\n </div>\n \\`;\n }\n\n if (!state.roadmap) {\n return \\`\n <div class=\"flex-1 flex items-center justify-center\">\n <div class=\"text-center max-w-md\">\n <div class=\"text-6xl mb-4\">🗺️</div>\n <h3 class=\"text-xl font-semibold text-canvas-800 mb-2\">No Roadmap Yet</h3>\n <p class=\"text-canvas-500 mb-6\">Generate a strategic feature roadmap using AI to analyze your project and suggest features.</p>\n <button onclick=\"state.showModal = 'roadmap'; render();\" class=\"btn btn-primary px-6 py-2\">\n 🚀 Generate Roadmap\n </button>\n </div>\n </div>\n \\`;\n }\n\n const roadmap = state.roadmap;\n const phases = roadmap.phases || [];\n const features = roadmap.features || [];\n\n return \\`\n <div class=\"flex-1 overflow-y-auto p-6\">\n <!-- Roadmap Header -->\n <div class=\"mb-6 flex items-start justify-between\">\n <div>\n <h2 class=\"text-2xl font-semibold text-canvas-900\">\\${escapeHtml(roadmap.projectName)} Roadmap</h2>\n <p class=\"text-canvas-500 mt-1\">\\${escapeHtml(roadmap.projectDescription || '')}</p>\n <p class=\"text-sm text-canvas-400 mt-2\">Target: \\${escapeHtml(roadmap.targetAudience || 'Developers')}</p>\n </div>\n <div class=\"flex gap-2\">\n <button onclick=\"state.showModal = 'roadmap'; render();\" class=\"btn btn-ghost text-sm\">\n 🔄 Regenerate\n </button>\n </div>\n </div>\n\n <!-- Competitors (if available) -->\n \\${roadmap.competitors && roadmap.competitors.length > 0 ? \\`\n <div class=\"mb-6\">\n <h3 class=\"text-sm font-medium text-canvas-700 mb-2\">Competitor Insights</h3>\n <div class=\"flex gap-2 flex-wrap\">\n \\${roadmap.competitors.map(c => \\`\n <span class=\"px-3 py-1 bg-canvas-100 rounded-full text-sm text-canvas-600\">\n \\${escapeHtml(c.name)}\n </span>\n \\`).join('')}\n </div>\n </div>\n \\` : ''}\n\n <!-- Phases -->\n <div class=\"space-y-8\">\n \\${phases.map(phase => {\n const phaseFeatures = features.filter(f => f.phase === phase.name);\n return \\`\n <div class=\"phase-section\">\n <div class=\"flex items-center gap-3 mb-4\">\n <h3 class=\"text-lg font-semibold text-canvas-800\">\\${escapeHtml(phase.name)}</h3>\n <span class=\"text-xs bg-canvas-100 px-2 py-0.5 rounded-full text-canvas-500\">\\${phaseFeatures.length} features</span>\n </div>\n <p class=\"text-sm text-canvas-500 mb-4\">\\${escapeHtml(phase.description || '')}</p>\n <div class=\"grid gap-3 md:grid-cols-2 lg:grid-cols-3\">\n \\${phaseFeatures.map(f => renderRoadmapFeature(f)).join('')}\n </div>\n </div>\n \\`;\n }).join('')}\n </div>\n </div>\n \\`;\n}\n\nfunction renderRoadmapFeature(feature) {\n const priorityColors = {\n must: 'bg-red-100 text-red-700',\n should: 'bg-orange-100 text-orange-700',\n could: 'bg-blue-100 text-blue-700',\n wont: 'bg-gray-100 text-gray-500'\n };\n const priorityLabels = {\n must: 'Must Have',\n should: 'Should Have',\n could: 'Could Have',\n wont: \"Won't Have\"\n };\n const effortIcons = {\n low: '⚡',\n medium: '⏱️',\n high: '🏋️'\n };\n const impactIcons = {\n low: '📉',\n medium: '📊',\n high: '📈'\n };\n\n return \\`\n <div class=\"card p-4 \\${feature.addedToKanban ? 'opacity-60' : ''}\" onclick=\"state.roadmapSelectedFeature = '\\${feature.id}'; render();\">\n <div class=\"flex items-start justify-between mb-2\">\n <h4 class=\"font-medium text-canvas-800 text-sm\">\\${escapeHtml(feature.title)}</h4>\n <span class=\"text-xs px-2 py-0.5 rounded-full \\${priorityColors[feature.priority] || 'bg-gray-100'}\">\\${priorityLabels[feature.priority] || feature.priority}</span>\n </div>\n <p class=\"text-xs text-canvas-500 mb-3 line-clamp-2\">\\${escapeHtml(feature.description)}</p>\n <div class=\"flex items-center justify-between\">\n <div class=\"flex gap-2 text-xs text-canvas-400\">\n <span title=\"Effort\">\\${effortIcons[feature.effort] || '⏱️'} \\${feature.effort}</span>\n <span title=\"Impact\">\\${impactIcons[feature.impact] || '📊'} \\${feature.impact}</span>\n </div>\n \\${feature.addedToKanban ? \\`\n <span class=\"text-xs text-green-600\">✓ Added</span>\n \\` : \\`\n <button onclick=\"event.stopPropagation(); addFeatureToKanban('\\${feature.id}')\" class=\"text-xs text-accent hover:underline\">+ Add to Kanban</button>\n \\`}\n </div>\n </div>\n \\`;\n}\n\nfunction renderRoadmapModal() {\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-lg mx-4\">\n <div class=\"px-6 py-4 border-b border-canvas-200 flex justify-between items-center\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">🗺️ Generate Roadmap</h3>\n <button onclick=\"state.showModal = null; render();\" class=\"text-canvas-400 hover:text-canvas-600 text-xl leading-none\">×</button>\n </div>\n <div class=\"p-6\">\n <p class=\"text-sm text-canvas-500 mb-6\">\n Generate a strategic feature roadmap by analyzing your project structure and optionally researching competitors.\n </p>\n\n <div class=\"space-y-5\">\n <label class=\"flex items-start gap-3 cursor-pointer p-3 rounded-lg border border-canvas-200 hover:border-canvas-300 transition-colors\">\n <input type=\"checkbox\"\n \\${state.roadmapEnableCompetitors ? 'checked' : ''}\n onchange=\"state.roadmapEnableCompetitors = this.checked; render();\"\n class=\"w-4 h-4 mt-0.5 accent-accent\">\n <div>\n <span class=\"text-sm font-medium text-canvas-700\">Enable competitor research</span>\n <p class=\"text-xs text-canvas-400 mt-0.5\">Use web search to analyze competitors (takes longer)</p>\n </div>\n </label>\n\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-1.5\">Additional context (optional)</label>\n <textarea\n class=\"input w-full text-sm\"\n rows=\"3\"\n placeholder=\"E.g., Focus on mobile features, target enterprise users...\"\n oninput=\"state.roadmapCustomPrompt = this.value;\"\n >\\${escapeHtml(state.roadmapCustomPrompt)}</textarea>\n </div>\n </div>\n </div>\n <div class=\"px-6 py-4 border-t border-canvas-200 flex justify-end gap-3\">\n <button onclick=\"state.showModal = null; render();\" class=\"btn btn-ghost px-4 py-2\">Cancel</button>\n <button onclick=\"generateRoadmap(); state.showModal = null; render();\" class=\"btn btn-primary px-4 py-2\">\n 🚀 Generate Roadmap\n </button>\n </div>\n </div>\n </div>\n \\`;\n}\n\nfunction renderColumn(status, title, tasks) {\n const columnTasks = tasks.filter(t => t.status === status);\n const statusLabels = {\n draft: 'To Do',\n ready: 'Ready',\n in_progress: 'In Progress',\n completed: 'Done',\n failed: 'Failed'\n };\n const taskCount = columnTasks.length;\n\n return \\`\n <div class=\"flex-1 min-w-[240px] max-w-[300px]\">\n <div class=\"column-container\">\n <div class=\"column-header\">\n <span class=\"status-dot status-dot-\\${status}\"></span>\n <span class=\"font-medium text-canvas-700 text-sm\">\\${statusLabels[status] || title}</span>\n <span class=\"text-xs text-canvas-400 ml-auto\">\\${taskCount}</span>\n </div>\n <div class=\"column-zone column-drop-zone max-h-[calc(100vh-280px)] overflow-y-auto\"\n data-status=\"\\${status}\"\n ondragover=\"handleDragOver(event)\"\n ondragleave=\"handleDragLeave(event)\"\n ondrop=\"handleDrop(event, '\\${status}')\">\n \\${columnTasks.map(t => renderCard(t)).join('')}\n \\${columnTasks.length === 0 ? \\`\n <div class=\"flex items-center justify-center py-12 text-canvas-400 text-sm\">\n No tasks\n </div>\n \\` : ''}\n </div>\n </div>\n </div>\n \\`;\n}\n\nfunction renderLog() {\n const allTabs = Object.keys(state.taskOutput).filter(id => !state.closedTabs.has(id));\n const activeOutput = state.taskOutput[state.activeTab] || [];\n const activeTask = state.tasks.find(t => t.id === state.activeTab);\n const isRunning = state.running.includes(state.activeTab);\n\n // Filter output based on search\n let filteredOutput = activeOutput;\n if (state.logSearch) {\n const searchLower = state.logSearch.toLowerCase();\n filteredOutput = activeOutput.filter(l => {\n const text = l.text || l;\n return text.toLowerCase().includes(searchLower);\n });\n }\n\n const fullscreenClass = state.logFullscreen ? 'fixed inset-4 z-50' : 'mt-6';\n const logHeight = state.logFullscreen ? 'calc(100vh - 180px)' : '300px';\n\n return \\`\n <div class=\"terminal \\${fullscreenClass} \\${state.logFullscreen ? 'shadow-2xl' : ''}\">\n <!-- Tab bar -->\n <div class=\"terminal-header flex items-center rounded-t-xl\">\n <div class=\"flex-1 flex items-center overflow-x-auto\">\n \\${allTabs.length > 0 ? allTabs.map(id => {\n const task = state.tasks.find(t => t.id === id);\n const isActive = state.activeTab === id;\n const isTaskRunning = state.running.includes(id);\n return \\`\n <div class=\"tab flex items-center gap-2 group \\${isActive ? 'active' : ''}\"\n onclick=\"state.activeTab = '\\${id}'; render();\">\n <span class=\"w-2 h-2 rounded-full \\${isTaskRunning ? 'bg-status-running animate-pulse' : task?.status === 'completed' ? 'bg-status-success' : task?.status === 'failed' ? 'bg-status-failed' : 'bg-canvas-400'}\"></span>\n <span class=\"text-sm truncate max-w-[150px]\">\\${task?.title?.substring(0, 25) || id}</span>\n <button onclick=\"event.stopPropagation(); closeLogTab('\\${id}')\"\n class=\"opacity-0 group-hover:opacity-100 text-canvas-500 hover:text-status-failed transition-opacity ml-1\">\n ✕\n </button>\n </div>\n \\`;\n }).join('') : '<div class=\"px-4 py-2.5 text-canvas-500 text-sm\">No logs</div>'}\n </div>\n\n <!-- Controls -->\n <div class=\"flex items-center gap-1 px-3 border-l border-white/5\">\n <button onclick=\"state.showLineNumbers = !state.showLineNumbers; render();\"\n class=\"btn btn-ghost p-1.5 text-xs tooltip\" data-tooltip=\"\\${state.showLineNumbers ? 'Hide' : 'Show'} line numbers\">\n #\n </button>\n <button onclick=\"state.autoScroll = !state.autoScroll; render();\"\n class=\"btn btn-ghost p-1.5 text-xs \\${state.autoScroll ? 'text-accent' : ''} tooltip\" data-tooltip=\"Auto-scroll \\${state.autoScroll ? 'on' : 'off'}\">\n ↓\n </button>\n <button onclick=\"copyLogToClipboard()\"\n class=\"btn btn-ghost p-1.5 text-xs tooltip\" data-tooltip=\"Copy to clipboard\">\n 📋\n </button>\n <button onclick=\"clearLog('\\${state.activeTab}')\"\n class=\"btn btn-ghost p-1.5 text-xs tooltip\" data-tooltip=\"Clear log\">\n 🗑\n </button>\n <button onclick=\"toggleLogFullscreen()\"\n class=\"btn btn-ghost p-1.5 text-xs tooltip\" data-tooltip=\"\\${state.logFullscreen ? 'Exit' : 'Enter'} fullscreen\">\n \\${state.logFullscreen ? '⊙' : '⛶'}\n </button>\n </div>\n </div>\n\n <!-- Search and task info bar -->\n <div class=\"flex items-center justify-between px-4 py-2.5 bg-canvas-50/50 border-b border-white/5\">\n <div class=\"flex items-center gap-3\">\n <span class=\"text-sm font-display font-medium text-canvas-700\">\n \\${activeTask ? activeTask.title : 'Execution Log'}\n </span>\n \\${isRunning ? \\`\n <span class=\"stat-pill text-status-running border-status-running/20\">\n <span class=\"stat-dot bg-status-running animate-pulse\"></span>\n Running \\${formatElapsed(state.taskStartTime[state.activeTab])}\n </span>\n \\` : ''}\n <span class=\"text-xs text-canvas-500 font-mono\">\\${filteredOutput.length} lines</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <div class=\"relative\">\n <input type=\"text\"\n placeholder=\"Search logs...\"\n value=\"\\${escapeHtml(state.logSearch)}\"\n oninput=\"state.logSearch = this.value; render();\"\n class=\"input text-xs py-1.5 pr-8 w-48\">\n \\${state.logSearch ? \\`\n <button onclick=\"state.logSearch = ''; render();\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 text-canvas-500 hover:text-canvas-700\">\n ✕\n </button>\n \\` : ''}\n </div>\n </div>\n </div>\n\n <!-- Log content -->\n <div id=\"log-content\"\n class=\"p-4 overflow-y-auto text-canvas-600 rounded-b-xl font-mono\"\n style=\"height: \\${logHeight}\">\n \\${filteredOutput.length > 0\n ? filteredOutput.map((l, i) => {\n const text = l.text || l;\n const timestamp = l.timestamp ? new Date(l.timestamp).toLocaleTimeString() : '';\n return \\`\n <div class=\"log-line flex items-start gap-3\">\n \\${state.showLineNumbers ? \\`<span class=\"text-canvas-400 select-none w-8 text-right flex-shrink-0\">\\${i + 1}</span>\\` : ''}\n \\${timestamp ? \\`<span class=\"text-canvas-400 select-none flex-shrink-0\">\\${timestamp}</span>\\` : ''}\n <span class=\"whitespace-pre-wrap flex-1\">\\${highlightLog(text)}</span>\n </div>\n \\`;\n }).join('')\n : \\`<div class=\"flex flex-col items-center justify-center h-full text-canvas-500\">\n <span class=\"text-4xl mb-3 opacity-20\">📋</span>\n <span class=\"text-sm\">\\${state.logSearch ? 'No matching lines' : 'No output yet. Run a task to see logs here.'}</span>\n </div>\\`}\n </div>\n </div>\n \\`;\n}\n\nfunction renderModal() {\n if (!state.showModal) return '';\n\n if (state.showModal === 'new' || state.showModal === 'edit') {\n const task = state.editingTask || { title: '', description: '', category: 'functional', priority: 'medium', steps: [], status: 'draft' };\n const isEdit = state.showModal === 'edit';\n\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; state.editingTask = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-lg mx-4 max-h-[90vh] overflow-y-auto\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">\\${isEdit ? '✏️ Edit Task' : '📝 New Task'}</h3>\n <button onclick=\"state.showModal = null; state.editingTask = null; render();\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:text-canvas-700\">✕</button>\n </div>\n <form onsubmit=\"handleTaskSubmit(event, \\${isEdit})\" class=\"p-6 space-y-5\">\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Title *</label>\n <input type=\"text\" name=\"title\" value=\"\\${escapeHtml(task.title)}\" required\n class=\"input w-full\" placeholder=\"Add login functionality\">\n </div>\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Description *</label>\n <textarea name=\"description\" rows=\"4\" required\n class=\"input w-full resize-none\" placeholder=\"Describe what needs to be done...\">\\${escapeHtml(task.description)}</textarea>\n </div>\n <div class=\"grid grid-cols-2 gap-4\">\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Category</label>\n <select name=\"category\" class=\"input w-full\">\n <option value=\"functional\" \\${task.category === 'functional' ? 'selected' : ''}>⚙️ Functional</option>\n <option value=\"ui\" \\${task.category === 'ui' ? 'selected' : ''}>🎨 UI</option>\n <option value=\"bug\" \\${task.category === 'bug' ? 'selected' : ''}>🐛 Bug</option>\n <option value=\"enhancement\" \\${task.category === 'enhancement' ? 'selected' : ''}>✨ Enhancement</option>\n <option value=\"testing\" \\${task.category === 'testing' ? 'selected' : ''}>🧪 Testing</option>\n <option value=\"refactor\" \\${task.category === 'refactor' ? 'selected' : ''}>🔧 Refactor</option>\n </select>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Priority</label>\n <select name=\"priority\" class=\"input w-full\">\n <option value=\"low\" \\${task.priority === 'low' ? 'selected' : ''}>Low</option>\n <option value=\"medium\" \\${task.priority === 'medium' ? 'selected' : ''}>Medium</option>\n <option value=\"high\" \\${task.priority === 'high' ? 'selected' : ''}>High</option>\n <option value=\"critical\" \\${task.priority === 'critical' ? 'selected' : ''}>Critical</option>\n </select>\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Verification Steps (one per line)</label>\n <textarea name=\"steps\" rows=\"4\"\n class=\"input w-full resize-none font-mono text-sm\" placeholder=\"Navigate to /login Enter valid credentials Click submit button Verify redirect\">\\${task.steps.join('\\\\n')}</textarea>\n </div>\n <div class=\"flex justify-end gap-3 pt-4 border-t border-white/5\">\n <button type=\"button\" onclick=\"state.showModal = null; state.editingTask = null; render();\"\n class=\"btn btn-ghost px-4 py-2.5\">Cancel</button>\n \\${!isEdit ? \\`\n <button type=\"submit\" name=\"action\" value=\"draft\"\n class=\"btn btn-ghost px-4 py-2.5\">Save as Draft</button>\n \\` : ''}\n <button type=\"submit\" name=\"action\" value=\"\\${isEdit ? 'save' : 'ready'}\"\n class=\"btn btn-primary px-5 py-2.5\">\\${isEdit ? 'Save Changes' : 'Create Ready'}</button>\n </div>\n </form>\n </div>\n </div>\n \\`;\n }\n\n if (state.showModal === 'templates') {\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-2xl mx-4 max-h-[80vh] overflow-y-auto\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center sticky top-0 bg-canvas-100/95 backdrop-blur rounded-t-xl z-10\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">📋 Task Templates</h3>\n <button onclick=\"state.showModal = null; render();\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:text-canvas-700\">✕</button>\n </div>\n <div class=\"p-6 grid grid-cols-2 gap-4\">\n \\${state.templates.map(t => \\`\n <button onclick=\"applyTemplate('\\${t.id}')\"\n class=\"card text-left p-4 hover:border-accent/30 transition-all group\">\n <div class=\"font-display font-medium text-canvas-800 group-hover:text-accent transition-colors\">\\${t.icon} \\${t.name}</div>\n <div class=\"text-sm text-canvas-500 mt-1\">\\${t.description}</div>\n </button>\n \\`).join('')}\n </div>\n </div>\n </div>\n \\`;\n }\n\n if (state.showModal === 'afk') {\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-md mx-4\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">🔄 AFK Mode</h3>\n <button onclick=\"state.showModal = null; render();\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:text-canvas-700\">✕</button>\n </div>\n <div class=\"p-6\">\n <p class=\"text-sm text-canvas-600 mb-5\">Run the agent in a loop, automatically picking up tasks from the \"Ready\" column one at a time until complete.</p>\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Maximum Iterations</label>\n <input type=\"number\" id=\"afk-iterations\" value=\"10\" min=\"1\" max=\"100\"\n class=\"input w-full\">\n </div>\n <div class=\"bg-status-running/10 border border-status-running/20 rounded-lg p-3\">\n <p class=\"text-xs text-status-running\">⚠️ You can close this tab - the agent will continue running. Check back later or watch the terminal output.</p>\n </div>\n </div>\n <div class=\"flex justify-end gap-3 mt-6\">\n <button onclick=\"state.showModal = null; render();\"\n class=\"btn btn-ghost px-4 py-2.5\">Cancel</button>\n <button onclick=\"handleStartAFK()\"\n class=\"btn px-5 py-2.5 bg-status-success text-canvas font-medium\">🚀 Start AFK Mode</button>\n </div>\n </div>\n </div>\n </div>\n \\`;\n }\n\n // Planning input modal\n if (state.showModal === 'plan-input') {\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-lg mx-4\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">🎯 AI Task Planner</h3>\n <button onclick=\"state.showModal = null; render();\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:text-canvas-700\">✕</button>\n </div>\n <div class=\"p-6\">\n <p class=\"text-sm text-canvas-600 mb-4\">Describe your goal and the AI will analyze the codebase and break it down into concrete tasks.</p>\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">What do you want to build?</label>\n <textarea id=\"planning-goal\" rows=\"4\" placeholder=\"e.g., Add user authentication with JWT tokens, login/logout functionality, and protected routes...\"\n class=\"input w-full resize-none\"></textarea>\n </div>\n <div class=\"bg-blue-50 border border-blue-200 rounded-lg p-3\">\n <p class=\"text-xs text-blue-700\">💡 Be specific about what you want. The AI will explore your codebase and create 3-8 tasks with implementation guidance.</p>\n </div>\n </div>\n <div class=\"flex justify-end gap-3 mt-6\">\n <button onclick=\"state.showModal = null; render();\"\n class=\"btn btn-ghost px-4 py-2.5\">Cancel</button>\n <button onclick=\"handleStartPlanning()\"\n class=\"btn px-5 py-2.5 bg-blue-500 hover:bg-blue-600 text-white font-medium\">🚀 Start Planning</button>\n </div>\n </div>\n </div>\n </div>\n \\`;\n }\n\n // Planning progress modal\n if (state.showModal === 'planning') {\n const outputHtml = state.planningOutput.length > 0\n ? state.planningOutput.map(l => \\`<div class=\"log-line\">\\${highlightLog(l.text || l)}</div>\\`).join('')\n : '<div class=\"text-canvas-400 text-sm\">Analyzing codebase and generating tasks...</div>';\n\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\">\n <div class=\"modal-content card rounded-xl w-full max-w-3xl mx-4 max-h-[80vh] flex flex-col\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center flex-shrink-0\">\n <div class=\"flex items-center gap-3\">\n <span class=\"text-xl animate-pulse\">🎯</span>\n <div>\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">Planning in Progress</h3>\n <p class=\"text-xs text-canvas-500 truncate max-w-[400px]\">\\${escapeHtml(state.planningGoal)}</p>\n </div>\n </div>\n <button onclick=\"if(confirm('Cancel planning?')) cancelPlanning();\"\n class=\"btn btn-ghost px-3 py-1.5 text-sm text-status-failed hover:bg-status-failed/10\">\n ⏹ Cancel\n </button>\n </div>\n <div class=\"flex-1 overflow-hidden p-4\">\n <div class=\"log-container h-full overflow-y-auto\" id=\"planning-log\">\n \\${outputHtml}\n </div>\n </div>\n </div>\n </div>\n \\`;\n }\n\n // Roadmap generation modal\n if (state.showModal === 'roadmap') {\n return renderRoadmapModal();\n }\n\n return '';\n}\n\nfunction renderSidePanel() {\n if (!state.sidePanel) return '';\n\n const task = state.tasks.find(t => t.id === state.sidePanel);\n if (!task) return '';\n\n const isRunning = state.running.includes(task.id);\n const output = state.taskOutput[task.id] || [];\n const startTime = state.taskStartTime[task.id];\n const lastExec = task.executionHistory?.[task.executionHistory.length - 1];\n const elapsed = isRunning && startTime ? formatElapsed(startTime) : null;\n\n return \\`\n <div class=\"side-panel\">\n <!-- Compact Header -->\n <div class=\"side-panel-header\" style=\"padding: 12px 16px;\">\n <div class=\"flex justify-between items-start gap-2\">\n <div class=\"flex-1 min-w-0\">\n <h2 class=\"font-semibold text-canvas-900 text-base leading-tight truncate\" title=\"\\${escapeHtml(task.title)}\">\\${escapeHtml(task.title)}</h2>\n <div class=\"flex items-center gap-2 mt-1.5 flex-wrap\">\n <span class=\"status-badge status-badge-\\${task.status}\" style=\"font-size: 11px; padding: 2px 8px;\">\n <span class=\"w-1.5 h-1.5 rounded-full bg-current \\${isRunning ? 'animate-pulse' : ''}\"></span>\n \\${task.status.replace('_', ' ')}\n </span>\n \\${elapsed ? \\`<span class=\"text-xs text-canvas-500\">⏱ \\${elapsed}</span>\\` : ''}\n <span class=\"text-xs text-canvas-400\">\\${task.priority} · \\${task.category}</span>\n </div>\n </div>\n <div class=\"flex items-center gap-0.5 flex-shrink-0\">\n \\${task.status === 'in_progress' ? \\`\n <button onclick=\"cancelTask('\\${task.id}')\" class=\"btn btn-ghost p-1.5 text-status-failed hover:bg-status-failed/10\" title=\"Stop\">\n ⏹\n </button>\n \\` : task.status === 'ready' ? \\`\n <button onclick=\"runTask('\\${task.id}')\" class=\"btn btn-ghost p-1.5 text-status-success hover:bg-status-success/10\" title=\"Run\">\n ▶\n </button>\n \\` : task.status === 'draft' ? \\`\n <button onclick=\"updateTask('\\${task.id}', { status: 'ready' })\" class=\"btn btn-ghost p-1.5 text-blue-500 hover:bg-blue-50\" title=\"Move to Ready\">\n →\n </button>\n \\` : task.status === 'failed' ? \\`\n <button onclick=\"retryTask('\\${task.id}')\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:bg-canvas-100\" title=\"Retry\">\n ↻\n </button>\n \\` : ''}\n <button onclick=\"state.editingTask = state.tasks.find(t => t.id === '\\${task.id}'); state.showModal = 'edit'; render();\"\n class=\"btn btn-ghost p-1.5 text-canvas-400 hover:text-canvas-600\" title=\"Edit\">\n ✏️\n </button>\n <button onclick=\"closeSidePanel()\" class=\"btn btn-ghost p-1.5 text-canvas-400 hover:text-canvas-600\" title=\"Close\">\n ✕\n </button>\n </div>\n </div>\n </div>\n\n <!-- Tabs (moved up, right after header) -->\n <div class=\"side-panel-tabs\" style=\"padding: 0 12px;\">\n <div class=\"side-panel-tab \\${state.sidePanelTab === 'logs' ? 'active' : ''}\" onclick=\"state.sidePanelTab = 'logs'; render();\">\n 📋 Logs\n </div>\n <div class=\"side-panel-tab \\${state.sidePanelTab === 'details' ? 'active' : ''}\" onclick=\"state.sidePanelTab = 'details'; render();\">\n 📄 Details\n </div>\n </div>\n\n <!-- Tab Content -->\n <div class=\"side-panel-body\" style=\"flex: 1; overflow: hidden; display: flex; flex-direction: column;\">\n \\${state.sidePanelTab === 'logs' ? \\`\n <div class=\"log-container\" id=\"side-panel-log\" style=\"flex: 1; overflow-y: auto;\">\n \\${output.length > 0\n ? output.map((l, i) => {\n const text = l.text || l;\n return \\`<div class=\"log-line\">\\${highlightLog(text)}</div>\\`;\n }).join('')\n : \\`<div class=\"text-canvas-400 text-sm\">\\${isRunning ? 'Waiting for output...' : 'No logs available. Run the task to see output.'}</div>\\`\n }\n </div>\n \\` : \\`\n <div style=\"flex: 1; overflow-y: auto; padding: 16px;\">\n <!-- Description -->\n <div class=\"mb-5\">\n <div class=\"text-xs font-semibold text-canvas-500 uppercase tracking-wide mb-2\">Description</div>\n <div class=\"text-sm text-canvas-700 leading-relaxed bg-canvas-50 rounded-lg p-3 border border-canvas-200\">\n \\${escapeHtml(task.description || 'No description provided.')}\n </div>\n </div>\n\n <!-- Steps -->\n \\${task.steps && task.steps.length > 0 ? \\`\n <div class=\"mb-5\">\n <div class=\"text-xs font-semibold text-canvas-500 uppercase tracking-wide mb-2\">Steps (\\${task.steps.length})</div>\n <div class=\"bg-canvas-50 rounded-lg border border-canvas-200 divide-y divide-canvas-200\">\n \\${task.steps.map((step, i) => \\`\n <div class=\"flex gap-3 p-3 text-sm\">\n <span class=\"flex-shrink-0 w-5 h-5 rounded-full bg-canvas-200 text-canvas-500 flex items-center justify-center text-xs font-medium\">\\${i + 1}</span>\n <span class=\"text-canvas-700\">\\${escapeHtml(step)}</span>\n </div>\n \\`).join('')}\n </div>\n </div>\n \\` : ''}\n\n <!-- Metadata -->\n <div class=\"mb-5 grid grid-cols-2 gap-3\">\n <div class=\"bg-canvas-50 rounded-lg p-3 border border-canvas-200\">\n <div class=\"text-xs text-canvas-500 mb-1\">Created</div>\n <div class=\"text-sm text-canvas-700\">\\${new Date(task.createdAt).toLocaleDateString()}</div>\n </div>\n <div class=\"bg-canvas-50 rounded-lg p-3 border border-canvas-200\">\n <div class=\"text-xs text-canvas-500 mb-1\">Updated</div>\n <div class=\"text-sm text-canvas-700\">\\${new Date(task.updatedAt).toLocaleDateString()}</div>\n </div>\n </div>\n\n <!-- Execution History -->\n \\${task.executionHistory && task.executionHistory.length > 0 ? \\`\n <div>\n <div class=\"text-xs font-semibold text-canvas-500 uppercase tracking-wide mb-2\">Execution History</div>\n <div class=\"space-y-2\">\n \\${task.executionHistory.slice(-5).reverse().map(exec => \\`\n <div class=\"bg-canvas-50 rounded-lg p-3 border border-canvas-200\">\n <div class=\"flex justify-between items-center\">\n <span class=\"text-sm font-medium \\${exec.status === 'completed' ? 'text-status-success' : 'text-status-failed'}\">\n \\${exec.status === 'completed' ? '✓' : '✗'} \\${exec.status}\n </span>\n <span class=\"text-xs text-canvas-500\">\\${Math.round(exec.duration / 1000)}s</span>\n </div>\n <div class=\"text-xs text-canvas-500 mt-1\">\\${new Date(exec.startedAt).toLocaleString()}</div>\n \\${exec.error ? \\`<div class=\"text-xs text-status-failed mt-2 bg-status-failed/5 rounded p-2\">\\${escapeHtml(exec.error)}</div>\\` : ''}\n </div>\n \\`).join('')}\n </div>\n </div>\n \\` : ''}\n </div>\n \\`}\n </div>\n </div>\n \\`;\n}\n\nfunction renderAFKBar() {\n if (!state.afk.running) return '';\n const progress = (state.afk.currentIteration / state.afk.maxIterations) * 100;\n return \\`\n <div class=\"bg-gradient-to-r from-status-success/10 to-status-success/5 border-b border-status-success/20 px-6 py-3\">\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-5\">\n <div class=\"flex items-center gap-2\">\n <span class=\"text-xl animate-pulse\">🔄</span>\n <span class=\"text-sm font-display font-semibold text-status-success\">AFK Mode Active</span>\n </div>\n <div class=\"h-4 w-px bg-status-success/20\"></div>\n <div class=\"flex items-center gap-4\">\n <div class=\"stat-pill border-status-success/20\">\n <span class=\"text-xs text-canvas-500\">Iteration</span>\n <span class=\"text-sm font-mono text-status-success\">\\${state.afk.currentIteration}/\\${state.afk.maxIterations}</span>\n </div>\n <div class=\"w-32 h-1.5 bg-canvas-200 rounded-full overflow-hidden\">\n <div class=\"h-full bg-gradient-to-r from-status-success to-status-success/70 rounded-full transition-all\" style=\"width: \\${progress}%\"></div>\n </div>\n </div>\n <div class=\"h-4 w-px bg-status-success/20\"></div>\n <div class=\"stat-pill border-status-success/20\">\n <span class=\"text-xs text-canvas-500\">Completed</span>\n <span class=\"text-sm font-mono text-status-success\">\\${state.afk.tasksCompleted}</span>\n </div>\n </div>\n <button onclick=\"stopAFK()\"\n class=\"btn text-sm px-4 py-1.5 bg-status-failed/15 hover:bg-status-failed/25 text-status-failed border border-status-failed/20\">\n ⏹ Stop AFK\n </button>\n </div>\n </div>\n \\`;\n}\n\nfunction renderStats() {\n const counts = {\n draft: state.tasks.filter(t => t.status === 'draft').length,\n ready: state.tasks.filter(t => t.status === 'ready').length,\n in_progress: state.tasks.filter(t => t.status === 'in_progress').length,\n completed: state.tasks.filter(t => t.status === 'completed').length,\n failed: state.tasks.filter(t => t.status === 'failed').length,\n };\n const total = state.tasks.length;\n\n return \\`\n <div class=\"flex items-center gap-3\">\n <div class=\"stat-pill\">\n <span class=\"text-xs text-canvas-500\">Total</span>\n <span class=\"text-sm font-semibold text-canvas-700\">\\${total}</span>\n </div>\n \\${counts.in_progress > 0 ? \\`\n <div class=\"stat-pill border-status-running/20\">\n <span class=\"stat-dot bg-status-running animate-pulse\"></span>\n <span class=\"text-xs text-status-running font-medium\">\\${counts.in_progress} running</span>\n </div>\n \\` : ''}\n \\${counts.completed > 0 ? \\`\n <div class=\"stat-pill border-status-success/20\">\n <span class=\"stat-dot bg-status-success\"></span>\n <span class=\"text-xs text-status-success\">\\${counts.completed} done</span>\n </div>\n \\` : ''}\n </div>\n \\`;\n}\n\n// Event handlers\nasync function handleTaskSubmit(e, isEdit) {\n e.preventDefault();\n const form = e.target;\n const data = new FormData(form);\n const action = e.submitter?.value || 'ready';\n\n const task = {\n title: data.get('title'),\n description: data.get('description'),\n category: data.get('category'),\n priority: data.get('priority'),\n steps: data.get('steps').split('\\\\n').filter(s => s.trim()),\n status: action === 'draft' ? 'draft' : 'ready'\n };\n\n if (isEdit && state.editingTask) {\n await updateTask(state.editingTask.id, task);\n } else {\n await createTask(task);\n }\n\n state.showModal = null;\n state.editingTask = null;\n render();\n}\n\nasync function handleAIGenerate() {\n const prompt = document.getElementById('ai-prompt').value;\n if (!prompt.trim()) return;\n\n state.aiPrompt = prompt;\n try {\n const task = await generateTask(prompt);\n state.editingTask = task;\n state.showModal = 'edit';\n state.aiPrompt = '';\n } catch (e) {\n alert('Failed to generate task: ' + e.message);\n }\n render();\n}\n\nfunction applyTemplate(templateId) {\n const template = state.templates.find(t => t.id === templateId);\n if (!template) return;\n\n state.editingTask = {\n title: template.titleTemplate,\n description: template.descriptionTemplate,\n category: template.category,\n priority: template.priority,\n steps: template.stepsTemplate,\n status: 'draft'\n };\n state.showModal = 'edit';\n render();\n}\n\nfunction handleStartAFK() {\n const iterations = parseInt(document.getElementById('afk-iterations').value) || 10;\n startAFK(iterations);\n state.showModal = null;\n render();\n}\n\nasync function handleStartPlanning() {\n const goal = document.getElementById('planning-goal').value;\n if (!goal.trim()) {\n showToast('Please enter a goal', 'warning');\n return;\n }\n try {\n await startPlanning(goal);\n // Modal will be shown when planning:started event is received\n } catch (e) {\n showToast('Failed to start planning: ' + e.message, 'error');\n }\n}\n\nfunction openPlanningModal() {\n state.showModal = 'plan-input';\n render();\n}\n\n// Filter tasks based on search query\nfunction filterTasks(tasks) {\n if (!state.searchQuery) return tasks;\n const query = state.searchQuery.toLowerCase();\n return tasks.filter(t =>\n t.title.toLowerCase().includes(query) ||\n t.description.toLowerCase().includes(query) ||\n t.category.toLowerCase().includes(query)\n );\n}\n\n// Utility\nfunction escapeHtml(str) {\n if (!str) return '';\n return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\n// Main render\nfunction render() {\n const app = document.getElementById('app');\n const hasSidePanel = state.sidePanel !== null && state.currentView === 'board';\n\n app.innerHTML = \\`\n <div class=\"min-h-screen flex flex-col bg-canvas-50\">\n <!-- Header -->\n <header class=\"sticky top-0 z-40 bg-white border-b border-canvas-200\">\n <div class=\"px-6 py-3 flex items-center justify-between\">\n <div class=\"flex items-center gap-6\">\n <h1 class=\"text-lg font-semibold text-canvas-900\">Claude Kanban</h1>\n <!-- Navigation Tabs -->\n <nav class=\"flex items-center gap-1 bg-canvas-100 rounded-lg p-1\">\n <button onclick=\"state.currentView = 'board'; render();\"\n class=\"px-3 py-1.5 text-sm rounded-md transition-colors \\${state.currentView === 'board' ? 'bg-white shadow-sm text-canvas-900 font-medium' : 'text-canvas-500 hover:text-canvas-700'}\">\n 📋 Board\n </button>\n <button onclick=\"state.currentView = 'roadmap'; render();\"\n class=\"px-3 py-1.5 text-sm rounded-md transition-colors \\${state.currentView === 'roadmap' ? 'bg-white shadow-sm text-canvas-900 font-medium' : 'text-canvas-500 hover:text-canvas-700'}\">\n 🗺️ Roadmap\n </button>\n </nav>\n \\${state.currentView === 'board' ? \\`\n <div class=\"flex items-center gap-2\">\n <input type=\"text\"\n placeholder=\"Search tasks...\"\n value=\"\\${escapeHtml(state.searchQuery)}\"\n oninput=\"state.searchQuery = this.value; render();\"\n class=\"input text-sm py-1.5 w-48\">\n </div>\n \\` : ''}\n </div>\n <div class=\"flex items-center gap-2\">\n \\${state.currentView === 'board' ? \\`\n <button onclick=\"openPlanningModal();\"\n class=\"btn px-4 py-2 text-sm bg-blue-500 hover:bg-blue-600 text-white \\${state.planning ? 'opacity-50 cursor-not-allowed' : ''}\"\n \\${state.planning ? 'disabled' : ''}\n title=\"AI Task Planner\">\n 🎯 \\${state.planning ? 'Planning...' : 'Plan'}\n </button>\n <button onclick=\"state.showModal = 'new'; render();\"\n class=\"btn btn-primary px-4 py-2 text-sm\">\n + Add Task\n </button>\n <button onclick=\"state.showModal = 'afk'; render();\"\n class=\"btn btn-ghost px-3 py-2 text-sm \\${state.afk.running ? 'text-status-success' : ''}\"\n title=\"AFK Mode\">\n 🔄 \\${state.afk.running ? 'AFK On' : 'AFK'}\n </button>\n \\` : \\`\n <button onclick=\"state.showModal = 'roadmap'; render();\"\n class=\"btn btn-primary px-4 py-2 text-sm \\${state.roadmapGenerating ? 'opacity-50 cursor-not-allowed' : ''}\"\n \\${state.roadmapGenerating ? 'disabled' : ''}>\n \\${state.roadmapGenerating ? '⏳ Generating...' : '🚀 Generate Roadmap'}\n </button>\n \\`}\n </div>\n </div>\n </header>\n\n \\${state.currentView === 'board' ? renderAFKBar() : ''}\n\n <!-- Main Content Area -->\n <div class=\"flex flex-1 overflow-hidden\">\n \\${state.currentView === 'board' ? \\`\n <!-- Kanban Board -->\n <main class=\"main-content overflow-x-auto p-6\">\n <div class=\"flex gap-4\">\n \\${renderColumn('draft', 'To Do', filterTasks(state.tasks))}\n \\${renderColumn('ready', 'Ready', filterTasks(state.tasks))}\n \\${renderColumn('in_progress', 'In Progress', filterTasks(state.tasks))}\n \\${renderColumn('completed', 'Done', filterTasks(state.tasks))}\n \\${renderColumn('failed', 'Failed', filterTasks(state.tasks))}\n </div>\n </main>\n <!-- Side Panel (pushes content when open) -->\n \\${hasSidePanel ? renderSidePanel() : ''}\n \\` : \\`\n <!-- Roadmap View -->\n \\${renderRoadmap()}\n \\`}\n </div>\n\n \\${renderModal()}\n </div>\n \\`;\n\n // Auto-scroll side panel logs\n if (hasSidePanel && state.sidePanelTab === 'logs' && state.autoScroll) {\n const logEl = document.getElementById('side-panel-log');\n if (logEl) logEl.scrollTop = logEl.scrollHeight;\n }\n\n // Update running task elapsed times every second\n if (state.running.length > 0 && !window.elapsedInterval) {\n window.elapsedInterval = setInterval(() => {\n if (state.running.length > 0) render();\n else {\n clearInterval(window.elapsedInterval);\n window.elapsedInterval = null;\n }\n }, 1000);\n }\n}\n\n// Expose to window for inline handlers\nwindow.state = state;\nwindow.render = render;\nwindow.createTask = createTask;\nwindow.updateTask = updateTask;\nwindow.deleteTask = deleteTask;\nwindow.runTask = runTask;\nwindow.cancelTask = cancelTask;\nwindow.retryTask = retryTask;\nwindow.generateTask = generateTask;\nwindow.startAFK = startAFK;\nwindow.stopAFK = stopAFK;\nwindow.startPlanning = startPlanning;\nwindow.cancelPlanning = cancelPlanning;\nwindow.handleStartPlanning = handleStartPlanning;\nwindow.openPlanningModal = openPlanningModal;\nwindow.handleDragStart = handleDragStart;\nwindow.handleDragEnd = handleDragEnd;\nwindow.handleDragOver = handleDragOver;\nwindow.handleDragLeave = handleDragLeave;\nwindow.handleDrop = handleDrop;\nwindow.handleTaskSubmit = handleTaskSubmit;\nwindow.handleAIGenerate = handleAIGenerate;\nwindow.applyTemplate = applyTemplate;\nwindow.handleStartAFK = handleStartAFK;\nwindow.showToast = showToast;\nwindow.scrollLogToBottom = scrollLogToBottom;\nwindow.copyLogToClipboard = copyLogToClipboard;\nwindow.clearLog = clearLog;\nwindow.closeLogTab = closeLogTab;\nwindow.toggleLogFullscreen = toggleLogFullscreen;\nwindow.escapeHtml = escapeHtml;\nwindow.openSidePanel = openSidePanel;\nwindow.closeSidePanel = closeSidePanel;\nwindow.showTaskMenu = showTaskMenu;\nwindow.filterTasks = filterTasks;\nwindow.scrollSidePanelLog = scrollSidePanelLog;\nwindow.generateRoadmap = generateRoadmap;\nwindow.cancelRoadmap = cancelRoadmap;\nwindow.addFeatureToKanban = addFeatureToKanban;\nwindow.deleteRoadmapFeature = deleteRoadmapFeature;\nwindow.loadRoadmap = loadRoadmap;\n\n// Keyboard shortcuts\ndocument.addEventListener('keydown', (e) => {\n // Ignore if typing in input\n if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;\n\n // ? - Show help\n if (e.key === '?') {\n showToast('Shortcuts: N=New, T=Templates, F=AFK, Esc=Close modal', 'info');\n }\n // n - New task\n if (e.key === 'n' && !e.metaKey && !e.ctrlKey) {\n state.showModal = 'new';\n render();\n }\n // t - Templates\n if (e.key === 't' && !e.metaKey && !e.ctrlKey) {\n state.showModal = 'templates';\n render();\n }\n // f - AFK mode\n if (e.key === 'f' && !e.metaKey && !e.ctrlKey && !state.afk.running) {\n state.showModal = 'afk';\n render();\n }\n // Escape - Close modal or side panel\n if (e.key === 'Escape') {\n if (state.showModal) {\n state.showModal = null;\n state.editingTask = null;\n render();\n } else if (state.sidePanel) {\n closeSidePanel();\n }\n }\n // / - Focus log search\n if (e.key === '/' && !state.showModal) {\n e.preventDefault();\n const searchInput = document.querySelector('#log-content ~ input, input[placeholder=\"Search logs...\"]');\n if (searchInput) searchInput.focus();\n }\n});\n\n// Initial render\nrender();\n`;\n}\n","import { spawn } from 'child_process';\nimport { join } from 'path';\nimport { writeFileSync, unlinkSync, mkdirSync, existsSync, appendFileSync, readFileSync } from 'fs';\nimport { EventEmitter } from 'events';\nimport type { Config, Task } from '../../types.js';\nimport { getConfig } from './project.js';\nimport { updateTask, addExecutionEntry, getTaskById, getNextReadyTask } from './prd.js';\nimport { logTaskExecution } from './progress.js';\n\nconst KANBAN_DIR = '.claude-kanban';\nconst LOGS_DIR = 'logs';\n\ninterface RunningTask {\n taskId: string;\n process: import('child_process').ChildProcess;\n startedAt: Date;\n output: string[];\n}\n\ninterface PlanningSession {\n goal: string;\n process: import('child_process').ChildProcess;\n startedAt: Date;\n output: string[];\n}\n\ninterface QASession {\n taskId: string;\n process: import('child_process').ChildProcess;\n startedAt: Date;\n output: string[];\n attempt: number;\n}\n\n/**\n * Task Executor - manages running tasks (one at a time)\n */\nexport class TaskExecutor extends EventEmitter {\n private projectPath: string;\n private runningTask: RunningTask | null = null;\n private planningSession: PlanningSession | null = null;\n private qaSession: QASession | null = null;\n private pendingQATaskId: string | null = null; // Task waiting for QA\n private qaRetryCount: Map<string, number> = new Map(); // Track QA retries per task\n private afkMode = false;\n private afkIteration = 0;\n private afkMaxIterations = 0;\n private afkTasksCompleted = 0;\n\n constructor(projectPath: string) {\n super();\n this.projectPath = projectPath;\n this.ensureLogsDir();\n }\n\n /**\n * Ensure logs directory exists\n */\n private ensureLogsDir(): void {\n const logsPath = join(this.projectPath, KANBAN_DIR, LOGS_DIR);\n if (!existsSync(logsPath)) {\n mkdirSync(logsPath, { recursive: true });\n }\n }\n\n /**\n * Get log file path for a task\n */\n private getLogFilePath(taskId: string): string {\n return join(this.projectPath, KANBAN_DIR, LOGS_DIR, `${taskId}.log`);\n }\n\n /**\n * Initialize log file for a task (clear existing)\n */\n private initLogFile(taskId: string): void {\n const logPath = this.getLogFilePath(taskId);\n writeFileSync(logPath, '');\n }\n\n /**\n * Append to task log file\n */\n private appendToLog(taskId: string, text: string): void {\n const logPath = this.getLogFilePath(taskId);\n appendFileSync(logPath, text);\n }\n\n /**\n * Read task log file\n */\n getTaskLog(taskId: string): string | null {\n const logPath = this.getLogFilePath(taskId);\n if (!existsSync(logPath)) return null;\n return readFileSync(logPath, 'utf-8');\n }\n\n /**\n * Format tool use for display in logs\n */\n private formatToolUse(toolName: string, input: Record<string, unknown>): string {\n const truncate = (str: string, maxLen = 80): string => {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen) + '...';\n };\n\n switch (toolName) {\n case 'Bash':\n return `[Bash] $ ${truncate(String(input.command || ''))}\\n`;\n case 'Read':\n return `[Read] ${input.file_path}\\n`;\n case 'Edit':\n return `[Edit] ${input.file_path}\\n`;\n case 'Write':\n return `[Write] ${input.file_path}\\n`;\n case 'Grep':\n return `[Grep] \"${truncate(String(input.pattern || ''))}\" in ${input.path || '.'}\\n`;\n case 'Glob':\n return `[Glob] ${input.pattern} in ${input.path || '.'}\\n`;\n case 'Task':\n return `[Task] ${input.description || truncate(String(input.prompt || ''))}\\n`;\n case 'TodoWrite':\n const todos = input.todos as Array<{ content: string; status: string }> | undefined;\n if (todos && Array.isArray(todos)) {\n const summary = todos.map(t => ` ${t.status === 'completed' ? '✓' : t.status === 'in_progress' ? '→' : '○'} ${truncate(t.content, 60)}`).join('\\n');\n return `[TodoWrite]\\n${summary}\\n`;\n }\n return `[TodoWrite]\\n`;\n case 'WebFetch':\n return `[WebFetch] ${input.url}\\n`;\n case 'WebSearch':\n return `[WebSearch] \"${truncate(String(input.query || ''))}\"\\n`;\n default:\n const meaningfulParams = ['file_path', 'path', 'command', 'query', 'url', 'pattern', 'target'];\n for (const param of meaningfulParams) {\n if (input[param]) {\n return `[${toolName}] ${truncate(String(input[param]))}\\n`;\n }\n }\n return `[${toolName}]\\n`;\n }\n }\n\n /**\n * Check if a task is currently running\n */\n isRunning(): boolean {\n return this.runningTask !== null;\n }\n\n /**\n * Get the currently running task ID\n */\n getRunningTaskId(): string | null {\n return this.runningTask?.taskId || null;\n }\n\n /**\n * Get running task output\n */\n getTaskOutput(taskId: string): string[] | undefined {\n if (this.runningTask?.taskId === taskId) {\n return this.runningTask.output;\n }\n return undefined;\n }\n\n /**\n * Check if a planning session is running\n */\n isPlanning(): boolean {\n return this.planningSession !== null;\n }\n\n /**\n * Check if executor is busy (task, planning, or QA running)\n */\n isBusy(): boolean {\n return this.runningTask !== null || this.planningSession !== null || this.qaSession !== null;\n }\n\n /**\n * Check if QA is running\n */\n isQARunning(): boolean {\n return this.qaSession !== null;\n }\n\n /**\n * Get planning session output\n */\n getPlanningOutput(): string[] | undefined {\n return this.planningSession?.output;\n }\n\n /**\n * Get planning session goal\n */\n getPlanningGoal(): string | undefined {\n return this.planningSession?.goal;\n }\n\n /**\n * Build the prompt for a task - simplified Ralph-style\n */\n private buildTaskPrompt(task: Task, config: Config): string {\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const prdPath = join(kanbanDir, 'prd.json');\n const progressPath = join(kanbanDir, 'progress.txt');\n\n const stepsText = task.steps.length > 0\n ? `\\nVerification steps:\\n${task.steps.map((s, i) => `${i + 1}. ${s}`).join('\\n')}`\n : '';\n\n // Build verification commands if configured\n const verifyCommands: string[] = [];\n if (config.project.typecheckCommand) {\n verifyCommands.push(config.project.typecheckCommand);\n }\n if (config.project.testCommand) {\n verifyCommands.push(config.project.testCommand);\n }\n\n const verifySection = verifyCommands.length > 0\n ? `3. Run quality checks:\\n${verifyCommands.map(cmd => ` - ${cmd}`).join('\\n')}\\n\\n4.`\n : '3.';\n\n return `You are an AI coding agent. Complete the following task:\n\n## TASK\nTitle: ${task.title}\nCategory: ${task.category}\nPriority: ${task.priority}\n\n${task.description}\n${stepsText}\n\n## INSTRUCTIONS\n\n1. Implement this task as described above.\n\n2. Make sure your changes work correctly.\n\n${verifySection} When complete, update the PRD file at ${prdPath}:\n - Find the task with id \"${task.id}\"\n - Set \"passes\": true\n - Set \"status\": \"completed\"\n\n${verifyCommands.length > 0 ? '5' : '4'}. Document your work in ${progressPath}:\n - What you implemented\n - Key decisions made\n - Any gotchas or important notes for future work\n\n${verifyCommands.length > 0 ? '6' : '5'}. Commit your changes with a descriptive message.\n\nFocus only on this task. When done, output: <promise>COMPLETE</promise>`;\n }\n\n /**\n * Build the prompt for a planning session\n */\n private buildPlanningPrompt(goal: string): string {\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const prdPath = join(kanbanDir, 'prd.json');\n\n return `You are an AI coding planner. Your job is to analyze a codebase and break down a user's goal into concrete, actionable tasks.\n\n## GOAL\n${goal}\n\n## INSTRUCTIONS\n\n1. Explore the codebase to understand:\n - Project structure and architecture\n - Existing patterns and conventions\n - Related existing code that you'll build upon\n - What technologies and frameworks are being used\n\n2. Break down the goal into 3-8 specific, actionable tasks that can each be completed in a single coding session.\n\n3. For each task, determine:\n - Clear, action-oriented title (start with a verb: Add, Create, Implement, Fix, etc.)\n - Detailed description with implementation guidance\n - Category: functional, ui, bug, enhancement, testing, or refactor\n - Priority: low, medium, high, or critical\n - 3-7 verification steps to confirm the task is complete\n\n4. Read the current PRD file at ${prdPath}, then update it:\n - Add your new tasks to the \"tasks\" array\n - Each task must have this structure:\n {\n \"id\": \"task_\" + 8 random alphanumeric characters,\n \"title\": \"...\",\n \"description\": \"...\",\n \"category\": \"functional|ui|bug|enhancement|testing|refactor\",\n \"priority\": \"low|medium|high|critical\",\n \"status\": \"draft\",\n \"steps\": [\"Step 1\", \"Step 2\", ...],\n \"passes\": false,\n \"createdAt\": ISO timestamp,\n \"updatedAt\": ISO timestamp,\n \"executionHistory\": []\n }\n\n5. Commit your changes with message: \"Plan: ${goal}\"\n\nIMPORTANT:\n- Tasks should be ordered logically (dependencies first)\n- Each task should be completable independently\n- Be specific about implementation details\n- Consider edge cases and error handling\n\nWhen done, output: <promise>PLANNING_COMPLETE</promise>`;\n }\n\n /**\n * Run a planning session to break down a goal into tasks\n */\n async runPlanningSession(goal: string): Promise<void> {\n // Block if busy\n if (this.isBusy()) {\n throw new Error('Another operation is in progress. Wait for it to complete or cancel it first.');\n }\n\n const config = getConfig(this.projectPath);\n const startedAt = new Date();\n const prompt = this.buildPlanningPrompt(goal);\n\n // Write prompt to temp file\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const promptFile = join(kanbanDir, 'prompt-planning.txt');\n writeFileSync(promptFile, prompt);\n\n // Build command arguments\n const args: string[] = [];\n if (config.agent.model) {\n args.push('--model', config.agent.model);\n }\n args.push('--permission-mode', config.agent.permissionMode);\n args.push('-p');\n args.push('--verbose');\n args.push('--output-format', 'stream-json');\n args.push(`@${promptFile}`);\n\n const commandDisplay = `${config.agent.command} ${args.join(' ')}`;\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n console.log('[executor] Planning command:', fullCommand);\n console.log('[executor] CWD:', this.projectPath);\n\n // Spawn Claude CLI\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: this.projectPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0',\n NO_COLOR: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.planningSession = {\n goal,\n process: childProcess,\n startedAt,\n output: [],\n };\n\n // Helper to log and emit\n const logOutput = (line: string) => {\n this.planningSession?.output.push(line);\n this.emit('planning:output', { line, lineType: 'stdout' });\n };\n\n // Emit started event\n this.emit('planning:started', { goal, timestamp: startedAt.toISOString() });\n logOutput(`[claude-kanban] Starting planning session\\n`);\n logOutput(`[claude-kanban] Goal: ${goal}\\n`);\n logOutput(`[claude-kanban] Command: ${commandDisplay}\\n`);\n\n // Handle stdout - parse stream-json format\n let stdoutBuffer = '';\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString();\n\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const json = JSON.parse(line);\n let text = '';\n\n if (json.type === 'assistant' && json.message?.content) {\n for (const block of json.message.content) {\n if (block.type === 'text') {\n text += block.text;\n } else if (block.type === 'tool_use') {\n text += this.formatToolUse(block.name, block.input);\n }\n }\n } else if (json.type === 'content_block_delta' && json.delta?.text) {\n text = json.delta.text;\n } else if (json.type === 'result' && json.result) {\n text = `\\n[Result: ${json.result}]\\n`;\n }\n\n if (text) {\n logOutput(text);\n }\n } catch {\n const cleanText = line.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, '');\n if (cleanText.trim()) {\n logOutput(cleanText + '\\n');\n }\n }\n }\n });\n\n // Handle stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n logOutput(`[stderr] ${text}`);\n });\n\n // Handle spawn error\n childProcess.on('error', (error) => {\n console.log('[executor] Planning spawn error:', error.message);\n this.emit('planning:output', { line: `[claude-kanban] Error: ${error.message}\\n`, lineType: 'stderr' });\n\n try { unlinkSync(promptFile); } catch {}\n this.emit('planning:failed', { error: error.message });\n this.planningSession = null;\n });\n\n // Handle process exit\n childProcess.on('close', (code, signal) => {\n console.log('[executor] Planning process closed with code:', code, 'signal:', signal);\n try { unlinkSync(promptFile); } catch {}\n logOutput(`[claude-kanban] Planning process exited with code ${code}\\n`);\n this.handlePlanningComplete(code);\n });\n\n // Set timeout (planning can take longer, give it 15 minutes)\n const timeoutMs = 15 * 60 * 1000;\n setTimeout(() => {\n if (this.planningSession) {\n this.cancelPlanning('Planning timeout exceeded');\n }\n }, timeoutMs);\n }\n\n /**\n * Handle planning session completion\n */\n private handlePlanningComplete(exitCode: number | null): void {\n if (!this.planningSession) return;\n\n const output = this.planningSession.output.join('');\n const isComplete = output.includes('<promise>PLANNING_COMPLETE</promise>');\n\n if (isComplete || exitCode === 0) {\n // Count new tasks by reading prd.json\n // We can't easily count tasks created, so just emit success\n this.emit('planning:completed', { success: true });\n } else {\n const error = `Planning process exited with code ${exitCode}`;\n this.emit('planning:failed', { error });\n }\n\n this.planningSession = null;\n }\n\n /**\n * Cancel the planning session\n */\n cancelPlanning(reason = 'Cancelled by user'): boolean {\n if (!this.planningSession) return false;\n\n const { process: childProcess } = this.planningSession;\n\n // Kill the process\n try {\n childProcess.kill('SIGTERM');\n setTimeout(() => {\n try {\n if (!childProcess.killed) {\n childProcess.kill('SIGKILL');\n }\n } catch {}\n }, 2000);\n } catch {}\n\n this.emit('planning:cancelled', { reason });\n this.planningSession = null;\n\n return true;\n }\n\n /**\n * Run a task\n */\n async runTask(taskId: string): Promise<void> {\n // Block if already running or planning\n if (this.isBusy()) {\n throw new Error('Another operation is in progress. Wait for it to complete or cancel it first.');\n }\n\n const config = getConfig(this.projectPath);\n const task = getTaskById(this.projectPath, taskId);\n\n if (!task) {\n throw new Error(`Task not found: ${taskId}`);\n }\n\n // Update task status\n updateTask(this.projectPath, taskId, { status: 'in_progress' });\n\n const startedAt = new Date();\n const prompt = this.buildTaskPrompt(task, config);\n\n // Write prompt to temp file\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const promptFile = join(kanbanDir, `prompt-${taskId}.txt`);\n writeFileSync(promptFile, prompt);\n\n // Build command arguments\n const args: string[] = [];\n if (config.agent.model) {\n args.push('--model', config.agent.model);\n }\n args.push('--permission-mode', config.agent.permissionMode);\n args.push('-p');\n args.push('--verbose');\n args.push('--output-format', 'stream-json');\n args.push(`@${promptFile}`);\n\n const commandDisplay = `${config.agent.command} ${args.join(' ')}`;\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n console.log('[executor] Command:', fullCommand);\n console.log('[executor] CWD:', this.projectPath);\n\n // Spawn Claude CLI\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: this.projectPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0',\n NO_COLOR: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.runningTask = {\n taskId,\n process: childProcess,\n startedAt,\n output: [],\n };\n\n // Initialize log file\n this.initLogFile(taskId);\n\n // Helper to log and emit\n const logOutput = (line: string) => {\n this.appendToLog(taskId, line);\n this.runningTask?.output.push(line);\n this.emit('task:output', { taskId, line, lineType: 'stdout' });\n };\n\n // Emit started event\n this.emit('task:started', { taskId, timestamp: startedAt.toISOString() });\n logOutput(`[claude-kanban] Starting task: ${task.title}\\n`);\n logOutput(`[claude-kanban] Command: ${commandDisplay}\\n`);\n logOutput(`[claude-kanban] Process spawned (PID: ${childProcess.pid})\\n`);\n\n // Handle stdout - parse stream-json format\n let stdoutBuffer = '';\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString();\n\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const json = JSON.parse(line);\n let text = '';\n\n if (json.type === 'assistant' && json.message?.content) {\n for (const block of json.message.content) {\n if (block.type === 'text') {\n text += block.text;\n } else if (block.type === 'tool_use') {\n text += this.formatToolUse(block.name, block.input);\n }\n }\n } else if (json.type === 'content_block_delta' && json.delta?.text) {\n text = json.delta.text;\n } else if (json.type === 'result' && json.result) {\n text = `\\n[Result: ${json.result}]\\n`;\n }\n\n if (text) {\n logOutput(text);\n }\n } catch {\n const cleanText = line.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, '');\n if (cleanText.trim()) {\n logOutput(cleanText + '\\n');\n }\n }\n }\n });\n\n // Handle stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n logOutput(`[stderr] ${text}`);\n });\n\n // Handle spawn error\n childProcess.on('error', (error) => {\n console.log('[executor] Spawn error:', error.message);\n this.emit('task:output', { taskId, line: `[claude-kanban] Error: ${error.message}\\n`, lineType: 'stderr' });\n\n try { unlinkSync(promptFile); } catch {}\n updateTask(this.projectPath, taskId, { status: 'failed', passes: false });\n const endedAt = new Date();\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'failed',\n duration: endedAt.getTime() - startedAt.getTime(),\n error: error.message,\n });\n this.emit('task:failed', { taskId, error: error.message });\n this.runningTask = null;\n });\n\n // Handle process exit\n childProcess.on('close', (code, signal) => {\n console.log('[executor] Process closed with code:', code, 'signal:', signal);\n try { unlinkSync(promptFile); } catch {}\n logOutput(`[claude-kanban] Process exited with code ${code}\\n`);\n this.handleTaskComplete(taskId, code, startedAt);\n });\n\n // Set timeout\n const timeoutMs = (config.execution.timeout || 30) * 60 * 1000;\n setTimeout(() => {\n if (this.runningTask?.taskId === taskId) {\n this.cancelTask('Timeout exceeded');\n }\n }, timeoutMs);\n }\n\n /**\n * Handle task completion\n */\n private handleTaskComplete(taskId: string, exitCode: number | null, startedAt: Date): void {\n if (!this.runningTask || this.runningTask.taskId !== taskId) return;\n\n const endedAt = new Date();\n const duration = endedAt.getTime() - startedAt.getTime();\n const output = this.runningTask.output.join('');\n const config = getConfig(this.projectPath);\n\n // Check if task was marked complete by Claude\n const isComplete = output.includes('<promise>COMPLETE</promise>');\n const task = getTaskById(this.projectPath, taskId);\n\n if (isComplete || exitCode === 0) {\n // Task completed successfully - check if QA is enabled\n if (config.execution.enableQA) {\n // Mark task as pending QA verification\n updateTask(this.projectPath, taskId, {\n status: 'in_progress', // Keep in progress until QA passes\n passes: false,\n });\n\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'completed',\n duration,\n });\n\n this.runningTask = null;\n this.pendingQATaskId = taskId;\n\n // Start QA verification\n this.runQAVerification(taskId).catch((error) => {\n console.error('QA verification error:', error);\n this.handleQAComplete(taskId, false, ['QA process failed to start']);\n });\n } else {\n // No QA - mark as completed\n updateTask(this.projectPath, taskId, {\n status: 'completed',\n passes: true,\n });\n\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'completed',\n duration,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'completed',\n duration,\n });\n\n this.emit('task:completed', { taskId, duration });\n this.afkTasksCompleted++;\n this.runningTask = null;\n\n // Continue AFK mode if active\n if (this.afkMode) {\n this.continueAFKMode();\n }\n }\n } else {\n // Task failed\n updateTask(this.projectPath, taskId, {\n status: 'failed',\n passes: false,\n });\n\n const error = `Process exited with code ${exitCode}`;\n\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'failed',\n duration,\n error,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'failed',\n duration,\n error,\n });\n\n this.emit('task:failed', { taskId, error });\n this.runningTask = null;\n\n // Continue AFK mode if active\n if (this.afkMode) {\n this.continueAFKMode();\n }\n }\n }\n\n /**\n * Build the QA verification prompt\n */\n private buildQAPrompt(task: Task, config: Config): string {\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const prdPath = join(kanbanDir, 'prd.json');\n\n const stepsText = task.steps.length > 0\n ? `\\nVerification steps:\\n${task.steps.map((s, i) => `${i + 1}. ${s}`).join('\\n')}`\n : '';\n\n // Build verification commands\n const verifyCommands: string[] = [];\n if (config.project.typecheckCommand) {\n verifyCommands.push(config.project.typecheckCommand);\n }\n if (config.project.testCommand) {\n verifyCommands.push(config.project.testCommand);\n }\n\n return `You are a QA reviewer. Verify that the following task has been correctly implemented.\n\n## TASK TO VERIFY\nTitle: ${task.title}\nCategory: ${task.category}\nPriority: ${task.priority}\n\n${task.description}\n${stepsText}\n\n## YOUR JOB\n\n1. Review the recent git commits and changes made for this task.\n\n2. Check that all verification steps (if any) have been satisfied.\n\n3. Run the following quality checks:\n${verifyCommands.length > 0 ? verifyCommands.map(cmd => ` - ${cmd}`).join('\\n') : ' (No verification commands configured)'}\n\n4. Check that the implementation meets the task requirements.\n\n5. If everything passes, output: <qa>PASSED</qa>\n\n6. If there are issues, output: <qa>FAILED</qa>\n Then list the issues that need to be fixed:\n <issues>\n - Issue 1\n - Issue 2\n </issues>\n\nBe thorough but fair. Only fail the QA if there are real issues that affect functionality or quality.`;\n }\n\n /**\n * Run QA verification for a task\n */\n async runQAVerification(taskId: string): Promise<void> {\n const config = getConfig(this.projectPath);\n const task = getTaskById(this.projectPath, taskId);\n\n if (!task) {\n throw new Error(`Task not found: ${taskId}`);\n }\n\n const currentRetries = this.qaRetryCount.get(taskId) || 0;\n const attempt = currentRetries + 1;\n\n const startedAt = new Date();\n const prompt = this.buildQAPrompt(task, config);\n\n // Write prompt to temp file\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const promptFile = join(kanbanDir, `qa-${taskId}.txt`);\n writeFileSync(promptFile, prompt);\n\n // Build command arguments\n const args: string[] = [];\n if (config.agent.model) {\n args.push('--model', config.agent.model);\n }\n args.push('--permission-mode', config.agent.permissionMode);\n args.push('-p');\n args.push('--verbose');\n args.push('--output-format', 'stream-json');\n args.push(`@${promptFile}`);\n\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n console.log('[executor] QA command:', fullCommand);\n\n // Spawn Claude CLI for QA\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: this.projectPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0',\n NO_COLOR: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.qaSession = {\n taskId,\n process: childProcess,\n startedAt,\n output: [],\n attempt,\n };\n\n // Emit QA started event\n this.emit('qa:started', { taskId, attempt });\n\n // Helper to log QA output\n const logOutput = (line: string) => {\n this.qaSession?.output.push(line);\n this.emit('qa:output', { taskId, line });\n };\n\n logOutput(`[claude-kanban] Starting QA verification (attempt ${attempt})\\n`);\n\n // Handle stdout - parse stream-json format\n let stdoutBuffer = '';\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString();\n\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const json = JSON.parse(line);\n let text = '';\n\n if (json.type === 'assistant' && json.message?.content) {\n for (const block of json.message.content) {\n if (block.type === 'text') {\n text += block.text;\n } else if (block.type === 'tool_use') {\n text += this.formatToolUse(block.name, block.input);\n }\n }\n } else if (json.type === 'content_block_delta' && json.delta?.text) {\n text = json.delta.text;\n }\n\n if (text) {\n logOutput(text);\n }\n } catch {\n const cleanText = line.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, '');\n if (cleanText.trim()) {\n logOutput(cleanText + '\\n');\n }\n }\n }\n });\n\n // Handle stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n logOutput(`[stderr] ${text}`);\n });\n\n // Handle spawn error\n childProcess.on('error', (error) => {\n console.log('[executor] QA spawn error:', error.message);\n try { unlinkSync(promptFile); } catch {}\n this.handleQAComplete(taskId, false, [`QA process error: ${error.message}`]);\n });\n\n // Handle process exit\n childProcess.on('close', (code) => {\n console.log('[executor] QA process closed with code:', code);\n try { unlinkSync(promptFile); } catch {}\n this.parseQAResult(taskId);\n });\n\n // Set timeout for QA (5 minutes)\n const timeoutMs = 5 * 60 * 1000;\n setTimeout(() => {\n if (this.qaSession?.taskId === taskId) {\n this.cancelQA('QA timeout exceeded');\n }\n }, timeoutMs);\n }\n\n /**\n * Parse QA result from output\n */\n private parseQAResult(taskId: string): void {\n if (!this.qaSession || this.qaSession.taskId !== taskId) return;\n\n const output = this.qaSession.output.join('');\n\n // Check for QA result\n const passedMatch = output.includes('<qa>PASSED</qa>');\n const failedMatch = output.includes('<qa>FAILED</qa>');\n\n if (passedMatch) {\n this.handleQAComplete(taskId, true, []);\n } else if (failedMatch) {\n // Extract issues\n const issuesMatch = output.match(/<issues>([\\s\\S]*?)<\\/issues>/);\n const issues: string[] = [];\n if (issuesMatch) {\n const issueLines = issuesMatch[1].split('\\n');\n for (const line of issueLines) {\n const trimmed = line.trim();\n if (trimmed.startsWith('-')) {\n issues.push(trimmed.substring(1).trim());\n } else if (trimmed) {\n issues.push(trimmed);\n }\n }\n }\n this.handleQAComplete(taskId, false, issues.length > 0 ? issues : ['QA verification failed']);\n } else {\n // No clear result - treat as failed\n this.handleQAComplete(taskId, false, ['QA did not provide a clear PASSED/FAILED result']);\n }\n }\n\n /**\n * Handle QA completion\n */\n private handleQAComplete(taskId: string, passed: boolean, issues: string[]): void {\n const config = getConfig(this.projectPath);\n const task = getTaskById(this.projectPath, taskId);\n const currentRetries = this.qaRetryCount.get(taskId) || 0;\n\n this.qaSession = null;\n this.pendingQATaskId = null;\n\n if (passed) {\n // QA passed - mark task as completed\n updateTask(this.projectPath, taskId, {\n status: 'completed',\n passes: true,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'completed',\n duration: 0,\n });\n\n this.emit('qa:passed', { taskId });\n this.emit('task:completed', { taskId, duration: 0 });\n this.afkTasksCompleted++;\n this.qaRetryCount.delete(taskId);\n } else {\n // QA failed - check if we should retry\n const willRetry = currentRetries < config.execution.qaMaxRetries;\n\n this.emit('qa:failed', { taskId, issues, willRetry });\n\n if (willRetry) {\n // Increment retry count and run fix + QA again\n this.qaRetryCount.set(taskId, currentRetries + 1);\n\n // Run task again to fix issues\n console.log(`[executor] QA failed, retrying (${currentRetries + 1}/${config.execution.qaMaxRetries})`);\n this.runTaskWithQAFix(taskId, issues).catch((error) => {\n console.error('QA fix error:', error);\n this.handleQAComplete(taskId, false, ['Failed to run QA fix']);\n });\n } else {\n // Max retries reached - mark as failed\n updateTask(this.projectPath, taskId, {\n status: 'failed',\n passes: false,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'failed',\n duration: 0,\n error: `QA failed after ${config.execution.qaMaxRetries} retries: ${issues.join(', ')}`,\n });\n\n this.emit('task:failed', { taskId, error: `QA failed: ${issues.join(', ')}` });\n this.qaRetryCount.delete(taskId);\n }\n }\n\n // Continue AFK mode if active\n if (this.afkMode && !this.isBusy()) {\n this.continueAFKMode();\n }\n }\n\n /**\n * Run task again with QA fix instructions\n */\n private async runTaskWithQAFix(taskId: string, issues: string[]): Promise<void> {\n const config = getConfig(this.projectPath);\n const task = getTaskById(this.projectPath, taskId);\n\n if (!task) {\n throw new Error(`Task not found: ${taskId}`);\n }\n\n const startedAt = new Date();\n\n // Build fix prompt\n const issuesText = issues.map((i, idx) => `${idx + 1}. ${i}`).join('\\n');\n const prompt = `You are fixing issues found during QA verification for a task.\n\n## ORIGINAL TASK\nTitle: ${task.title}\n${task.description}\n\n## QA ISSUES TO FIX\n${issuesText}\n\n## INSTRUCTIONS\n1. Review the issues reported by QA.\n2. Fix each issue.\n3. Run verification commands to ensure fixes work.\n4. When done, output: <promise>COMPLETE</promise>\n\nFocus only on fixing the reported issues, don't make unrelated changes.`;\n\n // Write prompt to temp file\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const promptFile = join(kanbanDir, `qafix-${taskId}.txt`);\n writeFileSync(promptFile, prompt);\n\n // Build command\n const args: string[] = [];\n if (config.agent.model) {\n args.push('--model', config.agent.model);\n }\n args.push('--permission-mode', config.agent.permissionMode);\n args.push('-p');\n args.push('--verbose');\n args.push('--output-format', 'stream-json');\n args.push(`@${promptFile}`);\n\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n // Spawn fix process\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: this.projectPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0',\n NO_COLOR: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.runningTask = {\n taskId,\n process: childProcess,\n startedAt,\n output: [],\n };\n\n this.emit('task:started', { taskId, timestamp: startedAt.toISOString() });\n\n // Handle output\n let stdoutBuffer = '';\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString();\n\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const json = JSON.parse(line);\n let text = '';\n\n if (json.type === 'assistant' && json.message?.content) {\n for (const block of json.message.content) {\n if (block.type === 'text') {\n text += block.text;\n } else if (block.type === 'tool_use') {\n text += this.formatToolUse(block.name, block.input);\n }\n }\n } else if (json.type === 'content_block_delta' && json.delta?.text) {\n text = json.delta.text;\n }\n\n if (text) {\n this.runningTask?.output.push(text);\n this.emit('task:output', { taskId, line: text, lineType: 'stdout' });\n }\n } catch {\n const cleanText = line.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, '');\n if (cleanText.trim()) {\n this.runningTask?.output.push(cleanText + '\\n');\n this.emit('task:output', { taskId, line: cleanText + '\\n', lineType: 'stdout' });\n }\n }\n }\n });\n\n childProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n this.runningTask?.output.push(`[stderr] ${text}`);\n this.emit('task:output', { taskId, line: `[stderr] ${text}`, lineType: 'stderr' });\n });\n\n childProcess.on('error', (error) => {\n try { unlinkSync(promptFile); } catch {}\n this.handleQAComplete(taskId, false, [`Fix process error: ${error.message}`]);\n });\n\n childProcess.on('close', (code) => {\n try { unlinkSync(promptFile); } catch {}\n\n const output = this.runningTask?.output.join('') || '';\n const isComplete = output.includes('<promise>COMPLETE</promise>');\n\n this.runningTask = null;\n\n if (isComplete || code === 0) {\n // Run QA again\n this.runQAVerification(taskId).catch((error) => {\n console.error('QA verification error after fix:', error);\n this.handleQAComplete(taskId, false, ['QA verification failed after fix']);\n });\n } else {\n this.handleQAComplete(taskId, false, [`Fix process exited with code ${code}`]);\n }\n });\n }\n\n /**\n * Cancel QA verification\n */\n cancelQA(reason = 'Cancelled'): boolean {\n if (!this.qaSession) return false;\n\n const { taskId, process: childProcess } = this.qaSession;\n\n try {\n childProcess.kill('SIGTERM');\n setTimeout(() => {\n try {\n if (!childProcess.killed) {\n childProcess.kill('SIGKILL');\n }\n } catch {}\n }, 2000);\n } catch {}\n\n this.emit('qa:failed', { taskId, issues: [reason], willRetry: false });\n this.qaSession = null;\n this.pendingQATaskId = null;\n\n return true;\n }\n\n /**\n * Cancel the running task\n */\n cancelTask(reason = 'Cancelled by user'): boolean {\n if (!this.runningTask) return false;\n\n const { taskId, process: childProcess, startedAt } = this.runningTask;\n const endedAt = new Date();\n const duration = endedAt.getTime() - startedAt.getTime();\n const task = getTaskById(this.projectPath, taskId);\n\n // Kill the process\n try {\n childProcess.kill('SIGTERM');\n setTimeout(() => {\n try {\n if (!childProcess.killed) {\n childProcess.kill('SIGKILL');\n }\n } catch {}\n }, 2000);\n } catch {}\n\n // Update task status back to ready\n updateTask(this.projectPath, taskId, { status: 'ready' });\n\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'cancelled',\n duration,\n error: reason,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'cancelled',\n duration,\n error: reason,\n });\n\n this.emit('task:cancelled', { taskId });\n this.runningTask = null;\n\n return true;\n }\n\n /**\n * Start AFK mode - run tasks sequentially until done\n */\n startAFKMode(maxIterations: number): void {\n if (this.afkMode) {\n throw new Error('AFK mode already running');\n }\n\n if (this.isBusy()) {\n throw new Error('Cannot start AFK mode while another operation is in progress');\n }\n\n this.afkMode = true;\n this.afkIteration = 0;\n this.afkMaxIterations = maxIterations;\n this.afkTasksCompleted = 0;\n\n this.emitAFKStatus();\n this.continueAFKMode();\n }\n\n /**\n * Continue AFK mode - pick next task\n */\n private continueAFKMode(): void {\n if (!this.afkMode) return;\n\n // Check if we've reached max iterations\n if (this.afkIteration >= this.afkMaxIterations) {\n this.stopAFKMode();\n return;\n }\n\n // Don't start another if busy\n if (this.isBusy()) return;\n\n const nextTask = getNextReadyTask(this.projectPath);\n if (!nextTask) {\n // No more tasks to run\n this.stopAFKMode();\n return;\n }\n\n this.afkIteration++;\n this.runTask(nextTask.id).catch((error) => {\n console.error('AFK task error:', error);\n });\n\n this.emitAFKStatus();\n }\n\n /**\n * Stop AFK mode\n */\n stopAFKMode(): void {\n this.afkMode = false;\n this.emitAFKStatus();\n }\n\n /**\n * Emit AFK status\n */\n private emitAFKStatus(): void {\n this.emit('afk:status', {\n running: this.afkMode,\n currentIteration: this.afkIteration,\n maxIterations: this.afkMaxIterations,\n tasksCompleted: this.afkTasksCompleted,\n });\n }\n\n /**\n * Get AFK status\n */\n getAFKStatus(): {\n running: boolean;\n currentIteration: number;\n maxIterations: number;\n tasksCompleted: number;\n } {\n return {\n running: this.afkMode,\n currentIteration: this.afkIteration,\n maxIterations: this.afkMaxIterations,\n tasksCompleted: this.afkTasksCompleted,\n };\n }\n\n /**\n * Cancel running task/planning/QA and stop AFK mode\n */\n cancelAll(): void {\n if (this.runningTask) {\n try {\n this.runningTask.process.kill('SIGKILL');\n } catch {}\n this.runningTask = null;\n }\n if (this.planningSession) {\n try {\n this.planningSession.process.kill('SIGKILL');\n } catch {}\n this.planningSession = null;\n }\n if (this.qaSession) {\n try {\n this.qaSession.process.kill('SIGKILL');\n } catch {}\n this.qaSession = null;\n }\n this.pendingQATaskId = null;\n this.qaRetryCount.clear();\n this.stopAFKMode();\n }\n}\n","import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';\nimport { join, basename } from 'path';\nimport type { PRD, Config } from '../../types.js';\n\nconst KANBAN_DIR = '.claude-kanban';\nconst SCRIPTS_DIR = 'scripts';\n\n/**\n * Check if project has been initialized\n */\nexport async function isProjectInitialized(projectPath: string): Promise<boolean> {\n const kanbanDir = join(projectPath, KANBAN_DIR);\n const prdPath = join(kanbanDir, 'prd.json');\n const configPath = join(kanbanDir, 'config.json');\n\n return existsSync(kanbanDir) && existsSync(prdPath) && existsSync(configPath);\n}\n\n/**\n * Detect package manager from lock files\n */\nexport function detectPackageManager(projectPath: string): 'pnpm' | 'yarn' | 'bun' | 'npm' {\n if (existsSync(join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(join(projectPath, 'yarn.lock'))) return 'yarn';\n if (existsSync(join(projectPath, 'bun.lockb'))) return 'bun';\n return 'npm';\n}\n\n/**\n * Detect project type\n */\nexport type ProjectType = 'node' | 'python' | 'php' | 'ruby' | 'go' | 'rust' | 'unknown';\n\nexport function detectProjectType(projectPath: string): ProjectType {\n // Check for project type indicators (order matters - more specific first)\n if (existsSync(join(projectPath, 'package.json'))) return 'node';\n if (existsSync(join(projectPath, 'pyproject.toml')) ||\n existsSync(join(projectPath, 'requirements.txt')) ||\n existsSync(join(projectPath, 'setup.py'))) return 'python';\n if (existsSync(join(projectPath, 'composer.json'))) return 'php';\n if (existsSync(join(projectPath, 'Gemfile'))) return 'ruby';\n if (existsSync(join(projectPath, 'go.mod'))) return 'go';\n if (existsSync(join(projectPath, 'Cargo.toml'))) return 'rust';\n return 'unknown';\n}\n\n/**\n * Detect project type and common commands\n */\nexport function detectProjectCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n const projectType = detectProjectType(projectPath);\n\n switch (projectType) {\n case 'node':\n return detectNodeCommands(projectPath);\n case 'python':\n return detectPythonCommands(projectPath);\n case 'php':\n return detectPHPCommands(projectPath);\n case 'ruby':\n return detectRubyCommands(projectPath);\n case 'go':\n return detectGoCommands(projectPath);\n case 'rust':\n return detectRustCommands(projectPath);\n default:\n return {\n testCommand: '',\n typecheckCommand: '',\n buildCommand: '',\n };\n }\n}\n\n/**\n * Detect Node.js project commands\n */\nfunction detectNodeCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n const pm = detectPackageManager(projectPath);\n const run = pm === 'npm' ? 'npm run' : pm;\n\n // Check for package.json scripts\n const packageJsonPath = join(projectPath, 'package.json');\n let scripts: Record<string, string> = {};\n\n if (existsSync(packageJsonPath)) {\n try {\n const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n scripts = pkg.scripts || {};\n } catch {\n // Ignore parse errors\n }\n }\n\n // Determine commands based on available scripts\n const testCommand = scripts.test ? `${run} test` : '';\n const typecheckCommand = scripts.typecheck\n ? `${run} typecheck`\n : scripts['type-check']\n ? `${run} type-check`\n : existsSync(join(projectPath, 'tsconfig.json'))\n ? `${run === 'npm run' ? 'npx' : pm} tsc --noEmit`\n : '';\n const buildCommand = scripts.build ? `${run} build` : '';\n\n return { testCommand, typecheckCommand, buildCommand };\n}\n\n/**\n * Detect Python project commands\n */\nfunction detectPythonCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n // Check for pytest\n const hasPytest = existsSync(join(projectPath, 'pytest.ini')) ||\n existsSync(join(projectPath, 'pyproject.toml')) ||\n existsSync(join(projectPath, 'tests'));\n\n // Check for mypy\n const hasMypy = existsSync(join(projectPath, 'mypy.ini')) ||\n existsSync(join(projectPath, '.mypy.ini'));\n\n return {\n testCommand: hasPytest ? 'pytest' : '',\n typecheckCommand: hasMypy ? 'mypy .' : '',\n buildCommand: '',\n };\n}\n\n/**\n * Detect PHP project commands\n */\nfunction detectPHPCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n const hasPhpunit = existsSync(join(projectPath, 'phpunit.xml')) ||\n existsSync(join(projectPath, 'phpunit.xml.dist'));\n const hasPest = existsSync(join(projectPath, 'tests', 'Pest.php'));\n const hasPhpstan = existsSync(join(projectPath, 'phpstan.neon')) ||\n existsSync(join(projectPath, 'phpstan.neon.dist'));\n\n let testCommand = '';\n if (hasPest) {\n testCommand = './vendor/bin/pest';\n } else if (hasPhpunit) {\n testCommand = './vendor/bin/phpunit';\n }\n\n return {\n testCommand,\n typecheckCommand: hasPhpstan ? './vendor/bin/phpstan analyse' : '',\n buildCommand: '',\n };\n}\n\n/**\n * Detect Ruby project commands\n */\nfunction detectRubyCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n const hasRspec = existsSync(join(projectPath, 'spec')) ||\n existsSync(join(projectPath, '.rspec'));\n const hasMinitest = existsSync(join(projectPath, 'test'));\n\n return {\n testCommand: hasRspec ? 'bundle exec rspec' : hasMinitest ? 'bundle exec rake test' : '',\n typecheckCommand: '', // Ruby doesn't have built-in typechecking (sorbet is optional)\n buildCommand: '',\n };\n}\n\n/**\n * Detect Go project commands\n */\nfunction detectGoCommands(_projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n return {\n testCommand: 'go test ./...',\n typecheckCommand: 'go build ./...', // Go's compiler is the typechecker\n buildCommand: 'go build',\n };\n}\n\n/**\n * Detect Rust project commands\n */\nfunction detectRustCommands(_projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n return {\n testCommand: 'cargo test',\n typecheckCommand: 'cargo check',\n buildCommand: 'cargo build',\n };\n}\n\n/**\n * Create initial PRD\n */\nfunction createInitialPRD(projectName: string): PRD {\n return {\n version: '1.0',\n projectName,\n tasks: [],\n };\n}\n\n/**\n * Create initial config\n */\nfunction createInitialConfig(projectPath: string): Config {\n const { testCommand, typecheckCommand, buildCommand } = detectProjectCommands(projectPath);\n\n return {\n version: '1.0',\n agent: {\n command: 'claude',\n permissionMode: 'acceptEdits',\n model: null,\n },\n project: {\n testCommand,\n typecheckCommand,\n buildCommand,\n },\n ui: {\n port: 4242,\n theme: 'system',\n },\n execution: {\n maxConcurrent: 3,\n timeout: 30, // 30 minutes\n enableQA: false, // QA verification disabled by default\n qaMaxRetries: 2, // Max QA retry attempts\n },\n };\n}\n\n/**\n * Create ralph.sh script\n */\nfunction createRalphScript(config: Config): string {\n const { testCommand, typecheckCommand } = config.project;\n\n return `#!/bin/bash\nset -e\n\nif [ -z \"$1\" ]; then\n echo \"Usage: $0 <iterations>\"\n exit 1\nfi\n\nKANBAN_DIR=\".claude-kanban\"\n\nfor ((i=1; i<=$1; i++)); do\n echo \"\"\n echo \"=== Iteration $i of $1 ===\"\n echo \"\"\n\n result=$(${config.agent.command} --permission-mode ${config.agent.permissionMode} -p \\\\\n \"@$KANBAN_DIR/prd.json\" \\\\\n \"@$KANBAN_DIR/progress.txt\" \\\\\n \"You are working on tasks from a Kanban board. Follow these steps:\n\n1. Read the PRD to find the highest-priority task with status 'ready'.\n Priority order: critical > high > medium > low\n If multiple tasks have the same priority, pick the first one.\n\n2. Work on ONLY that single task. Do not touch other tasks.\n\n3. Check that types pass via: ${typecheckCommand}\n Check that tests pass via: ${testCommand}\n\n4. When the task is complete:\n - Update the task's 'passes' field to true\n - Update the task's 'status' field to 'completed'\n\n5. Append your progress to progress.txt with:\n - Date and time\n - Task ID and title\n - What you implemented\n - Files changed\n - Any notes for future work\n\n6. Make a git commit with a descriptive message.\n\nIMPORTANT: Only work on ONE task per iteration.\n\nIf all tasks with status 'ready' are complete (passes: true), output <promise>COMPLETE</promise>.\")\n\n echo \"$result\"\n\n if [[ \"$result\" == *\"<promise>COMPLETE</promise>\"* ]]; then\n echo \"\"\n echo \"=== All tasks complete! ===\"\n exit 0\n fi\ndone\n\necho \"\"\necho \"=== Completed $1 iterations ===\"\n`;\n}\n\n/**\n * Create ralph-once.sh script\n */\nfunction createRalphOnceScript(config: Config): string {\n const { testCommand, typecheckCommand } = config.project;\n\n return `#!/bin/bash\nset -e\n\nKANBAN_DIR=\".claude-kanban\"\nTASK_ID=\"$1\"\n\nif [ -z \"$TASK_ID\" ]; then\n # No task ID specified, let Claude pick\n ${config.agent.command} --permission-mode ${config.agent.permissionMode} -p \\\\\n \"@$KANBAN_DIR/prd.json\" \\\\\n \"@$KANBAN_DIR/progress.txt\" \\\\\n \"You are working on tasks from a Kanban board. Follow these steps:\n\n1. Read the PRD to find the highest-priority task with status 'ready'.\n Priority order: critical > high > medium > low\n\n2. Work on ONLY that single task.\n\n3. Check that types pass via: ${typecheckCommand}\n Check that tests pass via: ${testCommand}\n\n4. When complete:\n - Update the task's 'passes' field to true\n - Update the task's 'status' field to 'completed'\n\n5. Append progress to progress.txt.\n\n6. Make a git commit.\n\nIf all 'ready' tasks are complete, output <promise>COMPLETE</promise>.\"\nelse\n # Specific task ID provided\n ${config.agent.command} --permission-mode ${config.agent.permissionMode} -p \\\\\n \"@$KANBAN_DIR/prd.json\" \\\\\n \"@$KANBAN_DIR/progress.txt\" \\\\\n \"You are working on a specific task from the Kanban board.\n\nTASK ID: $TASK_ID\n\nFind this task in the PRD and work on it. Follow these steps:\n\n1. Locate the task with id '$TASK_ID' in the PRD.\n\n2. Implement the feature as described.\n\n3. Check that types pass via: ${typecheckCommand}\n Check that tests pass via: ${testCommand}\n\n4. When complete:\n - Update the task's 'passes' field to true\n - Update the task's 'status' field to 'completed'\n\n5. Append progress to progress.txt.\n\n6. Make a git commit.\n\nIf the task is complete, output <promise>COMPLETE</promise>.\"\nfi\n`;\n}\n\n/**\n * Initialize the project\n */\nexport async function initializeProject(projectPath: string, reset = false): Promise<void> {\n const kanbanDir = join(projectPath, KANBAN_DIR);\n const scriptsDir = join(projectPath, SCRIPTS_DIR);\n const projectName = basename(projectPath);\n\n // Create directories\n if (!existsSync(kanbanDir)) {\n mkdirSync(kanbanDir, { recursive: true });\n }\n if (!existsSync(scriptsDir)) {\n mkdirSync(scriptsDir, { recursive: true });\n }\n\n // Create or reset PRD\n const prdPath = join(kanbanDir, 'prd.json');\n if (!existsSync(prdPath) || reset) {\n const prd = createInitialPRD(projectName);\n writeFileSync(prdPath, JSON.stringify(prd, null, 2));\n }\n\n // Create config (preserve existing if not reset)\n const configPath = join(kanbanDir, 'config.json');\n let config: Config;\n if (!existsSync(configPath)) {\n config = createInitialConfig(projectPath);\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n } else {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n\n // Create or update progress.txt\n const progressPath = join(kanbanDir, 'progress.txt');\n if (!existsSync(progressPath)) {\n const date = new Date().toISOString().split('T')[0];\n writeFileSync(progressPath, `# Claude Kanban Progress Log\\n\\nProject: ${projectName}\\nCreated: ${date}\\n\\n---\\n\\n`);\n }\n\n // Create scripts\n const ralphPath = join(scriptsDir, 'ralph.sh');\n const ralphOncePath = join(scriptsDir, 'ralph-once.sh');\n\n writeFileSync(ralphPath, createRalphScript(config));\n writeFileSync(ralphOncePath, createRalphOnceScript(config));\n\n // Make scripts executable (Unix-like systems)\n try {\n const { chmodSync } = await import('fs');\n chmodSync(ralphPath, 0o755);\n chmodSync(ralphOncePath, 0o755);\n } catch {\n // Ignore chmod errors on Windows\n }\n\n // Add .claude-kanban to .gitignore if it exists (optional - users may want to commit)\n // For now, we'll let users decide\n}\n\n/**\n * Get the kanban directory path\n */\nexport function getKanbanDir(projectPath: string): string {\n return join(projectPath, KANBAN_DIR);\n}\n\n/**\n * Get project config\n */\nexport function getConfig(projectPath: string): Config {\n const configPath = join(projectPath, KANBAN_DIR, 'config.json');\n return JSON.parse(readFileSync(configPath, 'utf-8'));\n}\n\n/**\n * Save project config\n */\nexport function saveConfig(projectPath: string, config: Config): void {\n const configPath = join(projectPath, KANBAN_DIR, 'config.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n}\n","import { readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { nanoid } from 'nanoid';\nimport type { PRD, Task, CreateTaskRequest, UpdateTaskRequest, TaskStatus } from '../../types.js';\n\nconst KANBAN_DIR = '.claude-kanban';\n\n/**\n * Get PRD file path\n */\nfunction getPRDPath(projectPath: string): string {\n return join(projectPath, KANBAN_DIR, 'prd.json');\n}\n\n/**\n * Read PRD from file\n */\nexport function readPRD(projectPath: string): PRD {\n const path = getPRDPath(projectPath);\n return JSON.parse(readFileSync(path, 'utf-8'));\n}\n\n/**\n * Write PRD to file\n */\nexport function writePRD(projectPath: string, prd: PRD): void {\n const path = getPRDPath(projectPath);\n writeFileSync(path, JSON.stringify(prd, null, 2));\n}\n\n/**\n * Get all tasks\n */\nexport function getAllTasks(projectPath: string): Task[] {\n const prd = readPRD(projectPath);\n return prd.tasks;\n}\n\n/**\n * Get task by ID\n */\nexport function getTaskById(projectPath: string, taskId: string): Task | undefined {\n const prd = readPRD(projectPath);\n return prd.tasks.find((t) => t.id === taskId);\n}\n\n/**\n * Get tasks by status\n */\nexport function getTasksByStatus(projectPath: string, status: TaskStatus): Task[] {\n const prd = readPRD(projectPath);\n return prd.tasks.filter((t) => t.status === status);\n}\n\n/**\n * Create a new task\n */\nexport function createTask(projectPath: string, request: CreateTaskRequest): Task {\n const prd = readPRD(projectPath);\n const now = new Date().toISOString();\n\n const task: Task = {\n id: `task_${nanoid(8)}`,\n title: request.title,\n description: request.description,\n category: request.category || 'functional',\n priority: request.priority || 'medium',\n status: request.status || 'draft',\n steps: request.steps || [],\n passes: false,\n createdAt: now,\n updatedAt: now,\n executionHistory: [],\n dependsOn: request.dependsOn || [],\n };\n\n prd.tasks.push(task);\n writePRD(projectPath, prd);\n\n return task;\n}\n\n/**\n * Update a task\n */\nexport function updateTask(projectPath: string, taskId: string, updates: UpdateTaskRequest): Task | null {\n const prd = readPRD(projectPath);\n const taskIndex = prd.tasks.findIndex((t) => t.id === taskId);\n\n if (taskIndex === -1) {\n return null;\n }\n\n const task = prd.tasks[taskIndex];\n const updatedTask: Task = {\n ...task,\n ...updates,\n updatedAt: new Date().toISOString(),\n };\n\n prd.tasks[taskIndex] = updatedTask;\n writePRD(projectPath, prd);\n\n return updatedTask;\n}\n\n/**\n * Delete a task\n */\nexport function deleteTask(projectPath: string, taskId: string): boolean {\n const prd = readPRD(projectPath);\n const initialLength = prd.tasks.length;\n prd.tasks = prd.tasks.filter((t) => t.id !== taskId);\n\n if (prd.tasks.length < initialLength) {\n writePRD(projectPath, prd);\n return true;\n }\n\n return false;\n}\n\n/**\n * Move task to a new status\n */\nexport function moveTask(projectPath: string, taskId: string, newStatus: TaskStatus): Task | null {\n return updateTask(projectPath, taskId, { status: newStatus });\n}\n\n/**\n * Mark task as completed\n */\nexport function completeTask(projectPath: string, taskId: string): Task | null {\n return updateTask(projectPath, taskId, {\n status: 'completed',\n passes: true,\n });\n}\n\n/**\n * Mark task as failed\n */\nexport function failTask(projectPath: string, taskId: string): Task | null {\n return updateTask(projectPath, taskId, {\n status: 'failed',\n passes: false,\n });\n}\n\n/**\n * Add execution entry to task history\n */\nexport function addExecutionEntry(\n projectPath: string,\n taskId: string,\n entry: {\n startedAt: string;\n endedAt: string;\n status: 'completed' | 'failed' | 'cancelled';\n duration: number;\n error?: string;\n }\n): Task | null {\n const prd = readPRD(projectPath);\n const taskIndex = prd.tasks.findIndex((t) => t.id === taskId);\n\n if (taskIndex === -1) {\n return null;\n }\n\n prd.tasks[taskIndex].executionHistory.push(entry);\n prd.tasks[taskIndex].updatedAt = new Date().toISOString();\n writePRD(projectPath, prd);\n\n return prd.tasks[taskIndex];\n}\n\n/**\n * Check if a task's dependencies are all completed\n */\nexport function areDependenciesMet(projectPath: string, task: Task): boolean {\n if (!task.dependsOn || task.dependsOn.length === 0) {\n return true;\n }\n\n const prd = readPRD(projectPath);\n for (const depId of task.dependsOn) {\n const depTask = prd.tasks.find((t) => t.id === depId);\n if (!depTask || depTask.status !== 'completed') {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Get blocked dependencies for a task (dependencies not yet completed)\n */\nexport function getBlockedDependencies(projectPath: string, taskId: string): Task[] {\n const task = getTaskById(projectPath, taskId);\n if (!task || !task.dependsOn || task.dependsOn.length === 0) {\n return [];\n }\n\n const prd = readPRD(projectPath);\n return task.dependsOn\n .map((depId) => prd.tasks.find((t) => t.id === depId))\n .filter((t): t is Task => t !== undefined && t.status !== 'completed');\n}\n\n/**\n * Get next task to execute (highest priority \"ready\" task with met dependencies)\n */\nexport function getNextReadyTask(projectPath: string): Task | null {\n const readyTasks = getTasksByStatus(projectPath, 'ready');\n\n if (readyTasks.length === 0) {\n return null;\n }\n\n // Filter tasks with met dependencies\n const executableTasks = readyTasks.filter((task) => areDependenciesMet(projectPath, task));\n\n if (executableTasks.length === 0) {\n return null;\n }\n\n // Sort by priority (critical > high > medium > low)\n const priorityOrder: Record<string, number> = {\n critical: 0,\n high: 1,\n medium: 2,\n low: 3,\n };\n\n executableTasks.sort((a, b) => {\n const priorityDiff = (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2);\n if (priorityDiff !== 0) return priorityDiff;\n // If same priority, use creation date (older first)\n return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();\n });\n\n return executableTasks[0];\n}\n\n/**\n * Get count of tasks by status\n */\nexport function getTaskCounts(projectPath: string): Record<TaskStatus, number> {\n const tasks = getAllTasks(projectPath);\n const counts: Record<TaskStatus, number> = {\n draft: 0,\n ready: 0,\n in_progress: 0,\n completed: 0,\n failed: 0,\n };\n\n for (const task of tasks) {\n counts[task.status]++;\n }\n\n return counts;\n}\n","import { readFileSync, writeFileSync, appendFileSync } from 'fs';\nimport { join } from 'path';\n\nconst KANBAN_DIR = '.claude-kanban';\n\n/**\n * Get progress file path\n */\nfunction getProgressPath(projectPath: string): string {\n return join(projectPath, KANBAN_DIR, 'progress.txt');\n}\n\n/**\n * Read entire progress log\n */\nexport function readProgress(projectPath: string): string {\n const path = getProgressPath(projectPath);\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Append to progress log\n */\nexport function appendProgress(projectPath: string, entry: string): void {\n const path = getProgressPath(projectPath);\n appendFileSync(path, entry + '\\n');\n}\n\n/**\n * Add a task execution entry to progress log\n */\nexport function logTaskExecution(\n projectPath: string,\n options: {\n taskId: string;\n taskTitle: string;\n status: 'completed' | 'failed' | 'cancelled';\n duration: number;\n output?: string;\n error?: string;\n }\n): void {\n const now = new Date();\n const dateStr = now.toISOString().split('T')[0];\n const timeStr = now.toTimeString().split(' ')[0];\n\n const durationStr = formatDuration(options.duration);\n const statusEmoji = options.status === 'completed' ? '✓' : options.status === 'failed' ? '✗' : '○';\n\n let entry = `\\n## ${dateStr} ${timeStr}\\n\\n`;\n entry += `### Task: ${options.taskId} - ${options.taskTitle}\\n`;\n entry += `Status: ${statusEmoji} ${options.status.toUpperCase()}\\n`;\n entry += `Duration: ${durationStr}\\n`;\n\n if (options.error) {\n entry += `\\nError: ${options.error}\\n`;\n }\n\n entry += '\\n---\\n';\n\n appendProgress(projectPath, entry);\n}\n\n/**\n * Format duration in human-readable format\n */\nfunction formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}m ${seconds % 60}s`;\n }\n if (minutes > 0) {\n return `${minutes}m ${seconds % 60}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Get recent progress entries (last N lines)\n */\nexport function getRecentProgress(projectPath: string, lines = 100): string {\n const content = readProgress(projectPath);\n const allLines = content.split('\\n');\n\n if (allLines.length <= lines) {\n return content;\n }\n\n return allLines.slice(-lines).join('\\n');\n}\n\n/**\n * Clear progress log (keeps header)\n */\nexport function clearProgress(projectPath: string, projectName: string): void {\n const path = getProgressPath(projectPath);\n const date = new Date().toISOString().split('T')[0];\n const header = `# Claude Kanban Progress Log\\n\\nProject: ${projectName}\\nCleared: ${date}\\n\\n---\\n\\n`;\n writeFileSync(path, header);\n}\n","import { spawn, type ChildProcess } from 'child_process';\nimport { EventEmitter } from 'events';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { nanoid } from 'nanoid';\nimport type {\n Roadmap,\n RoadmapFeature,\n RoadmapPhase,\n RoadmapGenerateRequest,\n RoadmapPriority,\n TaskCategory,\n CompetitorInsight,\n Config,\n} from '../../types.js';\n\nconst ROADMAP_DIR = '.claude-kanban';\nconst ROADMAP_FILE = 'roadmap.json';\n\ninterface RoadmapSession {\n process: ChildProcess;\n startedAt: Date;\n output: string[];\n}\n\nexport class RoadmapService extends EventEmitter {\n private projectPath: string;\n private session: RoadmapSession | null = null;\n private config: Config | null = null;\n\n constructor(projectPath: string) {\n super();\n this.projectPath = projectPath;\n this.loadConfig();\n }\n\n private loadConfig(): void {\n const configPath = join(this.projectPath, ROADMAP_DIR, 'config.json');\n if (existsSync(configPath)) {\n this.config = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n }\n\n /**\n * Check if roadmap generation is in progress\n */\n isRunning(): boolean {\n return this.session !== null;\n }\n\n /**\n * Get existing roadmap if it exists\n */\n getRoadmap(): Roadmap | null {\n const roadmapPath = join(this.projectPath, ROADMAP_DIR, ROADMAP_FILE);\n if (!existsSync(roadmapPath)) {\n return null;\n }\n try {\n return JSON.parse(readFileSync(roadmapPath, 'utf-8'));\n } catch {\n return null;\n }\n }\n\n /**\n * Save roadmap to file\n */\n saveRoadmap(roadmap: Roadmap): void {\n const roadmapDir = join(this.projectPath, ROADMAP_DIR);\n if (!existsSync(roadmapDir)) {\n mkdirSync(roadmapDir, { recursive: true });\n }\n const roadmapPath = join(roadmapDir, ROADMAP_FILE);\n writeFileSync(roadmapPath, JSON.stringify(roadmap, null, 2));\n }\n\n /**\n * Generate a new roadmap using Claude\n */\n async generate(request: RoadmapGenerateRequest = {}): Promise<void> {\n if (this.session) {\n throw new Error('Roadmap generation already in progress');\n }\n\n this.emit('roadmap:started', {\n type: 'roadmap:started',\n timestamp: new Date().toISOString(),\n });\n\n try {\n // Phase 1: Analyze project\n this.emit('roadmap:progress', {\n type: 'roadmap:progress',\n phase: 'analyzing',\n message: 'Analyzing project structure...',\n });\n\n const projectInfo = await this.analyzeProject();\n\n // Phase 2: Optional competitor research\n let competitors: CompetitorInsight[] | undefined;\n if (request.enableCompetitorResearch) {\n this.emit('roadmap:progress', {\n type: 'roadmap:progress',\n phase: 'researching',\n message: 'Researching competitors...',\n });\n competitors = await this.researchCompetitors(projectInfo);\n }\n\n // Phase 3: Generate roadmap\n this.emit('roadmap:progress', {\n type: 'roadmap:progress',\n phase: 'generating',\n message: 'Generating feature roadmap...',\n });\n\n const roadmap = await this.generateRoadmap(projectInfo, competitors, request);\n\n // Save and emit completion\n this.saveRoadmap(roadmap);\n\n this.emit('roadmap:completed', {\n type: 'roadmap:completed',\n roadmap,\n });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n this.emit('roadmap:failed', {\n type: 'roadmap:failed',\n error: errorMessage,\n });\n throw error;\n } finally {\n this.session = null;\n }\n }\n\n /**\n * Cancel ongoing roadmap generation\n */\n cancel(): void {\n if (this.session) {\n this.session.process.kill('SIGTERM');\n this.session = null;\n }\n }\n\n /**\n * Analyze the project structure\n */\n private async analyzeProject(): Promise<ProjectInfo> {\n const projectName = basename(this.projectPath);\n let description = '';\n let stack: string[] = [];\n let existingFeatures: string[] = [];\n\n // Read package.json for Node projects\n const packageJsonPath = join(this.projectPath, 'package.json');\n if (existsSync(packageJsonPath)) {\n try {\n const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n description = pkg.description || '';\n\n // Detect tech stack from dependencies\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps.react) stack.push('React');\n if (deps.vue) stack.push('Vue');\n if (deps.angular) stack.push('Angular');\n if (deps.next) stack.push('Next.js');\n if (deps.express) stack.push('Express');\n if (deps.fastify) stack.push('Fastify');\n if (deps.typescript) stack.push('TypeScript');\n if (deps.tailwindcss) stack.push('Tailwind CSS');\n } catch {\n // Ignore parse errors\n }\n }\n\n // Read README for context\n const readmePaths = ['README.md', 'readme.md', 'README.txt', 'readme.txt'];\n for (const readmePath of readmePaths) {\n const fullPath = join(this.projectPath, readmePath);\n if (existsSync(fullPath)) {\n const readme = readFileSync(fullPath, 'utf-8');\n if (!description) {\n // Try to extract description from README\n const lines = readme.split('\\n').filter(l => l.trim() && !l.startsWith('#'));\n if (lines.length > 0) {\n description = lines[0].trim().slice(0, 500);\n }\n }\n break;\n }\n }\n\n // Read existing PRD tasks to understand what's already planned\n const prdPath = join(this.projectPath, ROADMAP_DIR, 'prd.json');\n if (existsSync(prdPath)) {\n try {\n const prd = JSON.parse(readFileSync(prdPath, 'utf-8'));\n existingFeatures = prd.tasks?.map((t: { title: string }) => t.title) || [];\n } catch {\n // Ignore parse errors\n }\n }\n\n return {\n name: projectName,\n description,\n stack,\n existingFeatures,\n };\n }\n\n /**\n * Research competitors using Claude with web search\n */\n private async researchCompetitors(projectInfo: ProjectInfo): Promise<CompetitorInsight[]> {\n const prompt = this.buildCompetitorResearchPrompt(projectInfo);\n const result = await this.runClaudeCommand(prompt);\n\n try {\n // Parse JSON from Claude's response\n const jsonMatch = result.match(/```json\\n([\\s\\S]*?)\\n```/);\n if (jsonMatch) {\n return JSON.parse(jsonMatch[1]);\n }\n // Try parsing the whole response as JSON\n return JSON.parse(result);\n } catch {\n console.error('[roadmap] Failed to parse competitor research:', result);\n return [];\n }\n }\n\n /**\n * Generate the roadmap using Claude\n */\n private async generateRoadmap(\n projectInfo: ProjectInfo,\n competitors: CompetitorInsight[] | undefined,\n request: RoadmapGenerateRequest\n ): Promise<Roadmap> {\n const prompt = this.buildRoadmapPrompt(projectInfo, competitors, request);\n const result = await this.runClaudeCommand(prompt);\n\n try {\n // Parse JSON from Claude's response\n const jsonMatch = result.match(/```json\\n([\\s\\S]*?)\\n```/);\n let roadmapData: RoadmapData;\n if (jsonMatch) {\n roadmapData = JSON.parse(jsonMatch[1]);\n } else {\n roadmapData = JSON.parse(result);\n }\n\n // Build the roadmap with proper IDs\n const roadmap: Roadmap = {\n id: `roadmap_${nanoid(8)}`,\n projectName: projectInfo.name,\n projectDescription: roadmapData.projectDescription || projectInfo.description,\n targetAudience: roadmapData.targetAudience || 'Developers',\n phases: roadmapData.phases.map((p, i) => ({\n id: `phase_${nanoid(8)}`,\n name: p.name,\n description: p.description,\n order: i,\n })),\n features: roadmapData.features.map((f) => ({\n id: `feature_${nanoid(8)}`,\n title: f.title,\n description: f.description,\n priority: f.priority as RoadmapPriority,\n category: (f.category || 'functional') as TaskCategory,\n effort: f.effort || 'medium',\n impact: f.impact || 'medium',\n phase: f.phase,\n rationale: f.rationale || '',\n steps: f.steps,\n addedToKanban: false,\n })),\n competitors,\n generatedAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n return roadmap;\n } catch (error) {\n console.error('[roadmap] Failed to parse roadmap:', result);\n throw new Error('Failed to parse roadmap from Claude response');\n }\n }\n\n /**\n * Build the competitor research prompt\n */\n private buildCompetitorResearchPrompt(projectInfo: ProjectInfo): string {\n return `You are a product research analyst. Research competitors for the following project:\n\nProject: ${projectInfo.name}\nDescription: ${projectInfo.description}\nTech Stack: ${projectInfo.stack.join(', ') || 'Unknown'}\n\nYour task:\n1. Use web search to find 3-5 competitors or similar projects\n2. Analyze their strengths and weaknesses\n3. Identify differentiating features\n\nReturn your findings as JSON in this format:\n\\`\\`\\`json\n[\n {\n \"name\": \"Competitor Name\",\n \"url\": \"https://example.com\",\n \"strengths\": [\"strength 1\", \"strength 2\"],\n \"weaknesses\": [\"weakness 1\", \"weakness 2\"],\n \"differentiators\": [\"feature 1\", \"feature 2\"]\n }\n]\n\\`\\`\\`\n\nOnly return the JSON, no other text.`;\n }\n\n /**\n * Build the roadmap generation prompt\n */\n private buildRoadmapPrompt(\n projectInfo: ProjectInfo,\n competitors: CompetitorInsight[] | undefined,\n request: RoadmapGenerateRequest\n ): string {\n let prompt = `You are a product strategist creating a feature roadmap for a software project.\n\n## Project Information\nName: ${projectInfo.name}\nDescription: ${projectInfo.description}\nTech Stack: ${projectInfo.stack.join(', ') || 'Unknown'}\n${projectInfo.existingFeatures.length > 0 ? `\\nExisting Features/Tasks:\\n${projectInfo.existingFeatures.map(f => `- ${f}`).join('\\n')}` : ''}\n`;\n\n if (competitors && competitors.length > 0) {\n prompt += `\n## Competitor Analysis\n${competitors.map(c => `\n### ${c.name}\n- Strengths: ${c.strengths.join(', ')}\n- Weaknesses: ${c.weaknesses.join(', ')}\n- Key Features: ${c.differentiators.join(', ')}\n`).join('\\n')}\n`;\n }\n\n if (request.focusAreas && request.focusAreas.length > 0) {\n prompt += `\n## Focus Areas\nThe user wants to focus on: ${request.focusAreas.join(', ')}\n`;\n }\n\n if (request.customPrompt) {\n prompt += `\n## Additional Context\n${request.customPrompt}\n`;\n }\n\n prompt += `\n## Your Task\nCreate a comprehensive product roadmap with features organized into phases.\n\nUse the MoSCoW prioritization framework:\n- must: Critical features that must be implemented\n- should: Important features that should be implemented\n- could: Nice-to-have features that could be implemented\n- wont: Features that won't be implemented in the near term\n\nFor each feature, estimate:\n- effort: low, medium, or high\n- impact: low, medium, or high\n\nCategories should be one of: functional, ui, bug, enhancement, testing, refactor\n\nReturn your roadmap as JSON:\n\\`\\`\\`json\n{\n \"projectDescription\": \"Brief description of the project\",\n \"targetAudience\": \"Who this project is for\",\n \"phases\": [\n {\n \"name\": \"Phase 1: MVP\",\n \"description\": \"Core features for minimum viable product\"\n },\n {\n \"name\": \"Phase 2: Enhancement\",\n \"description\": \"Features to improve user experience\"\n }\n ],\n \"features\": [\n {\n \"title\": \"Feature title\",\n \"description\": \"What this feature does\",\n \"priority\": \"must\",\n \"category\": \"functional\",\n \"effort\": \"medium\",\n \"impact\": \"high\",\n \"phase\": \"Phase 1: MVP\",\n \"rationale\": \"Why this feature is important\",\n \"steps\": [\"Step 1\", \"Step 2\"]\n }\n ]\n}\n\\`\\`\\`\n\nGenerate 10-20 strategic features across 3-4 phases. Be specific and actionable.\nDon't duplicate features that already exist in the project.\nOnly return the JSON, no other text.`;\n\n return prompt;\n }\n\n /**\n * Run a Claude command and return the output\n */\n private runClaudeCommand(prompt: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const command = this.config?.agent.command || 'claude';\n const permissionMode = this.config?.agent.permissionMode || 'auto';\n const model = this.config?.agent.model;\n\n // Write prompt to file\n const promptPath = join(this.projectPath, ROADMAP_DIR, 'prompt-roadmap.txt');\n writeFileSync(promptPath, prompt);\n\n const args = [\n '--permission-mode', permissionMode,\n '-p',\n '--verbose',\n '--output-format', 'text',\n `@${promptPath}`,\n ];\n\n if (model) {\n args.unshift('--model', model);\n }\n\n console.log(`[roadmap] Command: ${command} ${args.join(' ')}`);\n\n const childProcess = spawn(command, args, {\n cwd: this.projectPath,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: {\n ...process.env,\n FORCE_COLOR: '0',\n },\n });\n\n this.session = {\n process: childProcess,\n startedAt: new Date(),\n output: [],\n };\n\n let stdout = '';\n let stderr = '';\n\n childProcess.stdout?.on('data', (data: Buffer) => {\n const chunk = data.toString();\n stdout += chunk;\n this.session?.output.push(chunk);\n });\n\n childProcess.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n childProcess.on('close', (code) => {\n this.session = null;\n\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(new Error(`Claude command failed with code ${code}: ${stderr}`));\n }\n });\n\n childProcess.on('error', (error) => {\n this.session = null;\n reject(error);\n });\n });\n }\n\n /**\n * Mark a feature as added to kanban\n */\n markFeatureAdded(featureId: string): Roadmap | null {\n const roadmap = this.getRoadmap();\n if (!roadmap) return null;\n\n const feature = roadmap.features.find(f => f.id === featureId);\n if (feature) {\n feature.addedToKanban = true;\n roadmap.updatedAt = new Date().toISOString();\n this.saveRoadmap(roadmap);\n }\n\n return roadmap;\n }\n\n /**\n * Delete a feature from the roadmap\n */\n deleteFeature(featureId: string): Roadmap | null {\n const roadmap = this.getRoadmap();\n if (!roadmap) return null;\n\n const index = roadmap.features.findIndex(f => f.id === featureId);\n if (index !== -1) {\n roadmap.features.splice(index, 1);\n roadmap.updatedAt = new Date().toISOString();\n this.saveRoadmap(roadmap);\n }\n\n return roadmap;\n }\n}\n\n// Internal types\ninterface ProjectInfo {\n name: string;\n description: string;\n stack: string[];\n existingFeatures: string[];\n}\n\ninterface RoadmapData {\n projectDescription: string;\n targetAudience: string;\n phases: Array<{\n name: string;\n description: string;\n }>;\n features: Array<{\n title: string;\n description: string;\n priority: string;\n category?: string;\n effort?: 'low' | 'medium' | 'high';\n impact?: 'low' | 'medium' | 'high';\n phase: string;\n rationale?: string;\n steps?: string[];\n }>;\n}\n","import type { TaskTemplate } from '../../types.js';\n\n/**\n * Pre-built task templates\n */\nexport const taskTemplates: TaskTemplate[] = [\n {\n id: 'auth-login',\n name: 'Authentication - Login',\n icon: '🔐',\n description: 'User login functionality with email/password',\n category: 'functional',\n priority: 'high',\n titleTemplate: 'Add user login functionality',\n descriptionTemplate: `Implement user login with email and password authentication.\n\nRequirements:\n- Login form with email and password fields\n- Form validation with error messages\n- Submit credentials to auth API\n- Handle success (redirect) and failure (show error)\n- Store auth token/session`,\n stepsTemplate: [\n 'Navigate to /login',\n 'Verify login form displays with email and password fields',\n 'Enter invalid credentials and verify error message',\n 'Enter valid credentials and submit',\n 'Verify redirect to dashboard/home',\n 'Verify auth state persists on page reload',\n ],\n },\n {\n id: 'auth-signup',\n name: 'Authentication - Signup',\n icon: '🔐',\n description: 'User registration with validation',\n category: 'functional',\n priority: 'high',\n titleTemplate: 'Add user signup/registration',\n descriptionTemplate: `Implement user registration with form validation.\n\nRequirements:\n- Signup form with name, email, password fields\n- Password confirmation field\n- Client-side validation\n- Submit to registration API\n- Handle success and error states`,\n stepsTemplate: [\n 'Navigate to /signup',\n 'Verify signup form displays all required fields',\n 'Submit with invalid data and verify validation errors',\n 'Submit with valid data',\n 'Verify success message or redirect',\n 'Verify new user can log in',\n ],\n },\n {\n id: 'auth-logout',\n name: 'Authentication - Logout',\n icon: '🔐',\n description: 'User logout functionality',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add user logout functionality',\n descriptionTemplate: `Implement logout functionality.\n\nRequirements:\n- Logout button/link in navigation\n- Clear auth token/session on logout\n- Redirect to login or home page\n- Protect routes after logout`,\n stepsTemplate: [\n 'Log in as a user',\n 'Click logout button',\n 'Verify redirect to login/home page',\n 'Verify auth state is cleared',\n 'Verify protected routes redirect to login',\n ],\n },\n {\n id: 'crud-create',\n name: 'CRUD - Create',\n icon: '📝',\n description: 'Create new entity form',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add create [Entity] form',\n descriptionTemplate: `Implement form to create a new [Entity].\n\nRequirements:\n- Form with all required fields\n- Client-side validation\n- Submit to create API endpoint\n- Handle loading, success, and error states\n- Redirect or show success message after creation`,\n stepsTemplate: [\n 'Navigate to create form',\n 'Verify all form fields display correctly',\n 'Submit empty form and verify validation',\n 'Fill in valid data and submit',\n 'Verify entity is created',\n 'Verify redirect or success message',\n ],\n },\n {\n id: 'crud-read',\n name: 'CRUD - Read/List',\n icon: '📝',\n description: 'List and view entities',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add [Entity] list view',\n descriptionTemplate: `Implement list view for [Entity] items.\n\nRequirements:\n- Fetch and display list of entities\n- Show loading state\n- Handle empty state\n- Display relevant fields for each item\n- Link to detail view`,\n stepsTemplate: [\n 'Navigate to list page',\n 'Verify loading state displays',\n 'Verify items render correctly',\n 'Verify empty state when no items',\n 'Click item and verify navigation to detail',\n ],\n },\n {\n id: 'crud-update',\n name: 'CRUD - Update',\n icon: '📝',\n description: 'Edit existing entity',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add edit [Entity] form',\n descriptionTemplate: `Implement form to edit an existing [Entity].\n\nRequirements:\n- Pre-populate form with existing data\n- Allow modification of fields\n- Submit changes to update API\n- Handle validation and errors\n- Show success feedback`,\n stepsTemplate: [\n 'Navigate to edit form for existing item',\n 'Verify form pre-populates with current data',\n 'Modify fields',\n 'Submit form',\n 'Verify changes are saved',\n 'Verify updated data displays correctly',\n ],\n },\n {\n id: 'crud-delete',\n name: 'CRUD - Delete',\n icon: '📝',\n description: 'Delete entity with confirmation',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add delete [Entity] functionality',\n descriptionTemplate: `Implement delete functionality for [Entity].\n\nRequirements:\n- Delete button on item or list\n- Confirmation dialog before delete\n- Call delete API endpoint\n- Remove item from UI on success\n- Handle errors gracefully`,\n stepsTemplate: [\n 'Navigate to item with delete option',\n 'Click delete button',\n 'Verify confirmation dialog appears',\n 'Confirm deletion',\n 'Verify item is removed from list',\n 'Verify item no longer accessible',\n ],\n },\n {\n id: 'api-endpoint',\n name: 'API Endpoint',\n icon: '🌐',\n description: 'REST API endpoint with validation',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add [METHOD] /api/[resource] endpoint',\n descriptionTemplate: `Implement REST API endpoint.\n\nRequirements:\n- Define route handler\n- Validate request body/params\n- Implement business logic\n- Return appropriate response codes\n- Add error handling`,\n stepsTemplate: [\n 'Endpoint responds to correct HTTP method',\n 'Invalid requests return 400 with error details',\n 'Valid requests process correctly',\n 'Returns appropriate status codes',\n 'Handles edge cases gracefully',\n ],\n },\n {\n id: 'ui-component',\n name: 'UI Component',\n icon: '🎨',\n description: 'Reusable UI component',\n category: 'ui',\n priority: 'medium',\n titleTemplate: 'Create [ComponentName] component',\n descriptionTemplate: `Create a reusable UI component.\n\nRequirements:\n- Component accepts appropriate props\n- Handles different states (loading, error, empty)\n- Follows design system/styling conventions\n- Accessible (keyboard, screen reader)\n- Includes any needed interactivity`,\n stepsTemplate: [\n 'Component renders without errors',\n 'Props affect rendering correctly',\n 'Different states display appropriately',\n 'Component is accessible',\n 'Interactive elements work correctly',\n ],\n },\n {\n id: 'form-validation',\n name: 'Form with Validation',\n icon: '📄',\n description: 'Form with client-side validation',\n category: 'ui',\n priority: 'medium',\n titleTemplate: 'Create [FormName] form with validation',\n descriptionTemplate: `Create a form with comprehensive validation.\n\nRequirements:\n- All necessary input fields\n- Real-time validation feedback\n- Clear error messages\n- Submit button state management\n- Form submission handling`,\n stepsTemplate: [\n 'Form displays all required fields',\n 'Invalid input shows error message',\n 'Valid input clears error',\n 'Submit disabled when form invalid',\n 'Form submits with valid data',\n ],\n },\n {\n id: 'test-unit',\n name: 'Unit Tests',\n icon: '🧪',\n description: 'Unit test suite for module',\n category: 'testing',\n priority: 'medium',\n titleTemplate: 'Add unit tests for [module/component]',\n descriptionTemplate: `Write comprehensive unit tests.\n\nRequirements:\n- Test all public functions/methods\n- Cover edge cases\n- Test error handling\n- Achieve good coverage\n- Tests should be fast and isolated`,\n stepsTemplate: [\n 'All tests pass',\n 'Core functionality covered',\n 'Edge cases tested',\n 'Error cases handled',\n 'No flaky tests',\n ],\n },\n {\n id: 'test-e2e',\n name: 'E2E Tests',\n icon: '🧪',\n description: 'End-to-end test for user flow',\n category: 'testing',\n priority: 'medium',\n titleTemplate: 'Add E2E tests for [feature/flow]',\n descriptionTemplate: `Write end-to-end tests for user flow.\n\nRequirements:\n- Test complete user journey\n- Use realistic test data\n- Verify UI and data changes\n- Handle async operations\n- Clean up test data`,\n stepsTemplate: [\n 'Tests run successfully',\n 'User flow completes correctly',\n 'UI updates verified',\n 'Data persisted correctly',\n 'Tests are reliable (not flaky)',\n ],\n },\n];\n\n/**\n * Get all templates\n */\nexport function getAllTemplates(): TaskTemplate[] {\n return taskTemplates;\n}\n\n/**\n * Get template by ID\n */\nexport function getTemplateById(id: string): TaskTemplate | undefined {\n return taskTemplates.find((t) => t.id === id);\n}\n\n/**\n * Get templates by category\n */\nexport function getTemplatesByCategory(category: string): TaskTemplate[] {\n return taskTemplates.filter((t) => t.category === category);\n}\n","import { spawn } from 'child_process';\nimport { getConfig } from './project.js';\nimport type { CreateTaskRequest } from '../../types.js';\n\n/**\n * Generate task details from a natural language description using Claude\n */\nexport async function generateTaskFromPrompt(\n projectPath: string,\n userPrompt: string\n): Promise<CreateTaskRequest> {\n const config = getConfig(projectPath);\n\n const systemPrompt = `You are a task generator for a Kanban board used in software development.\nGiven a user's description of what they want to build, generate a structured task.\n\nRespond with ONLY valid JSON in this exact format:\n{\n \"title\": \"Short, action-oriented title (max 80 chars)\",\n \"description\": \"Detailed description of what needs to be implemented\",\n \"category\": \"functional|ui|bug|enhancement|testing|refactor\",\n \"priority\": \"low|medium|high|critical\",\n \"steps\": [\"Step 1 to verify\", \"Step 2 to verify\", \"...\"]\n}\n\nGuidelines:\n- Title should be concise and start with a verb (Add, Create, Implement, Fix, etc.)\n- Description should be comprehensive but focused\n- Steps should be verification steps to confirm the feature works\n- Include 3-7 verification steps\n- Choose appropriate category based on the work type\n- Priority should reflect typical importance (most features are medium)\n\nRespond with ONLY the JSON, no other text.`;\n\n const fullPrompt = `${systemPrompt}\n\nUser request: ${userPrompt}`;\n\n return new Promise((resolve, reject) => {\n const args = [\n '--permission-mode', 'ask',\n '-p',\n fullPrompt,\n ];\n\n if (config.agent.model) {\n args.unshift('--model', config.agent.model);\n }\n\n let output = '';\n let errorOutput = '';\n\n const proc = spawn(config.agent.command, args, {\n cwd: projectPath,\n shell: true,\n env: { ...process.env },\n });\n\n proc.stdout?.on('data', (data: Buffer) => {\n output += data.toString();\n });\n\n proc.stderr?.on('data', (data: Buffer) => {\n errorOutput += data.toString();\n });\n\n proc.on('close', (code) => {\n if (code !== 0) {\n reject(new Error(`AI generation failed: ${errorOutput || 'Unknown error'}`));\n return;\n }\n\n try {\n // Extract JSON from output (Claude might add some text around it)\n const jsonMatch = output.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) {\n throw new Error('No JSON found in response');\n }\n\n const parsed = JSON.parse(jsonMatch[0]);\n\n // Validate required fields\n if (!parsed.title || !parsed.description) {\n throw new Error('Missing required fields in response');\n }\n\n const task: CreateTaskRequest = {\n title: String(parsed.title).slice(0, 200),\n description: String(parsed.description),\n category: validateCategory(parsed.category),\n priority: validatePriority(parsed.priority),\n steps: Array.isArray(parsed.steps) ? parsed.steps.map(String) : [],\n status: 'draft',\n };\n\n resolve(task);\n } catch (parseError) {\n reject(new Error(`Failed to parse AI response: ${parseError}`));\n }\n });\n\n proc.on('error', (error) => {\n reject(new Error(`Failed to spawn AI process: ${error.message}`));\n });\n\n // Timeout after 60 seconds\n setTimeout(() => {\n proc.kill();\n reject(new Error('AI generation timed out'));\n }, 60000);\n });\n}\n\nfunction validateCategory(category: unknown): 'functional' | 'ui' | 'bug' | 'enhancement' | 'testing' | 'refactor' {\n const valid = ['functional', 'ui', 'bug', 'enhancement', 'testing', 'refactor'];\n if (typeof category === 'string' && valid.includes(category)) {\n return category as 'functional' | 'ui' | 'bug' | 'enhancement' | 'testing' | 'refactor';\n }\n return 'functional';\n}\n\nfunction validatePriority(priority: unknown): 'low' | 'medium' | 'high' | 'critical' {\n const valid = ['low', 'medium', 'high', 'critical'];\n if (typeof priority === 'string' && valid.includes(priority)) {\n return priority as 'low' | 'medium' | 'high' | 'critical';\n }\n return 'medium';\n}\n","import { createServer } from 'net';\n\n/**\n * Check if a port is available\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n\n server.once('error', () => {\n resolve(false);\n });\n\n server.once('listening', () => {\n server.close();\n resolve(true);\n });\n\n server.listen(port, '127.0.0.1');\n });\n}\n\n/**\n * Find an available port starting from the given port\n */\nexport async function findAvailablePort(startPort: number, maxAttempts = 10): Promise<number> {\n for (let i = 0; i < maxAttempts; i++) {\n const port = startPort + i;\n if (await isPortAvailable(port)) {\n return port;\n }\n }\n throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`);\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoeP,YAAY,CAAC;AAAA;AAAA;AAAA;AAIf;AAEA,SAAS,cAAsB;AAC7B,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["existsSync","join","join","existsSync","join","writeFileSync","mkdirSync","existsSync","appendFileSync","readFileSync","readFileSync","writeFileSync","join","KANBAN_DIR","readFileSync","writeFileSync","join","KANBAN_DIR","KANBAN_DIR","join","existsSync","mkdirSync","writeFileSync","appendFileSync","readFileSync","spawn","EventEmitter","existsSync","readFileSync","writeFileSync","mkdirSync","join","basename","nanoid","spawn","spawn","__dirname","join","existsSync","createServer","existsSync","join"]}
|
|
1
|
+
{"version":3,"sources":["../../src/bin/cli.ts","../../src/server/index.ts","../../src/server/services/executor.ts","../../src/server/services/project.ts","../../src/server/services/prd.ts","../../src/server/services/progress.ts","../../src/server/services/roadmap.ts","../../src/server/services/templates.ts","../../src/server/services/ai.ts","../../src/server/utils/port.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport open from 'open';\nimport { existsSync } from 'fs';\nimport { execSync } from 'child_process';\nimport { createInterface } from 'readline';\nimport { join } from 'path';\nimport { createServer } from '../server/index.js';\nimport { initializeProject, isProjectInitialized } from '../server/services/project.js';\nimport { findAvailablePort } from '../server/utils/port.js';\n\n/**\n * Check if directory is a git repository\n */\nfunction isGitRepo(dir: string): boolean {\n return existsSync(join(dir, '.git'));\n}\n\n/**\n * Check if git repo has a remote configured\n */\nfunction hasGitRemote(dir: string): boolean {\n try {\n const result = execSync('git remote -v', { cwd: dir, stdio: 'pipe' }).toString();\n return result.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Detect the type of git remote (github, gitlab, bitbucket, other)\n */\nfunction detectRemoteType(dir: string): 'github' | 'gitlab' | 'bitbucket' | 'other' | null {\n try {\n const result = execSync('git remote get-url origin', { cwd: dir, stdio: 'pipe' }).toString().toLowerCase();\n if (result.includes('github.com')) return 'github';\n if (result.includes('gitlab.com') || result.includes('gitlab')) return 'gitlab';\n if (result.includes('bitbucket.org') || result.includes('bitbucket')) return 'bitbucket';\n if (result.trim()) return 'other';\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Initialize git in directory\n */\nfunction initializeGit(dir: string): void {\n execSync('git init', { cwd: dir, stdio: 'pipe' });\n // Create initial commit if there are files\n try {\n execSync('git add -A', { cwd: dir, stdio: 'pipe' });\n execSync('git commit -m \"Initial commit\"', { cwd: dir, stdio: 'pipe' });\n } catch {\n // Ignore if nothing to commit\n }\n}\n\n/**\n * Prompt user for yes/no confirmation\n */\nasync function promptYesNo(question: string): Promise<boolean> {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.toLowerCase().startsWith('y'));\n });\n });\n}\n\nconst VERSION = '0.1.0';\n\nconst banner = `\n${chalk.cyan('╔═══════════════════════════════════════╗')}\n${chalk.cyan('║')} ${chalk.bold.white('Claude Kanban')} ${chalk.gray(`v${VERSION}`)} ${chalk.cyan('║')}\n${chalk.cyan('║')} ${chalk.gray('Visual AI-powered development')} ${chalk.cyan('║')}\n${chalk.cyan('╚═══════════════════════════════════════╝')}\n`;\n\nasync function main() {\n const program = new Command();\n\n program\n .name('claude-kanban')\n .description('Visual Kanban board for AI-powered development with Claude')\n .version(VERSION)\n .option('-p, --port <number>', 'Port to run server on', '4242')\n .option('-n, --no-open', 'Do not auto-open browser')\n .option('--init', 'Re-initialize project files')\n .option('--reset', 'Reset all tasks (keeps config)')\n .action(async (options) => {\n console.log(banner);\n\n const cwd = process.cwd();\n\n // Check if directory is a git repository\n if (!isGitRepo(cwd)) {\n console.log(chalk.yellow('\\n⚠ This directory is not a git repository.'));\n console.log(chalk.gray('Git is required for task isolation and parallel execution.\\n'));\n\n const shouldInit = await promptYesNo(chalk.white('Would you like to initialize git now? (y/n): '));\n\n if (shouldInit) {\n try {\n console.log(chalk.gray('Initializing git repository...'));\n initializeGit(cwd);\n console.log(chalk.green('✓ Git repository initialized'));\n } catch (error) {\n console.error(chalk.red('Failed to initialize git:'), error);\n process.exit(1);\n }\n } else {\n console.log(chalk.red('\\nGit is required to run Claude Kanban.'));\n console.log(chalk.gray('Please run \"git init\" manually and try again.'));\n process.exit(1);\n }\n } else {\n // Check for remote and show info\n const remoteType = detectRemoteType(cwd);\n if (remoteType) {\n console.log(chalk.gray(`Git remote detected: ${remoteType}`));\n } else if (!hasGitRemote(cwd)) {\n console.log(chalk.gray('Git repository (local only, no remote)'));\n }\n }\n\n // Check if project needs initialization\n const initialized = await isProjectInitialized(cwd);\n\n if (!initialized || options.init) {\n console.log(chalk.yellow('Initializing project...'));\n try {\n await initializeProject(cwd, options.reset);\n console.log(chalk.green('✓ Project initialized successfully'));\n } catch (error) {\n console.error(chalk.red('Failed to initialize project:'), error);\n process.exit(1);\n }\n } else {\n console.log(chalk.gray('Found existing configuration'));\n }\n\n // Find available port\n let port = parseInt(options.port, 10);\n try {\n port = await findAvailablePort(port);\n } catch {\n console.error(chalk.red(`Port ${port} is in use and no alternatives found`));\n process.exit(1);\n }\n\n // Start server\n console.log(chalk.gray(`Starting server on port ${port}...`));\n\n try {\n const server = await createServer(cwd, port);\n\n const url = `http://localhost:${port}`;\n console.log(chalk.green(`\\n✓ Server running at ${chalk.bold(url)}\\n`));\n\n // Open browser\n if (options.open !== false) {\n console.log(chalk.gray('Opening browser...'));\n await open(url);\n }\n\n // Handle graceful shutdown\n let isShuttingDown = false;\n const shutdown = () => {\n if (isShuttingDown) {\n console.log(chalk.red('\\nForce exiting...'));\n process.exit(1);\n }\n isShuttingDown = true;\n console.log(chalk.yellow('\\nShutting down...'));\n\n // Call cleanup function if available\n if ((server as any).cleanup) {\n (server as any).cleanup();\n }\n\n // Force exit after 3 seconds if server doesn't close\n const forceExitTimeout = setTimeout(() => {\n console.log(chalk.red('Forcing exit after timeout'));\n process.exit(0);\n }, 3000);\n\n server.close(() => {\n clearTimeout(forceExitTimeout);\n console.log(chalk.green('✓ Server stopped'));\n process.exit(0);\n });\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n // Keep process running\n console.log(chalk.gray('Press Ctrl+C to stop\\n'));\n\n } catch (error) {\n console.error(chalk.red('Failed to start server:'), error);\n process.exit(1);\n }\n });\n\n await program.parseAsync();\n}\n\nmain().catch((error) => {\n console.error(chalk.red('Fatal error:'), error);\n process.exit(1);\n});\n","import express from 'express';\nimport { createServer as createHttpServer, Server } from 'http';\nimport { Server as SocketIOServer } from 'socket.io';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { existsSync } from 'fs';\n\nimport { TaskExecutor } from './services/executor.js';\nimport { RoadmapService } from './services/roadmap.js';\nimport * as prdService from './services/prd.js';\nimport * as progressService from './services/progress.js';\nimport * as projectService from './services/project.js';\nimport * as templateService from './services/templates.js';\nimport { generateTaskFromPrompt } from './services/ai.js';\nimport type { CreateTaskRequest, UpdateTaskRequest, RoadmapGenerateRequest } from '../types.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport async function createServer(projectPath: string, port: number): Promise<Server> {\n const app = express();\n const httpServer = createHttpServer(app);\n const io = new SocketIOServer(httpServer, {\n cors: { origin: '*' },\n });\n\n // Middleware\n app.use(express.json());\n\n // Initialize executor\n const executor = new TaskExecutor(projectPath);\n\n // Initialize roadmap service\n const roadmapService = new RoadmapService(projectPath);\n\n // Forward executor events to WebSocket\n executor.on('task:started', (data) => io.emit('task:started', data));\n executor.on('task:output', (data) => io.emit('task:output', data));\n executor.on('task:completed', (data) => io.emit('task:completed', data));\n executor.on('task:failed', (data) => io.emit('task:failed', data));\n executor.on('task:cancelled', (data) => io.emit('task:cancelled', data));\n executor.on('afk:status', (data) => io.emit('afk:status', data));\n\n // Planning events\n executor.on('planning:started', (data) => io.emit('planning:started', data));\n executor.on('planning:output', (data) => io.emit('planning:output', data));\n executor.on('planning:completed', (data) => io.emit('planning:completed', data));\n executor.on('planning:failed', (data) => io.emit('planning:failed', data));\n executor.on('planning:cancelled', (data) => io.emit('planning:cancelled', data));\n\n // Roadmap events\n roadmapService.on('roadmap:started', (data) => io.emit('roadmap:started', data));\n roadmapService.on('roadmap:progress', (data) => io.emit('roadmap:progress', data));\n roadmapService.on('roadmap:completed', (data) => io.emit('roadmap:completed', data));\n roadmapService.on('roadmap:failed', (data) => io.emit('roadmap:failed', data));\n\n // ===== API Routes =====\n\n // Get all tasks\n app.get('/api/tasks', (_req, res) => {\n try {\n const tasks = prdService.getAllTasks(projectPath);\n res.json({ tasks });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Create task\n app.post('/api/tasks', (req, res) => {\n try {\n const request = req.body as CreateTaskRequest;\n if (!request.title || !request.description) {\n res.status(400).json({ error: 'Title and description are required' });\n return;\n }\n const task = prdService.createTask(projectPath, request);\n io.emit('task:created', task);\n res.status(201).json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Generate task with AI\n app.post('/api/tasks/generate', async (req, res) => {\n try {\n const { prompt } = req.body as { prompt: string };\n if (!prompt) {\n res.status(400).json({ error: 'Prompt is required' });\n return;\n }\n const taskRequest = await generateTaskFromPrompt(projectPath, prompt);\n res.json({ task: taskRequest });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get single task\n app.get('/api/tasks/:id', (req, res) => {\n try {\n const task = prdService.getTaskById(projectPath, req.params.id);\n if (!task) {\n res.status(404).json({ error: 'Task not found' });\n return;\n }\n res.json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Update task\n app.put('/api/tasks/:id', (req, res) => {\n try {\n const updates = req.body as UpdateTaskRequest;\n const task = prdService.updateTask(projectPath, req.params.id, updates);\n if (!task) {\n res.status(404).json({ error: 'Task not found' });\n return;\n }\n io.emit('task:updated', task);\n res.json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Delete task\n app.delete('/api/tasks/:id', (req, res) => {\n try {\n const deleted = prdService.deleteTask(projectPath, req.params.id);\n if (!deleted) {\n res.status(404).json({ error: 'Task not found' });\n return;\n }\n io.emit('task:deleted', { id: req.params.id });\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Run task\n app.post('/api/tasks/:id/run', async (req, res) => {\n try {\n await executor.runTask(req.params.id);\n res.json({ success: true });\n } catch (error) {\n res.status(400).json({ error: String(error) });\n }\n });\n\n // Cancel task (only one can run at a time)\n app.post('/api/tasks/:id/cancel', (req, res) => {\n try {\n // Verify the requested task is actually the one running\n const runningId = executor.getRunningTaskId();\n if (runningId !== req.params.id) {\n res.status(404).json({ error: 'Task not running' });\n return;\n }\n const cancelled = executor.cancelTask();\n if (!cancelled) {\n res.status(404).json({ error: 'Task not running' });\n return;\n }\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Retry task (move back to ready and optionally run)\n app.post('/api/tasks/:id/retry', async (req, res) => {\n try {\n const task = prdService.updateTask(projectPath, req.params.id, {\n status: 'ready',\n passes: false,\n });\n if (!task) {\n res.status(404).json({ error: 'Task not found' });\n return;\n }\n io.emit('task:updated', task);\n\n // Optionally auto-run\n if (req.body.autoRun) {\n await executor.runTask(req.params.id);\n }\n\n res.json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get task output (for running tasks)\n // Get task logs from file (persisted)\n app.get('/api/tasks/:id/logs', (req, res) => {\n try {\n const logs = executor.getTaskLog(req.params.id);\n if (logs === null) {\n res.json({ logs: '' }); // No logs yet\n return;\n }\n res.json({ logs });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n app.get('/api/tasks/:id/output', (req, res) => {\n try {\n const output = executor.getTaskOutput(req.params.id);\n if (!output) {\n res.status(404).json({ error: 'Task not running or not found' });\n return;\n }\n res.json({ output });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get progress log\n app.get('/api/progress', (req, res) => {\n try {\n const lines = parseInt(String(req.query.lines)) || 100;\n const content = progressService.getRecentProgress(projectPath, lines);\n res.json({ content });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get config\n app.get('/api/config', (_req, res) => {\n try {\n const config = projectService.getConfig(projectPath);\n res.json({ config });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Update config\n app.put('/api/config', (req, res) => {\n try {\n const currentConfig = projectService.getConfig(projectPath);\n const updatedConfig = { ...currentConfig, ...req.body };\n projectService.saveConfig(projectPath, updatedConfig);\n res.json({ config: updatedConfig });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get templates\n app.get('/api/templates', (_req, res) => {\n try {\n const templates = templateService.getAllTemplates();\n res.json({ templates });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get template by ID\n app.get('/api/templates/:id', (req, res) => {\n try {\n const template = templateService.getTemplateById(req.params.id);\n if (!template) {\n res.status(404).json({ error: 'Template not found' });\n return;\n }\n res.json({ template });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // AFK Mode endpoints (sequential only - one task at a time)\n app.post('/api/afk/start', (req, res) => {\n try {\n const { maxIterations } = req.body as { maxIterations?: number };\n executor.startAFKMode(maxIterations || 10);\n res.json({ success: true, status: executor.getAFKStatus() });\n } catch (error) {\n res.status(400).json({ error: String(error) });\n }\n });\n\n app.post('/api/afk/stop', (_req, res) => {\n try {\n executor.stopAFKMode();\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n app.get('/api/afk/status', (_req, res) => {\n try {\n const status = executor.getAFKStatus();\n res.json({ status });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // ===== Planning Endpoints =====\n\n // Start planning session\n app.post('/api/plan', async (req, res) => {\n try {\n const { goal } = req.body as { goal: string };\n if (!goal) {\n res.status(400).json({ error: 'Goal is required' });\n return;\n }\n await executor.runPlanningSession(goal);\n res.json({ success: true });\n } catch (error) {\n res.status(400).json({ error: String(error) });\n }\n });\n\n // Cancel planning session\n app.post('/api/plan/cancel', (_req, res) => {\n try {\n const cancelled = executor.cancelPlanning();\n if (!cancelled) {\n res.status(404).json({ error: 'No planning session running' });\n return;\n }\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get planning status\n app.get('/api/plan/status', (_req, res) => {\n try {\n const planning = executor.isPlanning();\n const goal = executor.getPlanningGoal();\n res.json({ planning, goal: goal || null });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get running task info (only one can run at a time)\n app.get('/api/running', (_req, res) => {\n try {\n const taskId = executor.getRunningTaskId();\n res.json({ running: taskId ? [taskId] : [], count: taskId ? 1 : 0 });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get task counts\n app.get('/api/stats', (_req, res) => {\n try {\n const counts = prdService.getTaskCounts(projectPath);\n const running = executor.isRunning() ? 1 : 0;\n const afk = executor.getAFKStatus();\n const planning = executor.isPlanning();\n res.json({ counts, running, afk, planning });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // ===== Roadmap Endpoints =====\n\n // Get existing roadmap\n app.get('/api/roadmap', (_req, res) => {\n try {\n const roadmap = roadmapService.getRoadmap();\n res.json({ roadmap });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Generate new roadmap\n app.post('/api/roadmap/generate', async (req, res) => {\n try {\n const request = req.body as RoadmapGenerateRequest;\n if (roadmapService.isRunning()) {\n res.status(400).json({ error: 'Roadmap generation already in progress' });\n return;\n }\n // Don't await - let it run in background and send events\n roadmapService.generate(request).catch(console.error);\n res.json({ success: true, message: 'Roadmap generation started' });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Cancel roadmap generation\n app.post('/api/roadmap/cancel', (_req, res) => {\n try {\n if (!roadmapService.isRunning()) {\n res.status(400).json({ error: 'No roadmap generation in progress' });\n return;\n }\n roadmapService.cancel();\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Add roadmap feature to kanban\n app.post('/api/roadmap/features/:id/add-to-kanban', (req, res) => {\n try {\n const roadmap = roadmapService.getRoadmap();\n if (!roadmap) {\n res.status(404).json({ error: 'No roadmap found' });\n return;\n }\n\n const feature = roadmap.features.find(f => f.id === req.params.id);\n if (!feature) {\n res.status(404).json({ error: 'Feature not found' });\n return;\n }\n\n if (feature.addedToKanban) {\n res.status(400).json({ error: 'Feature already added to kanban' });\n return;\n }\n\n // Convert MoSCoW priority to task priority\n const priorityMap: Record<string, 'critical' | 'high' | 'medium' | 'low'> = {\n must: 'critical',\n should: 'high',\n could: 'medium',\n wont: 'low',\n };\n\n // Create task from feature\n const task = prdService.createTask(projectPath, {\n title: feature.title,\n description: feature.description,\n category: feature.category,\n priority: priorityMap[feature.priority] || 'medium',\n steps: feature.steps || [],\n status: 'draft',\n });\n\n // Mark feature as added\n roadmapService.markFeatureAdded(req.params.id);\n\n io.emit('task:created', task);\n res.json({ task });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Delete roadmap feature\n app.delete('/api/roadmap/features/:id', (req, res) => {\n try {\n const roadmap = roadmapService.deleteFeature(req.params.id);\n if (!roadmap) {\n res.status(404).json({ error: 'Roadmap or feature not found' });\n return;\n }\n io.emit('roadmap:updated', roadmap);\n res.json({ success: true });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // Get roadmap generation status\n app.get('/api/roadmap/status', (_req, res) => {\n try {\n res.json({ generating: roadmapService.isRunning() });\n } catch (error) {\n res.status(500).json({ error: String(error) });\n }\n });\n\n // ===== Static Files =====\n\n // Serve the client files\n const clientPath = join(__dirname, '..', 'client');\n\n // Check if we're in development or production\n if (existsSync(clientPath)) {\n app.use(express.static(clientPath));\n }\n\n // Serve index.html for client-side routing\n app.get('*', (_req, res) => {\n // Serve inline HTML with the full client application\n res.send(getClientHTML());\n });\n\n // ===== WebSocket =====\n\n io.on('connection', (socket) => {\n console.log('Client connected');\n\n // Get logs for running task (only one can run at a time)\n const runningId = executor.getRunningTaskId();\n const runningIds = runningId ? [runningId] : [];\n const taskLogs: Record<string, string> = {};\n\n // Get logs for running task\n if (runningId) {\n const logs = executor.getTaskLog(runningId);\n if (logs) {\n taskLogs[runningId] = logs;\n }\n }\n\n // Send current state with logs\n socket.emit('init', {\n tasks: prdService.getAllTasks(projectPath),\n running: runningIds,\n afk: executor.getAFKStatus(),\n taskLogs, // Include logs for running task\n planning: executor.isPlanning(),\n planningGoal: executor.getPlanningGoal() || null,\n planningOutput: executor.getPlanningOutput() || [],\n });\n\n // Handle client requesting logs for a specific task\n socket.on('get-logs', (taskId: string) => {\n const logs = executor.getTaskLog(taskId);\n socket.emit('task-logs', { taskId, logs: logs || '' });\n });\n\n socket.on('disconnect', () => {\n console.log('Client disconnected');\n });\n });\n\n // ===== Start Server =====\n\n return new Promise((resolve, reject) => {\n httpServer.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n reject(new Error(`Port ${port} is already in use. Try a different port with --port`));\n } else {\n reject(error);\n }\n });\n\n httpServer.listen(port, () => {\n // Attach cleanup function to server for graceful shutdown\n (httpServer as any).cleanup = () => {\n console.log('Cleaning up executor...');\n executor.cancelAll();\n io.close();\n };\n resolve(httpServer);\n });\n });\n}\n\nfunction getClientHTML(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Claude Kanban</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=DM+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <script src=\"/socket.io/socket.io.js\"></script>\n <script>\n tailwind.config = {\n theme: {\n extend: {\n fontFamily: {\n display: ['DM Sans', 'system-ui', 'sans-serif'],\n sans: ['DM Sans', 'system-ui', 'sans-serif'],\n mono: ['IBM Plex Mono', 'monospace'],\n },\n colors: {\n canvas: {\n DEFAULT: '#ffffff',\n 50: '#fafafa',\n 100: '#f5f5f5',\n 200: '#e5e5e5',\n 300: '#d4d4d4',\n 400: '#a3a3a3',\n 500: '#737373',\n 600: '#525252',\n 700: '#404040',\n 800: '#262626',\n 900: '#171717',\n },\n accent: {\n DEFAULT: '#f97316',\n light: '#fb923c',\n dark: '#ea580c',\n muted: 'rgba(249, 115, 22, 0.1)',\n },\n status: {\n draft: '#a3a3a3',\n ready: '#3b82f6',\n running: '#f97316',\n success: '#22c55e',\n failed: '#ef4444',\n }\n },\n },\n },\n };\n </script>\n <style>\n /* ═══════════════════════════════════════════════════════════════\n CLEAN LIGHT THEME - Inspired by vibe-kanban\n ═══════════════════════════════════════════════════════════════ */\n\n :root {\n --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);\n --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);\n }\n\n * { box-sizing: border-box; }\n\n html {\n background: #fafafa;\n color: #171717;\n }\n\n body {\n font-family: 'DM Sans', system-ui, sans-serif;\n background: #fafafa;\n min-height: 100vh;\n overflow-x: hidden;\n }\n\n /* Clean scrollbar */\n ::-webkit-scrollbar { width: 6px; height: 6px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb {\n background: rgba(0, 0, 0, 0.15);\n border-radius: 3px;\n }\n ::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.25); }\n\n /* ─── Typography ─── */\n .font-display { font-family: 'DM Sans', system-ui, sans-serif; }\n .font-mono { font-family: 'IBM Plex Mono', monospace; }\n\n /* ─── Card System ─── */\n .card {\n background: #ffffff;\n border: 1px solid #e5e5e5;\n border-radius: 8px;\n transition: all 0.2s var(--ease-out-expo);\n }\n .card:hover {\n border-color: #d4d4d4;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n }\n\n /* ─── Task Card - Minimal like vibe-kanban ─── */\n .task-card {\n cursor: pointer;\n padding: 12px 14px;\n }\n .task-card:hover {\n background: #fafafa;\n }\n .task-card.selected {\n border-color: #f97316;\n background: rgba(249, 115, 22, 0.03);\n }\n\n /* ─── Drag & Drop ─── */\n .dragging {\n opacity: 0.9;\n transform: scale(1.02) rotate(0.5deg);\n box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);\n z-index: 1000;\n }\n .drag-over {\n background: rgba(249, 115, 22, 0.05);\n border: 2px dashed #f97316;\n border-radius: 8px;\n }\n\n /* ─── Column Headers - Minimal ─── */\n .column-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 4px;\n margin-bottom: 8px;\n border-bottom: 1px solid #f0f0f0;\n }\n .status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n .status-dot-draft { background: #a3a3a3; }\n .status-dot-ready { background: #3b82f6; }\n .status-dot-in_progress { background: #f97316; }\n .status-dot-completed { background: #22c55e; }\n .status-dot-failed { background: #ef4444; }\n\n /* ─── Buttons ─── */\n .btn {\n font-weight: 500;\n border-radius: 6px;\n transition: all 0.15s ease;\n cursor: pointer;\n border: none;\n }\n .btn:active { transform: scale(0.98); }\n\n .btn-primary {\n background: #f97316;\n color: white;\n }\n .btn-primary:hover {\n background: #ea580c;\n }\n\n .btn-ghost {\n background: transparent;\n border: 1px solid #e5e5e5;\n color: #525252;\n }\n .btn-ghost:hover {\n background: #f5f5f5;\n border-color: #d4d4d4;\n }\n\n .btn-danger {\n background: #fef2f2;\n color: #ef4444;\n border: 1px solid #fecaca;\n }\n .btn-danger:hover {\n background: #fee2e2;\n }\n\n /* ─── Side Panel (pushes content, not overlay) ─── */\n .side-panel {\n width: 420px;\n flex-shrink: 0;\n background: #ffffff;\n border-left: 1px solid #e5e5e5;\n display: flex;\n flex-direction: column;\n height: calc(100vh - 57px);\n overflow: hidden;\n }\n .main-content {\n flex: 1;\n min-width: 0;\n transition: all 0.2s var(--ease-out-expo);\n }\n .side-panel-header {\n padding: 12px 16px;\n border-bottom: 1px solid #e5e5e5;\n flex-shrink: 0;\n }\n .side-panel-body {\n flex: 1;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n min-height: 0;\n }\n .side-panel-tabs {\n display: flex;\n border-bottom: 1px solid #e5e5e5;\n padding: 0 20px;\n flex-shrink: 0;\n }\n .side-panel-tab {\n padding: 10px 14px;\n font-size: 13px;\n color: #737373;\n cursor: pointer;\n border-bottom: 2px solid transparent;\n transition: all 0.15s ease;\n }\n .side-panel-tab:hover { color: #171717; }\n .side-panel-tab.active {\n color: #171717;\n border-bottom-color: #f97316;\n }\n\n /* ─── Terminal/Log Panel ─── */\n .log-container {\n background: #1a1a1a;\n color: #e5e5e5;\n font-family: 'IBM Plex Mono', monospace;\n font-size: 12px;\n line-height: 1.6;\n padding: 16px;\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n min-height: 0;\n }\n .log-line {\n padding: 2px 0;\n word-wrap: break-word;\n white-space: pre-wrap;\n }\n .log-line:hover {\n background: rgba(255, 255, 255, 0.05);\n }\n\n /* ANSI colors */\n .ansi-red { color: #f87171; }\n .ansi-green { color: #86efac; }\n .ansi-yellow { color: #fde047; }\n .ansi-blue { color: #93c5fd; }\n .ansi-magenta { color: #e879f9; }\n .ansi-cyan { color: #67e8f9; }\n .ansi-bold { font-weight: 600; }\n .log-path { color: #93c5fd; }\n .log-error { color: #fca5a5; }\n .log-success { color: #86efac; }\n\n /* ─── Tab System ─── */\n .tab {\n padding: 10px 16px;\n color: #737373;\n cursor: pointer;\n border-bottom: 2px solid transparent;\n transition: all 0.15s ease;\n }\n .tab:hover { color: #171717; }\n .tab.active {\n color: #171717;\n border-bottom-color: #f97316;\n }\n\n /* ─── Modal ─── */\n .modal-backdrop {\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n }\n .modal-content {\n animation: modal-enter 0.2s var(--ease-out-expo);\n }\n @keyframes modal-enter {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n }\n\n /* ─── Form Inputs ─── */\n .input {\n background: #ffffff;\n border: 1px solid #e5e5e5;\n border-radius: 6px;\n padding: 10px 12px;\n color: #171717;\n transition: all 0.15s ease;\n font-size: 14px;\n }\n .input::placeholder { color: #a3a3a3; }\n .input:focus {\n outline: none;\n border-color: #f97316;\n box-shadow: 0 0 0 3px rgba(249, 115, 22, 0.1);\n }\n\n /* ─── Toast Notifications ─── */\n .toast-container {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .toast {\n padding: 12px 16px;\n border-radius: 8px;\n background: #ffffff;\n border: 1px solid #e5e5e5;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n animation: toast-enter 0.3s var(--ease-out-expo);\n display: flex;\n align-items: center;\n gap: 8px;\n color: #171717;\n }\n @keyframes toast-enter {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .toast-success { border-left: 3px solid #22c55e; }\n .toast-error { border-left: 3px solid #ef4444; }\n .toast-info { border-left: 3px solid #3b82f6; }\n .toast-warning { border-left: 3px solid #f97316; }\n\n /* ─── Status Badges ─── */\n .status-badge {\n font-size: 12px;\n font-weight: 500;\n padding: 4px 10px;\n border-radius: 12px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n .status-badge-draft { background: #f5f5f5; color: #737373; }\n .status-badge-ready { background: #eff6ff; color: #3b82f6; }\n .status-badge-in_progress { background: #fff7ed; color: #f97316; }\n .status-badge-completed { background: #f0fdf4; color: #22c55e; }\n .status-badge-failed { background: #fef2f2; color: #ef4444; }\n\n /* ─── Priority Badges ─── */\n .badge {\n font-size: 11px;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: 4px;\n }\n .badge-critical { background: #fef2f2; color: #ef4444; }\n .badge-high { background: #fff7ed; color: #f97316; }\n .badge-medium { background: #eff6ff; color: #3b82f6; }\n .badge-low { background: #f5f5f5; color: #737373; }\n\n /* ─── Stats Pills ─── */\n .stat-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n background: #f5f5f5;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 500;\n color: #525252;\n }\n .stat-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n }\n\n /* ─── Column Container ─── */\n .column-container {\n background: #ffffff;\n border: 1px solid #e5e5e5;\n border-radius: 12px;\n padding: 12px;\n min-height: 500px;\n }\n\n /* ─── Column Drop Zone ─── */\n .column-zone {\n min-height: 400px;\n padding: 4px;\n transition: all 0.2s ease;\n }\n\n /* ─── Chat Input ─── */\n .chat-input-container {\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 16px 24px;\n background: #ffffff;\n border-top: 1px solid #e5e5e5;\n z-index: 50;\n }\n .chat-input {\n display: flex;\n align-items: center;\n gap: 12px;\n background: #f5f5f5;\n border: 1px solid #e5e5e5;\n border-radius: 8px;\n padding: 10px 16px;\n }\n .chat-input input {\n flex: 1;\n background: transparent;\n border: none;\n outline: none;\n font-size: 14px;\n color: #171717;\n }\n .chat-input input::placeholder { color: #a3a3a3; }\n .chat-input button {\n background: #f97316;\n color: white;\n border: none;\n border-radius: 6px;\n padding: 8px 16px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s;\n }\n .chat-input button:hover { background: #ea580c; }\n\n /* ─── Details Grid ─── */\n .details-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 16px;\n padding: 16px 20px;\n border-bottom: 1px solid #e5e5e5;\n flex-shrink: 0;\n }\n .details-item {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .details-label {\n font-size: 11px;\n font-weight: 500;\n color: #737373;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .details-value {\n font-size: 13px;\n color: #171717;\n }\n\n /* ─── Page animations ─── */\n .fade-in {\n animation: fade-in 0.3s ease;\n }\n @keyframes fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n </style>\n</head>\n<body class=\"grain\">\n <div id=\"app\"></div>\n <div id=\"toast-container\" class=\"toast-container\"></div>\n <script type=\"module\">\n${getClientJS()}\n </script>\n</body>\n</html>`;\n}\n\nfunction getClientJS(): string {\n return `\n// State\nlet state = {\n tasks: [],\n running: [],\n afk: { running: false, currentIteration: 0, maxIterations: 0, tasksCompleted: 0 },\n templates: [],\n config: null,\n selectedTask: null,\n taskOutput: {},\n taskStartTime: {},\n activeTab: null,\n showModal: null,\n aiPrompt: '',\n aiGenerating: false,\n editingTask: null,\n searchQuery: '',\n logSearch: '',\n showLineNumbers: false,\n autoScroll: true,\n logFullscreen: false,\n closedTabs: new Set(),\n sidePanel: null, // task id for side panel\n // Planning state\n planning: false,\n planningGoal: '',\n planningOutput: [],\n sidePanelTab: 'logs', // 'logs' or 'details'\n darkMode: localStorage.getItem('darkMode') === 'true', // Add dark mode state\n // View state (board or roadmap)\n currentView: 'board', // 'board' or 'roadmap'\n // Roadmap state\n roadmap: null,\n roadmapGenerating: false,\n roadmapProgress: null,\n roadmapError: null,\n roadmapSelectedFeature: null,\n roadmapEnableCompetitors: false,\n roadmapCustomPrompt: '',\n};\n\n// Toast notifications\nfunction showToast(message, type = 'info') {\n const container = document.getElementById('toast-container');\n const toast = document.createElement('div');\n toast.className = 'toast toast-' + type;\n const icons = { success: '✓', error: '✕', info: 'ℹ', warning: '⚠' };\n toast.innerHTML = '<span>' + (icons[type] || '') + '</span><span>' + escapeHtml(message) + '</span>';\n container.appendChild(toast);\n setTimeout(() => toast.remove(), 4000);\n}\n\n// ANSI color parser\nfunction parseAnsi(text) {\n const ansiRegex = /\\\\x1B\\\\[([0-9;]*)m/g;\n let result = '';\n let lastIndex = 0;\n let currentClasses = [];\n\n text.replace(ansiRegex, (match, codes, offset) => {\n result += escapeHtml(text.slice(lastIndex, offset));\n lastIndex = offset + match.length;\n\n codes.split(';').forEach(code => {\n const c = parseInt(code);\n if (c === 0) currentClasses = [];\n else if (c === 1) currentClasses.push('ansi-bold');\n else if (c === 2) currentClasses.push('ansi-dim');\n else if (c === 3) currentClasses.push('ansi-italic');\n else if (c === 4) currentClasses.push('ansi-underline');\n else if (c === 30) currentClasses.push('ansi-black');\n else if (c === 31) currentClasses.push('ansi-red');\n else if (c === 32) currentClasses.push('ansi-green');\n else if (c === 33) currentClasses.push('ansi-yellow');\n else if (c === 34) currentClasses.push('ansi-blue');\n else if (c === 35) currentClasses.push('ansi-magenta');\n else if (c === 36) currentClasses.push('ansi-cyan');\n else if (c === 37) currentClasses.push('ansi-white');\n else if (c === 90) currentClasses.push('ansi-bright-black');\n else if (c === 91) currentClasses.push('ansi-bright-red');\n else if (c === 92) currentClasses.push('ansi-bright-green');\n else if (c === 93) currentClasses.push('ansi-bright-yellow');\n else if (c === 94) currentClasses.push('ansi-bright-blue');\n else if (c === 95) currentClasses.push('ansi-bright-magenta');\n else if (c === 96) currentClasses.push('ansi-bright-cyan');\n else if (c === 97) currentClasses.push('ansi-bright-white');\n });\n\n if (currentClasses.length > 0) {\n result += '<span class=\"' + currentClasses.join(' ') + '\">';\n }\n });\n\n result += escapeHtml(text.slice(lastIndex));\n return result;\n}\n\n// Syntax highlight log lines\nfunction highlightLog(text) {\n let highlighted = parseAnsi(text);\n // Highlight file paths\n highlighted = highlighted.replace(/([\\\\/\\\\w.-]+\\\\.(ts|js|tsx|jsx|json|md|css|html))/g, '<span class=\"log-path\">$1</span>');\n // Highlight errors\n highlighted = highlighted.replace(/(error|Error|ERROR|failed|Failed|FAILED)/g, '<span class=\"log-error\">$1</span>');\n // Highlight success\n highlighted = highlighted.replace(/(success|Success|SUCCESS|complete|Complete|COMPLETE|passed|Passed|PASSED)/g, '<span class=\"log-success\">$1</span>');\n // Highlight warnings\n highlighted = highlighted.replace(/(warning|Warning|WARNING|warn|Warn|WARN)/g, '<span class=\"log-warning\">$1</span>');\n return highlighted;\n}\n\n// Format elapsed time\nfunction formatElapsed(startTime) {\n if (!startTime) return '';\n const elapsed = Date.now() - new Date(startTime).getTime();\n const seconds = Math.floor(elapsed / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm';\n if (minutes > 0) return minutes + 'm ' + (seconds % 60) + 's';\n return seconds + 's';\n}\n\n// Socket connection\nconst socket = io();\n\nsocket.on('init', (data) => {\n state.tasks = data.tasks;\n state.running = data.running;\n state.afk = data.afk;\n\n // Planning state\n state.planning = data.planning || false;\n state.planningGoal = data.planningGoal || '';\n state.planningOutput = (data.planningOutput || []).map(text => ({\n text: text,\n timestamp: new Date().toISOString()\n }));\n\n // Load persisted logs for running tasks\n if (data.taskLogs) {\n for (const [taskId, logs] of Object.entries(data.taskLogs)) {\n if (logs) {\n // Parse logs into lines for the taskOutput format\n state.taskOutput[taskId] = String(logs).split('\\\\n').filter(l => l).map(text => ({\n text: text + '\\\\n',\n timestamp: new Date().toISOString()\n }));\n }\n }\n }\n\n render();\n});\n\n// Handle logs response from server\nsocket.on('task-logs', ({ taskId, logs }) => {\n if (logs) {\n state.taskOutput[taskId] = logs.split('\\\\n').filter(l => l).map(text => ({\n text: text + '\\\\n',\n timestamp: new Date().toISOString()\n }));\n render();\n if (state.autoScroll) {\n requestAnimationFrame(() => scrollSidePanelLog());\n }\n }\n});\n\nsocket.on('task:created', (task) => {\n state.tasks.push(task);\n showToast('Task created: ' + task.title, 'success');\n render();\n});\n\nsocket.on('task:updated', (task) => {\n const idx = state.tasks.findIndex(t => t.id === task.id);\n if (idx >= 0) state.tasks[idx] = task;\n render();\n});\n\nsocket.on('task:deleted', ({ id }) => {\n state.tasks = state.tasks.filter(t => t.id !== id);\n render();\n});\n\nsocket.on('task:started', ({ taskId, timestamp }) => {\n if (!state.running.includes(taskId)) state.running.push(taskId);\n const task = state.tasks.find(t => t.id === taskId);\n if (task) task.status = 'in_progress';\n state.taskOutput[taskId] = [];\n state.taskStartTime[taskId] = timestamp || new Date().toISOString();\n state.activeTab = taskId;\n state.closedTabs.delete(taskId);\n showToast('Task started: ' + (task?.title || taskId), 'info');\n render();\n});\n\nsocket.on('task:output', ({ taskId, line }) => {\n if (!state.taskOutput[taskId]) state.taskOutput[taskId] = [];\n state.taskOutput[taskId].push({ text: line, timestamp: new Date().toISOString() });\n render();\n // Auto-scroll after DOM update\n if (state.autoScroll) {\n requestAnimationFrame(() => {\n scrollLogToBottom();\n scrollSidePanelLog();\n });\n }\n});\n\nsocket.on('task:completed', ({ taskId }) => {\n state.running = state.running.filter(id => id !== taskId);\n const task = state.tasks.find(t => t.id === taskId);\n if (task) {\n task.status = 'completed';\n task.passes = true;\n }\n showToast('Task completed: ' + (task?.title || taskId), 'success');\n render();\n});\n\nsocket.on('task:failed', ({ taskId }) => {\n state.running = state.running.filter(id => id !== taskId);\n const task = state.tasks.find(t => t.id === taskId);\n if (task) { task.status = 'failed'; task.passes = false; }\n showToast('Task failed: ' + (task?.title || taskId), 'error');\n render();\n});\n\nsocket.on('task:cancelled', ({ taskId }) => {\n state.running = state.running.filter(id => id !== taskId);\n const task = state.tasks.find(t => t.id === taskId);\n if (task) task.status = 'ready';\n showToast('Task cancelled', 'warning');\n render();\n});\n\nsocket.on('afk:status', (status) => {\n state.afk = status;\n render();\n});\n\n// Planning socket handlers\nsocket.on('planning:started', ({ goal, timestamp }) => {\n state.planning = true;\n state.planningGoal = goal;\n state.planningOutput = [];\n state.showModal = 'planning';\n showToast('Planning started: ' + goal.substring(0, 50) + (goal.length > 50 ? '...' : ''), 'info');\n render();\n});\n\nsocket.on('planning:output', ({ line }) => {\n state.planningOutput.push({\n text: line,\n timestamp: new Date().toISOString()\n });\n render();\n});\n\nsocket.on('planning:completed', () => {\n state.planning = false;\n showToast('Planning complete! New tasks added to board.', 'success');\n // Refresh tasks from server\n fetch('/api/tasks').then(r => r.json()).then(data => {\n state.tasks = data.tasks;\n state.showModal = null;\n render();\n });\n});\n\nsocket.on('planning:failed', ({ error }) => {\n state.planning = false;\n showToast('Planning failed: ' + error, 'error');\n render();\n});\n\nsocket.on('planning:cancelled', () => {\n state.planning = false;\n state.showModal = null;\n showToast('Planning cancelled', 'warning');\n render();\n});\n\n// Roadmap events\nsocket.on('roadmap:started', () => {\n state.roadmapGenerating = true;\n state.roadmapProgress = { phase: 'analyzing', message: 'Starting roadmap generation...' };\n state.roadmapError = null;\n render();\n});\n\nsocket.on('roadmap:progress', ({ phase, message }) => {\n state.roadmapProgress = { phase, message };\n render();\n});\n\nsocket.on('roadmap:completed', ({ roadmap }) => {\n state.roadmap = roadmap;\n state.roadmapGenerating = false;\n state.roadmapProgress = null;\n state.showModal = null;\n showToast('Roadmap generated successfully!', 'success');\n render();\n});\n\nsocket.on('roadmap:failed', ({ error }) => {\n state.roadmapGenerating = false;\n state.roadmapProgress = null;\n state.roadmapError = error;\n showToast('Roadmap generation failed: ' + error, 'error');\n render();\n});\n\nsocket.on('roadmap:updated', (roadmap) => {\n state.roadmap = roadmap;\n render();\n});\n\n// Load templates\nfetch('/api/templates').then(r => r.json()).then(data => {\n state.templates = data.templates;\n});\n\n// Load config\nfetch('/api/config').then(r => r.json()).then(data => {\n state.config = data.config;\n});\n\n// API calls\nasync function createTask(task) {\n const res = await fetch('/api/tasks', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(task)\n });\n return res.json();\n}\n\nasync function updateTask(id, updates) {\n const res = await fetch('/api/tasks/' + id, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(updates)\n });\n return res.json();\n}\n\nasync function deleteTask(id) {\n await fetch('/api/tasks/' + id, { method: 'DELETE' });\n}\n\nasync function runTask(id) {\n await fetch('/api/tasks/' + id + '/run', { method: 'POST' });\n}\n\nasync function cancelTask(id) {\n await fetch('/api/tasks/' + id + '/cancel', { method: 'POST' });\n}\n\nasync function retryTask(id) {\n await fetch('/api/tasks/' + id + '/retry', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ autoRun: false })\n });\n}\n\nasync function generateTask(prompt) {\n state.aiGenerating = true;\n render();\n try {\n const res = await fetch('/api/tasks/generate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ prompt })\n });\n const data = await res.json();\n state.aiGenerating = false;\n return data.task;\n } catch (e) {\n state.aiGenerating = false;\n throw e;\n }\n}\n\nasync function startAFK(maxIterations) {\n await fetch('/api/afk/start', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ maxIterations })\n });\n}\n\nasync function stopAFK() {\n await fetch('/api/afk/stop', { method: 'POST' });\n}\n\nasync function startPlanning(goal) {\n await fetch('/api/plan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ goal })\n });\n}\n\nasync function cancelPlanning() {\n await fetch('/api/plan/cancel', { method: 'POST' });\n}\n\n// Roadmap API functions\nasync function loadRoadmap() {\n const res = await fetch('/api/roadmap');\n const data = await res.json();\n state.roadmap = data.roadmap;\n render();\n}\n\nasync function generateRoadmap() {\n state.roadmapGenerating = true;\n state.roadmapError = null;\n render();\n try {\n await fetch('/api/roadmap/generate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n enableCompetitorResearch: state.roadmapEnableCompetitors,\n customPrompt: state.roadmapCustomPrompt || undefined\n })\n });\n } catch (e) {\n state.roadmapGenerating = false;\n state.roadmapError = e.message;\n render();\n }\n}\n\nasync function cancelRoadmap() {\n await fetch('/api/roadmap/cancel', { method: 'POST' });\n state.roadmapGenerating = false;\n state.roadmapProgress = null;\n render();\n}\n\nasync function addFeatureToKanban(featureId) {\n try {\n const res = await fetch('/api/roadmap/features/' + featureId + '/add-to-kanban', {\n method: 'POST'\n });\n const data = await res.json();\n if (data.task) {\n showToast('Feature added to kanban!', 'success');\n // Reload roadmap to update addedToKanban status\n await loadRoadmap();\n }\n } catch (e) {\n showToast('Failed to add feature: ' + e.message, 'error');\n }\n}\n\nasync function deleteRoadmapFeature(featureId) {\n try {\n await fetch('/api/roadmap/features/' + featureId, { method: 'DELETE' });\n showToast('Feature removed', 'info');\n } catch (e) {\n showToast('Failed to remove feature: ' + e.message, 'error');\n }\n}\n\n// Load roadmap on init\nloadRoadmap();\n\n// Enhanced Drag and drop\nlet draggedTask = null;\nlet draggedElement = null;\n\nfunction handleDragStart(e, taskId) {\n draggedTask = taskId;\n draggedElement = e.target;\n e.target.classList.add('dragging');\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', taskId);\n\n // Add visual feedback to valid drop zones\n setTimeout(() => {\n document.querySelectorAll('.column-drop-zone').forEach(zone => {\n const status = zone.dataset.status;\n if (status !== 'in_progress') {\n zone.classList.add('border-dashed', 'border-2', 'border-blue-400/30');\n }\n });\n }, 0);\n}\n\nfunction handleDragEnd(e) {\n e.target.classList.remove('dragging');\n document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));\n document.querySelectorAll('.column-drop-zone').forEach(zone => {\n zone.classList.remove('border-dashed', 'border-2', 'border-blue-400/30');\n });\n draggedTask = null;\n draggedElement = null;\n}\n\nfunction handleDragOver(e) {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n const zone = e.currentTarget;\n if (!zone.classList.contains('drag-over') && zone.dataset.status !== 'in_progress') {\n zone.classList.add('drag-over');\n }\n}\n\nfunction handleDragLeave(e) {\n // Only remove if actually leaving the zone\n const rect = e.currentTarget.getBoundingClientRect();\n const x = e.clientX;\n const y = e.clientY;\n if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {\n e.currentTarget.classList.remove('drag-over');\n }\n}\n\nfunction handleDrop(e, newStatus) {\n e.preventDefault();\n e.currentTarget.classList.remove('drag-over');\n if (draggedTask && newStatus !== 'in_progress') {\n const task = state.tasks.find(t => t.id === draggedTask);\n if (task && task.status !== newStatus) {\n updateTask(draggedTask, { status: newStatus });\n showToast('Moved to ' + newStatus.replace('_', ' '), 'info');\n }\n }\n draggedTask = null;\n}\n\nfunction scrollLogToBottom() {\n const logEl = document.getElementById('log-content');\n if (logEl) logEl.scrollTop = logEl.scrollHeight;\n}\n\nfunction scrollSidePanelLog() {\n const logEl = document.getElementById('side-panel-log');\n if (logEl) logEl.scrollTop = logEl.scrollHeight;\n}\n\nfunction copyLogToClipboard() {\n const output = state.taskOutput[state.activeTab] || [];\n const text = output.map(l => l.text || l).join('');\n navigator.clipboard.writeText(text).then(() => {\n showToast('Log copied to clipboard', 'success');\n });\n}\n\nfunction clearLog(taskId) {\n state.taskOutput[taskId] = [];\n render();\n}\n\nfunction closeLogTab(taskId) {\n state.closedTabs.add(taskId);\n if (state.activeTab === taskId) {\n const remaining = Object.keys(state.taskOutput).filter(id => !state.closedTabs.has(id));\n state.activeTab = remaining[0] || null;\n }\n render();\n}\n\nfunction toggleLogFullscreen() {\n state.logFullscreen = !state.logFullscreen;\n render();\n}\n\nconst categoryIcons = {\n functional: '⚙️',\n ui: '🎨',\n bug: '🐛',\n enhancement: '✨',\n testing: '🧪',\n refactor: '🔧'\n};\n\n// Render functions\nfunction renderCard(task) {\n const isRunning = state.running.includes(task.id);\n const isSelected = state.sidePanel === task.id;\n\n return \\`\n <div class=\"card task-card mb-2 group \\${isRunning ? 'running' : ''} \\${isSelected ? 'selected' : ''}\"\n onclick=\"openSidePanel('\\${task.id}')\"\n draggable=\"\\${!isRunning}\"\n ondragstart=\"handleDragStart(event, '\\${task.id}')\"\n ondragend=\"handleDragEnd(event)\">\n <div class=\"flex justify-between items-start gap-2\">\n <h3 class=\"font-medium text-canvas-800 text-sm leading-snug\">\\${escapeHtml(task.title)}</h3>\n <button onclick=\"event.stopPropagation(); showTaskMenu('\\${task.id}')\"\n class=\"text-canvas-400 hover:text-canvas-600 p-1 -mr-1 opacity-0 group-hover:opacity-100\">\n ⋯\n </button>\n </div>\n <p class=\"text-xs text-canvas-500 mt-1 line-clamp-2\">\\${escapeHtml(task.description.substring(0, 80))}\\${task.description.length > 80 ? '...' : ''}</p>\n \\${isRunning ? \\`\n <div class=\"flex items-center gap-2 mt-2 text-xs text-status-running\">\n <span class=\"w-1.5 h-1.5 bg-status-running rounded-full animate-pulse\"></span>\n Running...\n </div>\n \\` : ''}\n </div>\n \\`;\n}\n\nfunction openSidePanel(taskId) {\n state.sidePanel = taskId;\n state.activeTab = taskId;\n state.closedTabs.delete(taskId);\n\n // Auto-switch to logs tab if task is running\n if (state.running.includes(taskId)) {\n state.sidePanelTab = 'logs';\n }\n\n // Request logs from server if not already loaded\n if (!state.taskOutput[taskId] || state.taskOutput[taskId].length === 0) {\n socket.emit('get-logs', taskId);\n }\n\n render();\n\n // Scroll to bottom after render\n if (state.autoScroll) {\n requestAnimationFrame(() => scrollSidePanelLog());\n }\n}\n\nfunction closeSidePanel() {\n state.sidePanel = null;\n render();\n}\n\nfunction showTaskMenu(taskId) {\n // For now just open side panel, could add dropdown menu later\n openSidePanel(taskId);\n}\n\n// Roadmap rendering functions\nfunction renderRoadmap() {\n if (state.roadmapGenerating) {\n return \\`\n <div class=\"flex-1 flex items-center justify-center\">\n <div class=\"text-center\">\n <div class=\"animate-spin rounded-full h-12 w-12 border-b-2 border-accent mx-auto mb-4\"></div>\n <h3 class=\"text-lg font-medium text-canvas-800 mb-2\">Generating Roadmap...</h3>\n <p class=\"text-canvas-500\">\\${state.roadmapProgress?.message || 'Please wait...'}</p>\n <button onclick=\"cancelRoadmap()\" class=\"btn btn-ghost mt-4 text-sm\">Cancel</button>\n </div>\n </div>\n \\`;\n }\n\n if (!state.roadmap) {\n return \\`\n <div class=\"flex-1 flex items-center justify-center\">\n <div class=\"text-center max-w-md\">\n <div class=\"text-6xl mb-4\">🗺️</div>\n <h3 class=\"text-xl font-semibold text-canvas-800 mb-2\">No Roadmap Yet</h3>\n <p class=\"text-canvas-500 mb-6\">Generate a strategic feature roadmap using AI to analyze your project and suggest features.</p>\n <button onclick=\"state.showModal = 'roadmap'; render();\" class=\"btn btn-primary px-6 py-2\">\n 🚀 Generate Roadmap\n </button>\n </div>\n </div>\n \\`;\n }\n\n const roadmap = state.roadmap;\n const phases = roadmap.phases || [];\n const features = roadmap.features || [];\n\n return \\`\n <div class=\"flex-1 overflow-y-auto p-6\">\n <!-- Roadmap Header -->\n <div class=\"mb-6 flex items-start justify-between\">\n <div>\n <h2 class=\"text-2xl font-semibold text-canvas-900\">\\${escapeHtml(roadmap.projectName)} Roadmap</h2>\n <p class=\"text-canvas-500 mt-1\">\\${escapeHtml(roadmap.projectDescription || '')}</p>\n <p class=\"text-sm text-canvas-400 mt-2\">Target: \\${escapeHtml(roadmap.targetAudience || 'Developers')}</p>\n </div>\n <div class=\"flex gap-2\">\n <button onclick=\"state.showModal = 'roadmap'; render();\" class=\"btn btn-ghost text-sm\">\n 🔄 Regenerate\n </button>\n </div>\n </div>\n\n <!-- Competitors (if available) -->\n \\${roadmap.competitors && roadmap.competitors.length > 0 ? \\`\n <div class=\"mb-6\">\n <h3 class=\"text-sm font-medium text-canvas-700 mb-2\">Competitor Insights</h3>\n <div class=\"flex gap-2 flex-wrap\">\n \\${roadmap.competitors.map(c => \\`\n <span class=\"px-3 py-1 bg-canvas-100 rounded-full text-sm text-canvas-600\">\n \\${escapeHtml(c.name)}\n </span>\n \\`).join('')}\n </div>\n </div>\n \\` : ''}\n\n <!-- Phases -->\n <div class=\"space-y-8\">\n \\${phases.map(phase => {\n const phaseFeatures = features.filter(f => f.phase === phase.name);\n return \\`\n <div class=\"phase-section\">\n <div class=\"flex items-center gap-3 mb-4\">\n <h3 class=\"text-lg font-semibold text-canvas-800\">\\${escapeHtml(phase.name)}</h3>\n <span class=\"text-xs bg-canvas-100 px-2 py-0.5 rounded-full text-canvas-500\">\\${phaseFeatures.length} features</span>\n </div>\n <p class=\"text-sm text-canvas-500 mb-4\">\\${escapeHtml(phase.description || '')}</p>\n <div class=\"grid gap-3 md:grid-cols-2 lg:grid-cols-3\">\n \\${phaseFeatures.map(f => renderRoadmapFeature(f)).join('')}\n </div>\n </div>\n \\`;\n }).join('')}\n </div>\n </div>\n \\`;\n}\n\nfunction renderRoadmapFeature(feature) {\n const priorityColors = {\n must: 'bg-red-100 text-red-700',\n should: 'bg-orange-100 text-orange-700',\n could: 'bg-blue-100 text-blue-700',\n wont: 'bg-gray-100 text-gray-500'\n };\n const priorityLabels = {\n must: 'Must Have',\n should: 'Should Have',\n could: 'Could Have',\n wont: \"Won't Have\"\n };\n const effortIcons = {\n low: '⚡',\n medium: '⏱️',\n high: '🏋️'\n };\n const impactIcons = {\n low: '📉',\n medium: '📊',\n high: '📈'\n };\n\n return \\`\n <div class=\"card p-4 \\${feature.addedToKanban ? 'opacity-60' : ''}\" onclick=\"state.roadmapSelectedFeature = '\\${feature.id}'; render();\">\n <div class=\"flex items-start justify-between mb-2\">\n <h4 class=\"font-medium text-canvas-800 text-sm\">\\${escapeHtml(feature.title)}</h4>\n <span class=\"text-xs px-2 py-0.5 rounded-full \\${priorityColors[feature.priority] || 'bg-gray-100'}\">\\${priorityLabels[feature.priority] || feature.priority}</span>\n </div>\n <p class=\"text-xs text-canvas-500 mb-3 line-clamp-2\">\\${escapeHtml(feature.description)}</p>\n <div class=\"flex items-center justify-between\">\n <div class=\"flex gap-2 text-xs text-canvas-400\">\n <span title=\"Effort\">\\${effortIcons[feature.effort] || '⏱️'} \\${feature.effort}</span>\n <span title=\"Impact\">\\${impactIcons[feature.impact] || '📊'} \\${feature.impact}</span>\n </div>\n \\${feature.addedToKanban ? \\`\n <span class=\"text-xs text-green-600\">✓ Added</span>\n \\` : \\`\n <button onclick=\"event.stopPropagation(); addFeatureToKanban('\\${feature.id}')\" class=\"text-xs text-accent hover:underline\">+ Add to Kanban</button>\n \\`}\n </div>\n </div>\n \\`;\n}\n\nfunction renderRoadmapModal() {\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-lg mx-4\">\n <div class=\"px-6 py-4 border-b border-canvas-200 flex justify-between items-center\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">🗺️ Generate Roadmap</h3>\n <button onclick=\"state.showModal = null; render();\" class=\"text-canvas-400 hover:text-canvas-600 text-xl leading-none\">×</button>\n </div>\n <div class=\"p-6\">\n <p class=\"text-sm text-canvas-500 mb-6\">\n Generate a strategic feature roadmap by analyzing your project structure and optionally researching competitors.\n </p>\n\n <div class=\"space-y-5\">\n <label class=\"flex items-start gap-3 cursor-pointer p-3 rounded-lg border border-canvas-200 hover:border-canvas-300 transition-colors\">\n <input type=\"checkbox\"\n \\${state.roadmapEnableCompetitors ? 'checked' : ''}\n onchange=\"state.roadmapEnableCompetitors = this.checked; render();\"\n class=\"w-4 h-4 mt-0.5 accent-accent\">\n <div>\n <span class=\"text-sm font-medium text-canvas-700\">Enable competitor research</span>\n <p class=\"text-xs text-canvas-400 mt-0.5\">Use web search to analyze competitors (takes longer)</p>\n </div>\n </label>\n\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-1.5\">Additional context (optional)</label>\n <textarea\n class=\"input w-full text-sm\"\n rows=\"3\"\n placeholder=\"E.g., Focus on mobile features, target enterprise users...\"\n oninput=\"state.roadmapCustomPrompt = this.value;\"\n >\\${escapeHtml(state.roadmapCustomPrompt)}</textarea>\n </div>\n </div>\n </div>\n <div class=\"px-6 py-4 border-t border-canvas-200 flex justify-end gap-3\">\n <button onclick=\"state.showModal = null; render();\" class=\"btn btn-ghost px-4 py-2\">Cancel</button>\n <button onclick=\"generateRoadmap(); state.showModal = null; render();\" class=\"btn btn-primary px-4 py-2\">\n 🚀 Generate Roadmap\n </button>\n </div>\n </div>\n </div>\n \\`;\n}\n\nfunction renderColumn(status, title, tasks) {\n const columnTasks = tasks.filter(t => t.status === status);\n const statusLabels = {\n draft: 'To Do',\n ready: 'Ready',\n in_progress: 'In Progress',\n completed: 'Done',\n failed: 'Failed'\n };\n const taskCount = columnTasks.length;\n\n return \\`\n <div class=\"flex-1 min-w-[240px] max-w-[300px]\">\n <div class=\"column-container\">\n <div class=\"column-header\">\n <span class=\"status-dot status-dot-\\${status}\"></span>\n <span class=\"font-medium text-canvas-700 text-sm\">\\${statusLabels[status] || title}</span>\n <span class=\"text-xs text-canvas-400 ml-auto\">\\${taskCount}</span>\n </div>\n <div class=\"column-zone column-drop-zone max-h-[calc(100vh-280px)] overflow-y-auto\"\n data-status=\"\\${status}\"\n ondragover=\"handleDragOver(event)\"\n ondragleave=\"handleDragLeave(event)\"\n ondrop=\"handleDrop(event, '\\${status}')\">\n \\${columnTasks.map(t => renderCard(t)).join('')}\n \\${columnTasks.length === 0 ? \\`\n <div class=\"flex items-center justify-center py-12 text-canvas-400 text-sm\">\n No tasks\n </div>\n \\` : ''}\n </div>\n </div>\n </div>\n \\`;\n}\n\nfunction renderLog() {\n const allTabs = Object.keys(state.taskOutput).filter(id => !state.closedTabs.has(id));\n const activeOutput = state.taskOutput[state.activeTab] || [];\n const activeTask = state.tasks.find(t => t.id === state.activeTab);\n const isRunning = state.running.includes(state.activeTab);\n\n // Filter output based on search\n let filteredOutput = activeOutput;\n if (state.logSearch) {\n const searchLower = state.logSearch.toLowerCase();\n filteredOutput = activeOutput.filter(l => {\n const text = l.text || l;\n return text.toLowerCase().includes(searchLower);\n });\n }\n\n const fullscreenClass = state.logFullscreen ? 'fixed inset-4 z-50' : 'mt-6';\n const logHeight = state.logFullscreen ? 'calc(100vh - 180px)' : '300px';\n\n return \\`\n <div class=\"terminal \\${fullscreenClass} \\${state.logFullscreen ? 'shadow-2xl' : ''}\">\n <!-- Tab bar -->\n <div class=\"terminal-header flex items-center rounded-t-xl\">\n <div class=\"flex-1 flex items-center overflow-x-auto\">\n \\${allTabs.length > 0 ? allTabs.map(id => {\n const task = state.tasks.find(t => t.id === id);\n const isActive = state.activeTab === id;\n const isTaskRunning = state.running.includes(id);\n return \\`\n <div class=\"tab flex items-center gap-2 group \\${isActive ? 'active' : ''}\"\n onclick=\"state.activeTab = '\\${id}'; render();\">\n <span class=\"w-2 h-2 rounded-full \\${isTaskRunning ? 'bg-status-running animate-pulse' : task?.status === 'completed' ? 'bg-status-success' : task?.status === 'failed' ? 'bg-status-failed' : 'bg-canvas-400'}\"></span>\n <span class=\"text-sm truncate max-w-[150px]\">\\${task?.title?.substring(0, 25) || id}</span>\n <button onclick=\"event.stopPropagation(); closeLogTab('\\${id}')\"\n class=\"opacity-0 group-hover:opacity-100 text-canvas-500 hover:text-status-failed transition-opacity ml-1\">\n ✕\n </button>\n </div>\n \\`;\n }).join('') : '<div class=\"px-4 py-2.5 text-canvas-500 text-sm\">No logs</div>'}\n </div>\n\n <!-- Controls -->\n <div class=\"flex items-center gap-1 px-3 border-l border-white/5\">\n <button onclick=\"state.showLineNumbers = !state.showLineNumbers; render();\"\n class=\"btn btn-ghost p-1.5 text-xs tooltip\" data-tooltip=\"\\${state.showLineNumbers ? 'Hide' : 'Show'} line numbers\">\n #\n </button>\n <button onclick=\"state.autoScroll = !state.autoScroll; render();\"\n class=\"btn btn-ghost p-1.5 text-xs \\${state.autoScroll ? 'text-accent' : ''} tooltip\" data-tooltip=\"Auto-scroll \\${state.autoScroll ? 'on' : 'off'}\">\n ↓\n </button>\n <button onclick=\"copyLogToClipboard()\"\n class=\"btn btn-ghost p-1.5 text-xs tooltip\" data-tooltip=\"Copy to clipboard\">\n 📋\n </button>\n <button onclick=\"clearLog('\\${state.activeTab}')\"\n class=\"btn btn-ghost p-1.5 text-xs tooltip\" data-tooltip=\"Clear log\">\n 🗑\n </button>\n <button onclick=\"toggleLogFullscreen()\"\n class=\"btn btn-ghost p-1.5 text-xs tooltip\" data-tooltip=\"\\${state.logFullscreen ? 'Exit' : 'Enter'} fullscreen\">\n \\${state.logFullscreen ? '⊙' : '⛶'}\n </button>\n </div>\n </div>\n\n <!-- Search and task info bar -->\n <div class=\"flex items-center justify-between px-4 py-2.5 bg-canvas-50/50 border-b border-white/5\">\n <div class=\"flex items-center gap-3\">\n <span class=\"text-sm font-display font-medium text-canvas-700\">\n \\${activeTask ? activeTask.title : 'Execution Log'}\n </span>\n \\${isRunning ? \\`\n <span class=\"stat-pill text-status-running border-status-running/20\">\n <span class=\"stat-dot bg-status-running animate-pulse\"></span>\n Running \\${formatElapsed(state.taskStartTime[state.activeTab])}\n </span>\n \\` : ''}\n <span class=\"text-xs text-canvas-500 font-mono\">\\${filteredOutput.length} lines</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <div class=\"relative\">\n <input type=\"text\"\n placeholder=\"Search logs...\"\n value=\"\\${escapeHtml(state.logSearch)}\"\n oninput=\"state.logSearch = this.value; render();\"\n class=\"input text-xs py-1.5 pr-8 w-48\">\n \\${state.logSearch ? \\`\n <button onclick=\"state.logSearch = ''; render();\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 text-canvas-500 hover:text-canvas-700\">\n ✕\n </button>\n \\` : ''}\n </div>\n </div>\n </div>\n\n <!-- Log content -->\n <div id=\"log-content\"\n class=\"p-4 overflow-y-auto text-canvas-600 rounded-b-xl font-mono\"\n style=\"height: \\${logHeight}\">\n \\${filteredOutput.length > 0\n ? filteredOutput.map((l, i) => {\n const text = l.text || l;\n const timestamp = l.timestamp ? new Date(l.timestamp).toLocaleTimeString() : '';\n return \\`\n <div class=\"log-line flex items-start gap-3\">\n \\${state.showLineNumbers ? \\`<span class=\"text-canvas-400 select-none w-8 text-right flex-shrink-0\">\\${i + 1}</span>\\` : ''}\n \\${timestamp ? \\`<span class=\"text-canvas-400 select-none flex-shrink-0\">\\${timestamp}</span>\\` : ''}\n <span class=\"whitespace-pre-wrap flex-1\">\\${highlightLog(text)}</span>\n </div>\n \\`;\n }).join('')\n : \\`<div class=\"flex flex-col items-center justify-center h-full text-canvas-500\">\n <span class=\"text-4xl mb-3 opacity-20\">📋</span>\n <span class=\"text-sm\">\\${state.logSearch ? 'No matching lines' : 'No output yet. Run a task to see logs here.'}</span>\n </div>\\`}\n </div>\n </div>\n \\`;\n}\n\nfunction renderModal() {\n if (!state.showModal) return '';\n\n if (state.showModal === 'new' || state.showModal === 'edit') {\n const task = state.editingTask || { title: '', description: '', category: 'functional', priority: 'medium', steps: [], status: 'draft' };\n const isEdit = state.showModal === 'edit';\n\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; state.editingTask = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-lg mx-4 max-h-[90vh] overflow-y-auto\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">\\${isEdit ? '✏️ Edit Task' : '📝 New Task'}</h3>\n <button onclick=\"state.showModal = null; state.editingTask = null; render();\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:text-canvas-700\">✕</button>\n </div>\n <form onsubmit=\"handleTaskSubmit(event, \\${isEdit})\" class=\"p-6 space-y-5\">\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Title *</label>\n <input type=\"text\" name=\"title\" value=\"\\${escapeHtml(task.title)}\" required\n class=\"input w-full\" placeholder=\"Add login functionality\">\n </div>\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Description *</label>\n <textarea name=\"description\" rows=\"4\" required\n class=\"input w-full resize-none\" placeholder=\"Describe what needs to be done...\">\\${escapeHtml(task.description)}</textarea>\n </div>\n <div class=\"grid grid-cols-2 gap-4\">\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Category</label>\n <select name=\"category\" class=\"input w-full\">\n <option value=\"functional\" \\${task.category === 'functional' ? 'selected' : ''}>⚙️ Functional</option>\n <option value=\"ui\" \\${task.category === 'ui' ? 'selected' : ''}>🎨 UI</option>\n <option value=\"bug\" \\${task.category === 'bug' ? 'selected' : ''}>🐛 Bug</option>\n <option value=\"enhancement\" \\${task.category === 'enhancement' ? 'selected' : ''}>✨ Enhancement</option>\n <option value=\"testing\" \\${task.category === 'testing' ? 'selected' : ''}>🧪 Testing</option>\n <option value=\"refactor\" \\${task.category === 'refactor' ? 'selected' : ''}>🔧 Refactor</option>\n </select>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Priority</label>\n <select name=\"priority\" class=\"input w-full\">\n <option value=\"low\" \\${task.priority === 'low' ? 'selected' : ''}>Low</option>\n <option value=\"medium\" \\${task.priority === 'medium' ? 'selected' : ''}>Medium</option>\n <option value=\"high\" \\${task.priority === 'high' ? 'selected' : ''}>High</option>\n <option value=\"critical\" \\${task.priority === 'critical' ? 'selected' : ''}>Critical</option>\n </select>\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Verification Steps (one per line)</label>\n <textarea name=\"steps\" rows=\"4\"\n class=\"input w-full resize-none font-mono text-sm\" placeholder=\"Navigate to /login Enter valid credentials Click submit button Verify redirect\">\\${task.steps.join('\\\\n')}</textarea>\n </div>\n <div class=\"flex justify-end gap-3 pt-4 border-t border-white/5\">\n <button type=\"button\" onclick=\"state.showModal = null; state.editingTask = null; render();\"\n class=\"btn btn-ghost px-4 py-2.5\">Cancel</button>\n \\${!isEdit ? \\`\n <button type=\"submit\" name=\"action\" value=\"draft\"\n class=\"btn btn-ghost px-4 py-2.5\">Save as Draft</button>\n \\` : ''}\n <button type=\"submit\" name=\"action\" value=\"\\${isEdit ? 'save' : 'ready'}\"\n class=\"btn btn-primary px-5 py-2.5\">\\${isEdit ? 'Save Changes' : 'Create Ready'}</button>\n </div>\n </form>\n </div>\n </div>\n \\`;\n }\n\n if (state.showModal === 'templates') {\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-2xl mx-4 max-h-[80vh] overflow-y-auto\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center sticky top-0 bg-canvas-100/95 backdrop-blur rounded-t-xl z-10\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">📋 Task Templates</h3>\n <button onclick=\"state.showModal = null; render();\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:text-canvas-700\">✕</button>\n </div>\n <div class=\"p-6 grid grid-cols-2 gap-4\">\n \\${state.templates.map(t => \\`\n <button onclick=\"applyTemplate('\\${t.id}')\"\n class=\"card text-left p-4 hover:border-accent/30 transition-all group\">\n <div class=\"font-display font-medium text-canvas-800 group-hover:text-accent transition-colors\">\\${t.icon} \\${t.name}</div>\n <div class=\"text-sm text-canvas-500 mt-1\">\\${t.description}</div>\n </button>\n \\`).join('')}\n </div>\n </div>\n </div>\n \\`;\n }\n\n if (state.showModal === 'afk') {\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-md mx-4\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">🔄 AFK Mode</h3>\n <button onclick=\"state.showModal = null; render();\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:text-canvas-700\">✕</button>\n </div>\n <div class=\"p-6\">\n <p class=\"text-sm text-canvas-600 mb-5\">Run the agent in a loop, automatically picking up tasks from the \"Ready\" column one at a time until complete.</p>\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Maximum Iterations</label>\n <input type=\"number\" id=\"afk-iterations\" value=\"10\" min=\"1\" max=\"100\"\n class=\"input w-full\">\n </div>\n <div class=\"bg-status-running/10 border border-status-running/20 rounded-lg p-3\">\n <p class=\"text-xs text-status-running\">⚠️ You can close this tab - the agent will continue running. Check back later or watch the terminal output.</p>\n </div>\n </div>\n <div class=\"flex justify-end gap-3 mt-6\">\n <button onclick=\"state.showModal = null; render();\"\n class=\"btn btn-ghost px-4 py-2.5\">Cancel</button>\n <button onclick=\"handleStartAFK()\"\n class=\"btn px-5 py-2.5 bg-status-success text-canvas font-medium\">🚀 Start AFK Mode</button>\n </div>\n </div>\n </div>\n </div>\n \\`;\n }\n\n // Planning input modal\n if (state.showModal === 'plan-input') {\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\" onclick=\"if(event.target === event.currentTarget) { state.showModal = null; render(); }\">\n <div class=\"modal-content card rounded-xl w-full max-w-lg mx-4\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center\">\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">🎯 AI Task Planner</h3>\n <button onclick=\"state.showModal = null; render();\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:text-canvas-700\">✕</button>\n </div>\n <div class=\"p-6\">\n <p class=\"text-sm text-canvas-600 mb-4\">Describe your goal and the AI will analyze the codebase and break it down into concrete tasks.</p>\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">What do you want to build?</label>\n <textarea id=\"planning-goal\" rows=\"4\" placeholder=\"e.g., Add user authentication with JWT tokens, login/logout functionality, and protected routes...\"\n class=\"input w-full resize-none\"></textarea>\n </div>\n <div class=\"bg-blue-50 border border-blue-200 rounded-lg p-3\">\n <p class=\"text-xs text-blue-700\">💡 Be specific about what you want. The AI will explore your codebase and create 3-8 tasks with implementation guidance.</p>\n </div>\n </div>\n <div class=\"flex justify-end gap-3 mt-6\">\n <button onclick=\"state.showModal = null; render();\"\n class=\"btn btn-ghost px-4 py-2.5\">Cancel</button>\n <button onclick=\"handleStartPlanning()\"\n class=\"btn px-5 py-2.5 bg-blue-500 hover:bg-blue-600 text-white font-medium\">🚀 Start Planning</button>\n </div>\n </div>\n </div>\n </div>\n \\`;\n }\n\n // Planning progress modal\n if (state.showModal === 'planning') {\n const outputHtml = state.planningOutput.length > 0\n ? state.planningOutput.map(l => \\`<div class=\"log-line\">\\${highlightLog(l.text || l)}</div>\\`).join('')\n : '<div class=\"text-canvas-400 text-sm\">Analyzing codebase and generating tasks...</div>';\n\n return \\`\n <div class=\"modal-backdrop fixed inset-0 flex items-center justify-center z-50\">\n <div class=\"modal-content card rounded-xl w-full max-w-3xl mx-4 max-h-[80vh] flex flex-col\">\n <div class=\"px-6 py-4 border-b border-white/5 flex justify-between items-center flex-shrink-0\">\n <div class=\"flex items-center gap-3\">\n <span class=\"text-xl animate-pulse\">🎯</span>\n <div>\n <h3 class=\"font-display font-semibold text-canvas-800 text-lg\">Planning in Progress</h3>\n <p class=\"text-xs text-canvas-500 truncate max-w-[400px]\">\\${escapeHtml(state.planningGoal)}</p>\n </div>\n </div>\n <button onclick=\"if(confirm('Cancel planning?')) cancelPlanning();\"\n class=\"btn btn-ghost px-3 py-1.5 text-sm text-status-failed hover:bg-status-failed/10\">\n ⏹ Cancel\n </button>\n </div>\n <div class=\"flex-1 overflow-hidden p-4\">\n <div class=\"log-container h-full overflow-y-auto\" id=\"planning-log\">\n \\${outputHtml}\n </div>\n </div>\n </div>\n </div>\n \\`;\n }\n\n // Roadmap generation modal\n if (state.showModal === 'roadmap') {\n return renderRoadmapModal();\n }\n\n return '';\n}\n\nfunction renderSidePanel() {\n if (!state.sidePanel) return '';\n\n const task = state.tasks.find(t => t.id === state.sidePanel);\n if (!task) return '';\n\n const isRunning = state.running.includes(task.id);\n const output = state.taskOutput[task.id] || [];\n const startTime = state.taskStartTime[task.id];\n const lastExec = task.executionHistory?.[task.executionHistory.length - 1];\n const elapsed = isRunning && startTime ? formatElapsed(startTime) : null;\n\n return \\`\n <div class=\"side-panel\">\n <!-- Compact Header -->\n <div class=\"side-panel-header\" style=\"padding: 12px 16px;\">\n <div class=\"flex justify-between items-start gap-2\">\n <div class=\"flex-1 min-w-0\">\n <h2 class=\"font-semibold text-canvas-900 text-base leading-tight truncate\" title=\"\\${escapeHtml(task.title)}\">\\${escapeHtml(task.title)}</h2>\n <div class=\"flex items-center gap-2 mt-1.5 flex-wrap\">\n <span class=\"status-badge status-badge-\\${task.status}\" style=\"font-size: 11px; padding: 2px 8px;\">\n <span class=\"w-1.5 h-1.5 rounded-full bg-current \\${isRunning ? 'animate-pulse' : ''}\"></span>\n \\${task.status.replace('_', ' ')}\n </span>\n \\${elapsed ? \\`<span class=\"text-xs text-canvas-500\">⏱ \\${elapsed}</span>\\` : ''}\n <span class=\"text-xs text-canvas-400\">\\${task.priority} · \\${task.category}</span>\n </div>\n </div>\n <div class=\"flex items-center gap-0.5 flex-shrink-0\">\n \\${task.status === 'in_progress' ? \\`\n <button onclick=\"cancelTask('\\${task.id}')\" class=\"btn btn-ghost p-1.5 text-status-failed hover:bg-status-failed/10\" title=\"Stop\">\n ⏹\n </button>\n \\` : task.status === 'ready' ? \\`\n <button onclick=\"runTask('\\${task.id}')\" class=\"btn btn-ghost p-1.5 text-status-success hover:bg-status-success/10\" title=\"Run\">\n ▶\n </button>\n \\` : task.status === 'draft' ? \\`\n <button onclick=\"updateTask('\\${task.id}', { status: 'ready' })\" class=\"btn btn-ghost p-1.5 text-blue-500 hover:bg-blue-50\" title=\"Move to Ready\">\n →\n </button>\n \\` : task.status === 'failed' ? \\`\n <button onclick=\"retryTask('\\${task.id}')\" class=\"btn btn-ghost p-1.5 text-canvas-500 hover:bg-canvas-100\" title=\"Retry\">\n ↻\n </button>\n \\` : ''}\n <button onclick=\"state.editingTask = state.tasks.find(t => t.id === '\\${task.id}'); state.showModal = 'edit'; render();\"\n class=\"btn btn-ghost p-1.5 text-canvas-400 hover:text-canvas-600\" title=\"Edit\">\n ✏️\n </button>\n <button onclick=\"closeSidePanel()\" class=\"btn btn-ghost p-1.5 text-canvas-400 hover:text-canvas-600\" title=\"Close\">\n ✕\n </button>\n </div>\n </div>\n </div>\n\n <!-- Tabs (moved up, right after header) -->\n <div class=\"side-panel-tabs\" style=\"padding: 0 12px;\">\n <div class=\"side-panel-tab \\${state.sidePanelTab === 'logs' ? 'active' : ''}\" onclick=\"state.sidePanelTab = 'logs'; render();\">\n 📋 Logs\n </div>\n <div class=\"side-panel-tab \\${state.sidePanelTab === 'details' ? 'active' : ''}\" onclick=\"state.sidePanelTab = 'details'; render();\">\n 📄 Details\n </div>\n </div>\n\n <!-- Tab Content -->\n <div class=\"side-panel-body\" style=\"flex: 1; overflow: hidden; display: flex; flex-direction: column;\">\n \\${state.sidePanelTab === 'logs' ? \\`\n <div class=\"log-container\" id=\"side-panel-log\" style=\"flex: 1; overflow-y: auto;\">\n \\${output.length > 0\n ? output.map((l, i) => {\n const text = l.text || l;\n return \\`<div class=\"log-line\">\\${highlightLog(text)}</div>\\`;\n }).join('')\n : \\`<div class=\"text-canvas-400 text-sm\">\\${isRunning ? 'Waiting for output...' : 'No logs available. Run the task to see output.'}</div>\\`\n }\n </div>\n \\` : \\`\n <div style=\"flex: 1; overflow-y: auto; padding: 16px;\">\n <!-- Description -->\n <div class=\"mb-5\">\n <div class=\"text-xs font-semibold text-canvas-500 uppercase tracking-wide mb-2\">Description</div>\n <div class=\"text-sm text-canvas-700 leading-relaxed bg-canvas-50 rounded-lg p-3 border border-canvas-200\">\n \\${escapeHtml(task.description || 'No description provided.')}\n </div>\n </div>\n\n <!-- Steps -->\n \\${task.steps && task.steps.length > 0 ? \\`\n <div class=\"mb-5\">\n <div class=\"text-xs font-semibold text-canvas-500 uppercase tracking-wide mb-2\">Steps (\\${task.steps.length})</div>\n <div class=\"bg-canvas-50 rounded-lg border border-canvas-200 divide-y divide-canvas-200\">\n \\${task.steps.map((step, i) => \\`\n <div class=\"flex gap-3 p-3 text-sm\">\n <span class=\"flex-shrink-0 w-5 h-5 rounded-full bg-canvas-200 text-canvas-500 flex items-center justify-center text-xs font-medium\">\\${i + 1}</span>\n <span class=\"text-canvas-700\">\\${escapeHtml(step)}</span>\n </div>\n \\`).join('')}\n </div>\n </div>\n \\` : ''}\n\n <!-- Metadata -->\n <div class=\"mb-5 grid grid-cols-2 gap-3\">\n <div class=\"bg-canvas-50 rounded-lg p-3 border border-canvas-200\">\n <div class=\"text-xs text-canvas-500 mb-1\">Created</div>\n <div class=\"text-sm text-canvas-700\">\\${new Date(task.createdAt).toLocaleDateString()}</div>\n </div>\n <div class=\"bg-canvas-50 rounded-lg p-3 border border-canvas-200\">\n <div class=\"text-xs text-canvas-500 mb-1\">Updated</div>\n <div class=\"text-sm text-canvas-700\">\\${new Date(task.updatedAt).toLocaleDateString()}</div>\n </div>\n </div>\n\n <!-- Execution History -->\n \\${task.executionHistory && task.executionHistory.length > 0 ? \\`\n <div>\n <div class=\"text-xs font-semibold text-canvas-500 uppercase tracking-wide mb-2\">Execution History</div>\n <div class=\"space-y-2\">\n \\${task.executionHistory.slice(-5).reverse().map(exec => \\`\n <div class=\"bg-canvas-50 rounded-lg p-3 border border-canvas-200\">\n <div class=\"flex justify-between items-center\">\n <span class=\"text-sm font-medium \\${exec.status === 'completed' ? 'text-status-success' : 'text-status-failed'}\">\n \\${exec.status === 'completed' ? '✓' : '✗'} \\${exec.status}\n </span>\n <span class=\"text-xs text-canvas-500\">\\${Math.round(exec.duration / 1000)}s</span>\n </div>\n <div class=\"text-xs text-canvas-500 mt-1\">\\${new Date(exec.startedAt).toLocaleString()}</div>\n \\${exec.error ? \\`<div class=\"text-xs text-status-failed mt-2 bg-status-failed/5 rounded p-2\">\\${escapeHtml(exec.error)}</div>\\` : ''}\n </div>\n \\`).join('')}\n </div>\n </div>\n \\` : ''}\n </div>\n \\`}\n </div>\n </div>\n \\`;\n}\n\nfunction renderAFKBar() {\n if (!state.afk.running) return '';\n const progress = (state.afk.currentIteration / state.afk.maxIterations) * 100;\n return \\`\n <div class=\"bg-gradient-to-r from-status-success/10 to-status-success/5 border-b border-status-success/20 px-6 py-3\">\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-5\">\n <div class=\"flex items-center gap-2\">\n <span class=\"text-xl animate-pulse\">🔄</span>\n <span class=\"text-sm font-display font-semibold text-status-success\">AFK Mode Active</span>\n </div>\n <div class=\"h-4 w-px bg-status-success/20\"></div>\n <div class=\"flex items-center gap-4\">\n <div class=\"stat-pill border-status-success/20\">\n <span class=\"text-xs text-canvas-500\">Iteration</span>\n <span class=\"text-sm font-mono text-status-success\">\\${state.afk.currentIteration}/\\${state.afk.maxIterations}</span>\n </div>\n <div class=\"w-32 h-1.5 bg-canvas-200 rounded-full overflow-hidden\">\n <div class=\"h-full bg-gradient-to-r from-status-success to-status-success/70 rounded-full transition-all\" style=\"width: \\${progress}%\"></div>\n </div>\n </div>\n <div class=\"h-4 w-px bg-status-success/20\"></div>\n <div class=\"stat-pill border-status-success/20\">\n <span class=\"text-xs text-canvas-500\">Completed</span>\n <span class=\"text-sm font-mono text-status-success\">\\${state.afk.tasksCompleted}</span>\n </div>\n </div>\n <button onclick=\"stopAFK()\"\n class=\"btn text-sm px-4 py-1.5 bg-status-failed/15 hover:bg-status-failed/25 text-status-failed border border-status-failed/20\">\n ⏹ Stop AFK\n </button>\n </div>\n </div>\n \\`;\n}\n\nfunction renderStats() {\n const counts = {\n draft: state.tasks.filter(t => t.status === 'draft').length,\n ready: state.tasks.filter(t => t.status === 'ready').length,\n in_progress: state.tasks.filter(t => t.status === 'in_progress').length,\n completed: state.tasks.filter(t => t.status === 'completed').length,\n failed: state.tasks.filter(t => t.status === 'failed').length,\n };\n const total = state.tasks.length;\n\n return \\`\n <div class=\"flex items-center gap-3\">\n <div class=\"stat-pill\">\n <span class=\"text-xs text-canvas-500\">Total</span>\n <span class=\"text-sm font-semibold text-canvas-700\">\\${total}</span>\n </div>\n \\${counts.in_progress > 0 ? \\`\n <div class=\"stat-pill border-status-running/20\">\n <span class=\"stat-dot bg-status-running animate-pulse\"></span>\n <span class=\"text-xs text-status-running font-medium\">\\${counts.in_progress} running</span>\n </div>\n \\` : ''}\n \\${counts.completed > 0 ? \\`\n <div class=\"stat-pill border-status-success/20\">\n <span class=\"stat-dot bg-status-success\"></span>\n <span class=\"text-xs text-status-success\">\\${counts.completed} done</span>\n </div>\n \\` : ''}\n </div>\n \\`;\n}\n\n// Event handlers\nasync function handleTaskSubmit(e, isEdit) {\n e.preventDefault();\n const form = e.target;\n const data = new FormData(form);\n const action = e.submitter?.value || 'ready';\n\n const task = {\n title: data.get('title'),\n description: data.get('description'),\n category: data.get('category'),\n priority: data.get('priority'),\n steps: data.get('steps').split('\\\\n').filter(s => s.trim()),\n status: action === 'draft' ? 'draft' : 'ready'\n };\n\n if (isEdit && state.editingTask) {\n await updateTask(state.editingTask.id, task);\n } else {\n await createTask(task);\n }\n\n state.showModal = null;\n state.editingTask = null;\n render();\n}\n\nasync function handleAIGenerate() {\n const prompt = document.getElementById('ai-prompt').value;\n if (!prompt.trim()) return;\n\n state.aiPrompt = prompt;\n try {\n const task = await generateTask(prompt);\n state.editingTask = task;\n state.showModal = 'edit';\n state.aiPrompt = '';\n } catch (e) {\n alert('Failed to generate task: ' + e.message);\n }\n render();\n}\n\nfunction applyTemplate(templateId) {\n const template = state.templates.find(t => t.id === templateId);\n if (!template) return;\n\n state.editingTask = {\n title: template.titleTemplate,\n description: template.descriptionTemplate,\n category: template.category,\n priority: template.priority,\n steps: template.stepsTemplate,\n status: 'draft'\n };\n state.showModal = 'edit';\n render();\n}\n\nfunction handleStartAFK() {\n const iterations = parseInt(document.getElementById('afk-iterations').value) || 10;\n startAFK(iterations);\n state.showModal = null;\n render();\n}\n\nasync function handleStartPlanning() {\n const goal = document.getElementById('planning-goal').value;\n if (!goal.trim()) {\n showToast('Please enter a goal', 'warning');\n return;\n }\n try {\n await startPlanning(goal);\n // Modal will be shown when planning:started event is received\n } catch (e) {\n showToast('Failed to start planning: ' + e.message, 'error');\n }\n}\n\nfunction openPlanningModal() {\n state.showModal = 'plan-input';\n render();\n}\n\n// Filter tasks based on search query\nfunction filterTasks(tasks) {\n if (!state.searchQuery) return tasks;\n const query = state.searchQuery.toLowerCase();\n return tasks.filter(t =>\n t.title.toLowerCase().includes(query) ||\n t.description.toLowerCase().includes(query) ||\n t.category.toLowerCase().includes(query)\n );\n}\n\n// Utility\nfunction escapeHtml(str) {\n if (!str) return '';\n return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\n// Main render\nfunction render() {\n const app = document.getElementById('app');\n const hasSidePanel = state.sidePanel !== null && state.currentView === 'board';\n\n app.innerHTML = \\`\n <div class=\"min-h-screen flex flex-col bg-canvas-50\">\n <!-- Header -->\n <header class=\"sticky top-0 z-40 bg-white border-b border-canvas-200\">\n <div class=\"px-6 py-3 flex items-center justify-between\">\n <div class=\"flex items-center gap-6\">\n <h1 class=\"text-lg font-semibold text-canvas-900\">Claude Kanban</h1>\n <!-- Navigation Tabs -->\n <nav class=\"flex items-center gap-1 bg-canvas-100 rounded-lg p-1\">\n <button onclick=\"state.currentView = 'board'; render();\"\n class=\"px-3 py-1.5 text-sm rounded-md transition-colors \\${state.currentView === 'board' ? 'bg-white shadow-sm text-canvas-900 font-medium' : 'text-canvas-500 hover:text-canvas-700'}\">\n 📋 Board\n </button>\n <button onclick=\"state.currentView = 'roadmap'; render();\"\n class=\"px-3 py-1.5 text-sm rounded-md transition-colors \\${state.currentView === 'roadmap' ? 'bg-white shadow-sm text-canvas-900 font-medium' : 'text-canvas-500 hover:text-canvas-700'}\">\n 🗺️ Roadmap\n </button>\n </nav>\n \\${state.currentView === 'board' ? \\`\n <div class=\"flex items-center gap-2\">\n <input type=\"text\"\n placeholder=\"Search tasks...\"\n value=\"\\${escapeHtml(state.searchQuery)}\"\n oninput=\"state.searchQuery = this.value; render();\"\n class=\"input text-sm py-1.5 w-48\">\n </div>\n \\` : ''}\n </div>\n <div class=\"flex items-center gap-2\">\n \\${state.currentView === 'board' ? \\`\n <button onclick=\"openPlanningModal();\"\n class=\"btn px-4 py-2 text-sm bg-blue-500 hover:bg-blue-600 text-white \\${state.planning ? 'opacity-50 cursor-not-allowed' : ''}\"\n \\${state.planning ? 'disabled' : ''}\n title=\"AI Task Planner\">\n 🎯 \\${state.planning ? 'Planning...' : 'Plan'}\n </button>\n <button onclick=\"state.showModal = 'new'; render();\"\n class=\"btn btn-primary px-4 py-2 text-sm\">\n + Add Task\n </button>\n <button onclick=\"state.showModal = 'afk'; render();\"\n class=\"btn btn-ghost px-3 py-2 text-sm \\${state.afk.running ? 'text-status-success' : ''}\"\n title=\"AFK Mode\">\n 🔄 \\${state.afk.running ? 'AFK On' : 'AFK'}\n </button>\n \\` : \\`\n <button onclick=\"state.showModal = 'roadmap'; render();\"\n class=\"btn btn-primary px-4 py-2 text-sm \\${state.roadmapGenerating ? 'opacity-50 cursor-not-allowed' : ''}\"\n \\${state.roadmapGenerating ? 'disabled' : ''}>\n \\${state.roadmapGenerating ? '⏳ Generating...' : '🚀 Generate Roadmap'}\n </button>\n \\`}\n </div>\n </div>\n </header>\n\n \\${state.currentView === 'board' ? renderAFKBar() : ''}\n\n <!-- Main Content Area -->\n <div class=\"flex flex-1 overflow-hidden\">\n \\${state.currentView === 'board' ? \\`\n <!-- Kanban Board -->\n <main class=\"main-content overflow-x-auto p-6\">\n <div class=\"flex gap-4\">\n \\${renderColumn('draft', 'To Do', filterTasks(state.tasks))}\n \\${renderColumn('ready', 'Ready', filterTasks(state.tasks))}\n \\${renderColumn('in_progress', 'In Progress', filterTasks(state.tasks))}\n \\${renderColumn('completed', 'Done', filterTasks(state.tasks))}\n \\${renderColumn('failed', 'Failed', filterTasks(state.tasks))}\n </div>\n </main>\n <!-- Side Panel (pushes content when open) -->\n \\${hasSidePanel ? renderSidePanel() : ''}\n \\` : \\`\n <!-- Roadmap View -->\n \\${renderRoadmap()}\n \\`}\n </div>\n\n \\${renderModal()}\n </div>\n \\`;\n\n // Auto-scroll side panel logs\n if (hasSidePanel && state.sidePanelTab === 'logs' && state.autoScroll) {\n const logEl = document.getElementById('side-panel-log');\n if (logEl) logEl.scrollTop = logEl.scrollHeight;\n }\n\n // Update running task elapsed times every second\n if (state.running.length > 0 && !window.elapsedInterval) {\n window.elapsedInterval = setInterval(() => {\n if (state.running.length > 0) render();\n else {\n clearInterval(window.elapsedInterval);\n window.elapsedInterval = null;\n }\n }, 1000);\n }\n}\n\n// Expose to window for inline handlers\nwindow.state = state;\nwindow.render = render;\nwindow.createTask = createTask;\nwindow.updateTask = updateTask;\nwindow.deleteTask = deleteTask;\nwindow.runTask = runTask;\nwindow.cancelTask = cancelTask;\nwindow.retryTask = retryTask;\nwindow.generateTask = generateTask;\nwindow.startAFK = startAFK;\nwindow.stopAFK = stopAFK;\nwindow.startPlanning = startPlanning;\nwindow.cancelPlanning = cancelPlanning;\nwindow.handleStartPlanning = handleStartPlanning;\nwindow.openPlanningModal = openPlanningModal;\nwindow.handleDragStart = handleDragStart;\nwindow.handleDragEnd = handleDragEnd;\nwindow.handleDragOver = handleDragOver;\nwindow.handleDragLeave = handleDragLeave;\nwindow.handleDrop = handleDrop;\nwindow.handleTaskSubmit = handleTaskSubmit;\nwindow.handleAIGenerate = handleAIGenerate;\nwindow.applyTemplate = applyTemplate;\nwindow.handleStartAFK = handleStartAFK;\nwindow.showToast = showToast;\nwindow.scrollLogToBottom = scrollLogToBottom;\nwindow.copyLogToClipboard = copyLogToClipboard;\nwindow.clearLog = clearLog;\nwindow.closeLogTab = closeLogTab;\nwindow.toggleLogFullscreen = toggleLogFullscreen;\nwindow.escapeHtml = escapeHtml;\nwindow.openSidePanel = openSidePanel;\nwindow.closeSidePanel = closeSidePanel;\nwindow.showTaskMenu = showTaskMenu;\nwindow.filterTasks = filterTasks;\nwindow.scrollSidePanelLog = scrollSidePanelLog;\nwindow.generateRoadmap = generateRoadmap;\nwindow.cancelRoadmap = cancelRoadmap;\nwindow.addFeatureToKanban = addFeatureToKanban;\nwindow.deleteRoadmapFeature = deleteRoadmapFeature;\nwindow.loadRoadmap = loadRoadmap;\n\n// Keyboard shortcuts\ndocument.addEventListener('keydown', (e) => {\n // Ignore if typing in input\n if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;\n\n // ? - Show help\n if (e.key === '?') {\n showToast('Shortcuts: N=New, T=Templates, F=AFK, Esc=Close modal', 'info');\n }\n // n - New task\n if (e.key === 'n' && !e.metaKey && !e.ctrlKey) {\n state.showModal = 'new';\n render();\n }\n // t - Templates\n if (e.key === 't' && !e.metaKey && !e.ctrlKey) {\n state.showModal = 'templates';\n render();\n }\n // f - AFK mode\n if (e.key === 'f' && !e.metaKey && !e.ctrlKey && !state.afk.running) {\n state.showModal = 'afk';\n render();\n }\n // Escape - Close modal or side panel\n if (e.key === 'Escape') {\n if (state.showModal) {\n state.showModal = null;\n state.editingTask = null;\n render();\n } else if (state.sidePanel) {\n closeSidePanel();\n }\n }\n // / - Focus log search\n if (e.key === '/' && !state.showModal) {\n e.preventDefault();\n const searchInput = document.querySelector('#log-content ~ input, input[placeholder=\"Search logs...\"]');\n if (searchInput) searchInput.focus();\n }\n});\n\n// Initial render\nrender();\n`;\n}\n","import { spawn } from 'child_process';\nimport { join } from 'path';\nimport { writeFileSync, unlinkSync, mkdirSync, existsSync, appendFileSync, readFileSync } from 'fs';\nimport { EventEmitter } from 'events';\nimport type { Config, Task } from '../../types.js';\nimport { getConfig } from './project.js';\nimport { updateTask, addExecutionEntry, getTaskById, getNextReadyTask } from './prd.js';\nimport { logTaskExecution } from './progress.js';\n\nconst KANBAN_DIR = '.claude-kanban';\nconst LOGS_DIR = 'logs';\n\ninterface RunningTask {\n taskId: string;\n process: import('child_process').ChildProcess;\n startedAt: Date;\n output: string[];\n}\n\ninterface PlanningSession {\n goal: string;\n process: import('child_process').ChildProcess;\n startedAt: Date;\n output: string[];\n}\n\ninterface QASession {\n taskId: string;\n process: import('child_process').ChildProcess;\n startedAt: Date;\n output: string[];\n attempt: number;\n}\n\n/**\n * Task Executor - manages running tasks (one at a time)\n */\nexport class TaskExecutor extends EventEmitter {\n private projectPath: string;\n private runningTask: RunningTask | null = null;\n private planningSession: PlanningSession | null = null;\n private qaSession: QASession | null = null;\n private pendingQATaskId: string | null = null; // Task waiting for QA\n private qaRetryCount: Map<string, number> = new Map(); // Track QA retries per task\n private afkMode = false;\n private afkIteration = 0;\n private afkMaxIterations = 0;\n private afkTasksCompleted = 0;\n\n constructor(projectPath: string) {\n super();\n this.projectPath = projectPath;\n this.ensureLogsDir();\n }\n\n /**\n * Ensure logs directory exists\n */\n private ensureLogsDir(): void {\n const logsPath = join(this.projectPath, KANBAN_DIR, LOGS_DIR);\n if (!existsSync(logsPath)) {\n mkdirSync(logsPath, { recursive: true });\n }\n }\n\n /**\n * Get log file path for a task\n */\n private getLogFilePath(taskId: string): string {\n return join(this.projectPath, KANBAN_DIR, LOGS_DIR, `${taskId}.log`);\n }\n\n /**\n * Initialize log file for a task (clear existing)\n */\n private initLogFile(taskId: string): void {\n const logPath = this.getLogFilePath(taskId);\n writeFileSync(logPath, '');\n }\n\n /**\n * Append to task log file\n */\n private appendToLog(taskId: string, text: string): void {\n const logPath = this.getLogFilePath(taskId);\n appendFileSync(logPath, text);\n }\n\n /**\n * Read task log file\n */\n getTaskLog(taskId: string): string | null {\n const logPath = this.getLogFilePath(taskId);\n if (!existsSync(logPath)) return null;\n return readFileSync(logPath, 'utf-8');\n }\n\n /**\n * Format tool use for display in logs\n */\n private formatToolUse(toolName: string, input: Record<string, unknown>): string {\n const truncate = (str: string, maxLen = 80): string => {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen) + '...';\n };\n\n switch (toolName) {\n case 'Bash':\n return `[Bash] $ ${truncate(String(input.command || ''))}\\n`;\n case 'Read':\n return `[Read] ${input.file_path}\\n`;\n case 'Edit':\n return `[Edit] ${input.file_path}\\n`;\n case 'Write':\n return `[Write] ${input.file_path}\\n`;\n case 'Grep':\n return `[Grep] \"${truncate(String(input.pattern || ''))}\" in ${input.path || '.'}\\n`;\n case 'Glob':\n return `[Glob] ${input.pattern} in ${input.path || '.'}\\n`;\n case 'Task':\n return `[Task] ${input.description || truncate(String(input.prompt || ''))}\\n`;\n case 'TodoWrite':\n const todos = input.todos as Array<{ content: string; status: string }> | undefined;\n if (todos && Array.isArray(todos)) {\n const summary = todos.map(t => ` ${t.status === 'completed' ? '✓' : t.status === 'in_progress' ? '→' : '○'} ${truncate(t.content, 60)}`).join('\\n');\n return `[TodoWrite]\\n${summary}\\n`;\n }\n return `[TodoWrite]\\n`;\n case 'WebFetch':\n return `[WebFetch] ${input.url}\\n`;\n case 'WebSearch':\n return `[WebSearch] \"${truncate(String(input.query || ''))}\"\\n`;\n default:\n const meaningfulParams = ['file_path', 'path', 'command', 'query', 'url', 'pattern', 'target'];\n for (const param of meaningfulParams) {\n if (input[param]) {\n return `[${toolName}] ${truncate(String(input[param]))}\\n`;\n }\n }\n return `[${toolName}]\\n`;\n }\n }\n\n /**\n * Check if a task is currently running\n */\n isRunning(): boolean {\n return this.runningTask !== null;\n }\n\n /**\n * Get the currently running task ID\n */\n getRunningTaskId(): string | null {\n return this.runningTask?.taskId || null;\n }\n\n /**\n * Get running task output\n */\n getTaskOutput(taskId: string): string[] | undefined {\n if (this.runningTask?.taskId === taskId) {\n return this.runningTask.output;\n }\n return undefined;\n }\n\n /**\n * Check if a planning session is running\n */\n isPlanning(): boolean {\n return this.planningSession !== null;\n }\n\n /**\n * Check if executor is busy (task, planning, or QA running)\n */\n isBusy(): boolean {\n return this.runningTask !== null || this.planningSession !== null || this.qaSession !== null;\n }\n\n /**\n * Check if QA is running\n */\n isQARunning(): boolean {\n return this.qaSession !== null;\n }\n\n /**\n * Get planning session output\n */\n getPlanningOutput(): string[] | undefined {\n return this.planningSession?.output;\n }\n\n /**\n * Get planning session goal\n */\n getPlanningGoal(): string | undefined {\n return this.planningSession?.goal;\n }\n\n /**\n * Build the prompt for a task - simplified Ralph-style\n */\n private buildTaskPrompt(task: Task, config: Config): string {\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const prdPath = join(kanbanDir, 'prd.json');\n const progressPath = join(kanbanDir, 'progress.txt');\n\n const stepsText = task.steps.length > 0\n ? `\\nVerification steps:\\n${task.steps.map((s, i) => `${i + 1}. ${s}`).join('\\n')}`\n : '';\n\n // Build verification commands if configured\n const verifyCommands: string[] = [];\n if (config.project.typecheckCommand) {\n verifyCommands.push(config.project.typecheckCommand);\n }\n if (config.project.testCommand) {\n verifyCommands.push(config.project.testCommand);\n }\n\n const verifySection = verifyCommands.length > 0\n ? `3. Run quality checks:\\n${verifyCommands.map(cmd => ` - ${cmd}`).join('\\n')}\\n\\n4.`\n : '3.';\n\n return `You are an AI coding agent. Complete the following task:\n\n## TASK\nTitle: ${task.title}\nCategory: ${task.category}\nPriority: ${task.priority}\n\n${task.description}\n${stepsText}\n\n## INSTRUCTIONS\n\n1. Implement this task as described above.\n\n2. Make sure your changes work correctly.\n\n${verifySection} When complete, update the PRD file at ${prdPath}:\n - Find the task with id \"${task.id}\"\n - Set \"passes\": true\n - Set \"status\": \"completed\"\n\n${verifyCommands.length > 0 ? '5' : '4'}. Document your work in ${progressPath}:\n - What you implemented\n - Key decisions made\n - Any gotchas or important notes for future work\n\n${verifyCommands.length > 0 ? '6' : '5'}. Commit your changes with a descriptive message.\n\nFocus only on this task. When done, output: <promise>COMPLETE</promise>`;\n }\n\n /**\n * Build the prompt for a planning session\n */\n private buildPlanningPrompt(goal: string): string {\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const prdPath = join(kanbanDir, 'prd.json');\n\n return `You are an AI coding planner. Your job is to analyze a codebase and break down a user's goal into concrete, actionable tasks.\n\n## GOAL\n${goal}\n\n## INSTRUCTIONS\n\n1. Explore the codebase to understand:\n - Project structure and architecture\n - Existing patterns and conventions\n - Related existing code that you'll build upon\n - What technologies and frameworks are being used\n\n2. Break down the goal into 3-8 specific, actionable tasks that can each be completed in a single coding session.\n\n3. For each task, determine:\n - Clear, action-oriented title (start with a verb: Add, Create, Implement, Fix, etc.)\n - Detailed description with implementation guidance\n - Category: functional, ui, bug, enhancement, testing, or refactor\n - Priority: low, medium, high, or critical\n - 3-7 verification steps to confirm the task is complete\n\n4. Read the current PRD file at ${prdPath}, then update it:\n - Add your new tasks to the \"tasks\" array\n - Each task must have this structure:\n {\n \"id\": \"task_\" + 8 random alphanumeric characters,\n \"title\": \"...\",\n \"description\": \"...\",\n \"category\": \"functional|ui|bug|enhancement|testing|refactor\",\n \"priority\": \"low|medium|high|critical\",\n \"status\": \"draft\",\n \"steps\": [\"Step 1\", \"Step 2\", ...],\n \"passes\": false,\n \"createdAt\": ISO timestamp,\n \"updatedAt\": ISO timestamp,\n \"executionHistory\": []\n }\n\n5. Commit your changes with message: \"Plan: ${goal}\"\n\nIMPORTANT:\n- Tasks should be ordered logically (dependencies first)\n- Each task should be completable independently\n- Be specific about implementation details\n- Consider edge cases and error handling\n\nWhen done, output: <promise>PLANNING_COMPLETE</promise>`;\n }\n\n /**\n * Run a planning session to break down a goal into tasks\n */\n async runPlanningSession(goal: string): Promise<void> {\n // Block if busy\n if (this.isBusy()) {\n throw new Error('Another operation is in progress. Wait for it to complete or cancel it first.');\n }\n\n const config = getConfig(this.projectPath);\n const startedAt = new Date();\n const prompt = this.buildPlanningPrompt(goal);\n\n // Write prompt to temp file\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const promptFile = join(kanbanDir, 'prompt-planning.txt');\n writeFileSync(promptFile, prompt);\n\n // Build command arguments\n const args: string[] = [];\n if (config.agent.model) {\n args.push('--model', config.agent.model);\n }\n args.push('--permission-mode', config.agent.permissionMode);\n args.push('-p');\n args.push('--verbose');\n args.push('--output-format', 'stream-json');\n args.push(`@${promptFile}`);\n\n const commandDisplay = `${config.agent.command} ${args.join(' ')}`;\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n console.log('[executor] Planning command:', fullCommand);\n console.log('[executor] CWD:', this.projectPath);\n\n // Spawn Claude CLI\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: this.projectPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0',\n NO_COLOR: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.planningSession = {\n goal,\n process: childProcess,\n startedAt,\n output: [],\n };\n\n // Helper to log and emit\n const logOutput = (line: string) => {\n this.planningSession?.output.push(line);\n this.emit('planning:output', { line, lineType: 'stdout' });\n };\n\n // Emit started event\n this.emit('planning:started', { goal, timestamp: startedAt.toISOString() });\n logOutput(`[claude-kanban] Starting planning session\\n`);\n logOutput(`[claude-kanban] Goal: ${goal}\\n`);\n logOutput(`[claude-kanban] Command: ${commandDisplay}\\n`);\n\n // Handle stdout - parse stream-json format\n let stdoutBuffer = '';\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString();\n\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const json = JSON.parse(line);\n let text = '';\n\n if (json.type === 'assistant' && json.message?.content) {\n for (const block of json.message.content) {\n if (block.type === 'text') {\n text += block.text;\n } else if (block.type === 'tool_use') {\n text += this.formatToolUse(block.name, block.input);\n }\n }\n } else if (json.type === 'content_block_delta' && json.delta?.text) {\n text = json.delta.text;\n } else if (json.type === 'result' && json.result) {\n text = `\\n[Result: ${json.result}]\\n`;\n }\n\n if (text) {\n logOutput(text);\n }\n } catch {\n const cleanText = line.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, '');\n if (cleanText.trim()) {\n logOutput(cleanText + '\\n');\n }\n }\n }\n });\n\n // Handle stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n logOutput(`[stderr] ${text}`);\n });\n\n // Handle spawn error\n childProcess.on('error', (error) => {\n console.log('[executor] Planning spawn error:', error.message);\n this.emit('planning:output', { line: `[claude-kanban] Error: ${error.message}\\n`, lineType: 'stderr' });\n\n try { unlinkSync(promptFile); } catch {}\n this.emit('planning:failed', { error: error.message });\n this.planningSession = null;\n });\n\n // Handle process exit\n childProcess.on('close', (code, signal) => {\n console.log('[executor] Planning process closed with code:', code, 'signal:', signal);\n try { unlinkSync(promptFile); } catch {}\n logOutput(`[claude-kanban] Planning process exited with code ${code}\\n`);\n this.handlePlanningComplete(code);\n });\n\n // Set timeout (planning can take longer, give it 15 minutes)\n const timeoutMs = 15 * 60 * 1000;\n setTimeout(() => {\n if (this.planningSession) {\n this.cancelPlanning('Planning timeout exceeded');\n }\n }, timeoutMs);\n }\n\n /**\n * Handle planning session completion\n */\n private handlePlanningComplete(exitCode: number | null): void {\n if (!this.planningSession) return;\n\n const output = this.planningSession.output.join('');\n const isComplete = output.includes('<promise>PLANNING_COMPLETE</promise>');\n\n if (isComplete || exitCode === 0) {\n // Count new tasks by reading prd.json\n // We can't easily count tasks created, so just emit success\n this.emit('planning:completed', { success: true });\n } else {\n const error = `Planning process exited with code ${exitCode}`;\n this.emit('planning:failed', { error });\n }\n\n this.planningSession = null;\n }\n\n /**\n * Cancel the planning session\n */\n cancelPlanning(reason = 'Cancelled by user'): boolean {\n if (!this.planningSession) return false;\n\n const { process: childProcess } = this.planningSession;\n\n // Kill the process\n try {\n childProcess.kill('SIGTERM');\n setTimeout(() => {\n try {\n if (!childProcess.killed) {\n childProcess.kill('SIGKILL');\n }\n } catch {}\n }, 2000);\n } catch {}\n\n this.emit('planning:cancelled', { reason });\n this.planningSession = null;\n\n return true;\n }\n\n /**\n * Run a task\n */\n async runTask(taskId: string): Promise<void> {\n // Block if already running or planning\n if (this.isBusy()) {\n throw new Error('Another operation is in progress. Wait for it to complete or cancel it first.');\n }\n\n const config = getConfig(this.projectPath);\n const task = getTaskById(this.projectPath, taskId);\n\n if (!task) {\n throw new Error(`Task not found: ${taskId}`);\n }\n\n // Update task status\n updateTask(this.projectPath, taskId, { status: 'in_progress' });\n\n const startedAt = new Date();\n const prompt = this.buildTaskPrompt(task, config);\n\n // Write prompt to temp file\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const promptFile = join(kanbanDir, `prompt-${taskId}.txt`);\n writeFileSync(promptFile, prompt);\n\n // Build command arguments\n const args: string[] = [];\n if (config.agent.model) {\n args.push('--model', config.agent.model);\n }\n args.push('--permission-mode', config.agent.permissionMode);\n args.push('-p');\n args.push('--verbose');\n args.push('--output-format', 'stream-json');\n args.push(`@${promptFile}`);\n\n const commandDisplay = `${config.agent.command} ${args.join(' ')}`;\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n console.log('[executor] Command:', fullCommand);\n console.log('[executor] CWD:', this.projectPath);\n\n // Spawn Claude CLI\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: this.projectPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0',\n NO_COLOR: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.runningTask = {\n taskId,\n process: childProcess,\n startedAt,\n output: [],\n };\n\n // Initialize log file\n this.initLogFile(taskId);\n\n // Helper to log and emit\n const logOutput = (line: string) => {\n this.appendToLog(taskId, line);\n this.runningTask?.output.push(line);\n this.emit('task:output', { taskId, line, lineType: 'stdout' });\n };\n\n // Emit started event\n this.emit('task:started', { taskId, timestamp: startedAt.toISOString() });\n logOutput(`[claude-kanban] Starting task: ${task.title}\\n`);\n logOutput(`[claude-kanban] Command: ${commandDisplay}\\n`);\n logOutput(`[claude-kanban] Process spawned (PID: ${childProcess.pid})\\n`);\n\n // Handle stdout - parse stream-json format\n let stdoutBuffer = '';\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString();\n\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const json = JSON.parse(line);\n let text = '';\n\n if (json.type === 'assistant' && json.message?.content) {\n for (const block of json.message.content) {\n if (block.type === 'text') {\n text += block.text;\n } else if (block.type === 'tool_use') {\n text += this.formatToolUse(block.name, block.input);\n }\n }\n } else if (json.type === 'content_block_delta' && json.delta?.text) {\n text = json.delta.text;\n } else if (json.type === 'result' && json.result) {\n text = `\\n[Result: ${json.result}]\\n`;\n }\n\n if (text) {\n logOutput(text);\n }\n } catch {\n const cleanText = line.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, '');\n if (cleanText.trim()) {\n logOutput(cleanText + '\\n');\n }\n }\n }\n });\n\n // Handle stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n logOutput(`[stderr] ${text}`);\n });\n\n // Handle spawn error\n childProcess.on('error', (error) => {\n console.log('[executor] Spawn error:', error.message);\n this.emit('task:output', { taskId, line: `[claude-kanban] Error: ${error.message}\\n`, lineType: 'stderr' });\n\n try { unlinkSync(promptFile); } catch {}\n updateTask(this.projectPath, taskId, { status: 'failed', passes: false });\n const endedAt = new Date();\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'failed',\n duration: endedAt.getTime() - startedAt.getTime(),\n error: error.message,\n });\n this.emit('task:failed', { taskId, error: error.message });\n this.runningTask = null;\n });\n\n // Handle process exit\n childProcess.on('close', (code, signal) => {\n console.log('[executor] Process closed with code:', code, 'signal:', signal);\n try { unlinkSync(promptFile); } catch {}\n logOutput(`[claude-kanban] Process exited with code ${code}\\n`);\n this.handleTaskComplete(taskId, code, startedAt);\n });\n\n // Set timeout\n const timeoutMs = (config.execution.timeout || 30) * 60 * 1000;\n setTimeout(() => {\n if (this.runningTask?.taskId === taskId) {\n this.cancelTask('Timeout exceeded');\n }\n }, timeoutMs);\n }\n\n /**\n * Handle task completion\n */\n private handleTaskComplete(taskId: string, exitCode: number | null, startedAt: Date): void {\n if (!this.runningTask || this.runningTask.taskId !== taskId) return;\n\n const endedAt = new Date();\n const duration = endedAt.getTime() - startedAt.getTime();\n const output = this.runningTask.output.join('');\n const config = getConfig(this.projectPath);\n\n // Check if task was marked complete by Claude\n const isComplete = output.includes('<promise>COMPLETE</promise>');\n const task = getTaskById(this.projectPath, taskId);\n\n if (isComplete || exitCode === 0) {\n // Task completed successfully - check if QA is enabled\n if (config.execution.enableQA) {\n // Mark task as pending QA verification\n updateTask(this.projectPath, taskId, {\n status: 'in_progress', // Keep in progress until QA passes\n passes: false,\n });\n\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'completed',\n duration,\n });\n\n this.runningTask = null;\n this.pendingQATaskId = taskId;\n\n // Start QA verification\n this.runQAVerification(taskId).catch((error) => {\n console.error('QA verification error:', error);\n this.handleQAComplete(taskId, false, ['QA process failed to start']);\n });\n } else {\n // No QA - mark as completed\n updateTask(this.projectPath, taskId, {\n status: 'completed',\n passes: true,\n });\n\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'completed',\n duration,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'completed',\n duration,\n });\n\n this.emit('task:completed', { taskId, duration });\n this.afkTasksCompleted++;\n this.runningTask = null;\n\n // Continue AFK mode if active\n if (this.afkMode) {\n this.continueAFKMode();\n }\n }\n } else {\n // Task failed\n updateTask(this.projectPath, taskId, {\n status: 'failed',\n passes: false,\n });\n\n const error = `Process exited with code ${exitCode}`;\n\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'failed',\n duration,\n error,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'failed',\n duration,\n error,\n });\n\n this.emit('task:failed', { taskId, error });\n this.runningTask = null;\n\n // Continue AFK mode if active\n if (this.afkMode) {\n this.continueAFKMode();\n }\n }\n }\n\n /**\n * Build the QA verification prompt\n */\n private buildQAPrompt(task: Task, config: Config): string {\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const prdPath = join(kanbanDir, 'prd.json');\n\n const stepsText = task.steps.length > 0\n ? `\\nVerification steps:\\n${task.steps.map((s, i) => `${i + 1}. ${s}`).join('\\n')}`\n : '';\n\n // Build verification commands\n const verifyCommands: string[] = [];\n if (config.project.typecheckCommand) {\n verifyCommands.push(config.project.typecheckCommand);\n }\n if (config.project.testCommand) {\n verifyCommands.push(config.project.testCommand);\n }\n\n return `You are a QA reviewer. Verify that the following task has been correctly implemented.\n\n## TASK TO VERIFY\nTitle: ${task.title}\nCategory: ${task.category}\nPriority: ${task.priority}\n\n${task.description}\n${stepsText}\n\n## YOUR JOB\n\n1. Review the recent git commits and changes made for this task.\n\n2. Check that all verification steps (if any) have been satisfied.\n\n3. Run the following quality checks:\n${verifyCommands.length > 0 ? verifyCommands.map(cmd => ` - ${cmd}`).join('\\n') : ' (No verification commands configured)'}\n\n4. Check that the implementation meets the task requirements.\n\n5. If everything passes, output: <qa>PASSED</qa>\n\n6. If there are issues, output: <qa>FAILED</qa>\n Then list the issues that need to be fixed:\n <issues>\n - Issue 1\n - Issue 2\n </issues>\n\nBe thorough but fair. Only fail the QA if there are real issues that affect functionality or quality.`;\n }\n\n /**\n * Run QA verification for a task\n */\n async runQAVerification(taskId: string): Promise<void> {\n const config = getConfig(this.projectPath);\n const task = getTaskById(this.projectPath, taskId);\n\n if (!task) {\n throw new Error(`Task not found: ${taskId}`);\n }\n\n const currentRetries = this.qaRetryCount.get(taskId) || 0;\n const attempt = currentRetries + 1;\n\n const startedAt = new Date();\n const prompt = this.buildQAPrompt(task, config);\n\n // Write prompt to temp file\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const promptFile = join(kanbanDir, `qa-${taskId}.txt`);\n writeFileSync(promptFile, prompt);\n\n // Build command arguments\n const args: string[] = [];\n if (config.agent.model) {\n args.push('--model', config.agent.model);\n }\n args.push('--permission-mode', config.agent.permissionMode);\n args.push('-p');\n args.push('--verbose');\n args.push('--output-format', 'stream-json');\n args.push(`@${promptFile}`);\n\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n console.log('[executor] QA command:', fullCommand);\n\n // Spawn Claude CLI for QA\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: this.projectPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0',\n NO_COLOR: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.qaSession = {\n taskId,\n process: childProcess,\n startedAt,\n output: [],\n attempt,\n };\n\n // Emit QA started event\n this.emit('qa:started', { taskId, attempt });\n\n // Helper to log QA output\n const logOutput = (line: string) => {\n this.qaSession?.output.push(line);\n this.emit('qa:output', { taskId, line });\n };\n\n logOutput(`[claude-kanban] Starting QA verification (attempt ${attempt})\\n`);\n\n // Handle stdout - parse stream-json format\n let stdoutBuffer = '';\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString();\n\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const json = JSON.parse(line);\n let text = '';\n\n if (json.type === 'assistant' && json.message?.content) {\n for (const block of json.message.content) {\n if (block.type === 'text') {\n text += block.text;\n } else if (block.type === 'tool_use') {\n text += this.formatToolUse(block.name, block.input);\n }\n }\n } else if (json.type === 'content_block_delta' && json.delta?.text) {\n text = json.delta.text;\n }\n\n if (text) {\n logOutput(text);\n }\n } catch {\n const cleanText = line.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, '');\n if (cleanText.trim()) {\n logOutput(cleanText + '\\n');\n }\n }\n }\n });\n\n // Handle stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n logOutput(`[stderr] ${text}`);\n });\n\n // Handle spawn error\n childProcess.on('error', (error) => {\n console.log('[executor] QA spawn error:', error.message);\n try { unlinkSync(promptFile); } catch {}\n this.handleQAComplete(taskId, false, [`QA process error: ${error.message}`]);\n });\n\n // Handle process exit\n childProcess.on('close', (code) => {\n console.log('[executor] QA process closed with code:', code);\n try { unlinkSync(promptFile); } catch {}\n this.parseQAResult(taskId);\n });\n\n // Set timeout for QA (5 minutes)\n const timeoutMs = 5 * 60 * 1000;\n setTimeout(() => {\n if (this.qaSession?.taskId === taskId) {\n this.cancelQA('QA timeout exceeded');\n }\n }, timeoutMs);\n }\n\n /**\n * Parse QA result from output\n */\n private parseQAResult(taskId: string): void {\n if (!this.qaSession || this.qaSession.taskId !== taskId) return;\n\n const output = this.qaSession.output.join('');\n\n // Check for QA result\n const passedMatch = output.includes('<qa>PASSED</qa>');\n const failedMatch = output.includes('<qa>FAILED</qa>');\n\n if (passedMatch) {\n this.handleQAComplete(taskId, true, []);\n } else if (failedMatch) {\n // Extract issues\n const issuesMatch = output.match(/<issues>([\\s\\S]*?)<\\/issues>/);\n const issues: string[] = [];\n if (issuesMatch) {\n const issueLines = issuesMatch[1].split('\\n');\n for (const line of issueLines) {\n const trimmed = line.trim();\n if (trimmed.startsWith('-')) {\n issues.push(trimmed.substring(1).trim());\n } else if (trimmed) {\n issues.push(trimmed);\n }\n }\n }\n this.handleQAComplete(taskId, false, issues.length > 0 ? issues : ['QA verification failed']);\n } else {\n // No clear result - treat as failed\n this.handleQAComplete(taskId, false, ['QA did not provide a clear PASSED/FAILED result']);\n }\n }\n\n /**\n * Handle QA completion\n */\n private handleQAComplete(taskId: string, passed: boolean, issues: string[]): void {\n const config = getConfig(this.projectPath);\n const task = getTaskById(this.projectPath, taskId);\n const currentRetries = this.qaRetryCount.get(taskId) || 0;\n\n this.qaSession = null;\n this.pendingQATaskId = null;\n\n if (passed) {\n // QA passed - mark task as completed\n updateTask(this.projectPath, taskId, {\n status: 'completed',\n passes: true,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'completed',\n duration: 0,\n });\n\n this.emit('qa:passed', { taskId });\n this.emit('task:completed', { taskId, duration: 0 });\n this.afkTasksCompleted++;\n this.qaRetryCount.delete(taskId);\n } else {\n // QA failed - check if we should retry\n const willRetry = currentRetries < config.execution.qaMaxRetries;\n\n this.emit('qa:failed', { taskId, issues, willRetry });\n\n if (willRetry) {\n // Increment retry count and run fix + QA again\n this.qaRetryCount.set(taskId, currentRetries + 1);\n\n // Run task again to fix issues\n console.log(`[executor] QA failed, retrying (${currentRetries + 1}/${config.execution.qaMaxRetries})`);\n this.runTaskWithQAFix(taskId, issues).catch((error) => {\n console.error('QA fix error:', error);\n this.handleQAComplete(taskId, false, ['Failed to run QA fix']);\n });\n } else {\n // Max retries reached - mark as failed\n updateTask(this.projectPath, taskId, {\n status: 'failed',\n passes: false,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'failed',\n duration: 0,\n error: `QA failed after ${config.execution.qaMaxRetries} retries: ${issues.join(', ')}`,\n });\n\n this.emit('task:failed', { taskId, error: `QA failed: ${issues.join(', ')}` });\n this.qaRetryCount.delete(taskId);\n }\n }\n\n // Continue AFK mode if active\n if (this.afkMode && !this.isBusy()) {\n this.continueAFKMode();\n }\n }\n\n /**\n * Run task again with QA fix instructions\n */\n private async runTaskWithQAFix(taskId: string, issues: string[]): Promise<void> {\n const config = getConfig(this.projectPath);\n const task = getTaskById(this.projectPath, taskId);\n\n if (!task) {\n throw new Error(`Task not found: ${taskId}`);\n }\n\n const startedAt = new Date();\n\n // Build fix prompt\n const issuesText = issues.map((i, idx) => `${idx + 1}. ${i}`).join('\\n');\n const prompt = `You are fixing issues found during QA verification for a task.\n\n## ORIGINAL TASK\nTitle: ${task.title}\n${task.description}\n\n## QA ISSUES TO FIX\n${issuesText}\n\n## INSTRUCTIONS\n1. Review the issues reported by QA.\n2. Fix each issue.\n3. Run verification commands to ensure fixes work.\n4. When done, output: <promise>COMPLETE</promise>\n\nFocus only on fixing the reported issues, don't make unrelated changes.`;\n\n // Write prompt to temp file\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n const promptFile = join(kanbanDir, `qafix-${taskId}.txt`);\n writeFileSync(promptFile, prompt);\n\n // Build command\n const args: string[] = [];\n if (config.agent.model) {\n args.push('--model', config.agent.model);\n }\n args.push('--permission-mode', config.agent.permissionMode);\n args.push('-p');\n args.push('--verbose');\n args.push('--output-format', 'stream-json');\n args.push(`@${promptFile}`);\n\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n // Spawn fix process\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: this.projectPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0',\n NO_COLOR: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.runningTask = {\n taskId,\n process: childProcess,\n startedAt,\n output: [],\n };\n\n this.emit('task:started', { taskId, timestamp: startedAt.toISOString() });\n\n // Handle output\n let stdoutBuffer = '';\n childProcess.stdout?.on('data', (data: Buffer) => {\n stdoutBuffer += data.toString();\n\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const json = JSON.parse(line);\n let text = '';\n\n if (json.type === 'assistant' && json.message?.content) {\n for (const block of json.message.content) {\n if (block.type === 'text') {\n text += block.text;\n } else if (block.type === 'tool_use') {\n text += this.formatToolUse(block.name, block.input);\n }\n }\n } else if (json.type === 'content_block_delta' && json.delta?.text) {\n text = json.delta.text;\n }\n\n if (text) {\n this.runningTask?.output.push(text);\n this.emit('task:output', { taskId, line: text, lineType: 'stdout' });\n }\n } catch {\n const cleanText = line.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, '');\n if (cleanText.trim()) {\n this.runningTask?.output.push(cleanText + '\\n');\n this.emit('task:output', { taskId, line: cleanText + '\\n', lineType: 'stdout' });\n }\n }\n }\n });\n\n childProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n this.runningTask?.output.push(`[stderr] ${text}`);\n this.emit('task:output', { taskId, line: `[stderr] ${text}`, lineType: 'stderr' });\n });\n\n childProcess.on('error', (error) => {\n try { unlinkSync(promptFile); } catch {}\n this.handleQAComplete(taskId, false, [`Fix process error: ${error.message}`]);\n });\n\n childProcess.on('close', (code) => {\n try { unlinkSync(promptFile); } catch {}\n\n const output = this.runningTask?.output.join('') || '';\n const isComplete = output.includes('<promise>COMPLETE</promise>');\n\n this.runningTask = null;\n\n if (isComplete || code === 0) {\n // Run QA again\n this.runQAVerification(taskId).catch((error) => {\n console.error('QA verification error after fix:', error);\n this.handleQAComplete(taskId, false, ['QA verification failed after fix']);\n });\n } else {\n this.handleQAComplete(taskId, false, [`Fix process exited with code ${code}`]);\n }\n });\n }\n\n /**\n * Cancel QA verification\n */\n cancelQA(reason = 'Cancelled'): boolean {\n if (!this.qaSession) return false;\n\n const { taskId, process: childProcess } = this.qaSession;\n\n try {\n childProcess.kill('SIGTERM');\n setTimeout(() => {\n try {\n if (!childProcess.killed) {\n childProcess.kill('SIGKILL');\n }\n } catch {}\n }, 2000);\n } catch {}\n\n this.emit('qa:failed', { taskId, issues: [reason], willRetry: false });\n this.qaSession = null;\n this.pendingQATaskId = null;\n\n return true;\n }\n\n /**\n * Cancel the running task\n */\n cancelTask(reason = 'Cancelled by user'): boolean {\n if (!this.runningTask) return false;\n\n const { taskId, process: childProcess, startedAt } = this.runningTask;\n const endedAt = new Date();\n const duration = endedAt.getTime() - startedAt.getTime();\n const task = getTaskById(this.projectPath, taskId);\n\n // Kill the process\n try {\n childProcess.kill('SIGTERM');\n setTimeout(() => {\n try {\n if (!childProcess.killed) {\n childProcess.kill('SIGKILL');\n }\n } catch {}\n }, 2000);\n } catch {}\n\n // Update task status back to ready\n updateTask(this.projectPath, taskId, { status: 'ready' });\n\n addExecutionEntry(this.projectPath, taskId, {\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n status: 'cancelled',\n duration,\n error: reason,\n });\n\n logTaskExecution(this.projectPath, {\n taskId,\n taskTitle: task?.title || 'Unknown',\n status: 'cancelled',\n duration,\n error: reason,\n });\n\n this.emit('task:cancelled', { taskId });\n this.runningTask = null;\n\n return true;\n }\n\n /**\n * Start AFK mode - run tasks sequentially until done\n */\n startAFKMode(maxIterations: number): void {\n if (this.afkMode) {\n throw new Error('AFK mode already running');\n }\n\n if (this.isBusy()) {\n throw new Error('Cannot start AFK mode while another operation is in progress');\n }\n\n this.afkMode = true;\n this.afkIteration = 0;\n this.afkMaxIterations = maxIterations;\n this.afkTasksCompleted = 0;\n\n this.emitAFKStatus();\n this.continueAFKMode();\n }\n\n /**\n * Continue AFK mode - pick next task\n */\n private continueAFKMode(): void {\n if (!this.afkMode) return;\n\n // Check if we've reached max iterations\n if (this.afkIteration >= this.afkMaxIterations) {\n this.stopAFKMode();\n return;\n }\n\n // Don't start another if busy\n if (this.isBusy()) return;\n\n const nextTask = getNextReadyTask(this.projectPath);\n if (!nextTask) {\n // No more tasks to run\n this.stopAFKMode();\n return;\n }\n\n this.afkIteration++;\n this.runTask(nextTask.id).catch((error) => {\n console.error('AFK task error:', error);\n });\n\n this.emitAFKStatus();\n }\n\n /**\n * Stop AFK mode\n */\n stopAFKMode(): void {\n this.afkMode = false;\n this.emitAFKStatus();\n }\n\n /**\n * Emit AFK status\n */\n private emitAFKStatus(): void {\n this.emit('afk:status', {\n running: this.afkMode,\n currentIteration: this.afkIteration,\n maxIterations: this.afkMaxIterations,\n tasksCompleted: this.afkTasksCompleted,\n });\n }\n\n /**\n * Get AFK status\n */\n getAFKStatus(): {\n running: boolean;\n currentIteration: number;\n maxIterations: number;\n tasksCompleted: number;\n } {\n return {\n running: this.afkMode,\n currentIteration: this.afkIteration,\n maxIterations: this.afkMaxIterations,\n tasksCompleted: this.afkTasksCompleted,\n };\n }\n\n /**\n * Cancel running task/planning/QA and stop AFK mode\n */\n cancelAll(): void {\n if (this.runningTask) {\n try {\n this.runningTask.process.kill('SIGKILL');\n } catch {}\n this.runningTask = null;\n }\n if (this.planningSession) {\n try {\n this.planningSession.process.kill('SIGKILL');\n } catch {}\n this.planningSession = null;\n }\n if (this.qaSession) {\n try {\n this.qaSession.process.kill('SIGKILL');\n } catch {}\n this.qaSession = null;\n }\n this.pendingQATaskId = null;\n this.qaRetryCount.clear();\n this.stopAFKMode();\n }\n}\n","import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';\nimport { join, basename } from 'path';\nimport type { PRD, Config } from '../../types.js';\n\nconst KANBAN_DIR = '.claude-kanban';\nconst SCRIPTS_DIR = 'scripts';\n\n/**\n * Check if project has been initialized\n */\nexport async function isProjectInitialized(projectPath: string): Promise<boolean> {\n const kanbanDir = join(projectPath, KANBAN_DIR);\n const prdPath = join(kanbanDir, 'prd.json');\n const configPath = join(kanbanDir, 'config.json');\n\n return existsSync(kanbanDir) && existsSync(prdPath) && existsSync(configPath);\n}\n\n/**\n * Detect package manager from lock files\n */\nexport function detectPackageManager(projectPath: string): 'pnpm' | 'yarn' | 'bun' | 'npm' {\n if (existsSync(join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(join(projectPath, 'yarn.lock'))) return 'yarn';\n if (existsSync(join(projectPath, 'bun.lockb'))) return 'bun';\n return 'npm';\n}\n\n/**\n * Detect project type\n */\nexport type ProjectType = 'node' | 'python' | 'php' | 'ruby' | 'go' | 'rust' | 'unknown';\n\nexport function detectProjectType(projectPath: string): ProjectType {\n // Check for project type indicators (order matters - more specific first)\n if (existsSync(join(projectPath, 'package.json'))) return 'node';\n if (existsSync(join(projectPath, 'pyproject.toml')) ||\n existsSync(join(projectPath, 'requirements.txt')) ||\n existsSync(join(projectPath, 'setup.py'))) return 'python';\n if (existsSync(join(projectPath, 'composer.json'))) return 'php';\n if (existsSync(join(projectPath, 'Gemfile'))) return 'ruby';\n if (existsSync(join(projectPath, 'go.mod'))) return 'go';\n if (existsSync(join(projectPath, 'Cargo.toml'))) return 'rust';\n return 'unknown';\n}\n\n/**\n * Detect project type and common commands\n */\nexport function detectProjectCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n const projectType = detectProjectType(projectPath);\n\n switch (projectType) {\n case 'node':\n return detectNodeCommands(projectPath);\n case 'python':\n return detectPythonCommands(projectPath);\n case 'php':\n return detectPHPCommands(projectPath);\n case 'ruby':\n return detectRubyCommands(projectPath);\n case 'go':\n return detectGoCommands(projectPath);\n case 'rust':\n return detectRustCommands(projectPath);\n default:\n return {\n testCommand: '',\n typecheckCommand: '',\n buildCommand: '',\n };\n }\n}\n\n/**\n * Detect Node.js project commands\n */\nfunction detectNodeCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n const pm = detectPackageManager(projectPath);\n const run = pm === 'npm' ? 'npm run' : pm;\n\n // Check for package.json scripts\n const packageJsonPath = join(projectPath, 'package.json');\n let scripts: Record<string, string> = {};\n\n if (existsSync(packageJsonPath)) {\n try {\n const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n scripts = pkg.scripts || {};\n } catch {\n // Ignore parse errors\n }\n }\n\n // Determine commands based on available scripts\n const testCommand = scripts.test ? `${run} test` : '';\n const typecheckCommand = scripts.typecheck\n ? `${run} typecheck`\n : scripts['type-check']\n ? `${run} type-check`\n : existsSync(join(projectPath, 'tsconfig.json'))\n ? `${run === 'npm run' ? 'npx' : pm} tsc --noEmit`\n : '';\n const buildCommand = scripts.build ? `${run} build` : '';\n\n return { testCommand, typecheckCommand, buildCommand };\n}\n\n/**\n * Detect Python project commands\n */\nfunction detectPythonCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n // Check for pytest\n const hasPytest = existsSync(join(projectPath, 'pytest.ini')) ||\n existsSync(join(projectPath, 'pyproject.toml')) ||\n existsSync(join(projectPath, 'tests'));\n\n // Check for mypy\n const hasMypy = existsSync(join(projectPath, 'mypy.ini')) ||\n existsSync(join(projectPath, '.mypy.ini'));\n\n return {\n testCommand: hasPytest ? 'pytest' : '',\n typecheckCommand: hasMypy ? 'mypy .' : '',\n buildCommand: '',\n };\n}\n\n/**\n * Detect PHP project commands\n */\nfunction detectPHPCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n const hasPhpunit = existsSync(join(projectPath, 'phpunit.xml')) ||\n existsSync(join(projectPath, 'phpunit.xml.dist'));\n const hasPest = existsSync(join(projectPath, 'tests', 'Pest.php'));\n const hasPhpstan = existsSync(join(projectPath, 'phpstan.neon')) ||\n existsSync(join(projectPath, 'phpstan.neon.dist'));\n\n let testCommand = '';\n if (hasPest) {\n testCommand = './vendor/bin/pest';\n } else if (hasPhpunit) {\n testCommand = './vendor/bin/phpunit';\n }\n\n return {\n testCommand,\n typecheckCommand: hasPhpstan ? './vendor/bin/phpstan analyse' : '',\n buildCommand: '',\n };\n}\n\n/**\n * Detect Ruby project commands\n */\nfunction detectRubyCommands(projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n const hasRspec = existsSync(join(projectPath, 'spec')) ||\n existsSync(join(projectPath, '.rspec'));\n const hasMinitest = existsSync(join(projectPath, 'test'));\n\n return {\n testCommand: hasRspec ? 'bundle exec rspec' : hasMinitest ? 'bundle exec rake test' : '',\n typecheckCommand: '', // Ruby doesn't have built-in typechecking (sorbet is optional)\n buildCommand: '',\n };\n}\n\n/**\n * Detect Go project commands\n */\nfunction detectGoCommands(_projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n return {\n testCommand: 'go test ./...',\n typecheckCommand: 'go build ./...', // Go's compiler is the typechecker\n buildCommand: 'go build',\n };\n}\n\n/**\n * Detect Rust project commands\n */\nfunction detectRustCommands(_projectPath: string): {\n testCommand: string;\n typecheckCommand: string;\n buildCommand: string;\n} {\n return {\n testCommand: 'cargo test',\n typecheckCommand: 'cargo check',\n buildCommand: 'cargo build',\n };\n}\n\n/**\n * Create initial PRD\n */\nfunction createInitialPRD(projectName: string): PRD {\n return {\n version: '1.0',\n projectName,\n tasks: [],\n };\n}\n\n/**\n * Create initial config\n */\nfunction createInitialConfig(projectPath: string): Config {\n const { testCommand, typecheckCommand, buildCommand } = detectProjectCommands(projectPath);\n\n return {\n version: '1.0',\n agent: {\n command: 'claude',\n permissionMode: 'acceptEdits',\n model: null,\n },\n project: {\n testCommand,\n typecheckCommand,\n buildCommand,\n },\n ui: {\n port: 4242,\n theme: 'system',\n },\n execution: {\n maxConcurrent: 3,\n timeout: 30, // 30 minutes\n enableQA: false, // QA verification disabled by default\n qaMaxRetries: 2, // Max QA retry attempts\n },\n };\n}\n\n/**\n * Create ralph.sh script\n */\nfunction createRalphScript(config: Config): string {\n const { testCommand, typecheckCommand } = config.project;\n\n return `#!/bin/bash\nset -e\n\nif [ -z \"$1\" ]; then\n echo \"Usage: $0 <iterations>\"\n exit 1\nfi\n\nKANBAN_DIR=\".claude-kanban\"\n\nfor ((i=1; i<=$1; i++)); do\n echo \"\"\n echo \"=== Iteration $i of $1 ===\"\n echo \"\"\n\n result=$(${config.agent.command} --permission-mode ${config.agent.permissionMode} -p \\\\\n \"@$KANBAN_DIR/prd.json\" \\\\\n \"@$KANBAN_DIR/progress.txt\" \\\\\n \"You are working on tasks from a Kanban board. Follow these steps:\n\n1. Read the PRD to find the highest-priority task with status 'ready'.\n Priority order: critical > high > medium > low\n If multiple tasks have the same priority, pick the first one.\n\n2. Work on ONLY that single task. Do not touch other tasks.\n\n3. Check that types pass via: ${typecheckCommand}\n Check that tests pass via: ${testCommand}\n\n4. When the task is complete:\n - Update the task's 'passes' field to true\n - Update the task's 'status' field to 'completed'\n\n5. Append your progress to progress.txt with:\n - Date and time\n - Task ID and title\n - What you implemented\n - Files changed\n - Any notes for future work\n\n6. Make a git commit with a descriptive message.\n\nIMPORTANT: Only work on ONE task per iteration.\n\nIf all tasks with status 'ready' are complete (passes: true), output <promise>COMPLETE</promise>.\")\n\n echo \"$result\"\n\n if [[ \"$result\" == *\"<promise>COMPLETE</promise>\"* ]]; then\n echo \"\"\n echo \"=== All tasks complete! ===\"\n exit 0\n fi\ndone\n\necho \"\"\necho \"=== Completed $1 iterations ===\"\n`;\n}\n\n/**\n * Create ralph-once.sh script\n */\nfunction createRalphOnceScript(config: Config): string {\n const { testCommand, typecheckCommand } = config.project;\n\n return `#!/bin/bash\nset -e\n\nKANBAN_DIR=\".claude-kanban\"\nTASK_ID=\"$1\"\n\nif [ -z \"$TASK_ID\" ]; then\n # No task ID specified, let Claude pick\n ${config.agent.command} --permission-mode ${config.agent.permissionMode} -p \\\\\n \"@$KANBAN_DIR/prd.json\" \\\\\n \"@$KANBAN_DIR/progress.txt\" \\\\\n \"You are working on tasks from a Kanban board. Follow these steps:\n\n1. Read the PRD to find the highest-priority task with status 'ready'.\n Priority order: critical > high > medium > low\n\n2. Work on ONLY that single task.\n\n3. Check that types pass via: ${typecheckCommand}\n Check that tests pass via: ${testCommand}\n\n4. When complete:\n - Update the task's 'passes' field to true\n - Update the task's 'status' field to 'completed'\n\n5. Append progress to progress.txt.\n\n6. Make a git commit.\n\nIf all 'ready' tasks are complete, output <promise>COMPLETE</promise>.\"\nelse\n # Specific task ID provided\n ${config.agent.command} --permission-mode ${config.agent.permissionMode} -p \\\\\n \"@$KANBAN_DIR/prd.json\" \\\\\n \"@$KANBAN_DIR/progress.txt\" \\\\\n \"You are working on a specific task from the Kanban board.\n\nTASK ID: $TASK_ID\n\nFind this task in the PRD and work on it. Follow these steps:\n\n1. Locate the task with id '$TASK_ID' in the PRD.\n\n2. Implement the feature as described.\n\n3. Check that types pass via: ${typecheckCommand}\n Check that tests pass via: ${testCommand}\n\n4. When complete:\n - Update the task's 'passes' field to true\n - Update the task's 'status' field to 'completed'\n\n5. Append progress to progress.txt.\n\n6. Make a git commit.\n\nIf the task is complete, output <promise>COMPLETE</promise>.\"\nfi\n`;\n}\n\n/**\n * Initialize the project\n */\nexport async function initializeProject(projectPath: string, reset = false): Promise<void> {\n const kanbanDir = join(projectPath, KANBAN_DIR);\n const scriptsDir = join(projectPath, SCRIPTS_DIR);\n const projectName = basename(projectPath);\n\n // Create directories\n if (!existsSync(kanbanDir)) {\n mkdirSync(kanbanDir, { recursive: true });\n }\n if (!existsSync(scriptsDir)) {\n mkdirSync(scriptsDir, { recursive: true });\n }\n\n // Create or reset PRD\n const prdPath = join(kanbanDir, 'prd.json');\n if (!existsSync(prdPath) || reset) {\n const prd = createInitialPRD(projectName);\n writeFileSync(prdPath, JSON.stringify(prd, null, 2));\n }\n\n // Create config (preserve existing if not reset)\n const configPath = join(kanbanDir, 'config.json');\n let config: Config;\n if (!existsSync(configPath)) {\n config = createInitialConfig(projectPath);\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n } else {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n\n // Create or update progress.txt\n const progressPath = join(kanbanDir, 'progress.txt');\n if (!existsSync(progressPath)) {\n const date = new Date().toISOString().split('T')[0];\n writeFileSync(progressPath, `# Claude Kanban Progress Log\\n\\nProject: ${projectName}\\nCreated: ${date}\\n\\n---\\n\\n`);\n }\n\n // Create scripts\n const ralphPath = join(scriptsDir, 'ralph.sh');\n const ralphOncePath = join(scriptsDir, 'ralph-once.sh');\n\n writeFileSync(ralphPath, createRalphScript(config));\n writeFileSync(ralphOncePath, createRalphOnceScript(config));\n\n // Make scripts executable (Unix-like systems)\n try {\n const { chmodSync } = await import('fs');\n chmodSync(ralphPath, 0o755);\n chmodSync(ralphOncePath, 0o755);\n } catch {\n // Ignore chmod errors on Windows\n }\n\n // Add .claude-kanban to .gitignore if it exists (optional - users may want to commit)\n // For now, we'll let users decide\n}\n\n/**\n * Get the kanban directory path\n */\nexport function getKanbanDir(projectPath: string): string {\n return join(projectPath, KANBAN_DIR);\n}\n\n/**\n * Get project config\n */\nexport function getConfig(projectPath: string): Config {\n const configPath = join(projectPath, KANBAN_DIR, 'config.json');\n return JSON.parse(readFileSync(configPath, 'utf-8'));\n}\n\n/**\n * Save project config\n */\nexport function saveConfig(projectPath: string, config: Config): void {\n const configPath = join(projectPath, KANBAN_DIR, 'config.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n}\n","import { readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { nanoid } from 'nanoid';\nimport type { PRD, Task, CreateTaskRequest, UpdateTaskRequest, TaskStatus } from '../../types.js';\n\nconst KANBAN_DIR = '.claude-kanban';\n\n/**\n * Get PRD file path\n */\nfunction getPRDPath(projectPath: string): string {\n return join(projectPath, KANBAN_DIR, 'prd.json');\n}\n\n/**\n * Read PRD from file\n */\nexport function readPRD(projectPath: string): PRD {\n const path = getPRDPath(projectPath);\n return JSON.parse(readFileSync(path, 'utf-8'));\n}\n\n/**\n * Write PRD to file\n */\nexport function writePRD(projectPath: string, prd: PRD): void {\n const path = getPRDPath(projectPath);\n writeFileSync(path, JSON.stringify(prd, null, 2));\n}\n\n/**\n * Get all tasks\n */\nexport function getAllTasks(projectPath: string): Task[] {\n const prd = readPRD(projectPath);\n return prd.tasks;\n}\n\n/**\n * Get task by ID\n */\nexport function getTaskById(projectPath: string, taskId: string): Task | undefined {\n const prd = readPRD(projectPath);\n return prd.tasks.find((t) => t.id === taskId);\n}\n\n/**\n * Get tasks by status\n */\nexport function getTasksByStatus(projectPath: string, status: TaskStatus): Task[] {\n const prd = readPRD(projectPath);\n return prd.tasks.filter((t) => t.status === status);\n}\n\n/**\n * Create a new task\n */\nexport function createTask(projectPath: string, request: CreateTaskRequest): Task {\n const prd = readPRD(projectPath);\n const now = new Date().toISOString();\n\n const task: Task = {\n id: `task_${nanoid(8)}`,\n title: request.title,\n description: request.description,\n category: request.category || 'functional',\n priority: request.priority || 'medium',\n status: request.status || 'draft',\n steps: request.steps || [],\n passes: false,\n createdAt: now,\n updatedAt: now,\n executionHistory: [],\n dependsOn: request.dependsOn || [],\n };\n\n prd.tasks.push(task);\n writePRD(projectPath, prd);\n\n return task;\n}\n\n/**\n * Update a task\n */\nexport function updateTask(projectPath: string, taskId: string, updates: UpdateTaskRequest): Task | null {\n const prd = readPRD(projectPath);\n const taskIndex = prd.tasks.findIndex((t) => t.id === taskId);\n\n if (taskIndex === -1) {\n return null;\n }\n\n const task = prd.tasks[taskIndex];\n const updatedTask: Task = {\n ...task,\n ...updates,\n updatedAt: new Date().toISOString(),\n };\n\n prd.tasks[taskIndex] = updatedTask;\n writePRD(projectPath, prd);\n\n return updatedTask;\n}\n\n/**\n * Delete a task\n */\nexport function deleteTask(projectPath: string, taskId: string): boolean {\n const prd = readPRD(projectPath);\n const initialLength = prd.tasks.length;\n prd.tasks = prd.tasks.filter((t) => t.id !== taskId);\n\n if (prd.tasks.length < initialLength) {\n writePRD(projectPath, prd);\n return true;\n }\n\n return false;\n}\n\n/**\n * Move task to a new status\n */\nexport function moveTask(projectPath: string, taskId: string, newStatus: TaskStatus): Task | null {\n return updateTask(projectPath, taskId, { status: newStatus });\n}\n\n/**\n * Mark task as completed\n */\nexport function completeTask(projectPath: string, taskId: string): Task | null {\n return updateTask(projectPath, taskId, {\n status: 'completed',\n passes: true,\n });\n}\n\n/**\n * Mark task as failed\n */\nexport function failTask(projectPath: string, taskId: string): Task | null {\n return updateTask(projectPath, taskId, {\n status: 'failed',\n passes: false,\n });\n}\n\n/**\n * Add execution entry to task history\n */\nexport function addExecutionEntry(\n projectPath: string,\n taskId: string,\n entry: {\n startedAt: string;\n endedAt: string;\n status: 'completed' | 'failed' | 'cancelled';\n duration: number;\n error?: string;\n }\n): Task | null {\n const prd = readPRD(projectPath);\n const taskIndex = prd.tasks.findIndex((t) => t.id === taskId);\n\n if (taskIndex === -1) {\n return null;\n }\n\n prd.tasks[taskIndex].executionHistory.push(entry);\n prd.tasks[taskIndex].updatedAt = new Date().toISOString();\n writePRD(projectPath, prd);\n\n return prd.tasks[taskIndex];\n}\n\n/**\n * Check if a task's dependencies are all completed\n */\nexport function areDependenciesMet(projectPath: string, task: Task): boolean {\n if (!task.dependsOn || task.dependsOn.length === 0) {\n return true;\n }\n\n const prd = readPRD(projectPath);\n for (const depId of task.dependsOn) {\n const depTask = prd.tasks.find((t) => t.id === depId);\n if (!depTask || depTask.status !== 'completed') {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Get blocked dependencies for a task (dependencies not yet completed)\n */\nexport function getBlockedDependencies(projectPath: string, taskId: string): Task[] {\n const task = getTaskById(projectPath, taskId);\n if (!task || !task.dependsOn || task.dependsOn.length === 0) {\n return [];\n }\n\n const prd = readPRD(projectPath);\n return task.dependsOn\n .map((depId) => prd.tasks.find((t) => t.id === depId))\n .filter((t): t is Task => t !== undefined && t.status !== 'completed');\n}\n\n/**\n * Get next task to execute (highest priority \"ready\" task with met dependencies)\n */\nexport function getNextReadyTask(projectPath: string): Task | null {\n const readyTasks = getTasksByStatus(projectPath, 'ready');\n\n if (readyTasks.length === 0) {\n return null;\n }\n\n // Filter tasks with met dependencies\n const executableTasks = readyTasks.filter((task) => areDependenciesMet(projectPath, task));\n\n if (executableTasks.length === 0) {\n return null;\n }\n\n // Sort by priority (critical > high > medium > low)\n const priorityOrder: Record<string, number> = {\n critical: 0,\n high: 1,\n medium: 2,\n low: 3,\n };\n\n executableTasks.sort((a, b) => {\n const priorityDiff = (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2);\n if (priorityDiff !== 0) return priorityDiff;\n // If same priority, use creation date (older first)\n return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();\n });\n\n return executableTasks[0];\n}\n\n/**\n * Get count of tasks by status\n */\nexport function getTaskCounts(projectPath: string): Record<TaskStatus, number> {\n const tasks = getAllTasks(projectPath);\n const counts: Record<TaskStatus, number> = {\n draft: 0,\n ready: 0,\n in_progress: 0,\n completed: 0,\n failed: 0,\n };\n\n for (const task of tasks) {\n counts[task.status]++;\n }\n\n return counts;\n}\n","import { readFileSync, writeFileSync, appendFileSync } from 'fs';\nimport { join } from 'path';\n\nconst KANBAN_DIR = '.claude-kanban';\n\n/**\n * Get progress file path\n */\nfunction getProgressPath(projectPath: string): string {\n return join(projectPath, KANBAN_DIR, 'progress.txt');\n}\n\n/**\n * Read entire progress log\n */\nexport function readProgress(projectPath: string): string {\n const path = getProgressPath(projectPath);\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Append to progress log\n */\nexport function appendProgress(projectPath: string, entry: string): void {\n const path = getProgressPath(projectPath);\n appendFileSync(path, entry + '\\n');\n}\n\n/**\n * Add a task execution entry to progress log\n */\nexport function logTaskExecution(\n projectPath: string,\n options: {\n taskId: string;\n taskTitle: string;\n status: 'completed' | 'failed' | 'cancelled';\n duration: number;\n output?: string;\n error?: string;\n }\n): void {\n const now = new Date();\n const dateStr = now.toISOString().split('T')[0];\n const timeStr = now.toTimeString().split(' ')[0];\n\n const durationStr = formatDuration(options.duration);\n const statusEmoji = options.status === 'completed' ? '✓' : options.status === 'failed' ? '✗' : '○';\n\n let entry = `\\n## ${dateStr} ${timeStr}\\n\\n`;\n entry += `### Task: ${options.taskId} - ${options.taskTitle}\\n`;\n entry += `Status: ${statusEmoji} ${options.status.toUpperCase()}\\n`;\n entry += `Duration: ${durationStr}\\n`;\n\n if (options.error) {\n entry += `\\nError: ${options.error}\\n`;\n }\n\n entry += '\\n---\\n';\n\n appendProgress(projectPath, entry);\n}\n\n/**\n * Format duration in human-readable format\n */\nfunction formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}m ${seconds % 60}s`;\n }\n if (minutes > 0) {\n return `${minutes}m ${seconds % 60}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Get recent progress entries (last N lines)\n */\nexport function getRecentProgress(projectPath: string, lines = 100): string {\n const content = readProgress(projectPath);\n const allLines = content.split('\\n');\n\n if (allLines.length <= lines) {\n return content;\n }\n\n return allLines.slice(-lines).join('\\n');\n}\n\n/**\n * Clear progress log (keeps header)\n */\nexport function clearProgress(projectPath: string, projectName: string): void {\n const path = getProgressPath(projectPath);\n const date = new Date().toISOString().split('T')[0];\n const header = `# Claude Kanban Progress Log\\n\\nProject: ${projectName}\\nCleared: ${date}\\n\\n---\\n\\n`;\n writeFileSync(path, header);\n}\n","import { spawn, type ChildProcess } from 'child_process';\nimport { EventEmitter } from 'events';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { nanoid } from 'nanoid';\nimport type {\n Roadmap,\n RoadmapFeature,\n RoadmapPhase,\n RoadmapGenerateRequest,\n RoadmapPriority,\n TaskCategory,\n CompetitorInsight,\n Config,\n} from '../../types.js';\n\nconst ROADMAP_DIR = '.claude-kanban';\nconst ROADMAP_FILE = 'roadmap.json';\n\ninterface RoadmapSession {\n process: ChildProcess;\n startedAt: Date;\n output: string[];\n}\n\nexport class RoadmapService extends EventEmitter {\n private projectPath: string;\n private session: RoadmapSession | null = null;\n private config: Config | null = null;\n\n constructor(projectPath: string) {\n super();\n this.projectPath = projectPath;\n this.loadConfig();\n }\n\n private loadConfig(): void {\n const configPath = join(this.projectPath, ROADMAP_DIR, 'config.json');\n if (existsSync(configPath)) {\n this.config = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n }\n\n /**\n * Check if roadmap generation is in progress\n */\n isRunning(): boolean {\n return this.session !== null;\n }\n\n /**\n * Get existing roadmap if it exists\n */\n getRoadmap(): Roadmap | null {\n const roadmapPath = join(this.projectPath, ROADMAP_DIR, ROADMAP_FILE);\n if (!existsSync(roadmapPath)) {\n return null;\n }\n try {\n return JSON.parse(readFileSync(roadmapPath, 'utf-8'));\n } catch {\n return null;\n }\n }\n\n /**\n * Save roadmap to file\n */\n saveRoadmap(roadmap: Roadmap): void {\n const roadmapDir = join(this.projectPath, ROADMAP_DIR);\n if (!existsSync(roadmapDir)) {\n mkdirSync(roadmapDir, { recursive: true });\n }\n const roadmapPath = join(roadmapDir, ROADMAP_FILE);\n writeFileSync(roadmapPath, JSON.stringify(roadmap, null, 2));\n }\n\n /**\n * Generate a new roadmap using Claude\n */\n async generate(request: RoadmapGenerateRequest = {}): Promise<void> {\n if (this.session) {\n throw new Error('Roadmap generation already in progress');\n }\n\n this.emit('roadmap:started', {\n type: 'roadmap:started',\n timestamp: new Date().toISOString(),\n });\n\n try {\n // Phase 1: Analyze project\n this.emit('roadmap:progress', {\n type: 'roadmap:progress',\n phase: 'analyzing',\n message: 'Analyzing project structure...',\n });\n\n const projectInfo = await this.analyzeProject();\n\n // Phase 2: Optional competitor research\n let competitors: CompetitorInsight[] | undefined;\n if (request.enableCompetitorResearch) {\n this.emit('roadmap:progress', {\n type: 'roadmap:progress',\n phase: 'researching',\n message: 'Researching competitors...',\n });\n competitors = await this.researchCompetitors(projectInfo);\n }\n\n // Phase 3: Generate roadmap\n this.emit('roadmap:progress', {\n type: 'roadmap:progress',\n phase: 'generating',\n message: 'Generating feature roadmap...',\n });\n\n const roadmap = await this.generateRoadmap(projectInfo, competitors, request);\n\n // Save and emit completion\n this.saveRoadmap(roadmap);\n\n this.emit('roadmap:completed', {\n type: 'roadmap:completed',\n roadmap,\n });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n this.emit('roadmap:failed', {\n type: 'roadmap:failed',\n error: errorMessage,\n });\n throw error;\n } finally {\n this.session = null;\n }\n }\n\n /**\n * Cancel ongoing roadmap generation\n */\n cancel(): void {\n if (this.session) {\n this.session.process.kill('SIGTERM');\n this.session = null;\n }\n }\n\n /**\n * Analyze the project structure\n */\n private async analyzeProject(): Promise<ProjectInfo> {\n const projectName = basename(this.projectPath);\n let description = '';\n let stack: string[] = [];\n let existingFeatures: string[] = [];\n\n // First, try to read CLAUDE.md for the best project context\n const claudeMdPaths = ['CLAUDE.md', 'claude.md', '.claude/CLAUDE.md'];\n for (const claudePath of claudeMdPaths) {\n const fullPath = join(this.projectPath, claudePath);\n if (existsSync(fullPath)) {\n const claudeMd = readFileSync(fullPath, 'utf-8');\n // Extract first meaningful paragraph (skip headers and empty lines)\n const lines = claudeMd.split('\\n');\n const contentLines: string[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('```')) {\n contentLines.push(trimmed);\n if (contentLines.join(' ').length > 300) break;\n }\n }\n if (contentLines.length > 0) {\n description = contentLines.join(' ').slice(0, 500);\n }\n break;\n }\n }\n\n // Read package.json for Node projects\n const packageJsonPath = join(this.projectPath, 'package.json');\n if (existsSync(packageJsonPath)) {\n try {\n const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n // Only use package.json description if it's plain text (not HTML)\n if (!description && pkg.description && !pkg.description.includes('<')) {\n description = pkg.description;\n }\n\n // Detect tech stack from dependencies\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps.react) stack.push('React');\n if (deps.vue) stack.push('Vue');\n if (deps.angular) stack.push('Angular');\n if (deps.next) stack.push('Next.js');\n if (deps.express) stack.push('Express');\n if (deps.fastify) stack.push('Fastify');\n if (deps.typescript) stack.push('TypeScript');\n if (deps.tailwindcss) stack.push('Tailwind CSS');\n if (deps.laravel) stack.push('Laravel');\n } catch {\n // Ignore parse errors\n }\n }\n\n // Check for Laravel/PHP projects\n const composerPath = join(this.projectPath, 'composer.json');\n if (existsSync(composerPath)) {\n try {\n const composer = JSON.parse(readFileSync(composerPath, 'utf-8'));\n if (!description && composer.description && !composer.description.includes('<')) {\n description = composer.description;\n }\n if (composer.require?.['laravel/framework']) stack.push('Laravel');\n if (composer.require?.['livewire/livewire']) stack.push('Livewire');\n if (composer.require?.['inertiajs/inertia-laravel']) stack.push('Inertia.js');\n } catch {\n // Ignore parse errors\n }\n }\n\n // Read README for context if we still don't have a description\n if (!description) {\n const readmePaths = ['README.md', 'readme.md', 'README.txt', 'readme.txt'];\n for (const readmePath of readmePaths) {\n const fullPath = join(this.projectPath, readmePath);\n if (existsSync(fullPath)) {\n const readme = readFileSync(fullPath, 'utf-8');\n // Try to extract description from README (skip HTML, badges, and headers)\n const lines = readme.split('\\n').filter(l => {\n const trimmed = l.trim();\n return trimmed &&\n !trimmed.startsWith('#') &&\n !trimmed.startsWith('<') &&\n !trimmed.startsWith('![') &&\n !trimmed.startsWith('[!');\n });\n if (lines.length > 0) {\n description = lines[0].trim().slice(0, 500);\n }\n break;\n }\n }\n }\n\n // Fallback description\n if (!description) {\n description = `A ${stack.length > 0 ? stack.join('/') + ' ' : ''}software project`;\n }\n\n // Read existing PRD tasks to understand what's already planned\n const prdPath = join(this.projectPath, ROADMAP_DIR, 'prd.json');\n if (existsSync(prdPath)) {\n try {\n const prd = JSON.parse(readFileSync(prdPath, 'utf-8'));\n existingFeatures = prd.tasks?.map((t: { title: string }) => t.title) || [];\n } catch {\n // Ignore parse errors\n }\n }\n\n return {\n name: projectName,\n description,\n stack,\n existingFeatures,\n };\n }\n\n /**\n * Research competitors using Claude with web search\n */\n private async researchCompetitors(projectInfo: ProjectInfo): Promise<CompetitorInsight[]> {\n const prompt = this.buildCompetitorResearchPrompt(projectInfo);\n const result = await this.runClaudeCommand(prompt);\n\n try {\n // Parse JSON from Claude's response\n const jsonMatch = result.match(/```json\\n([\\s\\S]*?)\\n```/);\n if (jsonMatch) {\n return JSON.parse(jsonMatch[1]);\n }\n // Try parsing the whole response as JSON\n return JSON.parse(result);\n } catch {\n console.error('[roadmap] Failed to parse competitor research:', result);\n return [];\n }\n }\n\n /**\n * Generate the roadmap using Claude\n */\n private async generateRoadmap(\n projectInfo: ProjectInfo,\n competitors: CompetitorInsight[] | undefined,\n request: RoadmapGenerateRequest\n ): Promise<Roadmap> {\n const prompt = this.buildRoadmapPrompt(projectInfo, competitors, request);\n const result = await this.runClaudeCommand(prompt);\n\n try {\n // Parse JSON from Claude's response\n const jsonMatch = result.match(/```json\\n([\\s\\S]*?)\\n```/);\n let roadmapData: RoadmapData;\n if (jsonMatch) {\n roadmapData = JSON.parse(jsonMatch[1]);\n } else {\n roadmapData = JSON.parse(result);\n }\n\n // Build the roadmap with proper IDs\n const roadmap: Roadmap = {\n id: `roadmap_${nanoid(8)}`,\n projectName: projectInfo.name,\n projectDescription: roadmapData.projectDescription || projectInfo.description,\n targetAudience: roadmapData.targetAudience || 'Developers',\n phases: roadmapData.phases.map((p, i) => ({\n id: `phase_${nanoid(8)}`,\n name: p.name,\n description: p.description,\n order: i,\n })),\n features: roadmapData.features.map((f) => ({\n id: `feature_${nanoid(8)}`,\n title: f.title,\n description: f.description,\n priority: f.priority as RoadmapPriority,\n category: (f.category || 'functional') as TaskCategory,\n effort: f.effort || 'medium',\n impact: f.impact || 'medium',\n phase: f.phase,\n rationale: f.rationale || '',\n steps: f.steps,\n addedToKanban: false,\n })),\n competitors,\n generatedAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n return roadmap;\n } catch (error) {\n console.error('[roadmap] Failed to parse roadmap:', result);\n throw new Error('Failed to parse roadmap from Claude response');\n }\n }\n\n /**\n * Build the competitor research prompt\n */\n private buildCompetitorResearchPrompt(projectInfo: ProjectInfo): string {\n return `You are a product research analyst. Your task is to research competitors and return ONLY JSON output.\n\nIMPORTANT: Do not ask any questions. Do not request clarification. Just analyze and return the JSON.\n\n## Project Information\n- Name: ${projectInfo.name}\n- Description: ${projectInfo.description}\n- Tech Stack: ${projectInfo.stack.join(', ') || 'Unknown'}\n\n## Your Task\n1. Based on the project description, identify what type of product/service this is\n2. Find 3-5 competitors or similar projects in this space\n3. Analyze their strengths and weaknesses\n4. Identify differentiating features\n\n## Required Output Format\nReturn ONLY a JSON array (no markdown code blocks, no explanations):\n\n[\n {\n \"name\": \"Competitor Name\",\n \"url\": \"https://example.com\",\n \"strengths\": [\"strength 1\", \"strength 2\"],\n \"weaknesses\": [\"weakness 1\", \"weakness 2\"],\n \"differentiators\": [\"feature 1\", \"feature 2\"]\n }\n]\n\nBegin your response with [ and end with ]. No other text.`;\n }\n\n /**\n * Build the roadmap generation prompt\n */\n private buildRoadmapPrompt(\n projectInfo: ProjectInfo,\n competitors: CompetitorInsight[] | undefined,\n request: RoadmapGenerateRequest\n ): string {\n let prompt = `You are a product strategist creating a feature roadmap for a software project.\n\n## Project Information\nName: ${projectInfo.name}\nDescription: ${projectInfo.description}\nTech Stack: ${projectInfo.stack.join(', ') || 'Unknown'}\n${projectInfo.existingFeatures.length > 0 ? `\\nExisting Features/Tasks:\\n${projectInfo.existingFeatures.map(f => `- ${f}`).join('\\n')}` : ''}\n`;\n\n if (competitors && competitors.length > 0) {\n prompt += `\n## Competitor Analysis\n${competitors.map(c => `\n### ${c.name}\n- Strengths: ${c.strengths.join(', ')}\n- Weaknesses: ${c.weaknesses.join(', ')}\n- Key Features: ${c.differentiators.join(', ')}\n`).join('\\n')}\n`;\n }\n\n if (request.focusAreas && request.focusAreas.length > 0) {\n prompt += `\n## Focus Areas\nThe user wants to focus on: ${request.focusAreas.join(', ')}\n`;\n }\n\n if (request.customPrompt) {\n prompt += `\n## Additional Context\n${request.customPrompt}\n`;\n }\n\n prompt += `\n## Instructions\nIMPORTANT: Do not ask any questions. Do not request clarification. Generate the roadmap based on the information provided.\n\nCreate a comprehensive product roadmap with features organized into phases.\n\nUse the MoSCoW prioritization framework:\n- must: Critical features that must be implemented\n- should: Important features that should be implemented\n- could: Nice-to-have features that could be implemented\n- wont: Features that won't be implemented in the near term\n\nFor each feature, estimate:\n- effort: low, medium, or high\n- impact: low, medium, or high\n\nCategories should be one of: functional, ui, bug, enhancement, testing, refactor\n\n## Required Output Format\nReturn ONLY valid JSON (no markdown code blocks, no explanations). Begin with { and end with }:\n\n{\n \"projectDescription\": \"Brief description of the project\",\n \"targetAudience\": \"Who this project is for\",\n \"phases\": [\n {\n \"name\": \"Phase 1: MVP\",\n \"description\": \"Core features for minimum viable product\"\n },\n {\n \"name\": \"Phase 2: Enhancement\",\n \"description\": \"Features to improve user experience\"\n }\n ],\n \"features\": [\n {\n \"title\": \"Feature title\",\n \"description\": \"What this feature does\",\n \"priority\": \"must\",\n \"category\": \"functional\",\n \"effort\": \"medium\",\n \"impact\": \"high\",\n \"phase\": \"Phase 1: MVP\",\n \"rationale\": \"Why this feature is important\",\n \"steps\": [\"Step 1\", \"Step 2\"]\n }\n ]\n}\n\nGenerate 10-20 strategic features across 3-4 phases. Be specific and actionable.\nDon't duplicate features that already exist in the project.\nBegin your response with { - no other text before or after the JSON.`;\n\n return prompt;\n }\n\n /**\n * Run a Claude command and return the output\n */\n private runClaudeCommand(prompt: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const command = this.config?.agent.command || 'claude';\n const permissionMode = this.config?.agent.permissionMode || 'auto';\n const model = this.config?.agent.model;\n\n // Write prompt to file\n const promptPath = join(this.projectPath, ROADMAP_DIR, 'prompt-roadmap.txt');\n writeFileSync(promptPath, prompt);\n\n const args = [\n '--permission-mode', permissionMode,\n '-p',\n '--verbose',\n '--output-format', 'text',\n `@${promptPath}`,\n ];\n\n if (model) {\n args.unshift('--model', model);\n }\n\n console.log(`[roadmap] Command: ${command} ${args.join(' ')}`);\n\n const childProcess = spawn(command, args, {\n cwd: this.projectPath,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: {\n ...process.env,\n FORCE_COLOR: '0',\n },\n });\n\n this.session = {\n process: childProcess,\n startedAt: new Date(),\n output: [],\n };\n\n let stdout = '';\n let stderr = '';\n\n childProcess.stdout?.on('data', (data: Buffer) => {\n const chunk = data.toString();\n stdout += chunk;\n this.session?.output.push(chunk);\n });\n\n childProcess.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n childProcess.on('close', (code) => {\n this.session = null;\n\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(new Error(`Claude command failed with code ${code}: ${stderr}`));\n }\n });\n\n childProcess.on('error', (error) => {\n this.session = null;\n reject(error);\n });\n });\n }\n\n /**\n * Mark a feature as added to kanban\n */\n markFeatureAdded(featureId: string): Roadmap | null {\n const roadmap = this.getRoadmap();\n if (!roadmap) return null;\n\n const feature = roadmap.features.find(f => f.id === featureId);\n if (feature) {\n feature.addedToKanban = true;\n roadmap.updatedAt = new Date().toISOString();\n this.saveRoadmap(roadmap);\n }\n\n return roadmap;\n }\n\n /**\n * Delete a feature from the roadmap\n */\n deleteFeature(featureId: string): Roadmap | null {\n const roadmap = this.getRoadmap();\n if (!roadmap) return null;\n\n const index = roadmap.features.findIndex(f => f.id === featureId);\n if (index !== -1) {\n roadmap.features.splice(index, 1);\n roadmap.updatedAt = new Date().toISOString();\n this.saveRoadmap(roadmap);\n }\n\n return roadmap;\n }\n}\n\n// Internal types\ninterface ProjectInfo {\n name: string;\n description: string;\n stack: string[];\n existingFeatures: string[];\n}\n\ninterface RoadmapData {\n projectDescription: string;\n targetAudience: string;\n phases: Array<{\n name: string;\n description: string;\n }>;\n features: Array<{\n title: string;\n description: string;\n priority: string;\n category?: string;\n effort?: 'low' | 'medium' | 'high';\n impact?: 'low' | 'medium' | 'high';\n phase: string;\n rationale?: string;\n steps?: string[];\n }>;\n}\n","import type { TaskTemplate } from '../../types.js';\n\n/**\n * Pre-built task templates\n */\nexport const taskTemplates: TaskTemplate[] = [\n {\n id: 'auth-login',\n name: 'Authentication - Login',\n icon: '🔐',\n description: 'User login functionality with email/password',\n category: 'functional',\n priority: 'high',\n titleTemplate: 'Add user login functionality',\n descriptionTemplate: `Implement user login with email and password authentication.\n\nRequirements:\n- Login form with email and password fields\n- Form validation with error messages\n- Submit credentials to auth API\n- Handle success (redirect) and failure (show error)\n- Store auth token/session`,\n stepsTemplate: [\n 'Navigate to /login',\n 'Verify login form displays with email and password fields',\n 'Enter invalid credentials and verify error message',\n 'Enter valid credentials and submit',\n 'Verify redirect to dashboard/home',\n 'Verify auth state persists on page reload',\n ],\n },\n {\n id: 'auth-signup',\n name: 'Authentication - Signup',\n icon: '🔐',\n description: 'User registration with validation',\n category: 'functional',\n priority: 'high',\n titleTemplate: 'Add user signup/registration',\n descriptionTemplate: `Implement user registration with form validation.\n\nRequirements:\n- Signup form with name, email, password fields\n- Password confirmation field\n- Client-side validation\n- Submit to registration API\n- Handle success and error states`,\n stepsTemplate: [\n 'Navigate to /signup',\n 'Verify signup form displays all required fields',\n 'Submit with invalid data and verify validation errors',\n 'Submit with valid data',\n 'Verify success message or redirect',\n 'Verify new user can log in',\n ],\n },\n {\n id: 'auth-logout',\n name: 'Authentication - Logout',\n icon: '🔐',\n description: 'User logout functionality',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add user logout functionality',\n descriptionTemplate: `Implement logout functionality.\n\nRequirements:\n- Logout button/link in navigation\n- Clear auth token/session on logout\n- Redirect to login or home page\n- Protect routes after logout`,\n stepsTemplate: [\n 'Log in as a user',\n 'Click logout button',\n 'Verify redirect to login/home page',\n 'Verify auth state is cleared',\n 'Verify protected routes redirect to login',\n ],\n },\n {\n id: 'crud-create',\n name: 'CRUD - Create',\n icon: '📝',\n description: 'Create new entity form',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add create [Entity] form',\n descriptionTemplate: `Implement form to create a new [Entity].\n\nRequirements:\n- Form with all required fields\n- Client-side validation\n- Submit to create API endpoint\n- Handle loading, success, and error states\n- Redirect or show success message after creation`,\n stepsTemplate: [\n 'Navigate to create form',\n 'Verify all form fields display correctly',\n 'Submit empty form and verify validation',\n 'Fill in valid data and submit',\n 'Verify entity is created',\n 'Verify redirect or success message',\n ],\n },\n {\n id: 'crud-read',\n name: 'CRUD - Read/List',\n icon: '📝',\n description: 'List and view entities',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add [Entity] list view',\n descriptionTemplate: `Implement list view for [Entity] items.\n\nRequirements:\n- Fetch and display list of entities\n- Show loading state\n- Handle empty state\n- Display relevant fields for each item\n- Link to detail view`,\n stepsTemplate: [\n 'Navigate to list page',\n 'Verify loading state displays',\n 'Verify items render correctly',\n 'Verify empty state when no items',\n 'Click item and verify navigation to detail',\n ],\n },\n {\n id: 'crud-update',\n name: 'CRUD - Update',\n icon: '📝',\n description: 'Edit existing entity',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add edit [Entity] form',\n descriptionTemplate: `Implement form to edit an existing [Entity].\n\nRequirements:\n- Pre-populate form with existing data\n- Allow modification of fields\n- Submit changes to update API\n- Handle validation and errors\n- Show success feedback`,\n stepsTemplate: [\n 'Navigate to edit form for existing item',\n 'Verify form pre-populates with current data',\n 'Modify fields',\n 'Submit form',\n 'Verify changes are saved',\n 'Verify updated data displays correctly',\n ],\n },\n {\n id: 'crud-delete',\n name: 'CRUD - Delete',\n icon: '📝',\n description: 'Delete entity with confirmation',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add delete [Entity] functionality',\n descriptionTemplate: `Implement delete functionality for [Entity].\n\nRequirements:\n- Delete button on item or list\n- Confirmation dialog before delete\n- Call delete API endpoint\n- Remove item from UI on success\n- Handle errors gracefully`,\n stepsTemplate: [\n 'Navigate to item with delete option',\n 'Click delete button',\n 'Verify confirmation dialog appears',\n 'Confirm deletion',\n 'Verify item is removed from list',\n 'Verify item no longer accessible',\n ],\n },\n {\n id: 'api-endpoint',\n name: 'API Endpoint',\n icon: '🌐',\n description: 'REST API endpoint with validation',\n category: 'functional',\n priority: 'medium',\n titleTemplate: 'Add [METHOD] /api/[resource] endpoint',\n descriptionTemplate: `Implement REST API endpoint.\n\nRequirements:\n- Define route handler\n- Validate request body/params\n- Implement business logic\n- Return appropriate response codes\n- Add error handling`,\n stepsTemplate: [\n 'Endpoint responds to correct HTTP method',\n 'Invalid requests return 400 with error details',\n 'Valid requests process correctly',\n 'Returns appropriate status codes',\n 'Handles edge cases gracefully',\n ],\n },\n {\n id: 'ui-component',\n name: 'UI Component',\n icon: '🎨',\n description: 'Reusable UI component',\n category: 'ui',\n priority: 'medium',\n titleTemplate: 'Create [ComponentName] component',\n descriptionTemplate: `Create a reusable UI component.\n\nRequirements:\n- Component accepts appropriate props\n- Handles different states (loading, error, empty)\n- Follows design system/styling conventions\n- Accessible (keyboard, screen reader)\n- Includes any needed interactivity`,\n stepsTemplate: [\n 'Component renders without errors',\n 'Props affect rendering correctly',\n 'Different states display appropriately',\n 'Component is accessible',\n 'Interactive elements work correctly',\n ],\n },\n {\n id: 'form-validation',\n name: 'Form with Validation',\n icon: '📄',\n description: 'Form with client-side validation',\n category: 'ui',\n priority: 'medium',\n titleTemplate: 'Create [FormName] form with validation',\n descriptionTemplate: `Create a form with comprehensive validation.\n\nRequirements:\n- All necessary input fields\n- Real-time validation feedback\n- Clear error messages\n- Submit button state management\n- Form submission handling`,\n stepsTemplate: [\n 'Form displays all required fields',\n 'Invalid input shows error message',\n 'Valid input clears error',\n 'Submit disabled when form invalid',\n 'Form submits with valid data',\n ],\n },\n {\n id: 'test-unit',\n name: 'Unit Tests',\n icon: '🧪',\n description: 'Unit test suite for module',\n category: 'testing',\n priority: 'medium',\n titleTemplate: 'Add unit tests for [module/component]',\n descriptionTemplate: `Write comprehensive unit tests.\n\nRequirements:\n- Test all public functions/methods\n- Cover edge cases\n- Test error handling\n- Achieve good coverage\n- Tests should be fast and isolated`,\n stepsTemplate: [\n 'All tests pass',\n 'Core functionality covered',\n 'Edge cases tested',\n 'Error cases handled',\n 'No flaky tests',\n ],\n },\n {\n id: 'test-e2e',\n name: 'E2E Tests',\n icon: '🧪',\n description: 'End-to-end test for user flow',\n category: 'testing',\n priority: 'medium',\n titleTemplate: 'Add E2E tests for [feature/flow]',\n descriptionTemplate: `Write end-to-end tests for user flow.\n\nRequirements:\n- Test complete user journey\n- Use realistic test data\n- Verify UI and data changes\n- Handle async operations\n- Clean up test data`,\n stepsTemplate: [\n 'Tests run successfully',\n 'User flow completes correctly',\n 'UI updates verified',\n 'Data persisted correctly',\n 'Tests are reliable (not flaky)',\n ],\n },\n];\n\n/**\n * Get all templates\n */\nexport function getAllTemplates(): TaskTemplate[] {\n return taskTemplates;\n}\n\n/**\n * Get template by ID\n */\nexport function getTemplateById(id: string): TaskTemplate | undefined {\n return taskTemplates.find((t) => t.id === id);\n}\n\n/**\n * Get templates by category\n */\nexport function getTemplatesByCategory(category: string): TaskTemplate[] {\n return taskTemplates.filter((t) => t.category === category);\n}\n","import { spawn } from 'child_process';\nimport { getConfig } from './project.js';\nimport type { CreateTaskRequest } from '../../types.js';\n\n/**\n * Generate task details from a natural language description using Claude\n */\nexport async function generateTaskFromPrompt(\n projectPath: string,\n userPrompt: string\n): Promise<CreateTaskRequest> {\n const config = getConfig(projectPath);\n\n const systemPrompt = `You are a task generator for a Kanban board used in software development.\nGiven a user's description of what they want to build, generate a structured task.\n\nRespond with ONLY valid JSON in this exact format:\n{\n \"title\": \"Short, action-oriented title (max 80 chars)\",\n \"description\": \"Detailed description of what needs to be implemented\",\n \"category\": \"functional|ui|bug|enhancement|testing|refactor\",\n \"priority\": \"low|medium|high|critical\",\n \"steps\": [\"Step 1 to verify\", \"Step 2 to verify\", \"...\"]\n}\n\nGuidelines:\n- Title should be concise and start with a verb (Add, Create, Implement, Fix, etc.)\n- Description should be comprehensive but focused\n- Steps should be verification steps to confirm the feature works\n- Include 3-7 verification steps\n- Choose appropriate category based on the work type\n- Priority should reflect typical importance (most features are medium)\n\nRespond with ONLY the JSON, no other text.`;\n\n const fullPrompt = `${systemPrompt}\n\nUser request: ${userPrompt}`;\n\n return new Promise((resolve, reject) => {\n const args = [\n '--permission-mode', 'ask',\n '-p',\n fullPrompt,\n ];\n\n if (config.agent.model) {\n args.unshift('--model', config.agent.model);\n }\n\n let output = '';\n let errorOutput = '';\n\n const proc = spawn(config.agent.command, args, {\n cwd: projectPath,\n shell: true,\n env: { ...process.env },\n });\n\n proc.stdout?.on('data', (data: Buffer) => {\n output += data.toString();\n });\n\n proc.stderr?.on('data', (data: Buffer) => {\n errorOutput += data.toString();\n });\n\n proc.on('close', (code) => {\n if (code !== 0) {\n reject(new Error(`AI generation failed: ${errorOutput || 'Unknown error'}`));\n return;\n }\n\n try {\n // Extract JSON from output (Claude might add some text around it)\n const jsonMatch = output.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) {\n throw new Error('No JSON found in response');\n }\n\n const parsed = JSON.parse(jsonMatch[0]);\n\n // Validate required fields\n if (!parsed.title || !parsed.description) {\n throw new Error('Missing required fields in response');\n }\n\n const task: CreateTaskRequest = {\n title: String(parsed.title).slice(0, 200),\n description: String(parsed.description),\n category: validateCategory(parsed.category),\n priority: validatePriority(parsed.priority),\n steps: Array.isArray(parsed.steps) ? parsed.steps.map(String) : [],\n status: 'draft',\n };\n\n resolve(task);\n } catch (parseError) {\n reject(new Error(`Failed to parse AI response: ${parseError}`));\n }\n });\n\n proc.on('error', (error) => {\n reject(new Error(`Failed to spawn AI process: ${error.message}`));\n });\n\n // Timeout after 60 seconds\n setTimeout(() => {\n proc.kill();\n reject(new Error('AI generation timed out'));\n }, 60000);\n });\n}\n\nfunction validateCategory(category: unknown): 'functional' | 'ui' | 'bug' | 'enhancement' | 'testing' | 'refactor' {\n const valid = ['functional', 'ui', 'bug', 'enhancement', 'testing', 'refactor'];\n if (typeof category === 'string' && valid.includes(category)) {\n return category as 'functional' | 'ui' | 'bug' | 'enhancement' | 'testing' | 'refactor';\n }\n return 'functional';\n}\n\nfunction validatePriority(priority: unknown): 'low' | 'medium' | 'high' | 'critical' {\n const valid = ['low', 'medium', 'high', 'critical'];\n if (typeof priority === 'string' && valid.includes(priority)) {\n return priority as 'low' | 'medium' | 'high' | 'critical';\n }\n return 'medium';\n}\n","import { createServer } from 'net';\n\n/**\n * Check if a port is available\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n\n server.once('error', () => {\n resolve(false);\n });\n\n server.once('listening', () => {\n server.close();\n resolve(true);\n });\n\n server.listen(port, '127.0.0.1');\n });\n}\n\n/**\n * Find an available port starting from the given port\n */\nexport async function findAvailablePort(startPort: number, maxAttempts = 10): Promise<number> {\n for (let i = 0; i < maxAttempts; i++) {\n const port = startPort + i;\n if (await isPortAvailable(port)) {\n return port;\n }\n }\n throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`);\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoeP,YAAY,CAAC;AAAA;AAAA;AAAA;AAIf;AAEA,SAAS,cAAsB;AAC7B,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["existsSync","join","join","existsSync","join","writeFileSync","mkdirSync","existsSync","appendFileSync","readFileSync","readFileSync","writeFileSync","join","KANBAN_DIR","readFileSync","writeFileSync","join","KANBAN_DIR","KANBAN_DIR","join","existsSync","mkdirSync","writeFileSync","appendFileSync","readFileSync","spawn","EventEmitter","existsSync","readFileSync","writeFileSync","mkdirSync","join","basename","nanoid","spawn","spawn","__dirname","join","existsSync","createServer","existsSync","join"]}
|