claude-kanban 0.3.0 → 0.3.1

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.
@@ -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/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 { createServer } from '../server/index.js';\nimport { initializeProject, isProjectInitialized } from '../server/services/project.js';\nimport { findAvailablePort } from '../server/utils/port.js';\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 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 * 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, AFKStartRequest } 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 // 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 // ===== 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\n app.post('/api/tasks/:id/cancel', (req, res) => {\n try {\n const cancelled = executor.cancelTask(req.params.id);\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\n app.post('/api/afk/start', (req, res) => {\n try {\n const { maxIterations, concurrent } = req.body as AFKStartRequest;\n executor.startAFKMode(maxIterations || 10, concurrent || 1);\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 // Get running tasks info\n app.get('/api/running', (_req, res) => {\n try {\n const taskIds = executor.getRunningTaskIds();\n res.json({ running: taskIds, count: taskIds.length });\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.getRunningCount();\n const afk = executor.getAFKStatus();\n res.json({ counts, running, afk });\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 tasks and recent tasks\n const runningIds = executor.getRunningTaskIds();\n const taskLogs: Record<string, string> = {};\n\n // Get logs for all running tasks\n for (const taskId of runningIds) {\n const logs = executor.getTaskLog(taskId);\n if (logs) {\n taskLogs[taskId] = 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 tasks\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: 16px 20px;\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: 12px 16px;\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 sidePanelTab: 'logs', // 'logs' or 'details'\n darkMode: localStorage.getItem('darkMode') === 'true', // Add dark mode state\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 // 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) { task.status = 'completed'; task.passes = true; }\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// 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, concurrent) {\n await fetch('/api/afk/start', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ maxIterations, concurrent })\n });\n}\n\nasync function stopAFK() {\n await fetch('/api/afk/stop', { method: 'POST' });\n}\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 // 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\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&#10;Enter valid credentials&#10;Click submit button&#10;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 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>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Concurrent Tasks</label>\n <select id=\"afk-concurrent\" class=\"input w-full\">\n <option value=\"1\">1 (Sequential)</option>\n <option value=\"2\">2</option>\n <option value=\"3\">3 (Max)</option>\n </select>\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 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\n return \\`\n <div class=\"side-panel\">\n <!-- Header -->\n <div class=\"side-panel-header\">\n <div class=\"flex justify-between items-start\">\n <div class=\"flex-1 pr-4\">\n <h2 class=\"font-semibold text-canvas-900 text-lg leading-tight\">\\${escapeHtml(task.title)}</h2>\n <div class=\"flex items-center gap-2 mt-2\">\n <span class=\"status-badge status-badge-\\${task.status}\">\n <span class=\"w-1.5 h-1.5 rounded-full bg-current \\${isRunning ? 'animate-pulse' : ''}\"></span>\n \\${task.status.replace('_', ' ')}\n </span>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <button onclick=\"state.editingTask = state.tasks.find(t => t.id === '\\${task.id}'); state.showModal = 'edit'; render();\"\n class=\"btn btn-ghost p-2 text-canvas-500 hover:text-canvas-700\" title=\"Edit\">\n ✏️\n </button>\n <button onclick=\"if(confirm('Delete this task?')) { deleteTask('\\${task.id}'); closeSidePanel(); }\"\n class=\"btn btn-ghost p-2 text-canvas-500 hover:text-status-failed\" title=\"Delete\">\n 🗑️\n </button>\n <button onclick=\"closeSidePanel()\" class=\"btn btn-ghost p-2 text-canvas-500 hover:text-canvas-700\" title=\"Close\">\n ✕\n </button>\n </div>\n </div>\n </div>\n\n <!-- Description -->\n <div class=\"px-5 py-4 border-b border-canvas-200 flex-shrink-0\">\n <p class=\"text-sm text-canvas-600 leading-relaxed\">\\${escapeHtml(task.description)}</p>\n \\${task.steps && task.steps.length > 0 ? \\`\n <div class=\"mt-3\">\n <div class=\"text-xs font-medium text-canvas-500 mb-2\">Steps:</div>\n <ul class=\"text-sm text-canvas-600 space-y-1\">\n \\${task.steps.map(s => \\`<li class=\"flex gap-2\"><span class=\"text-canvas-400\">•</span>\\${escapeHtml(s)}</li>\\`).join('')}\n </ul>\n </div>\n \\` : ''}\n </div>\n\n <!-- Task Details Grid -->\n <div class=\"details-grid\">\n <div class=\"details-item\">\n <span class=\"details-label\">Priority</span>\n <span class=\"details-value capitalize\">\\${task.priority}</span>\n </div>\n <div class=\"details-item\">\n <span class=\"details-label\">Category</span>\n <span class=\"details-value\">\\${categoryIcons[task.category] || ''} \\${task.category}</span>\n </div>\n \\${startTime || lastExec ? \\`\n <div class=\"details-item\">\n <span class=\"details-label\">Started</span>\n <span class=\"details-value\">\\${new Date(startTime || lastExec?.startedAt).toLocaleString()}</span>\n </div>\n \\` : ''}\n \\${lastExec?.duration ? \\`\n <div class=\"details-item\">\n <span class=\"details-label\">Duration</span>\n <span class=\"details-value\">\\${Math.round(lastExec.duration / 1000)}s</span>\n </div>\n \\` : ''}\n </div>\n\n <!-- Action Buttons -->\n <div class=\"px-5 py-4 border-b border-canvas-200 flex gap-2 flex-shrink-0\">\n \\${task.status === 'draft' ? \\`\n <button onclick=\"updateTask('\\${task.id}', { status: 'ready' })\" class=\"btn btn-primary px-4 py-2 text-sm\">\n → Move to Ready\n </button>\n \\` : ''}\n \\${task.status === 'ready' ? \\`\n <button onclick=\"runTask('\\${task.id}')\" class=\"btn btn-primary px-4 py-2 text-sm\">\n ▶ Run Task\n </button>\n \\` : ''}\n \\${task.status === 'in_progress' ? \\`\n <button onclick=\"cancelTask('\\${task.id}')\" class=\"btn btn-danger px-4 py-2 text-sm\">\n ⏹ Stop Attempt\n </button>\n \\` : ''}\n \\${task.status === 'failed' ? \\`\n <button onclick=\"retryTask('\\${task.id}')\" class=\"btn btn-primary px-4 py-2 text-sm\">\n ↻ Retry\n </button>\n \\` : ''}\n </div>\n\n <!-- Tabs -->\n <div class=\"side-panel-tabs\">\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\">\n \\${state.sidePanelTab === 'logs' ? \\`\n <div class=\"log-container\" id=\"side-panel-log\">\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 class=\"p-5\">\n <div class=\"text-sm text-canvas-600\">\n <div class=\"mb-4\">\n <div class=\"text-xs font-medium text-canvas-500 mb-1\">Created</div>\n <div>\\${new Date(task.createdAt).toLocaleString()}</div>\n </div>\n <div class=\"mb-4\">\n <div class=\"text-xs font-medium text-canvas-500 mb-1\">Last Updated</div>\n <div>\\${new Date(task.updatedAt).toLocaleString()}</div>\n </div>\n \\${task.executionHistory && task.executionHistory.length > 0 ? \\`\n <div>\n <div class=\"text-xs font-medium text-canvas-500 mb-2\">Execution History</div>\n <div class=\"space-y-2\">\n \\${task.executionHistory.slice(-5).reverse().map(exec => \\`\n <div class=\"bg-canvas-100 rounded p-2 text-xs\">\n <div class=\"flex justify-between\">\n <span class=\"\\${exec.status === 'completed' ? 'text-status-success' : 'text-status-failed'}\">\\${exec.status}</span>\n <span class=\"text-canvas-500\">\\${Math.round(exec.duration / 1000)}s</span>\n </div>\n <div class=\"text-canvas-500 mt-1\">\\${new Date(exec.startedAt).toLocaleString()}</div>\n \\${exec.error ? \\`<div class=\"text-status-failed mt-1\">\\${escapeHtml(exec.error)}</div>\\` : ''}\n </div>\n \\`).join('')}\n </div>\n </div>\n \\` : ''}\n </div>\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 const concurrent = parseInt(document.getElementById('afk-concurrent').value) || 1;\n startAFK(iterations, concurrent);\n state.showModal = null;\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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\n\n// Main render\nfunction render() {\n const app = document.getElementById('app');\n const hasSidePanel = state.sidePanel !== null;\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 <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 </div>\n <div class=\"flex items-center gap-2\">\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 </div>\n </div>\n </header>\n\n \\${renderAFKBar()}\n\n <!-- Main Content Area - Flex container for board + sidebar -->\n <div class=\"flex flex-1 overflow-hidden\">\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\n <!-- Side Panel (pushes content when open) -->\n \\${hasSidePanel ? renderSidePanel() : ''}\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.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;\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, execSync } from 'child_process';\nimport { join } from 'path';\nimport { writeFileSync, unlinkSync, mkdirSync, existsSync, appendFileSync, readFileSync, rmSync, symlinkSync } from 'fs';\nimport { EventEmitter } from 'events';\nimport type { Config, Task, RunningTask } 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';\nconst WORKTREES_DIR = 'worktrees';\n\n/**\n * Task Executor - manages running tasks\n */\nexport class TaskExecutor extends EventEmitter {\n private projectPath: string;\n private runningTasks: Map<string, RunningTask> = new Map();\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 // Truncate long strings for display\n const truncate = (str: string, maxLen = 80): string => {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen) + '...';\n };\n\n // Format based on tool type\n switch (toolName) {\n case 'Bash':\n return `[Bash] $ ${truncate(String(input.command || ''))}\\n`;\n\n case 'Read':\n return `[Read] ${input.file_path}\\n`;\n\n case 'Edit':\n return `[Edit] ${input.file_path}\\n`;\n\n case 'Write':\n return `[Write] ${input.file_path}\\n`;\n\n case 'Grep':\n return `[Grep] \"${truncate(String(input.pattern || ''))}\" in ${input.path || '.'}\\n`;\n\n case 'Glob':\n return `[Glob] ${input.pattern} in ${input.path || '.'}\\n`;\n\n case 'Task':\n return `[Task] ${input.description || truncate(String(input.prompt || ''))}\\n`;\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\n case 'WebFetch':\n return `[WebFetch] ${input.url}\\n`;\n\n case 'WebSearch':\n return `[WebSearch] \"${truncate(String(input.query || ''))}\"\\n`;\n\n default:\n // For MCP tools and others, show tool name with first meaningful param\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 project is a git repository\n */\n private isGitRepo(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get worktrees directory path\n */\n private getWorktreesDir(): string {\n return join(this.projectPath, KANBAN_DIR, WORKTREES_DIR);\n }\n\n /**\n * Get worktree path for a task\n */\n private getWorktreePath(taskId: string): string {\n return join(this.getWorktreesDir(), taskId);\n }\n\n /**\n * Get branch name for a task\n */\n private getBranchName(taskId: string): string {\n return `task/${taskId}`;\n }\n\n /**\n * Create a git worktree for isolated task execution\n */\n private createWorktree(taskId: string): { worktreePath: string; branchName: string } | null {\n if (!this.isGitRepo()) {\n console.log('[executor] Not a git repo, skipping worktree creation');\n return null;\n }\n\n const worktreePath = this.getWorktreePath(taskId);\n const branchName = this.getBranchName(taskId);\n\n try {\n // Ensure worktrees directory exists\n const worktreesDir = this.getWorktreesDir();\n if (!existsSync(worktreesDir)) {\n mkdirSync(worktreesDir, { recursive: true });\n }\n\n // Remove existing worktree if it exists (from a previous failed run)\n if (existsSync(worktreePath)) {\n this.removeWorktree(taskId);\n }\n\n // Create new worktree with a new branch\n // First, check if branch already exists\n try {\n execSync(`git rev-parse --verify ${branchName}`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n // Branch exists, delete it first\n execSync(`git branch -D ${branchName}`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n } catch {\n // Branch doesn't exist, that's fine\n }\n\n // Create worktree with new branch\n execSync(`git worktree add -b ${branchName} \"${worktreePath}\"`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n\n // Symlink node_modules and other dependency directories\n // This ensures the worktree can run build/test commands without reinstalling\n this.symlinkDependencies(worktreePath);\n\n console.log(`[executor] Created worktree at ${worktreePath} on branch ${branchName}`);\n return { worktreePath, branchName };\n } catch (error) {\n console.error('[executor] Failed to create worktree:', error);\n return null;\n }\n }\n\n /**\n * Symlink dependency directories from main project to worktree\n */\n private symlinkDependencies(worktreePath: string): void {\n // Common dependency directories to symlink\n const depDirs = [\n 'node_modules',\n '.pnpm', // pnpm store\n '.yarn', // yarn cache\n 'vendor', // PHP/Ruby deps\n '__pycache__', // Python cache\n '.venv', // Python virtual env\n 'venv', // Python virtual env\n ];\n\n for (const dir of depDirs) {\n const sourcePath = join(this.projectPath, dir);\n const targetPath = join(worktreePath, dir);\n\n // Only symlink if source exists and target doesn't\n if (existsSync(sourcePath) && !existsSync(targetPath)) {\n try {\n symlinkSync(sourcePath, targetPath, 'junction');\n console.log(`[executor] Symlinked ${dir} to worktree`);\n } catch (error) {\n // Symlink might fail on some systems, log but continue\n console.log(`[executor] Could not symlink ${dir}:`, error);\n }\n }\n }\n }\n\n /**\n * Remove a git worktree\n */\n private removeWorktree(taskId: string): void {\n const worktreePath = this.getWorktreePath(taskId);\n const branchName = this.getBranchName(taskId);\n\n try {\n // Remove the worktree\n if (existsSync(worktreePath)) {\n execSync(`git worktree remove \"${worktreePath}\" --force`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n console.log(`[executor] Removed worktree at ${worktreePath}`);\n }\n } catch (error) {\n console.error('[executor] Failed to remove worktree via git:', error);\n // Fallback: force remove the directory\n try {\n if (existsSync(worktreePath)) {\n rmSync(worktreePath, { recursive: true, force: true });\n // Also prune worktrees\n execSync('git worktree prune', {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n }\n } catch {\n console.error('[executor] Failed to force remove worktree directory');\n }\n }\n\n // Delete the branch\n try {\n execSync(`git branch -D ${branchName}`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n console.log(`[executor] Deleted branch ${branchName}`);\n } catch {\n // Branch might not exist or have other issues\n }\n }\n\n /**\n * Merge worktree branch back to main branch\n */\n private mergeWorktreeBranch(taskId: string): boolean {\n const branchName = this.getBranchName(taskId);\n\n try {\n // Get current branch\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: this.projectPath,\n encoding: 'utf-8',\n }).trim();\n\n // Merge the task branch\n execSync(`git merge ${branchName} --no-edit`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n\n console.log(`[executor] Merged ${branchName} into ${currentBranch}`);\n return true;\n } catch (error) {\n console.error('[executor] Failed to merge branch:', error);\n return false;\n }\n }\n\n /**\n * Get number of currently running tasks\n */\n getRunningCount(): number {\n return this.runningTasks.size;\n }\n\n /**\n * Check if a task is running\n */\n isTaskRunning(taskId: string): boolean {\n return this.runningTasks.has(taskId);\n }\n\n /**\n * Get all running task IDs\n */\n getRunningTaskIds(): string[] {\n return Array.from(this.runningTasks.keys());\n }\n\n /**\n * Get running task output\n */\n getTaskOutput(taskId: string): string[] | undefined {\n return this.runningTasks.get(taskId)?.output;\n }\n\n /**\n * Build the prompt for a specific task\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 instructions only for configured commands\n const verifySteps: string[] = [];\n if (config.project.typecheckCommand) {\n verifySteps.push(`Run typecheck: ${config.project.typecheckCommand}`);\n }\n if (config.project.testCommand) {\n verifySteps.push(`Run tests: ${config.project.testCommand}`);\n }\n\n const verifySection = verifySteps.length > 0\n ? `2. Verify your work:\\n${verifySteps.map(s => ` - ${s}`).join('\\n')}\\n\\n`\n : '';\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\n1. Implement this task as described above.\n\n${verifySection}${verifySteps.length > 0 ? '3' : '2'}. When complete, update the task in ${prdPath}:\n - Find the task with id \"${task.id}\"\n - Set \"passes\": true\n - Set \"status\": \"completed\"\n\n${verifySteps.length > 0 ? '4' : '3'}. Document your work in ${progressPath}:\n - What you implemented and files changed\n - Key decisions made and why\n - Gotchas, edge cases, or tricky parts discovered\n - Useful patterns or approaches that worked well\n - Anything a future agent should know about this area of the codebase\n\n${verifySteps.length > 0 ? '5' : '4'}. Make a git commit with a descriptive message.\n\nFocus only on this task. When successfully complete, output: <promise>COMPLETE</promise>`;\n }\n\n /**\n * Run a specific task\n */\n async runTask(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 if (this.isTaskRunning(taskId)) {\n throw new Error(`Task already running: ${taskId}`);\n }\n\n const maxConcurrent = config.execution.maxConcurrent || 3;\n if (this.getRunningCount() >= maxConcurrent) {\n throw new Error(`Maximum concurrent tasks (${maxConcurrent}) reached`);\n }\n\n // Update task status\n updateTask(this.projectPath, taskId, { status: 'in_progress' });\n\n const startedAt = new Date();\n\n // Create git worktree for isolated execution\n const worktreeInfo = this.createWorktree(taskId);\n const executionPath = worktreeInfo?.worktreePath || this.projectPath;\n\n const prompt = this.buildTaskPrompt(task, config);\n\n // Spawn claude CLI process\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n\n // Write prompt to temp file to avoid shell escaping issues\n const promptFile = join(kanbanDir, `prompt-${taskId}.txt`);\n writeFileSync(promptFile, prompt);\n\n // Build command arguments\n // NOTE: We only pass the task prompt file, not the entire PRD\n // The prompt contains all task details, and Claude can read/update prd.json as needed\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'); // Enable streaming output\n args.push(`@${promptFile}`);\n\n const commandDisplay = `${config.agent.command} ${args.join(' ')}`;\n\n // Build the full command\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n // Debug logging\n console.log('[executor] Command:', fullCommand);\n console.log('[executor] CWD:', executionPath);\n if (worktreeInfo) {\n console.log('[executor] Using worktree:', worktreeInfo.worktreePath);\n console.log('[executor] Branch:', worktreeInfo.branchName);\n }\n\n // Spawn Claude CLI directly using bash\n // The -p flag puts Claude in \"print\" mode which should work without a TTY\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: executionPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0', // Disable colors to avoid escape codes\n NO_COLOR: '1', // Standard way to disable colors\n },\n stdio: ['ignore', 'pipe', 'pipe'], // Close stdin since we don't need interactive input\n });\n\n const runningTask: RunningTask = {\n taskId,\n process: childProcess,\n startedAt,\n output: [],\n worktreePath: worktreeInfo?.worktreePath,\n branchName: worktreeInfo?.branchName,\n };\n\n this.runningTasks.set(taskId, runningTask);\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 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 if (worktreeInfo) {\n logOutput(`[claude-kanban] Worktree: ${worktreeInfo.worktreePath}\\n`);\n logOutput(`[claude-kanban] Branch: ${worktreeInfo.branchName}\\n`);\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 // Process complete JSON lines\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || ''; // Keep incomplete line in buffer\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 // Extract text content from various message types\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 // Not JSON, emit as plain text\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 success\n childProcess.on('spawn', () => {\n console.log('[executor] Process spawned successfully');\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 // Clean up and mark failed\n try { unlinkSync(promptFile); } catch {}\n // Clean up worktree on error\n if (worktreeInfo) {\n this.removeWorktree(taskId);\n }\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.runningTasks.delete(taskId);\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.isTaskRunning(taskId)) {\n this.cancelTask(taskId, '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 const runningTask = this.runningTasks.get(taskId);\n if (!runningTask) return;\n\n const endedAt = new Date();\n const duration = endedAt.getTime() - startedAt.getTime();\n const output = runningTask.output.join('');\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\n // Merge worktree branch back to main if using worktrees\n if (runningTask.worktreePath && runningTask.branchName) {\n const merged = this.mergeWorktreeBranch(taskId);\n if (merged) {\n console.log(`[executor] Successfully merged ${runningTask.branchName}`);\n } else {\n console.log(`[executor] Failed to merge ${runningTask.branchName}, branch preserved for manual merge`);\n }\n // Clean up worktree (branch kept if merge failed for manual resolution)\n this.removeWorktree(taskId);\n }\n\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 } else {\n // Task failed - clean up worktree without merging\n if (runningTask.worktreePath) {\n this.removeWorktree(taskId);\n }\n\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 }\n\n this.runningTasks.delete(taskId);\n\n // Continue AFK mode if active\n if (this.afkMode) {\n this.continueAFKMode();\n }\n }\n\n /**\n * Cancel a running task\n */\n cancelTask(taskId: string, reason = 'Cancelled by user'): boolean {\n const runningTask = this.runningTasks.get(taskId);\n if (!runningTask) return false;\n\n const startedAt = runningTask.startedAt;\n const endedAt = new Date();\n const duration = endedAt.getTime() - startedAt.getTime();\n const task = getTaskById(this.projectPath, taskId);\n\n // Kill the process - try SIGTERM first, then SIGKILL\n try {\n runningTask.process.kill('SIGTERM');\n // Force kill after 2 seconds if still running\n setTimeout(() => {\n try {\n if (!runningTask.process.killed) {\n runningTask.process.kill('SIGKILL');\n }\n } catch {\n // Process might already be dead\n }\n }, 2000);\n } catch {\n // Process might already be dead\n }\n\n // Clean up worktree without merging (task was cancelled)\n if (runningTask.worktreePath) {\n this.removeWorktree(taskId);\n }\n\n // Update task status back to ready\n updateTask(this.projectPath, taskId, {\n status: 'ready',\n });\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.runningTasks.delete(taskId);\n\n return true;\n }\n\n /**\n * Start AFK mode\n */\n startAFKMode(maxIterations: number, concurrent: number): void {\n if (this.afkMode) {\n throw new Error('AFK mode already running');\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(concurrent);\n }\n\n /**\n * Continue AFK mode - pick up next tasks\n */\n private continueAFKMode(concurrent = 1): 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 // Fill up to concurrent limit\n const config = getConfig(this.projectPath);\n const maxConcurrent = Math.min(concurrent, config.execution.maxConcurrent || 3);\n\n while (this.getRunningCount() < maxConcurrent) {\n const nextTask = getNextReadyTask(this.projectPath);\n if (!nextTask) {\n // No more tasks to run\n if (this.getRunningCount() === 0) {\n this.stopAFKMode();\n }\n break;\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 /**\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 all running tasks\n */\n cancelAll(): void {\n for (const [taskId, runningTask] of this.runningTasks.entries()) {\n // Force kill immediately on shutdown\n try {\n runningTask.process.kill('SIGKILL');\n } catch {\n // Process might already be dead\n }\n // Clean up worktree\n if (runningTask.worktreePath) {\n this.removeWorktree(taskId);\n }\n this.runningTasks.delete(taskId);\n }\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 },\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 };\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 * Get next task to execute (highest priority \"ready\" task)\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 // 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 readyTasks.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 readyTasks[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 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,EAmeP,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;A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","names":["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","spawn","__dirname","join","existsSync","createServer"]}
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/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 { createServer } from '../server/index.js';\nimport { initializeProject, isProjectInitialized } from '../server/services/project.js';\nimport { findAvailablePort } from '../server/utils/port.js';\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 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 * 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, AFKStartRequest } 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 // 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 // ===== 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\n app.post('/api/tasks/:id/cancel', (req, res) => {\n try {\n const cancelled = executor.cancelTask(req.params.id);\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\n app.post('/api/afk/start', (req, res) => {\n try {\n const { maxIterations, concurrent } = req.body as AFKStartRequest;\n executor.startAFKMode(maxIterations || 10, concurrent || 1);\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 // Get running tasks info\n app.get('/api/running', (_req, res) => {\n try {\n const taskIds = executor.getRunningTaskIds();\n res.json({ running: taskIds, count: taskIds.length });\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.getRunningCount();\n const afk = executor.getAFKStatus();\n res.json({ counts, running, afk });\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 tasks and recent tasks\n const runningIds = executor.getRunningTaskIds();\n const taskLogs: Record<string, string> = {};\n\n // Get logs for all running tasks\n for (const taskId of runningIds) {\n const logs = executor.getTaskLog(taskId);\n if (logs) {\n taskLogs[taskId] = 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 tasks\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: 16px 20px;\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: 12px 16px;\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 sidePanelTab: 'logs', // 'logs' or 'details'\n darkMode: localStorage.getItem('darkMode') === 'true', // Add dark mode state\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 // 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) { task.status = 'completed'; task.passes = true; }\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// 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, concurrent) {\n await fetch('/api/afk/start', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ maxIterations, concurrent })\n });\n}\n\nasync function stopAFK() {\n await fetch('/api/afk/stop', { method: 'POST' });\n}\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 // 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\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&#10;Enter valid credentials&#10;Click submit button&#10;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 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>\n <label class=\"block text-sm font-medium text-canvas-700 mb-2\">Concurrent Tasks</label>\n <select id=\"afk-concurrent\" class=\"input w-full\">\n <option value=\"1\">1 (Sequential)</option>\n <option value=\"2\">2</option>\n <option value=\"3\">3 (Max)</option>\n </select>\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 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\n return \\`\n <div class=\"side-panel\">\n <!-- Header -->\n <div class=\"side-panel-header\">\n <div class=\"flex justify-between items-start\">\n <div class=\"flex-1 pr-4\">\n <h2 class=\"font-semibold text-canvas-900 text-lg leading-tight\">\\${escapeHtml(task.title)}</h2>\n <div class=\"flex items-center gap-2 mt-2\">\n <span class=\"status-badge status-badge-\\${task.status}\">\n <span class=\"w-1.5 h-1.5 rounded-full bg-current \\${isRunning ? 'animate-pulse' : ''}\"></span>\n \\${task.status.replace('_', ' ')}\n </span>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <button onclick=\"state.editingTask = state.tasks.find(t => t.id === '\\${task.id}'); state.showModal = 'edit'; render();\"\n class=\"btn btn-ghost p-2 text-canvas-500 hover:text-canvas-700\" title=\"Edit\">\n ✏️\n </button>\n <button onclick=\"if(confirm('Delete this task?')) { deleteTask('\\${task.id}'); closeSidePanel(); }\"\n class=\"btn btn-ghost p-2 text-canvas-500 hover:text-status-failed\" title=\"Delete\">\n 🗑️\n </button>\n <button onclick=\"closeSidePanel()\" class=\"btn btn-ghost p-2 text-canvas-500 hover:text-canvas-700\" title=\"Close\">\n ✕\n </button>\n </div>\n </div>\n </div>\n\n <!-- Description -->\n <div class=\"px-5 py-4 border-b border-canvas-200 flex-shrink-0\">\n <p class=\"text-sm text-canvas-600 leading-relaxed\">\\${escapeHtml(task.description)}</p>\n \\${task.steps && task.steps.length > 0 ? \\`\n <div class=\"mt-3\">\n <div class=\"text-xs font-medium text-canvas-500 mb-2\">Steps:</div>\n <ul class=\"text-sm text-canvas-600 space-y-1\">\n \\${task.steps.map(s => \\`<li class=\"flex gap-2\"><span class=\"text-canvas-400\">•</span>\\${escapeHtml(s)}</li>\\`).join('')}\n </ul>\n </div>\n \\` : ''}\n </div>\n\n <!-- Task Details Grid -->\n <div class=\"details-grid\">\n <div class=\"details-item\">\n <span class=\"details-label\">Priority</span>\n <span class=\"details-value capitalize\">\\${task.priority}</span>\n </div>\n <div class=\"details-item\">\n <span class=\"details-label\">Category</span>\n <span class=\"details-value\">\\${categoryIcons[task.category] || ''} \\${task.category}</span>\n </div>\n \\${startTime || lastExec ? \\`\n <div class=\"details-item\">\n <span class=\"details-label\">Started</span>\n <span class=\"details-value\">\\${new Date(startTime || lastExec?.startedAt).toLocaleString()}</span>\n </div>\n \\` : ''}\n \\${lastExec?.duration ? \\`\n <div class=\"details-item\">\n <span class=\"details-label\">Duration</span>\n <span class=\"details-value\">\\${Math.round(lastExec.duration / 1000)}s</span>\n </div>\n \\` : ''}\n </div>\n\n <!-- Action Buttons -->\n <div class=\"px-5 py-4 border-b border-canvas-200 flex gap-2 flex-shrink-0\">\n \\${task.status === 'draft' ? \\`\n <button onclick=\"updateTask('\\${task.id}', { status: 'ready' })\" class=\"btn btn-primary px-4 py-2 text-sm\">\n → Move to Ready\n </button>\n \\` : ''}\n \\${task.status === 'ready' ? \\`\n <button onclick=\"runTask('\\${task.id}')\" class=\"btn btn-primary px-4 py-2 text-sm\">\n ▶ Run Task\n </button>\n \\` : ''}\n \\${task.status === 'in_progress' ? \\`\n <button onclick=\"cancelTask('\\${task.id}')\" class=\"btn btn-danger px-4 py-2 text-sm\">\n ⏹ Stop Attempt\n </button>\n \\` : ''}\n \\${task.status === 'failed' ? \\`\n <button onclick=\"retryTask('\\${task.id}')\" class=\"btn btn-primary px-4 py-2 text-sm\">\n ↻ Retry\n </button>\n \\` : ''}\n </div>\n\n <!-- Tabs -->\n <div class=\"side-panel-tabs\">\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\">\n \\${state.sidePanelTab === 'logs' ? \\`\n <div class=\"log-container\" id=\"side-panel-log\">\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 class=\"p-5\">\n <div class=\"text-sm text-canvas-600\">\n <div class=\"mb-4\">\n <div class=\"text-xs font-medium text-canvas-500 mb-1\">Created</div>\n <div>\\${new Date(task.createdAt).toLocaleString()}</div>\n </div>\n <div class=\"mb-4\">\n <div class=\"text-xs font-medium text-canvas-500 mb-1\">Last Updated</div>\n <div>\\${new Date(task.updatedAt).toLocaleString()}</div>\n </div>\n \\${task.executionHistory && task.executionHistory.length > 0 ? \\`\n <div>\n <div class=\"text-xs font-medium text-canvas-500 mb-2\">Execution History</div>\n <div class=\"space-y-2\">\n \\${task.executionHistory.slice(-5).reverse().map(exec => \\`\n <div class=\"bg-canvas-100 rounded p-2 text-xs\">\n <div class=\"flex justify-between\">\n <span class=\"\\${exec.status === 'completed' ? 'text-status-success' : 'text-status-failed'}\">\\${exec.status}</span>\n <span class=\"text-canvas-500\">\\${Math.round(exec.duration / 1000)}s</span>\n </div>\n <div class=\"text-canvas-500 mt-1\">\\${new Date(exec.startedAt).toLocaleString()}</div>\n \\${exec.error ? \\`<div class=\"text-status-failed mt-1\">\\${escapeHtml(exec.error)}</div>\\` : ''}\n </div>\n \\`).join('')}\n </div>\n </div>\n \\` : ''}\n </div>\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 const concurrent = parseInt(document.getElementById('afk-concurrent').value) || 1;\n startAFK(iterations, concurrent);\n state.showModal = null;\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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\n\n// Main render\nfunction render() {\n const app = document.getElementById('app');\n const hasSidePanel = state.sidePanel !== null;\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 <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 </div>\n <div class=\"flex items-center gap-2\">\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 </div>\n </div>\n </header>\n\n \\${renderAFKBar()}\n\n <!-- Main Content Area - Flex container for board + sidebar -->\n <div class=\"flex flex-1 overflow-hidden\">\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\n <!-- Side Panel (pushes content when open) -->\n \\${hasSidePanel ? renderSidePanel() : ''}\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.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;\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, execSync } from 'child_process';\nimport { join } from 'path';\nimport { writeFileSync, unlinkSync, mkdirSync, existsSync, appendFileSync, readFileSync, rmSync } from 'fs';\nimport { EventEmitter } from 'events';\nimport type { Config, Task, RunningTask } 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';\nconst WORKTREES_DIR = 'worktrees';\n\n/**\n * Task Executor - manages running tasks\n */\nexport class TaskExecutor extends EventEmitter {\n private projectPath: string;\n private runningTasks: Map<string, RunningTask> = new Map();\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 // Truncate long strings for display\n const truncate = (str: string, maxLen = 80): string => {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen) + '...';\n };\n\n // Format based on tool type\n switch (toolName) {\n case 'Bash':\n return `[Bash] $ ${truncate(String(input.command || ''))}\\n`;\n\n case 'Read':\n return `[Read] ${input.file_path}\\n`;\n\n case 'Edit':\n return `[Edit] ${input.file_path}\\n`;\n\n case 'Write':\n return `[Write] ${input.file_path}\\n`;\n\n case 'Grep':\n return `[Grep] \"${truncate(String(input.pattern || ''))}\" in ${input.path || '.'}\\n`;\n\n case 'Glob':\n return `[Glob] ${input.pattern} in ${input.path || '.'}\\n`;\n\n case 'Task':\n return `[Task] ${input.description || truncate(String(input.prompt || ''))}\\n`;\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\n case 'WebFetch':\n return `[WebFetch] ${input.url}\\n`;\n\n case 'WebSearch':\n return `[WebSearch] \"${truncate(String(input.query || ''))}\"\\n`;\n\n default:\n // For MCP tools and others, show tool name with first meaningful param\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 project is a git repository\n */\n private isGitRepo(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get worktrees directory path\n */\n private getWorktreesDir(): string {\n return join(this.projectPath, KANBAN_DIR, WORKTREES_DIR);\n }\n\n /**\n * Get worktree path for a task\n */\n private getWorktreePath(taskId: string): string {\n return join(this.getWorktreesDir(), taskId);\n }\n\n /**\n * Get branch name for a task\n */\n private getBranchName(taskId: string): string {\n return `task/${taskId}`;\n }\n\n /**\n * Create a git worktree for isolated task execution\n */\n private createWorktree(taskId: string): { worktreePath: string; branchName: string } | null {\n if (!this.isGitRepo()) {\n console.log('[executor] Not a git repo, skipping worktree creation');\n return null;\n }\n\n const worktreePath = this.getWorktreePath(taskId);\n const branchName = this.getBranchName(taskId);\n\n try {\n // Ensure worktrees directory exists\n const worktreesDir = this.getWorktreesDir();\n if (!existsSync(worktreesDir)) {\n mkdirSync(worktreesDir, { recursive: true });\n }\n\n // Remove existing worktree if it exists (from a previous failed run)\n if (existsSync(worktreePath)) {\n this.removeWorktree(taskId);\n }\n\n // Create new worktree with a new branch\n // First, check if branch already exists\n try {\n execSync(`git rev-parse --verify ${branchName}`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n // Branch exists, delete it first\n execSync(`git branch -D ${branchName}`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n } catch {\n // Branch doesn't exist, that's fine\n }\n\n // Create worktree with new branch\n execSync(`git worktree add -b ${branchName} \"${worktreePath}\"`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n\n // Note: We don't install dependencies here - the Claude agent will handle it\n // This is more flexible and the agent can figure out the right approach\n\n console.log(`[executor] Created worktree at ${worktreePath} on branch ${branchName}`);\n return { worktreePath, branchName };\n } catch (error) {\n console.error('[executor] Failed to create worktree:', error);\n return null;\n }\n }\n\n /**\n * Remove a git worktree\n */\n private removeWorktree(taskId: string): void {\n const worktreePath = this.getWorktreePath(taskId);\n const branchName = this.getBranchName(taskId);\n\n try {\n // Remove the worktree\n if (existsSync(worktreePath)) {\n execSync(`git worktree remove \"${worktreePath}\" --force`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n console.log(`[executor] Removed worktree at ${worktreePath}`);\n }\n } catch (error) {\n console.error('[executor] Failed to remove worktree via git:', error);\n // Fallback: force remove the directory\n try {\n if (existsSync(worktreePath)) {\n rmSync(worktreePath, { recursive: true, force: true });\n // Also prune worktrees\n execSync('git worktree prune', {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n }\n } catch {\n console.error('[executor] Failed to force remove worktree directory');\n }\n }\n\n // Delete the branch\n try {\n execSync(`git branch -D ${branchName}`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n console.log(`[executor] Deleted branch ${branchName}`);\n } catch {\n // Branch might not exist or have other issues\n }\n }\n\n /**\n * Merge worktree branch back to main branch\n */\n private mergeWorktreeBranch(taskId: string): boolean {\n const branchName = this.getBranchName(taskId);\n\n try {\n // Get current branch\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: this.projectPath,\n encoding: 'utf-8',\n }).trim();\n\n // Merge the task branch\n execSync(`git merge ${branchName} --no-edit`, {\n cwd: this.projectPath,\n stdio: 'pipe',\n });\n\n console.log(`[executor] Merged ${branchName} into ${currentBranch}`);\n return true;\n } catch (error) {\n console.error('[executor] Failed to merge branch:', error);\n return false;\n }\n }\n\n /**\n * Get number of currently running tasks\n */\n getRunningCount(): number {\n return this.runningTasks.size;\n }\n\n /**\n * Check if a task is running\n */\n isTaskRunning(taskId: string): boolean {\n return this.runningTasks.has(taskId);\n }\n\n /**\n * Get all running task IDs\n */\n getRunningTaskIds(): string[] {\n return Array.from(this.runningTasks.keys());\n }\n\n /**\n * Get running task output\n */\n getTaskOutput(taskId: string): string[] | undefined {\n return this.runningTasks.get(taskId)?.output;\n }\n\n /**\n * Build the prompt for a specific task\n */\n private buildTaskPrompt(\n task: Task,\n config: Config,\n worktreeInfo: { worktreePath: string; branchName: string } | null\n ): 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 instructions only for configured commands\n const verifySteps: string[] = [];\n if (config.project.typecheckCommand) {\n verifySteps.push(`Run typecheck: ${config.project.typecheckCommand}`);\n }\n if (config.project.testCommand) {\n verifySteps.push(`Run tests: ${config.project.testCommand}`);\n }\n\n const verifySection = verifySteps.length > 0\n ? `3. Verify your work:\\n${verifySteps.map(s => ` - ${s}`).join('\\n')}\\n\\n`\n : '';\n\n // Worktree-specific instructions\n const worktreeSection = worktreeInfo\n ? `## ENVIRONMENT\nYou are running in an isolated git worktree on branch \"${worktreeInfo.branchName}\".\nThis is a fresh checkout - dependencies (node_modules, vendor, etc.) are NOT installed.\nBefore running any build/test commands, install dependencies first (e.g., npm install, composer install, pip install).\n\n`\n : '';\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${worktreeSection}## INSTRUCTIONS\n1. If dependencies are not installed, install them first.\n\n2. Implement this task as described above.\n\n${verifySection}${verifySteps.length > 0 ? '4' : '3'}. When complete, update the task in ${prdPath}:\n - Find the task with id \"${task.id}\"\n - Set \"passes\": true\n - Set \"status\": \"completed\"\n\n${verifySteps.length > 0 ? '5' : '4'}. Document your work in ${progressPath}:\n - What you implemented and files changed\n - Key decisions made and why\n - Gotchas, edge cases, or tricky parts discovered\n - Useful patterns or approaches that worked well\n - Anything a future agent should know about this area of the codebase\n\n${verifySteps.length > 0 ? '6' : '5'}. Make a git commit with a descriptive message.\n\nFocus only on this task. When successfully complete, output: <promise>COMPLETE</promise>`;\n }\n\n /**\n * Run a specific task\n */\n async runTask(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 if (this.isTaskRunning(taskId)) {\n throw new Error(`Task already running: ${taskId}`);\n }\n\n const maxConcurrent = config.execution.maxConcurrent || 3;\n if (this.getRunningCount() >= maxConcurrent) {\n throw new Error(`Maximum concurrent tasks (${maxConcurrent}) reached`);\n }\n\n // Update task status\n updateTask(this.projectPath, taskId, { status: 'in_progress' });\n\n const startedAt = new Date();\n\n // Create git worktree for isolated execution\n const worktreeInfo = this.createWorktree(taskId);\n const executionPath = worktreeInfo?.worktreePath || this.projectPath;\n\n const prompt = this.buildTaskPrompt(task, config, worktreeInfo);\n\n // Spawn claude CLI process\n const kanbanDir = join(this.projectPath, KANBAN_DIR);\n\n // Write prompt to temp file to avoid shell escaping issues\n const promptFile = join(kanbanDir, `prompt-${taskId}.txt`);\n writeFileSync(promptFile, prompt);\n\n // Build command arguments\n // NOTE: We only pass the task prompt file, not the entire PRD\n // The prompt contains all task details, and Claude can read/update prd.json as needed\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'); // Enable streaming output\n args.push(`@${promptFile}`);\n\n const commandDisplay = `${config.agent.command} ${args.join(' ')}`;\n\n // Build the full command\n const fullCommand = `${config.agent.command} ${args.join(' ')}`;\n\n // Debug logging\n console.log('[executor] Command:', fullCommand);\n console.log('[executor] CWD:', executionPath);\n if (worktreeInfo) {\n console.log('[executor] Using worktree:', worktreeInfo.worktreePath);\n console.log('[executor] Branch:', worktreeInfo.branchName);\n }\n\n // Spawn Claude CLI directly using bash\n // The -p flag puts Claude in \"print\" mode which should work without a TTY\n const childProcess = spawn('bash', ['-c', fullCommand], {\n cwd: executionPath,\n env: {\n ...process.env,\n TERM: 'xterm-256color',\n FORCE_COLOR: '0', // Disable colors to avoid escape codes\n NO_COLOR: '1', // Standard way to disable colors\n },\n stdio: ['ignore', 'pipe', 'pipe'], // Close stdin since we don't need interactive input\n });\n\n const runningTask: RunningTask = {\n taskId,\n process: childProcess,\n startedAt,\n output: [],\n worktreePath: worktreeInfo?.worktreePath,\n branchName: worktreeInfo?.branchName,\n };\n\n this.runningTasks.set(taskId, runningTask);\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 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 if (worktreeInfo) {\n logOutput(`[claude-kanban] Worktree: ${worktreeInfo.worktreePath}\\n`);\n logOutput(`[claude-kanban] Branch: ${worktreeInfo.branchName}\\n`);\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 // Process complete JSON lines\n const lines = stdoutBuffer.split('\\n');\n stdoutBuffer = lines.pop() || ''; // Keep incomplete line in buffer\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 // Extract text content from various message types\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 // Not JSON, emit as plain text\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 success\n childProcess.on('spawn', () => {\n console.log('[executor] Process spawned successfully');\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 // Clean up and mark failed\n try { unlinkSync(promptFile); } catch {}\n // Clean up worktree on error\n if (worktreeInfo) {\n this.removeWorktree(taskId);\n }\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.runningTasks.delete(taskId);\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.isTaskRunning(taskId)) {\n this.cancelTask(taskId, '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 const runningTask = this.runningTasks.get(taskId);\n if (!runningTask) return;\n\n const endedAt = new Date();\n const duration = endedAt.getTime() - startedAt.getTime();\n const output = runningTask.output.join('');\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\n // Merge worktree branch back to main if using worktrees\n if (runningTask.worktreePath && runningTask.branchName) {\n const merged = this.mergeWorktreeBranch(taskId);\n if (merged) {\n console.log(`[executor] Successfully merged ${runningTask.branchName}`);\n } else {\n console.log(`[executor] Failed to merge ${runningTask.branchName}, branch preserved for manual merge`);\n }\n // Clean up worktree (branch kept if merge failed for manual resolution)\n this.removeWorktree(taskId);\n }\n\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 } else {\n // Task failed - clean up worktree without merging\n if (runningTask.worktreePath) {\n this.removeWorktree(taskId);\n }\n\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 }\n\n this.runningTasks.delete(taskId);\n\n // Continue AFK mode if active\n if (this.afkMode) {\n this.continueAFKMode();\n }\n }\n\n /**\n * Cancel a running task\n */\n cancelTask(taskId: string, reason = 'Cancelled by user'): boolean {\n const runningTask = this.runningTasks.get(taskId);\n if (!runningTask) return false;\n\n const startedAt = runningTask.startedAt;\n const endedAt = new Date();\n const duration = endedAt.getTime() - startedAt.getTime();\n const task = getTaskById(this.projectPath, taskId);\n\n // Kill the process - try SIGTERM first, then SIGKILL\n try {\n runningTask.process.kill('SIGTERM');\n // Force kill after 2 seconds if still running\n setTimeout(() => {\n try {\n if (!runningTask.process.killed) {\n runningTask.process.kill('SIGKILL');\n }\n } catch {\n // Process might already be dead\n }\n }, 2000);\n } catch {\n // Process might already be dead\n }\n\n // Clean up worktree without merging (task was cancelled)\n if (runningTask.worktreePath) {\n this.removeWorktree(taskId);\n }\n\n // Update task status back to ready\n updateTask(this.projectPath, taskId, {\n status: 'ready',\n });\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.runningTasks.delete(taskId);\n\n return true;\n }\n\n /**\n * Start AFK mode\n */\n startAFKMode(maxIterations: number, concurrent: number): void {\n if (this.afkMode) {\n throw new Error('AFK mode already running');\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(concurrent);\n }\n\n /**\n * Continue AFK mode - pick up next tasks\n */\n private continueAFKMode(concurrent = 1): 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 // Fill up to concurrent limit\n const config = getConfig(this.projectPath);\n const maxConcurrent = Math.min(concurrent, config.execution.maxConcurrent || 3);\n\n while (this.getRunningCount() < maxConcurrent) {\n const nextTask = getNextReadyTask(this.projectPath);\n if (!nextTask) {\n // No more tasks to run\n if (this.getRunningCount() === 0) {\n this.stopAFKMode();\n }\n break;\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 /**\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 all running tasks\n */\n cancelAll(): void {\n for (const [taskId, runningTask] of this.runningTasks.entries()) {\n // Force kill immediately on shutdown\n try {\n runningTask.process.kill('SIGKILL');\n } catch {\n // Process might already be dead\n }\n // Clean up worktree\n if (runningTask.worktreePath) {\n this.removeWorktree(taskId);\n }\n this.runningTasks.delete(taskId);\n }\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 },\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 };\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 * Get next task to execute (highest priority \"ready\" task)\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 // 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 readyTasks.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 readyTasks[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 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":";;;AAEA,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,OAAO,UAAU;;;ACJjB,OAAO,aAAa;AACpB,SAAS,gBAAgB,wBAAgC;AACzD,SAAS,UAAU,sBAAsB;AACzC,SAAS,QAAAA,OAAM,eAAe;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,cAAAC,mBAAkB;;;ACL3B,SAAS,OAAO,gBAAgB;AAChC,SAAS,QAAAC,aAAY;AACrB,SAAS,iBAAAC,gBAAe,YAAY,aAAAC,YAAW,cAAAC,aAAY,kBAAAC,iBAAgB,gBAAAC,eAAc,cAAc;AACvG,SAAS,oBAAoB;;;ACH7B,SAAS,YAAY,WAAW,eAAe,oBAAoB;AACnE,SAAS,MAAM,gBAAgB;AAG/B,IAAM,aAAa;AACnB,IAAM,cAAc;AAKpB,eAAsB,qBAAqB,aAAuC;AAChF,QAAM,YAAY,KAAK,aAAa,UAAU;AAC9C,QAAM,UAAU,KAAK,WAAW,UAAU;AAC1C,QAAM,aAAa,KAAK,WAAW,aAAa;AAEhD,SAAO,WAAW,SAAS,KAAK,WAAW,OAAO,KAAK,WAAW,UAAU;AAC9E;AAKO,SAAS,qBAAqB,aAAsD;AACzF,MAAI,WAAW,KAAK,aAAa,gBAAgB,CAAC,EAAG,QAAO;AAC5D,MAAI,WAAW,KAAK,aAAa,WAAW,CAAC,EAAG,QAAO;AACvD,MAAI,WAAW,KAAK,aAAa,WAAW,CAAC,EAAG,QAAO;AACvD,SAAO;AACT;AAOO,SAAS,kBAAkB,aAAkC;AAElE,MAAI,WAAW,KAAK,aAAa,cAAc,CAAC,EAAG,QAAO;AAC1D,MAAI,WAAW,KAAK,aAAa,gBAAgB,CAAC,KAC9C,WAAW,KAAK,aAAa,kBAAkB,CAAC,KAChD,WAAW,KAAK,aAAa,UAAU,CAAC,EAAG,QAAO;AACtD,MAAI,WAAW,KAAK,aAAa,eAAe,CAAC,EAAG,QAAO;AAC3D,MAAI,WAAW,KAAK,aAAa,SAAS,CAAC,EAAG,QAAO;AACrD,MAAI,WAAW,KAAK,aAAa,QAAQ,CAAC,EAAG,QAAO;AACpD,MAAI,WAAW,KAAK,aAAa,YAAY,CAAC,EAAG,QAAO;AACxD,SAAO;AACT;AAKO,SAAS,sBAAsB,aAIpC;AACA,QAAM,cAAc,kBAAkB,WAAW;AAEjD,UAAQ,aAAa;AAAA,IACnB,KAAK;AACH,aAAO,mBAAmB,WAAW;AAAA,IACvC,KAAK;AACH,aAAO,qBAAqB,WAAW;AAAA,IACzC,KAAK;AACH,aAAO,kBAAkB,WAAW;AAAA,IACtC,KAAK;AACH,aAAO,mBAAmB,WAAW;AAAA,IACvC,KAAK;AACH,aAAO,iBAAiB,WAAW;AAAA,IACrC,KAAK;AACH,aAAO,mBAAmB,WAAW;AAAA,IACvC;AACE,aAAO;AAAA,QACL,aAAa;AAAA,QACb,kBAAkB;AAAA,QAClB,cAAc;AAAA,MAChB;AAAA,EACJ;AACF;AAKA,SAAS,mBAAmB,aAI1B;AACA,QAAM,KAAK,qBAAqB,WAAW;AAC3C,QAAM,MAAM,OAAO,QAAQ,YAAY;AAGvC,QAAM,kBAAkB,KAAK,aAAa,cAAc;AACxD,MAAI,UAAkC,CAAC;AAEvC,MAAI,WAAW,eAAe,GAAG;AAC/B,QAAI;AACF,YAAM,MAAM,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;AAC7D,gBAAU,IAAI,WAAW,CAAC;AAAA,IAC5B,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,cAAc,QAAQ,OAAO,GAAG,GAAG,UAAU;AACnD,QAAM,mBAAmB,QAAQ,YAC7B,GAAG,GAAG,eACN,QAAQ,YAAY,IAClB,GAAG,GAAG,gBACN,WAAW,KAAK,aAAa,eAAe,CAAC,IAC3C,GAAG,QAAQ,YAAY,QAAQ,EAAE,kBACjC;AACR,QAAM,eAAe,QAAQ,QAAQ,GAAG,GAAG,WAAW;AAEtD,SAAO,EAAE,aAAa,kBAAkB,aAAa;AACvD;AAKA,SAAS,qBAAqB,aAI5B;AAEA,QAAM,YAAY,WAAW,KAAK,aAAa,YAAY,CAAC,KAC1C,WAAW,KAAK,aAAa,gBAAgB,CAAC,KAC9C,WAAW,KAAK,aAAa,OAAO,CAAC;AAGvD,QAAM,UAAU,WAAW,KAAK,aAAa,UAAU,CAAC,KACxC,WAAW,KAAK,aAAa,WAAW,CAAC;AAEzD,SAAO;AAAA,IACL,aAAa,YAAY,WAAW;AAAA,IACpC,kBAAkB,UAAU,WAAW;AAAA,IACvC,cAAc;AAAA,EAChB;AACF;AAKA,SAAS,kBAAkB,aAIzB;AACA,QAAM,aAAa,WAAW,KAAK,aAAa,aAAa,CAAC,KAC3C,WAAW,KAAK,aAAa,kBAAkB,CAAC;AACnE,QAAM,UAAU,WAAW,KAAK,aAAa,SAAS,UAAU,CAAC;AACjE,QAAM,aAAa,WAAW,KAAK,aAAa,cAAc,CAAC,KAC5C,WAAW,KAAK,aAAa,mBAAmB,CAAC;AAEpE,MAAI,cAAc;AAClB,MAAI,SAAS;AACX,kBAAc;AAAA,EAChB,WAAW,YAAY;AACrB,kBAAc;AAAA,EAChB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB,aAAa,iCAAiC;AAAA,IAChE,cAAc;AAAA,EAChB;AACF;AAKA,SAAS,mBAAmB,aAI1B;AACA,QAAM,WAAW,WAAW,KAAK,aAAa,MAAM,CAAC,KACpC,WAAW,KAAK,aAAa,QAAQ,CAAC;AACvD,QAAM,cAAc,WAAW,KAAK,aAAa,MAAM,CAAC;AAExD,SAAO;AAAA,IACL,aAAa,WAAW,sBAAsB,cAAc,0BAA0B;AAAA,IACtF,kBAAkB;AAAA;AAAA,IAClB,cAAc;AAAA,EAChB;AACF;AAKA,SAAS,iBAAiB,cAIxB;AACA,SAAO;AAAA,IACL,aAAa;AAAA,IACb,kBAAkB;AAAA;AAAA,IAClB,cAAc;AAAA,EAChB;AACF;AAKA,SAAS,mBAAmB,cAI1B;AACA,SAAO;AAAA,IACL,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,cAAc;AAAA,EAChB;AACF;AAKA,SAAS,iBAAiB,aAA0B;AAClD,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,OAAO,CAAC;AAAA,EACV;AACF;AAKA,SAAS,oBAAoB,aAA6B;AACxD,QAAM,EAAE,aAAa,kBAAkB,aAAa,IAAI,sBAAsB,WAAW;AAEzF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,MACL,SAAS;AAAA,MACT,gBAAgB;AAAA,MAChB,OAAO;AAAA,IACT;AAAA,IACA,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,IAAI;AAAA,MACF,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,IACA,WAAW;AAAA,MACT,eAAe;AAAA,MACf,SAAS;AAAA;AAAA,IACX;AAAA,EACF;AACF;AAKA,SAAS,kBAAkB,QAAwB;AACjD,QAAM,EAAE,aAAa,iBAAiB,IAAI,OAAO;AAEjD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAeI,OAAO,MAAM,OAAO,sBAAsB,OAAO,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAWlD,gBAAgB;AAAA,gCAChB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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+B3C;AAKA,SAAS,sBAAsB,QAAwB;AACrD,QAAM,EAAE,aAAa,iBAAiB,IAAI,OAAO;AAEjD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQL,OAAO,MAAM,OAAO,sBAAsB,OAAO,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAUzC,gBAAgB;AAAA,gCAChB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAavC,OAAO,MAAM,OAAO,sBAAsB,OAAO,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAazC,gBAAgB;AAAA,gCAChB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAa3C;AAKA,eAAsB,kBAAkB,aAAqB,QAAQ,OAAsB;AACzF,QAAM,YAAY,KAAK,aAAa,UAAU;AAC9C,QAAM,aAAa,KAAK,aAAa,WAAW;AAChD,QAAM,cAAc,SAAS,WAAW;AAGxC,MAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,cAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,EAC1C;AACA,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,cAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AAGA,QAAM,UAAU,KAAK,WAAW,UAAU;AAC1C,MAAI,CAAC,WAAW,OAAO,KAAK,OAAO;AACjC,UAAM,MAAM,iBAAiB,WAAW;AACxC,kBAAc,SAAS,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA,EACrD;AAGA,QAAM,aAAa,KAAK,WAAW,aAAa;AAChD,MAAI;AACJ,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,aAAS,oBAAoB,WAAW;AACxC,kBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC3D,OAAO;AACL,aAAS,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AAAA,EACvD;AAGA,QAAM,eAAe,KAAK,WAAW,cAAc;AACnD,MAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,UAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAClD,kBAAc,cAAc;AAAA;AAAA,WAA4C,WAAW;AAAA,WAAc,IAAI;AAAA;AAAA;AAAA;AAAA,CAAa;AAAA,EACpH;AAGA,QAAM,YAAY,KAAK,YAAY,UAAU;AAC7C,QAAM,gBAAgB,KAAK,YAAY,eAAe;AAEtD,gBAAc,WAAW,kBAAkB,MAAM,CAAC;AAClD,gBAAc,eAAe,sBAAsB,MAAM,CAAC;AAG1D,MAAI;AACF,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,IAAI;AACvC,cAAU,WAAW,GAAK;AAC1B,cAAU,eAAe,GAAK;AAAA,EAChC,QAAQ;AAAA,EAER;AAIF;AAYO,SAAS,UAAU,aAA6B;AACrD,QAAM,aAAa,KAAK,aAAa,YAAY,aAAa;AAC9D,SAAO,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AACrD;AAKO,SAAS,WAAW,aAAqB,QAAsB;AACpE,QAAM,aAAa,KAAK,aAAa,YAAY,aAAa;AAC9D,gBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3D;;;ACvdA,SAAS,gBAAAC,eAAc,iBAAAC,sBAAqB;AAC5C,SAAS,QAAAC,aAAY;AACrB,SAAS,cAAc;AAGvB,IAAMC,cAAa;AAKnB,SAAS,WAAW,aAA6B;AAC/C,SAAOD,MAAK,aAAaC,aAAY,UAAU;AACjD;AAKO,SAAS,QAAQ,aAA0B;AAChD,QAAM,OAAO,WAAW,WAAW;AACnC,SAAO,KAAK,MAAMH,cAAa,MAAM,OAAO,CAAC;AAC/C;AAKO,SAAS,SAAS,aAAqB,KAAgB;AAC5D,QAAM,OAAO,WAAW,WAAW;AACnC,EAAAC,eAAc,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAClD;AAKO,SAAS,YAAY,aAA6B;AACvD,QAAM,MAAM,QAAQ,WAAW;AAC/B,SAAO,IAAI;AACb;AAKO,SAAS,YAAY,aAAqB,QAAkC;AACjF,QAAM,MAAM,QAAQ,WAAW;AAC/B,SAAO,IAAI,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AAC9C;AAKO,SAAS,iBAAiB,aAAqB,QAA4B;AAChF,QAAM,MAAM,QAAQ,WAAW;AAC/B,SAAO,IAAI,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM;AACpD;AAKO,SAAS,WAAW,aAAqB,SAAkC;AAChF,QAAM,MAAM,QAAQ,WAAW;AAC/B,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,QAAM,OAAa;AAAA,IACjB,IAAI,QAAQ,OAAO,CAAC,CAAC;AAAA,IACrB,OAAO,QAAQ;AAAA,IACf,aAAa,QAAQ;AAAA,IACrB,UAAU,QAAQ,YAAY;AAAA,IAC9B,UAAU,QAAQ,YAAY;AAAA,IAC9B,QAAQ,QAAQ,UAAU;AAAA,IAC1B,OAAO,QAAQ,SAAS,CAAC;AAAA,IACzB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,WAAW;AAAA,IACX,kBAAkB,CAAC;AAAA,EACrB;AAEA,MAAI,MAAM,KAAK,IAAI;AACnB,WAAS,aAAa,GAAG;AAEzB,SAAO;AACT;AAKO,SAAS,WAAW,aAAqB,QAAgB,SAAyC;AACvG,QAAM,MAAM,QAAQ,WAAW;AAC/B,QAAM,YAAY,IAAI,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,MAAM;AAE5D,MAAI,cAAc,IAAI;AACpB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,IAAI,MAAM,SAAS;AAChC,QAAM,cAAoB;AAAA,IACxB,GAAG;AAAA,IACH,GAAG;AAAA,IACH,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AAEA,MAAI,MAAM,SAAS,IAAI;AACvB,WAAS,aAAa,GAAG;AAEzB,SAAO;AACT;AAKO,SAAS,WAAW,aAAqB,QAAyB;AACvE,QAAM,MAAM,QAAQ,WAAW;AAC/B,QAAM,gBAAgB,IAAI,MAAM;AAChC,MAAI,QAAQ,IAAI,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,MAAM;AAEnD,MAAI,IAAI,MAAM,SAAS,eAAe;AACpC,aAAS,aAAa,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAgCO,SAAS,kBACd,aACA,QACA,OAOa;AACb,QAAM,MAAM,QAAQ,WAAW;AAC/B,QAAM,YAAY,IAAI,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,MAAM;AAE5D,MAAI,cAAc,IAAI;AACpB,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,SAAS,EAAE,iBAAiB,KAAK,KAAK;AAChD,MAAI,MAAM,SAAS,EAAE,aAAY,oBAAI,KAAK,GAAE,YAAY;AACxD,WAAS,aAAa,GAAG;AAEzB,SAAO,IAAI,MAAM,SAAS;AAC5B;AAKO,SAAS,iBAAiB,aAAkC;AACjE,QAAM,aAAa,iBAAiB,aAAa,OAAO;AAExD,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO;AAAA,EACT;AAGA,QAAM,gBAAwC;AAAA,IAC5C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,KAAK;AAAA,EACP;AAEA,aAAW,KAAK,CAAC,GAAG,MAAM;AACxB,UAAM,gBAAgB,cAAc,EAAE,QAAQ,KAAK,MAAM,cAAc,EAAE,QAAQ,KAAK;AACtF,QAAI,iBAAiB,EAAG,QAAO;AAE/B,WAAO,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ;AAAA,EACzE,CAAC;AAED,SAAO,WAAW,CAAC;AACrB;AAKO,SAAS,cAAc,aAAiD;AAC7E,QAAM,QAAQ,YAAY,WAAW;AACrC,QAAM,SAAqC;AAAA,IACzC,OAAO;AAAA,IACP,OAAO;AAAA,IACP,aAAa;AAAA,IACb,WAAW;AAAA,IACX,QAAQ;AAAA,EACV;AAEA,aAAW,QAAQ,OAAO;AACxB,WAAO,KAAK,MAAM;AAAA,EACpB;AAEA,SAAO;AACT;;;AC9NA,SAAS,gBAAAG,eAAc,iBAAAC,gBAAe,sBAAsB;AAC5D,SAAS,QAAAC,aAAY;AAErB,IAAMC,cAAa;AAKnB,SAAS,gBAAgB,aAA6B;AACpD,SAAOD,MAAK,aAAaC,aAAY,cAAc;AACrD;AAKO,SAAS,aAAa,aAA6B;AACxD,QAAM,OAAO,gBAAgB,WAAW;AACxC,SAAOH,cAAa,MAAM,OAAO;AACnC;AAKO,SAAS,eAAe,aAAqB,OAAqB;AACvE,QAAM,OAAO,gBAAgB,WAAW;AACxC,iBAAe,MAAM,QAAQ,IAAI;AACnC;AAKO,SAAS,iBACd,aACA,SAQM;AACN,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,UAAU,IAAI,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAC9C,QAAM,UAAU,IAAI,aAAa,EAAE,MAAM,GAAG,EAAE,CAAC;AAE/C,QAAM,cAAc,eAAe,QAAQ,QAAQ;AACnD,QAAM,cAAc,QAAQ,WAAW,cAAc,WAAM,QAAQ,WAAW,WAAW,WAAM;AAE/F,MAAI,QAAQ;AAAA,KAAQ,OAAO,IAAI,OAAO;AAAA;AAAA;AACtC,WAAS,aAAa,QAAQ,MAAM,MAAM,QAAQ,SAAS;AAAA;AAC3D,WAAS,WAAW,WAAW,IAAI,QAAQ,OAAO,YAAY,CAAC;AAAA;AAC/D,WAAS,aAAa,WAAW;AAAA;AAEjC,MAAI,QAAQ,OAAO;AACjB,aAAS;AAAA,SAAY,QAAQ,KAAK;AAAA;AAAA,EACpC;AAEA,WAAS;AAET,iBAAe,aAAa,KAAK;AACnC;AAKA,SAAS,eAAe,IAAoB;AAC1C,QAAM,UAAU,KAAK,MAAM,KAAK,GAAI;AACpC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;AAErC,MAAI,QAAQ,GAAG;AACb,WAAO,GAAG,KAAK,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE;AAAA,EACnD;AACA,MAAI,UAAU,GAAG;AACf,WAAO,GAAG,OAAO,KAAK,UAAU,EAAE;AAAA,EACpC;AACA,SAAO,GAAG,OAAO;AACnB;AAKO,SAAS,kBAAkB,aAAqB,QAAQ,KAAa;AAC1E,QAAM,UAAU,aAAa,WAAW;AACxC,QAAM,WAAW,QAAQ,MAAM,IAAI;AAEnC,MAAI,SAAS,UAAU,OAAO;AAC5B,WAAO;AAAA,EACT;AAEA,SAAO,SAAS,MAAM,CAAC,KAAK,EAAE,KAAK,IAAI;AACzC;;;AHnFA,IAAMI,cAAa;AACnB,IAAM,WAAW;AACjB,IAAM,gBAAgB;AAKf,IAAM,eAAN,cAA2B,aAAa;AAAA,EACrC;AAAA,EACA,eAAyC,oBAAI,IAAI;AAAA,EACjD,UAAU;AAAA,EACV,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EAE5B,YAAY,aAAqB;AAC/B,UAAM;AACN,SAAK,cAAc;AACnB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAsB;AAC5B,UAAM,WAAWC,MAAK,KAAK,aAAaD,aAAY,QAAQ;AAC5D,QAAI,CAACE,YAAW,QAAQ,GAAG;AACzB,MAAAC,WAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAAwB;AAC7C,WAAOF,MAAK,KAAK,aAAaD,aAAY,UAAU,GAAG,MAAM,MAAM;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,QAAsB;AACxC,UAAM,UAAU,KAAK,eAAe,MAAM;AAC1C,IAAAI,eAAc,SAAS,EAAE;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,QAAgB,MAAoB;AACtD,UAAM,UAAU,KAAK,eAAe,MAAM;AAC1C,IAAAC,gBAAe,SAAS,IAAI;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,QAA+B;AACxC,UAAM,UAAU,KAAK,eAAe,MAAM;AAC1C,QAAI,CAACH,YAAW,OAAO,EAAG,QAAO;AACjC,WAAOI,cAAa,SAAS,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,UAAkB,OAAwC;AAE9E,UAAM,WAAW,CAAC,KAAa,SAAS,OAAe;AACrD,UAAI,IAAI,UAAU,OAAQ,QAAO;AACjC,aAAO,IAAI,MAAM,GAAG,MAAM,IAAI;AAAA,IAChC;AAGA,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO,YAAY,SAAS,OAAO,MAAM,WAAW,EAAE,CAAC,CAAC;AAAA;AAAA,MAE1D,KAAK;AACH,eAAO,UAAU,MAAM,SAAS;AAAA;AAAA,MAElC,KAAK;AACH,eAAO,UAAU,MAAM,SAAS;AAAA;AAAA,MAElC,KAAK;AACH,eAAO,WAAW,MAAM,SAAS;AAAA;AAAA,MAEnC,KAAK;AACH,eAAO,WAAW,SAAS,OAAO,MAAM,WAAW,EAAE,CAAC,CAAC,QAAQ,MAAM,QAAQ,GAAG;AAAA;AAAA,MAElF,KAAK;AACH,eAAO,UAAU,MAAM,OAAO,OAAO,MAAM,QAAQ,GAAG;AAAA;AAAA,MAExD,KAAK;AACH,eAAO,UAAU,MAAM,eAAe,SAAS,OAAO,MAAM,UAAU,EAAE,CAAC,CAAC;AAAA;AAAA,MAE5E,KAAK;AACH,cAAM,QAAQ,MAAM;AACpB,YAAI,SAAS,MAAM,QAAQ,KAAK,GAAG;AACjC,gBAAM,UAAU,MAAM,IAAI,OAAK,KAAK,EAAE,WAAW,cAAc,WAAM,EAAE,WAAW,gBAAgB,WAAM,QAAG,IAAI,SAAS,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,KAAK,IAAI;AACnJ,iBAAO;AAAA,EAAgB,OAAO;AAAA;AAAA,QAChC;AACA,eAAO;AAAA;AAAA,MAET,KAAK;AACH,eAAO,cAAc,MAAM,GAAG;AAAA;AAAA,MAEhC,KAAK;AACH,eAAO,gBAAgB,SAAS,OAAO,MAAM,SAAS,EAAE,CAAC,CAAC;AAAA;AAAA,MAE5D;AAEE,cAAM,mBAAmB,CAAC,aAAa,QAAQ,WAAW,SAAS,OAAO,WAAW,QAAQ;AAC7F,mBAAW,SAAS,kBAAkB;AACpC,cAAI,MAAM,KAAK,GAAG;AAChB,mBAAO,IAAI,QAAQ,KAAK,SAAS,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC;AAAA;AAAA,UACxD;AAAA,QACF;AACA,eAAO,IAAI,QAAQ;AAAA;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAqB;AAC3B,QAAI;AACF,eAAS,uCAAuC;AAAA,QAC9C,KAAK,KAAK;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AACD,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAA0B;AAChC,WAAOL,MAAK,KAAK,aAAaD,aAAY,aAAa;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,QAAwB;AAC9C,WAAOC,MAAK,KAAK,gBAAgB,GAAG,MAAM;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,QAAwB;AAC5C,WAAO,QAAQ,MAAM;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAAqE;AAC1F,QAAI,CAAC,KAAK,UAAU,GAAG;AACrB,cAAQ,IAAI,uDAAuD;AACnE,aAAO;AAAA,IACT;AAEA,UAAM,eAAe,KAAK,gBAAgB,MAAM;AAChD,UAAM,aAAa,KAAK,cAAc,MAAM;AAE5C,QAAI;AAEF,YAAM,eAAe,KAAK,gBAAgB;AAC1C,UAAI,CAACC,YAAW,YAAY,GAAG;AAC7B,QAAAC,WAAU,cAAc,EAAE,WAAW,KAAK,CAAC;AAAA,MAC7C;AAGA,UAAID,YAAW,YAAY,GAAG;AAC5B,aAAK,eAAe,MAAM;AAAA,MAC5B;AAIA,UAAI;AACF,iBAAS,0BAA0B,UAAU,IAAI;AAAA,UAC/C,KAAK,KAAK;AAAA,UACV,OAAO;AAAA,QACT,CAAC;AAED,iBAAS,iBAAiB,UAAU,IAAI;AAAA,UACtC,KAAK,KAAK;AAAA,UACV,OAAO;AAAA,QACT,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAGA,eAAS,uBAAuB,UAAU,KAAK,YAAY,KAAK;AAAA,QAC9D,KAAK,KAAK;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAKD,cAAQ,IAAI,kCAAkC,YAAY,cAAc,UAAU,EAAE;AACpF,aAAO,EAAE,cAAc,WAAW;AAAA,IACpC,SAAS,OAAO;AACd,cAAQ,MAAM,yCAAyC,KAAK;AAC5D,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAAsB;AAC3C,UAAM,eAAe,KAAK,gBAAgB,MAAM;AAChD,UAAM,aAAa,KAAK,cAAc,MAAM;AAE5C,QAAI;AAEF,UAAIA,YAAW,YAAY,GAAG;AAC5B,iBAAS,wBAAwB,YAAY,aAAa;AAAA,UACxD,KAAK,KAAK;AAAA,UACV,OAAO;AAAA,QACT,CAAC;AACD,gBAAQ,IAAI,kCAAkC,YAAY,EAAE;AAAA,MAC9D;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,iDAAiD,KAAK;AAEpE,UAAI;AACF,YAAIA,YAAW,YAAY,GAAG;AAC5B,iBAAO,cAAc,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAErD,mBAAS,sBAAsB;AAAA,YAC7B,KAAK,KAAK;AAAA,YACV,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AACN,gBAAQ,MAAM,sDAAsD;AAAA,MACtE;AAAA,IACF;AAGA,QAAI;AACF,eAAS,iBAAiB,UAAU,IAAI;AAAA,QACtC,KAAK,KAAK;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AACD,cAAQ,IAAI,6BAA6B,UAAU,EAAE;AAAA,IACvD,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,QAAyB;AACnD,UAAM,aAAa,KAAK,cAAc,MAAM;AAE5C,QAAI;AAEF,YAAM,gBAAgB,SAAS,mCAAmC;AAAA,QAChE,KAAK,KAAK;AAAA,QACV,UAAU;AAAA,MACZ,CAAC,EAAE,KAAK;AAGR,eAAS,aAAa,UAAU,cAAc;AAAA,QAC5C,KAAK,KAAK;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAED,cAAQ,IAAI,qBAAqB,UAAU,SAAS,aAAa,EAAE;AACnE,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,sCAAsC,KAAK;AACzD,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA0B;AACxB,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,QAAyB;AACrC,WAAO,KAAK,aAAa,IAAI,MAAM;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA8B;AAC5B,WAAO,MAAM,KAAK,KAAK,aAAa,KAAK,CAAC;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,QAAsC;AAClD,WAAO,KAAK,aAAa,IAAI,MAAM,GAAG;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKQ,gBACN,MACA,QACA,cACQ;AACR,UAAM,YAAYD,MAAK,KAAK,aAAaD,WAAU;AACnD,UAAM,UAAUC,MAAK,WAAW,UAAU;AAC1C,UAAM,eAAeA,MAAK,WAAW,cAAc;AAEnD,UAAM,YAAY,KAAK,MAAM,SAAS,IAClC;AAAA;AAAA,EAA0B,KAAK,MAAM,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC,KAC/E;AAGJ,UAAM,cAAwB,CAAC;AAC/B,QAAI,OAAO,QAAQ,kBAAkB;AACnC,kBAAY,KAAK,kBAAkB,OAAO,QAAQ,gBAAgB,EAAE;AAAA,IACtE;AACA,QAAI,OAAO,QAAQ,aAAa;AAC9B,kBAAY,KAAK,cAAc,OAAO,QAAQ,WAAW,EAAE;AAAA,IAC7D;AAEA,UAAM,gBAAgB,YAAY,SAAS,IACvC;AAAA,EAAyB,YAAY,IAAI,OAAK,QAAQ,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,IACrE;AAGJ,UAAM,kBAAkB,eACpB;AAAA,yDACiD,aAAa,UAAU;AAAA;AAAA;AAAA;AAAA,IAKxE;AAEJ,WAAO;AAAA;AAAA;AAAA,SAGF,KAAK,KAAK;AAAA,YACP,KAAK,QAAQ;AAAA,YACb,KAAK,QAAQ;AAAA;AAAA,EAEvB,KAAK,WAAW;AAAA,EAChB,SAAS;AAAA;AAAA,EAET,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA,EAKf,aAAa,GAAG,YAAY,SAAS,IAAI,MAAM,GAAG,uCAAuC,OAAO;AAAA,8BACpE,KAAK,EAAE;AAAA;AAAA;AAAA;AAAA,EAInC,YAAY,SAAS,IAAI,MAAM,GAAG,2BAA2B,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzE,YAAY,SAAS,IAAI,MAAM,GAAG;AAAA;AAAA;AAAA,EAGlC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,QAA+B;AAC3C,UAAM,SAAS,UAAU,KAAK,WAAW;AACzC,UAAM,OAAO,YAAY,KAAK,aAAa,MAAM;AAEjD,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,mBAAmB,MAAM,EAAE;AAAA,IAC7C;AAEA,QAAI,KAAK,cAAc,MAAM,GAAG;AAC9B,YAAM,IAAI,MAAM,yBAAyB,MAAM,EAAE;AAAA,IACnD;AAEA,UAAM,gBAAgB,OAAO,UAAU,iBAAiB;AACxD,QAAI,KAAK,gBAAgB,KAAK,eAAe;AAC3C,YAAM,IAAI,MAAM,6BAA6B,aAAa,WAAW;AAAA,IACvE;AAGA,eAAW,KAAK,aAAa,QAAQ,EAAE,QAAQ,cAAc,CAAC;AAE9D,UAAM,YAAY,oBAAI,KAAK;AAG3B,UAAM,eAAe,KAAK,eAAe,MAAM;AAC/C,UAAM,gBAAgB,cAAc,gBAAgB,KAAK;AAEzD,UAAM,SAAS,KAAK,gBAAgB,MAAM,QAAQ,YAAY;AAG9D,UAAM,YAAYA,MAAK,KAAK,aAAaD,WAAU;AAGnD,UAAM,aAAaC,MAAK,WAAW,UAAU,MAAM,MAAM;AACzD,IAAAG,eAAc,YAAY,MAAM;AAKhC,UAAM,OAAiB,CAAC;AACxB,QAAI,OAAO,MAAM,OAAO;AACtB,WAAK,KAAK,WAAW,OAAO,MAAM,KAAK;AAAA,IACzC;AACA,SAAK,KAAK,qBAAqB,OAAO,MAAM,cAAc;AAC1D,SAAK,KAAK,IAAI;AACd,SAAK,KAAK,WAAW;AACrB,SAAK,KAAK,mBAAmB,aAAa;AAC1C,SAAK,KAAK,IAAI,UAAU,EAAE;AAE1B,UAAM,iBAAiB,GAAG,OAAO,MAAM,OAAO,IAAI,KAAK,KAAK,GAAG,CAAC;AAGhE,UAAM,cAAc,GAAG,OAAO,MAAM,OAAO,IAAI,KAAK,KAAK,GAAG,CAAC;AAG7D,YAAQ,IAAI,uBAAuB,WAAW;AAC9C,YAAQ,IAAI,mBAAmB,aAAa;AAC5C,QAAI,cAAc;AAChB,cAAQ,IAAI,8BAA8B,aAAa,YAAY;AACnE,cAAQ,IAAI,sBAAsB,aAAa,UAAU;AAAA,IAC3D;AAIA,UAAM,eAAe,MAAM,QAAQ,CAAC,MAAM,WAAW,GAAG;AAAA,MACtD,KAAK;AAAA,MACL,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,MAAM;AAAA,QACN,aAAa;AAAA;AAAA,QACb,UAAU;AAAA;AAAA,MACZ;AAAA,MACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA;AAAA,IAClC,CAAC;AAED,UAAM,cAA2B;AAAA,MAC/B;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,QAAQ,CAAC;AAAA,MACT,cAAc,cAAc;AAAA,MAC5B,YAAY,cAAc;AAAA,IAC5B;AAEA,SAAK,aAAa,IAAI,QAAQ,WAAW;AAGzC,SAAK,YAAY,MAAM;AAGvB,UAAM,YAAY,CAAC,SAAiB;AAClC,WAAK,YAAY,QAAQ,IAAI;AAC7B,kBAAY,OAAO,KAAK,IAAI;AAC5B,WAAK,KAAK,eAAe,EAAE,QAAQ,MAAM,UAAU,SAAS,CAAC;AAAA,IAC/D;AAGA,SAAK,KAAK,gBAAgB,EAAE,QAAQ,WAAW,UAAU,YAAY,EAAE,CAAC;AACxE,cAAU,kCAAkC,KAAK,KAAK;AAAA,CAAI;AAC1D,QAAI,cAAc;AAChB,gBAAU,6BAA6B,aAAa,YAAY;AAAA,CAAI;AACpE,gBAAU,2BAA2B,aAAa,UAAU;AAAA,CAAI;AAAA,IAClE;AACA,cAAU,4BAA4B,cAAc;AAAA,CAAI;AACxD,cAAU,yCAAyC,aAAa,GAAG;AAAA,CAAK;AAGxE,QAAI,eAAe;AACnB,iBAAa,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,sBAAgB,KAAK,SAAS;AAG9B,YAAM,QAAQ,aAAa,MAAM,IAAI;AACrC,qBAAe,MAAM,IAAI,KAAK;AAE9B,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,KAAK,EAAG;AAElB,YAAI;AACF,gBAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,cAAI,OAAO;AAGX,cAAI,KAAK,SAAS,eAAe,KAAK,SAAS,SAAS;AACtD,uBAAW,SAAS,KAAK,QAAQ,SAAS;AACxC,kBAAI,MAAM,SAAS,QAAQ;AACzB,wBAAQ,MAAM;AAAA,cAChB,WAAW,MAAM,SAAS,YAAY;AACpC,wBAAQ,KAAK,cAAc,MAAM,MAAM,MAAM,KAAK;AAAA,cACpD;AAAA,YACF;AAAA,UACF,WAAW,KAAK,SAAS,yBAAyB,KAAK,OAAO,MAAM;AAClE,mBAAO,KAAK,MAAM;AAAA,UACpB,WAAW,KAAK,SAAS,YAAY,KAAK,QAAQ;AAChD,mBAAO;AAAA,WAAc,KAAK,MAAM;AAAA;AAAA,UAClC;AAEA,cAAI,MAAM;AACR,sBAAU,IAAI;AAAA,UAChB;AAAA,QACF,QAAQ;AAEN,gBAAM,YAAY,KAAK,QAAQ,0BAA0B,EAAE;AAC3D,cAAI,UAAU,KAAK,GAAG;AACpB,sBAAU,YAAY,IAAI;AAAA,UAC5B;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAGD,iBAAa,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AAChD,YAAM,OAAO,KAAK,SAAS;AAC3B,gBAAU,YAAY,IAAI,EAAE;AAAA,IAC9B,CAAC;AAGD,iBAAa,GAAG,SAAS,MAAM;AAC7B,cAAQ,IAAI,yCAAyC;AAAA,IACvD,CAAC;AAGD,iBAAa,GAAG,SAAS,CAAC,UAAU;AAClC,cAAQ,IAAI,2BAA2B,MAAM,OAAO;AACpD,WAAK,KAAK,eAAe,EAAE,QAAQ,MAAM,0BAA0B,MAAM,OAAO;AAAA,GAAM,UAAU,SAAS,CAAC;AAG1G,UAAI;AAAE,mBAAW,UAAU;AAAA,MAAG,QAAQ;AAAA,MAAC;AAEvC,UAAI,cAAc;AAChB,aAAK,eAAe,MAAM;AAAA,MAC5B;AACA,iBAAW,KAAK,aAAa,QAAQ,EAAE,QAAQ,UAAU,QAAQ,MAAM,CAAC;AACxE,YAAM,UAAU,oBAAI,KAAK;AACzB,wBAAkB,KAAK,aAAa,QAAQ;AAAA,QAC1C,WAAW,UAAU,YAAY;AAAA,QACjC,SAAS,QAAQ,YAAY;AAAA,QAC7B,QAAQ;AAAA,QACR,UAAU,QAAQ,QAAQ,IAAI,UAAU,QAAQ;AAAA,QAChD,OAAO,MAAM;AAAA,MACf,CAAC;AACD,WAAK,KAAK,eAAe,EAAE,QAAQ,OAAO,MAAM,QAAQ,CAAC;AACzD,WAAK,aAAa,OAAO,MAAM;AAAA,IACjC,CAAC;AAGD,iBAAa,GAAG,SAAS,CAAC,MAAM,WAAW;AACzC,cAAQ,IAAI,wCAAwC,MAAM,WAAW,MAAM;AAC3E,UAAI;AAAE,mBAAW,UAAU;AAAA,MAAG,QAAQ;AAAA,MAAC;AACvC,gBAAU,4CAA4C,IAAI;AAAA,CAAI;AAC9D,WAAK,mBAAmB,QAAQ,MAAM,SAAS;AAAA,IACjD,CAAC;AAGD,UAAM,aAAa,OAAO,UAAU,WAAW,MAAM,KAAK;AAC1D,eAAW,MAAM;AACf,UAAI,KAAK,cAAc,MAAM,GAAG;AAC9B,aAAK,WAAW,QAAQ,kBAAkB;AAAA,MAC5C;AAAA,IACF,GAAG,SAAS;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,QAAgB,UAAyB,WAAuB;AACzF,UAAM,cAAc,KAAK,aAAa,IAAI,MAAM;AAChD,QAAI,CAAC,YAAa;AAElB,UAAM,UAAU,oBAAI,KAAK;AACzB,UAAM,WAAW,QAAQ,QAAQ,IAAI,UAAU,QAAQ;AACvD,UAAM,SAAS,YAAY,OAAO,KAAK,EAAE;AAGzC,UAAM,aAAa,OAAO,SAAS,6BAA6B;AAChE,UAAM,OAAO,YAAY,KAAK,aAAa,MAAM;AAEjD,QAAI,cAAc,aAAa,GAAG;AAGhC,UAAI,YAAY,gBAAgB,YAAY,YAAY;AACtD,cAAM,SAAS,KAAK,oBAAoB,MAAM;AAC9C,YAAI,QAAQ;AACV,kBAAQ,IAAI,kCAAkC,YAAY,UAAU,EAAE;AAAA,QACxE,OAAO;AACL,kBAAQ,IAAI,8BAA8B,YAAY,UAAU,qCAAqC;AAAA,QACvG;AAEA,aAAK,eAAe,MAAM;AAAA,MAC5B;AAEA,iBAAW,KAAK,aAAa,QAAQ;AAAA,QACnC,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AAED,wBAAkB,KAAK,aAAa,QAAQ;AAAA,QAC1C,WAAW,UAAU,YAAY;AAAA,QACjC,SAAS,QAAQ,YAAY;AAAA,QAC7B,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAED,uBAAiB,KAAK,aAAa;AAAA,QACjC;AAAA,QACA,WAAW,MAAM,SAAS;AAAA,QAC1B,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAED,WAAK,KAAK,kBAAkB,EAAE,QAAQ,SAAS,CAAC;AAChD,WAAK;AAAA,IACP,OAAO;AAEL,UAAI,YAAY,cAAc;AAC5B,aAAK,eAAe,MAAM;AAAA,MAC5B;AAEA,iBAAW,KAAK,aAAa,QAAQ;AAAA,QACnC,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AAED,YAAM,QAAQ,4BAA4B,QAAQ;AAElD,wBAAkB,KAAK,aAAa,QAAQ;AAAA,QAC1C,WAAW,UAAU,YAAY;AAAA,QACjC,SAAS,QAAQ,YAAY;AAAA,QAC7B,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MACF,CAAC;AAED,uBAAiB,KAAK,aAAa;AAAA,QACjC;AAAA,QACA,WAAW,MAAM,SAAS;AAAA,QAC1B,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MACF,CAAC;AAED,WAAK,KAAK,eAAe,EAAE,QAAQ,MAAM,CAAC;AAAA,IAC5C;AAEA,SAAK,aAAa,OAAO,MAAM;AAG/B,QAAI,KAAK,SAAS;AAChB,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,QAAgB,SAAS,qBAA8B;AAChE,UAAM,cAAc,KAAK,aAAa,IAAI,MAAM;AAChD,QAAI,CAAC,YAAa,QAAO;AAEzB,UAAM,YAAY,YAAY;AAC9B,UAAM,UAAU,oBAAI,KAAK;AACzB,UAAM,WAAW,QAAQ,QAAQ,IAAI,UAAU,QAAQ;AACvD,UAAM,OAAO,YAAY,KAAK,aAAa,MAAM;AAGjD,QAAI;AACF,kBAAY,QAAQ,KAAK,SAAS;AAElC,iBAAW,MAAM;AACf,YAAI;AACF,cAAI,CAAC,YAAY,QAAQ,QAAQ;AAC/B,wBAAY,QAAQ,KAAK,SAAS;AAAA,UACpC;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF,GAAG,GAAI;AAAA,IACT,QAAQ;AAAA,IAER;AAGA,QAAI,YAAY,cAAc;AAC5B,WAAK,eAAe,MAAM;AAAA,IAC5B;AAGA,eAAW,KAAK,aAAa,QAAQ;AAAA,MACnC,QAAQ;AAAA,IACV,CAAC;AAED,sBAAkB,KAAK,aAAa,QAAQ;AAAA,MAC1C,WAAW,UAAU,YAAY;AAAA,MACjC,SAAS,QAAQ,YAAY;AAAA,MAC7B,QAAQ;AAAA,MACR;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAED,qBAAiB,KAAK,aAAa;AAAA,MACjC;AAAA,MACA,WAAW,MAAM,SAAS;AAAA,MAC1B,QAAQ;AAAA,MACR;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAED,SAAK,KAAK,kBAAkB,EAAE,OAAO,CAAC;AACtC,SAAK,aAAa,OAAO,MAAM;AAE/B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,eAAuB,YAA0B;AAC5D,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,SAAK,UAAU;AACf,SAAK,eAAe;AACpB,SAAK,mBAAmB;AACxB,SAAK,oBAAoB;AAEzB,SAAK,cAAc;AACnB,SAAK,gBAAgB,UAAU;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,aAAa,GAAS;AAC5C,QAAI,CAAC,KAAK,QAAS;AAGnB,QAAI,KAAK,gBAAgB,KAAK,kBAAkB;AAC9C,WAAK,YAAY;AACjB;AAAA,IACF;AAGA,UAAM,SAAS,UAAU,KAAK,WAAW;AACzC,UAAM,gBAAgB,KAAK,IAAI,YAAY,OAAO,UAAU,iBAAiB,CAAC;AAE9E,WAAO,KAAK,gBAAgB,IAAI,eAAe;AAC7C,YAAM,WAAW,iBAAiB,KAAK,WAAW;AAClD,UAAI,CAAC,UAAU;AAEb,YAAI,KAAK,gBAAgB,MAAM,GAAG;AAChC,eAAK,YAAY;AAAA,QACnB;AACA;AAAA,MACF;AAEA,WAAK;AACL,WAAK,QAAQ,SAAS,EAAE,EAAE,MAAM,CAAC,UAAU;AACzC,gBAAQ,MAAM,mBAAmB,KAAK;AAAA,MACxC,CAAC;AAED,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAoB;AAClB,SAAK,UAAU;AACf,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAsB;AAC5B,SAAK,KAAK,cAAc;AAAA,MACtB,SAAS,KAAK;AAAA,MACd,kBAAkB,KAAK;AAAA,MACvB,eAAe,KAAK;AAAA,MACpB,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,eAKE;AACA,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,kBAAkB,KAAK;AAAA,MACvB,eAAe,KAAK;AAAA,MACpB,gBAAgB,KAAK;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,YAAkB;AAChB,eAAW,CAAC,QAAQ,WAAW,KAAK,KAAK,aAAa,QAAQ,GAAG;AAE/D,UAAI;AACF,oBAAY,QAAQ,KAAK,SAAS;AAAA,MACpC,QAAQ;AAAA,MAER;AAEA,UAAI,YAAY,cAAc;AAC5B,aAAK,eAAe,MAAM;AAAA,MAC5B;AACA,WAAK,aAAa,OAAO,MAAM;AAAA,IACjC;AACA,SAAK,YAAY;AAAA,EACnB;AACF;;;AIr1BO,IAAM,gBAAgC;AAAA,EAC3C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQrB,eAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAKO,SAAS,kBAAkC;AAChD,SAAO;AACT;AAKO,SAAS,gBAAgB,IAAsC;AACpE,SAAO,cAAc,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC9C;;;ACxTA,SAAS,SAAAG,cAAa;AAOtB,eAAsB,uBACpB,aACA,YAC4B;AAC5B,QAAM,SAAS,UAAU,WAAW;AAEpC,QAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBrB,QAAM,aAAa,GAAG,YAAY;AAAA;AAAA,gBAEpB,UAAU;AAExB,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,OAAO;AAAA,MACX;AAAA,MAAqB;AAAA,MACrB;AAAA,MACA;AAAA,IACF;AAEA,QAAI,OAAO,MAAM,OAAO;AACtB,WAAK,QAAQ,WAAW,OAAO,MAAM,KAAK;AAAA,IAC5C;AAEA,QAAI,SAAS;AACb,QAAI,cAAc;AAElB,UAAM,OAAOC,OAAM,OAAO,MAAM,SAAS,MAAM;AAAA,MAC7C,KAAK;AAAA,MACL,OAAO;AAAA,MACP,KAAK,EAAE,GAAG,QAAQ,IAAI;AAAA,IACxB,CAAC;AAED,SAAK,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACxC,gBAAU,KAAK,SAAS;AAAA,IAC1B,CAAC;AAED,SAAK,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACxC,qBAAe,KAAK,SAAS;AAAA,IAC/B,CAAC;AAED,SAAK,GAAG,SAAS,CAAC,SAAS;AACzB,UAAI,SAAS,GAAG;AACd,eAAO,IAAI,MAAM,yBAAyB,eAAe,eAAe,EAAE,CAAC;AAC3E;AAAA,MACF;AAEA,UAAI;AAEF,cAAM,YAAY,OAAO,MAAM,aAAa;AAC5C,YAAI,CAAC,WAAW;AACd,gBAAM,IAAI,MAAM,2BAA2B;AAAA,QAC7C;AAEA,cAAM,SAAS,KAAK,MAAM,UAAU,CAAC,CAAC;AAGtC,YAAI,CAAC,OAAO,SAAS,CAAC,OAAO,aAAa;AACxC,gBAAM,IAAI,MAAM,qCAAqC;AAAA,QACvD;AAEA,cAAM,OAA0B;AAAA,UAC9B,OAAO,OAAO,OAAO,KAAK,EAAE,MAAM,GAAG,GAAG;AAAA,UACxC,aAAa,OAAO,OAAO,WAAW;AAAA,UACtC,UAAU,iBAAiB,OAAO,QAAQ;AAAA,UAC1C,UAAU,iBAAiB,OAAO,QAAQ;AAAA,UAC1C,OAAO,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,MAAM,IAAI,MAAM,IAAI,CAAC;AAAA,UACjE,QAAQ;AAAA,QACV;AAEA,gBAAQ,IAAI;AAAA,MACd,SAAS,YAAY;AACnB,eAAO,IAAI,MAAM,gCAAgC,UAAU,EAAE,CAAC;AAAA,MAChE;AAAA,IACF,CAAC;AAED,SAAK,GAAG,SAAS,CAAC,UAAU;AAC1B,aAAO,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE,CAAC;AAAA,IAClE,CAAC;AAGD,eAAW,MAAM;AACf,WAAK,KAAK;AACV,aAAO,IAAI,MAAM,yBAAyB,CAAC;AAAA,IAC7C,GAAG,GAAK;AAAA,EACV,CAAC;AACH;AAEA,SAAS,iBAAiB,UAAyF;AACjH,QAAM,QAAQ,CAAC,cAAc,MAAM,OAAO,eAAe,WAAW,UAAU;AAC9E,MAAI,OAAO,aAAa,YAAY,MAAM,SAAS,QAAQ,GAAG;AAC5D,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,UAA2D;AACnF,QAAM,QAAQ,CAAC,OAAO,UAAU,QAAQ,UAAU;AAClD,MAAI,OAAO,aAAa,YAAY,MAAM,SAAS,QAAQ,GAAG;AAC5D,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;ANjHA,IAAMC,aAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,eAAsB,aAAa,aAAqB,MAA+B;AACrF,QAAM,MAAM,QAAQ;AACpB,QAAM,aAAa,iBAAiB,GAAG;AACvC,QAAM,KAAK,IAAI,eAAe,YAAY;AAAA,IACxC,MAAM,EAAE,QAAQ,IAAI;AAAA,EACtB,CAAC;AAGD,MAAI,IAAI,QAAQ,KAAK,CAAC;AAGtB,QAAM,WAAW,IAAI,aAAa,WAAW;AAG7C,WAAS,GAAG,gBAAgB,CAAC,SAAS,GAAG,KAAK,gBAAgB,IAAI,CAAC;AACnE,WAAS,GAAG,eAAe,CAAC,SAAS,GAAG,KAAK,eAAe,IAAI,CAAC;AACjE,WAAS,GAAG,kBAAkB,CAAC,SAAS,GAAG,KAAK,kBAAkB,IAAI,CAAC;AACvE,WAAS,GAAG,eAAe,CAAC,SAAS,GAAG,KAAK,eAAe,IAAI,CAAC;AACjE,WAAS,GAAG,kBAAkB,CAAC,SAAS,GAAG,KAAK,kBAAkB,IAAI,CAAC;AACvE,WAAS,GAAG,cAAc,CAAC,SAAS,GAAG,KAAK,cAAc,IAAI,CAAC;AAK/D,MAAI,IAAI,cAAc,CAAC,MAAM,QAAQ;AACnC,QAAI;AACF,YAAM,QAAmB,YAAY,WAAW;AAChD,UAAI,KAAK,EAAE,MAAM,CAAC;AAAA,IACpB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,KAAK,cAAc,CAAC,KAAK,QAAQ;AACnC,QAAI;AACF,YAAM,UAAU,IAAI;AACpB,UAAI,CAAC,QAAQ,SAAS,CAAC,QAAQ,aAAa;AAC1C,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,qCAAqC,CAAC;AACpE;AAAA,MACF;AACA,YAAM,OAAkB,WAAW,aAAa,OAAO;AACvD,SAAG,KAAK,gBAAgB,IAAI;AAC5B,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC;AAAA,IAC/B,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,KAAK,uBAAuB,OAAO,KAAK,QAAQ;AAClD,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,IAAI;AACvB,UAAI,CAAC,QAAQ;AACX,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,qBAAqB,CAAC;AACpD;AAAA,MACF;AACA,YAAM,cAAc,MAAM,uBAAuB,aAAa,MAAM;AACpE,UAAI,KAAK,EAAE,MAAM,YAAY,CAAC;AAAA,IAChC,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,kBAAkB,CAAC,KAAK,QAAQ;AACtC,QAAI;AACF,YAAM,OAAkB,YAAY,aAAa,IAAI,OAAO,EAAE;AAC9D,UAAI,CAAC,MAAM;AACT,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAChD;AAAA,MACF;AACA,UAAI,KAAK,EAAE,KAAK,CAAC;AAAA,IACnB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,kBAAkB,CAAC,KAAK,QAAQ;AACtC,QAAI;AACF,YAAM,UAAU,IAAI;AACpB,YAAM,OAAkB,WAAW,aAAa,IAAI,OAAO,IAAI,OAAO;AACtE,UAAI,CAAC,MAAM;AACT,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAChD;AAAA,MACF;AACA,SAAG,KAAK,gBAAgB,IAAI;AAC5B,UAAI,KAAK,EAAE,KAAK,CAAC;AAAA,IACnB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,OAAO,kBAAkB,CAAC,KAAK,QAAQ;AACzC,QAAI;AACF,YAAM,UAAqB,WAAW,aAAa,IAAI,OAAO,EAAE;AAChE,UAAI,CAAC,SAAS;AACZ,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAChD;AAAA,MACF;AACA,SAAG,KAAK,gBAAgB,EAAE,IAAI,IAAI,OAAO,GAAG,CAAC;AAC7C,UAAI,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,IAC5B,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,KAAK,sBAAsB,OAAO,KAAK,QAAQ;AACjD,QAAI;AACF,YAAM,SAAS,QAAQ,IAAI,OAAO,EAAE;AACpC,UAAI,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,IAC5B,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,KAAK,yBAAyB,CAAC,KAAK,QAAQ;AAC9C,QAAI;AACF,YAAM,YAAY,SAAS,WAAW,IAAI,OAAO,EAAE;AACnD,UAAI,CAAC,WAAW;AACd,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;AAAA,MACF;AACA,UAAI,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,IAC5B,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,KAAK,wBAAwB,OAAO,KAAK,QAAQ;AACnD,QAAI;AACF,YAAM,OAAkB,WAAW,aAAa,IAAI,OAAO,IAAI;AAAA,QAC7D,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,UAAI,CAAC,MAAM;AACT,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAChD;AAAA,MACF;AACA,SAAG,KAAK,gBAAgB,IAAI;AAG5B,UAAI,IAAI,KAAK,SAAS;AACpB,cAAM,SAAS,QAAQ,IAAI,OAAO,EAAE;AAAA,MACtC;AAEA,UAAI,KAAK,EAAE,KAAK,CAAC;AAAA,IACnB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAID,MAAI,IAAI,uBAAuB,CAAC,KAAK,QAAQ;AAC3C,QAAI;AACF,YAAM,OAAO,SAAS,WAAW,IAAI,OAAO,EAAE;AAC9C,UAAI,SAAS,MAAM;AACjB,YAAI,KAAK,EAAE,MAAM,GAAG,CAAC;AACrB;AAAA,MACF;AACA,UAAI,KAAK,EAAE,KAAK,CAAC;AAAA,IACnB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,MAAI,IAAI,yBAAyB,CAAC,KAAK,QAAQ;AAC7C,QAAI;AACF,YAAM,SAAS,SAAS,cAAc,IAAI,OAAO,EAAE;AACnD,UAAI,CAAC,QAAQ;AACX,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,gCAAgC,CAAC;AAC/D;AAAA,MACF;AACA,UAAI,KAAK,EAAE,OAAO,CAAC;AAAA,IACrB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,iBAAiB,CAAC,KAAK,QAAQ;AACrC,QAAI;AACF,YAAM,QAAQ,SAAS,OAAO,IAAI,MAAM,KAAK,CAAC,KAAK;AACnD,YAAM,UAA0B,kBAAkB,aAAa,KAAK;AACpE,UAAI,KAAK,EAAE,QAAQ,CAAC;AAAA,IACtB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,eAAe,CAAC,MAAM,QAAQ;AACpC,QAAI;AACF,YAAM,SAAwB,UAAU,WAAW;AACnD,UAAI,KAAK,EAAE,OAAO,CAAC;AAAA,IACrB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,eAAe,CAAC,KAAK,QAAQ;AACnC,QAAI;AACF,YAAM,gBAA+B,UAAU,WAAW;AAC1D,YAAM,gBAAgB,EAAE,GAAG,eAAe,GAAG,IAAI,KAAK;AACtD,MAAe,WAAW,aAAa,aAAa;AACpD,UAAI,KAAK,EAAE,QAAQ,cAAc,CAAC;AAAA,IACpC,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,kBAAkB,CAAC,MAAM,QAAQ;AACvC,QAAI;AACF,YAAM,YAA4B,gBAAgB;AAClD,UAAI,KAAK,EAAE,UAAU,CAAC;AAAA,IACxB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,sBAAsB,CAAC,KAAK,QAAQ;AAC1C,QAAI;AACF,YAAM,WAA2B,gBAAgB,IAAI,OAAO,EAAE;AAC9D,UAAI,CAAC,UAAU;AACb,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,qBAAqB,CAAC;AACpD;AAAA,MACF;AACA,UAAI,KAAK,EAAE,SAAS,CAAC;AAAA,IACvB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,KAAK,kBAAkB,CAAC,KAAK,QAAQ;AACvC,QAAI;AACF,YAAM,EAAE,eAAe,WAAW,IAAI,IAAI;AAC1C,eAAS,aAAa,iBAAiB,IAAI,cAAc,CAAC;AAC1D,UAAI,KAAK,EAAE,SAAS,MAAM,QAAQ,SAAS,aAAa,EAAE,CAAC;AAAA,IAC7D,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,MAAI,KAAK,iBAAiB,CAAC,MAAM,QAAQ;AACvC,QAAI;AACF,eAAS,YAAY;AACrB,UAAI,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,IAC5B,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,MAAI,IAAI,mBAAmB,CAAC,MAAM,QAAQ;AACxC,QAAI;AACF,YAAM,SAAS,SAAS,aAAa;AACrC,UAAI,KAAK,EAAE,OAAO,CAAC;AAAA,IACrB,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,gBAAgB,CAAC,MAAM,QAAQ;AACrC,QAAI;AACF,YAAM,UAAU,SAAS,kBAAkB;AAC3C,UAAI,KAAK,EAAE,SAAS,SAAS,OAAO,QAAQ,OAAO,CAAC;AAAA,IACtD,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,cAAc,CAAC,MAAM,QAAQ;AACnC,QAAI;AACF,YAAM,SAAoB,cAAc,WAAW;AACnD,YAAM,UAAU,SAAS,gBAAgB;AACzC,YAAM,MAAM,SAAS,aAAa;AAClC,UAAI,KAAK,EAAE,QAAQ,SAAS,IAAI,CAAC;AAAA,IACnC,SAAS,OAAO;AACd,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAKD,QAAM,aAAaC,MAAKD,YAAW,MAAM,QAAQ;AAGjD,MAAIE,YAAW,UAAU,GAAG;AAC1B,QAAI,IAAI,QAAQ,OAAO,UAAU,CAAC;AAAA,EACpC;AAGA,MAAI,IAAI,KAAK,CAAC,MAAM,QAAQ;AAE1B,QAAI,KAAK,cAAc,CAAC;AAAA,EAC1B,CAAC;AAID,KAAG,GAAG,cAAc,CAAC,WAAW;AAC9B,YAAQ,IAAI,kBAAkB;AAG9B,UAAM,aAAa,SAAS,kBAAkB;AAC9C,UAAM,WAAmC,CAAC;AAG1C,eAAW,UAAU,YAAY;AAC/B,YAAM,OAAO,SAAS,WAAW,MAAM;AACvC,UAAI,MAAM;AACR,iBAAS,MAAM,IAAI;AAAA,MACrB;AAAA,IACF;AAGA,WAAO,KAAK,QAAQ;AAAA,MAClB,OAAkB,YAAY,WAAW;AAAA,MACzC,SAAS;AAAA,MACT,KAAK,SAAS,aAAa;AAAA,MAC3B;AAAA;AAAA,IACF,CAAC;AAGD,WAAO,GAAG,YAAY,CAAC,WAAmB;AACxC,YAAM,OAAO,SAAS,WAAW,MAAM;AACvC,aAAO,KAAK,aAAa,EAAE,QAAQ,MAAM,QAAQ,GAAG,CAAC;AAAA,IACvD,CAAC;AAED,WAAO,GAAG,cAAc,MAAM;AAC5B,cAAQ,IAAI,qBAAqB;AAAA,IACnC,CAAC;AAAA,EACH,CAAC;AAID,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,eAAW,GAAG,SAAS,CAAC,UAAiC;AACvD,UAAI,MAAM,SAAS,cAAc;AAC/B,eAAO,IAAI,MAAM,QAAQ,IAAI,sDAAsD,CAAC;AAAA,MACtF,OAAO;AACL,eAAO,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAED,eAAW,OAAO,MAAM,MAAM;AAE5B,MAAC,WAAmB,UAAU,MAAM;AAClC,gBAAQ,IAAI,yBAAyB;AACrC,iBAAS,UAAU;AACnB,WAAG,MAAM;AAAA,MACX;AACA,cAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,gBAAwB;AAC/B,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,EAmeP,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;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","names":["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","spawn","__dirname","join","existsSync","createServer"]}