#!/usr/bin/env node
/**
 * auto-dev.mjs — Continuous Claude Code automation for Gergy v5.0 development.
 *
 * Runs ALL phases unattended overnight. Reads PLAN.md phases, executes each
 * task via Claude Code CLI, commits work, and advances automatically.
 * Supports parallel worktrees for independent tasks.
 *
 * Usage:
 *   node scripts/auto-dev.mjs                    # Run all phases unattended
 *   node scripts/auto-dev.mjs --phase 4          # Start from phase 4
 *   node scripts/auto-dev.mjs --dry-run           # Show what would run
 *   node scripts/auto-dev.mjs --parallel          # Enable worktree parallelization
 *   node scripts/auto-dev.mjs --model opus        # Use a specific model
 *   node scripts/auto-dev.mjs --max-budget 10     # Max budget per task in USD
 */

import { execSync, spawn } from 'child_process';
import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const ROOT = join(__dirname, '..');
const LOG_DIR = join(ROOT, '.auto-dev');
const LOG_FILE = join(LOG_DIR, 'auto-dev.log');
const STATE_FILE = join(LOG_DIR, 'state.json');

// ── CLI argument parsing ─────────────────────────────────────────

const args = process.argv.slice(2);
const flags = {};
for (let i = 0; i < args.length; i++) {
  if (args[i] === '--phase') flags.startPhase = parseInt(args[++i], 10);
  else if (args[i] === '--task') flags.startTask = parseInt(args[++i], 10);
  else if (args[i] === '--dry-run') flags.dryRun = true;
  else if (args[i] === '--parallel') flags.parallel = true;
  else if (args[i] === '--model') flags.model = args[++i];
  else if (args[i] === '--max-budget') flags.maxBudget = parseFloat(args[++i]);
  else if (args[i] === '--timeout') flags.timeout = parseInt(args[++i], 10) * 1000;
  else if (args[i] === '--help' || args[i] === '-h') {
    console.log(`
auto-dev.mjs — Gergy v5.0 Continuous Development Automation

Runs ALL phases unattended. Designed for overnight execution.

Usage:
  node scripts/auto-dev.mjs [options]

Options:
  --phase <n>        Start from phase N (default: 1, or resumes from last)
  --task <n>         Start from task N within the phase
  --dry-run          Show what would run without executing
  --parallel         Enable worktree parallelization for independent tasks
  --model <model>    Claude model (default: sonnet)
  --max-budget <$>   Max spend per task in USD (default: 5.00)
  --timeout <sec>    Max seconds per task (default: 900)
  -h, --help         Show this help
`);
    process.exit(0);
  }
}

const MODEL = flags.model || 'sonnet';
const MAX_BUDGET = flags.maxBudget || 5.00;
const TASK_TIMEOUT = flags.timeout || 900_000; // 15 min per task
const DRY_RUN = flags.dryRun || false;
const PARALLEL = flags.parallel || false;

// ── Logging ──────────────────────────────────────────────────────

mkdirSync(LOG_DIR, { recursive: true });

function log(msg, level = 'INFO') {
  const ts = new Date().toISOString();
  const line = `[${ts}] [${level}] ${msg}`;
  console.log(line);
  try { appendFileSync(LOG_FILE, line + '\n'); } catch {}
}

function logError(msg) { log(msg, 'ERROR'); }
function logWarn(msg) { log(msg, 'WARN'); }

// ── State management ────────────────────────────────────────────

function loadState() {
  if (existsSync(STATE_FILE)) {
    try { return JSON.parse(readFileSync(STATE_FILE, 'utf-8')); } catch {}
  }
  return { completedTasks: [], currentPhase: 1, currentTask: 1, startedAt: new Date().toISOString(), errors: [] };
}

function saveState(state) {
  try { writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); } catch {}
}

function markTaskComplete(state, phaseNum, taskId) {
  const key = `${phaseNum}.${taskId}`;
  if (!state.completedTasks.includes(key)) {
    state.completedTasks.push(key);
  }
  saveState(state);
}

function isTaskComplete(state, phaseNum, taskId) {
  return state.completedTasks.includes(`${phaseNum}.${taskId}`);
}

function recordError(state, phaseNum, taskId, error) {
  state.errors.push({ phase: phaseNum, task: taskId, error, time: new Date().toISOString() });
  saveState(state);
}

// ── Git helpers ─────────────────────────────────────────────────

function gitStatus() {
  try {
    return execSync('git status --porcelain', { cwd: ROOT, encoding: 'utf-8' }).trim();
  } catch { return ''; }
}

function gitCommit(message) {
  const status = gitStatus();
  if (!status) {
    log('No changes to commit');
    return false;
  }
  try {
    execSync('git add -A', { cwd: ROOT });
    // Use heredoc-style to handle special characters in commit messages
    const safeMsg = message.replace(/'/g, "'\\''");
    execSync(`git commit -m '${safeMsg}'`, { cwd: ROOT });
    log(`Committed: ${message}`);
    return true;
  } catch (err) {
    logError(`Commit failed: ${err.message}`);
    return false;
  }
}

function createWorktree(name) {
  const worktreePath = join(ROOT, '.auto-dev', 'worktrees', name);
  const branchName = `auto-dev/${name}`;
  try {
    execSync(`git worktree add -b "${branchName}" "${worktreePath}" HEAD`, { cwd: ROOT, stdio: 'pipe' });
    log(`Created worktree: ${name}`);
    return worktreePath;
  } catch {
    try {
      execSync(`git worktree add "${worktreePath}" "${branchName}"`, { cwd: ROOT, stdio: 'pipe' });
      return worktreePath;
    } catch (err) {
      logError(`Worktree creation failed for ${name}: ${err.message}`);
      return null;
    }
  }
}

function removeWorktree(name) {
  const worktreePath = join(ROOT, '.auto-dev', 'worktrees', name);
  try {
    execSync(`git worktree remove "${worktreePath}" --force`, { cwd: ROOT, stdio: 'pipe' });
    execSync(`git branch -D "auto-dev/${name}"`, { cwd: ROOT, stdio: 'pipe' });
    log(`Removed worktree: ${name}`);
  } catch { logWarn(`Could not remove worktree ${name}`); }
}

function mergeWorktree(name) {
  try {
    execSync(`git merge "auto-dev/${name}" --no-edit`, { cwd: ROOT, stdio: 'pipe' });
    log(`Merged worktree: ${name}`);
    return true;
  } catch (err) {
    logError(`Merge conflict in ${name}: ${err.message}`);
    try { execSync('git merge --abort', { cwd: ROOT, stdio: 'pipe' }); } catch {}
    return false;
  }
}

// ── Claude Code execution ───────────────────────────────────────

function runClaude(prompt, { cwd = ROOT, timeout = TASK_TIMEOUT } = {}) {
  return new Promise((resolve, reject) => {
    const claudeArgs = [
      '--print',
      '--model', MODEL,
      '--dangerously-skip-permissions',
      '--max-budget-usd', String(MAX_BUDGET),
      '--verbose'
    ];

    log(`Running Claude Code (model: ${MODEL}, budget: $${MAX_BUDGET}, timeout: ${Math.round(timeout / 1000)}s)`);
    log(`Prompt preview: ${prompt.slice(0, 300)}...`);

    if (DRY_RUN) {
      log('[DRY RUN] Skipped');
      return resolve('[DRY RUN] Skipped');
    }

    const child = spawn('claude', claudeArgs, {
      cwd,
      stdio: ['pipe', 'pipe', 'pipe'],
      env: { ...process.env, FORCE_COLOR: '0', CLAUDECODE: '' }
    });

    let stdout = '';
    let stderr = '';

    child.stdout.on('data', (chunk) => {
      const text = chunk.toString();
      stdout += text;
      process.stdout.write(text);
    });

    child.stderr.on('data', (chunk) => {
      stderr += chunk.toString();
    });

    child.stdin.write(prompt);
    child.stdin.end();

    const timer = setTimeout(() => {
      child.kill('SIGTERM');
      setTimeout(() => { try { child.kill('SIGKILL'); } catch {} }, 5000);
      reject(new Error(`Timed out after ${Math.round(timeout / 1000)}s`));
    }, timeout);

    child.on('close', (code) => {
      clearTimeout(timer);
      if (code !== 0) {
        logError(`Exit code ${code}: ${stderr.slice(-300)}`);
        reject(new Error(`Claude exited with code ${code}: ${stderr.slice(-200)}`));
      } else {
        resolve(stdout || stderr || '(no output)');
      }
    });

    child.on('error', (err) => {
      clearTimeout(timer);
      reject(err);
    });
  });
}

// ── The universal preamble injected into every prompt ────────────

const PREAMBLE = `You are working on the Gergy AI agent codebase. This is a Node.js ESM project (all .mjs files, no build step, no TypeScript).

Key conventions:
- Pure ESM: import/export, no require()
- SQLite via better-sqlite3 for persistence
- Express + WebSocket server in server.mjs
- UI: vanilla JS components in public/js/components/, CSS in public/css/
- State: Redux-inspired store in public/js/store.mjs
- Events: eventBus in public/js/event-bus.mjs
- API client: public/js/api.mjs
- Config: config.mjs with DEFAULTS, config.json at runtime
- Skills: ESM modules in skills/ exporting name, description, execute(ctx)
- Adapters: LLM backends in adapters/

Read existing files before modifying. Follow existing patterns. Keep changes minimal and focused.
After making changes, commit with the message specified in the task.

`;

// ── Phase definitions ───────────────────────────────────────────

const PHASES = [

  // ═══════════════════════════════════════════════════════════════
  // PHASE 1: CRITICAL BUG FIXES
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 1,
    name: 'Critical Bug Fixes',
    parallel: true,
    tasks: [
      {
        id: '1.1',
        name: 'Fix workspace path confusion',
        prompt: PREAMBLE + `Fix the workspace path confusion.

PROBLEM: config.mjs default workspaceRoot points to 'workspace/' (singular) but the actual directory used is 'workspaces/'. Multiple files have inconsistencies.

TASKS:
1. Read config.mjs — find the workspaceRoot default and change from join(__dirname, 'workspace') to join(__dirname, 'workspaces')
2. Read workspace-manager.mjs — verify it creates workspaces/default/ on first boot
3. Read workspace.mjs — check listWorkspaceFiles() fallback path references 'workspaces' not 'workspace'
4. Grep for any other references to 'workspace' (singular) path that should be 'workspaces'
5. Run: node tests/test-workspace-manager.mjs

Commit: "Fix workspace path: workspace/ → workspaces/ for consistency"`
      },
      {
        id: '1.2',
        name: 'Fix KG stats field names',
        prompt: PREAMBLE + `Fix the Knowledge Graph stats field name mismatch.

PROBLEM: /api/kg/stats in server.mjs returns { entityCount, relationCount } but knowledge-explorer.mjs reads stats.totalEntities and stats.totalRelations. The UI always shows 0.

TASKS:
1. Read server.mjs — find the /api/kg/stats endpoint
2. Read public/js/components/knowledge-explorer.mjs — find where it reads stats
3. Fix the server response to return { totalEntities, totalRelations, types }
4. Add entity type breakdown query: SELECT type, COUNT(*) as count FROM kg_entities GROUP BY type
5. Return types as an object: { Person: 5, Concept: 12, ... }
6. Verify knowledge-explorer.mjs can render the types breakdown

Commit: "Fix KG stats field names mismatch between server and UI"`
      },
      {
        id: '1.3',
        name: 'Fix Electron settings API',
        prompt: PREAMBLE + `Fix the Electron settings API mismatch.

PROBLEM: settings.mjs references window.electronAPI.selectDirectory but the preload exposes window.desktopBridge.

TASKS:
1. Read electron/preload.cjs — see what's on window.desktopBridge
2. Read public/js/components/settings.mjs — find all window.electronAPI references
3. Replace window.electronAPI with window.desktopBridge throughout
4. If desktopBridge doesn't have a directory picker method, add pickWorkspaceFolder() to preload.cjs and handle it in electron/main.mjs using dialog.showOpenDialog

Commit: "Fix Electron settings: use desktopBridge instead of electronAPI"`
      },
      {
        id: '1.4',
        name: 'Fix FILE_CREATING_SKILLS + publish-site',
        prompt: PREAMBLE + `Fix FILE_CREATING_SKILLS and create the missing publish-site skill.

TASKS:
1. Read skills.mjs — find the FILE_CREATING_SKILLS set
2. Add 'research' and 'publish-site' to FILE_CREATING_SKILLS
3. Create skills/publish-site.mjs:
   - export const name = 'publish-site'
   - export const description = 'Generate a static site index from workspace files'
   - export async function execute(ctx):
     a. List all .html and .md files in the workspace (use ctx or readdir)
     b. Ask the LLM to generate an index.html page that links to all files with nice styling
     c. Write it via ctx.writeOutputFile('index.html', content)
     d. ctx.addEvent('skill', 'agent', 'Published site index', { path: result.publicPath })
4. Follow the pattern of skills/build-page.mjs

Commit: "Add publish-site skill, fix FILE_CREATING_SKILLS membership"`
      },
      {
        id: '1.5',
        name: 'Fix settings field name mismatches',
        prompt: PREAMBLE + `Fix multiple field name mismatches in the settings UI.

PROBLEMS:
1. Settings saves 'thinkingDepth' but cognitive/thinker.mjs reads 'thinkerDepth'
2. Settings reads settings.name but config key is 'agentName'
3. Version hardcoded as '4.0.0'

TASKS:
1. Read public/js/components/settings.mjs
2. Find and fix 'thinkingDepth' → 'thinkerDepth' everywhere in the file
3. Find and fix settings.name → settings.agentName where it populates the name field
4. Add GET /api/version endpoint to server.mjs that reads version.json and returns it
5. In settings.mjs, replace hardcoded VERSION = '4.0.0' with a fetch to /api/version on init
6. Update the version display in the settings footer

Commit: "Fix settings field names: thinkerDepth, agentName, dynamic version"`
      },
      {
        id: '1.6',
        name: 'Fix config whitelist for agentic',
        prompt: PREAMBLE + `Fix the config whitelist so gergy adapter settings can be saved.

PROBLEM: PUT /api/config has an 'allowed' array whitelist. 'agentic' is not in it.

TASKS:
1. Read server.mjs — find the PUT /api/config handler and its 'allowed' array
2. Add 'agentic' to the allowed array
3. In GET /api/config handler, mask config.agentic.apiKey with '***' (like authToken is handled)

Commit: "Allow agentic config in PUT /api/config, mask API key in GET"`
      },
      {
        id: '1.7',
        name: 'Fix summarize skill paths',
        prompt: PREAMBLE + `Fix the summarize skill which looks in nonexistent directories.

PROBLEM: skills/summarize.mjs looks in 'uploads/' and 'output/' which don't exist. Files go to workspaces/default/.

TASKS:
1. Read skills/summarize.mjs
2. Read workspace.mjs or workspace-manager.mjs to find how to get the active workspace path
3. Change file discovery to use the active workspace directory
4. The skill should list .txt, .md, .html, .json files in the workspace
5. Use readdirSync on the workspace path, not hardcoded uploads/output

Commit: "Fix summarize skill: use workspace directory instead of uploads/output"`
      }
    ]
  },

  // ═══════════════════════════════════════════════════════════════
  // PHASE 2: CORE FEATURE COMPLETION
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 2,
    name: 'Core Feature Completion',
    parallel: false,
    tasks: [
      {
        id: '2.1',
        name: 'Zero-config onboarding wizard',
        prompt: PREAMBLE + `Create a zero-config first-run onboarding wizard.

Business users must be able to use the app without CLI commands.

TASKS:
1. Read config.mjs, server.mjs, public/js/app.mjs, and public/index.html
2. In server.mjs GET /api/config, add a 'firstRun' field: true if config.json doesn't exist or config.firstRun !== false
3. Create public/js/components/onboarding.mjs:
   - export function init() — check store.getState().settings.firstRun, if true show wizard
   - Wizard has 4 steps as full-screen glassmorphic overlay:
     Step 1: Welcome — "Welcome to AI-Do!" with animated avatar, "Get Started" button
     Step 2: API Key — single password input, helper text explains what to paste
       Auto-detect provider from key:
         starts with 'sk-ant-' → provider: 'anthropic'
         starts with 'sk-' → provider: 'openai'
         starts with 'xai-' → provider: 'xai'
         other → provider: 'gemini'
       Set cli to 'gergy' and agentic.provider accordingly
     Step 3: Persona — name input (placeholder: "AI-Do"), avatar style radio (default, friendly, professional, creative)
     Step 4: Done — "You're all set!" with brief UI tour bullets
   - On complete: POST /api/config with { cli: 'gergy', agentic: { provider, apiKey }, persona: { name, avatarStyle }, firstRun: false }
   - Dismiss wizard overlay
4. Create public/css/components/onboarding.css:
   - Full-screen overlay with z-index above everything
   - Centered card with glass effect using existing CSS variables
   - Step indicators (dots), animated transitions between steps
5. Add CSS link to public/index.html
6. Add onboarding.mjs to componentPaths in app.mjs (FIRST, before other components)

Commit: "Add zero-config onboarding wizard for first-run experience"`
      },
      {
        id: '2.2',
        name: 'Restore autonomous agent loop',
        prompt: PREAMBLE + `Restore autonomous skill running when the agent is idle.

PROBLEM: The old autonomous creative loop was replaced by background-queue.mjs which can only do raw LLM calls.

TASKS:
1. Read agent.mjs — find the main loop and idle processing section
2. Read background-queue.mjs
3. Add autonomy config defaults in config.mjs DEFAULTS:
   autonomy: { enabled: true, intervalMinutes: 30, budgetPerDay: 1.00 }
4. In agent.mjs idle section, add autonomous skill running:
   - Check autonomy.enabled in config
   - Check time since last autonomous action >= autonomy.intervalMinutes
   - Check daily budget not exceeded (track in api_usage table or memory)
   - Pick a skill: prefer build-page > write-doc > journal (bias toward artifacts)
   - Rate-limit journal to once per 3 loops (~15 min)
   - Run the skill via existing runSkill() or loadSkills()/execute pattern
   - Log the autonomous action as an event
5. In background-queue.mjs processNextTask(), if task has a 'skill' field, run that skill instead of raw LLM
6. Add UI toggle in public/js/components/settings.mjs under General section:
   - Checkbox "Autonomous Mode" bound to autonomy.enabled
   - When unchecked, agent only responds to user messages

Commit: "Restore autonomous agent loop with configurable autonomy settings"`
      },
      {
        id: '2.3',
        name: 'Skill toggle persistence + version endpoint',
        prompt: PREAMBLE + `Fix skill toggle persistence and add version endpoint.

TASKS:
1. Read public/js/components/function-palette.mjs — find the skill toggle handler
2. Wire it to call api.post('/api/skills/' + encodeURIComponent(skillName) + '/toggle')
3. Only update CSS/local state after the server call succeeds
4. On error, show a toast notification and revert the visual state
5. Read public/js/api.mjs — check if there's already a toggleSkill helper, if not add one

Also:
6. Read server.mjs — add GET /api/version endpoint that reads version.json and returns it as JSON
7. The endpoint needs no auth (like health checks)

Commit: "Wire skill toggle to server API, add /api/version endpoint"`
      }
    ]
  },

  // ═══════════════════════════════════════════════════════════════
  // PHASE 3: BUSINESS USER POLISH
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 3,
    name: 'Business User Polish',
    parallel: true,
    tasks: [
      {
        id: '3.1',
        name: 'Knowledge Explorer + Workspace Panel CSS',
        prompt: PREAMBLE + `Create missing CSS for Knowledge Explorer and Workspace Panel views.

These views have no stylesheets and look unstyled.

TASKS:
1. Read public/js/components/knowledge-explorer.mjs — understand the DOM structure
2. Create public/css/components/knowledge-explorer.css:
   - Search input with glass styling (var(--glass-bg), var(--glass-border))
   - Entity cards with type-colored left border: Person=#3b82f6, Concept=#a855f7, Technology=#22c55e, Location=#f97316, Other=#6b7280
   - Relations list with styled items
   - Stats header with type breakdown chips
   - @media (max-width: 768px) — stack vertically
3. Read public/js/components/workspace-panel.mjs — understand DOM structure
4. Create public/css/components/workspace-panel.css:
   - Workspace cards with glass effect, active workspace gets accent border
   - File list with type icons (use emoji or SVG for file types)
   - Create/switch/delete action buttons matching existing button styles
   - @media (max-width: 768px) — single column
5. Add both <link> tags to public/index.html (before </head>)
6. Use existing design tokens from variables.css

Commit: "Add CSS for knowledge explorer and workspace panel views"`
      },
      {
        id: '3.2',
        name: 'Chat UX: drag-drop, copy, export, feedback',
        prompt: PREAMBLE + `Add key UX improvements to the chat component.

TASKS:
1. Read public/js/components/chat.mjs thoroughly

2. Drag-and-drop file upload:
   - Add dragover/dragleave/drop event listeners on the chat container
   - On dragover: show a visual overlay "Drop file to upload"
   - On drop: process the file same as the existing upload button handler
   - Prevent default browser behavior

3. Code block copy button:
   - After markdown rendering (when messages are added), querySelectorAll('pre code')
   - For each, add a "Copy" button (absolute positioned top-right of the pre block)
   - Click handler: navigator.clipboard.writeText(codeBlock.textContent)
   - Show "Copied!" briefly then revert to "Copy"

4. Conversation export:
   - Add a small download/export button in the chat header area
   - Click: build a markdown string from all messages in store.getState().chat.messages
   - Format: "## User\\n{content}\\n\\n## Agent\\n{content}\\n\\n" for each pair
   - Trigger download as "conversation-{date}.md"

5. Message feedback (thumbs up/down):
   - For each agent message, add small 👍 👎 buttons (hidden until hover)
   - Click: api.post('/api/events', { type: 'feedback', source: 'user', content: 'positive|negative', metadata: { messageId } })
   - Visual feedback: highlight the selected thumb
   - In server.mjs, make sure POST /api/events exists or add it (addEvent via runtime API)

Commit: "Add drag-drop upload, code copy, export, and message feedback to chat"`
      },
      {
        id: '3.3',
        name: 'Hash-based routing',
        prompt: PREAMBLE + `Add hash-based routing so navigation persists on page refresh.

TASKS:
1. Read public/js/router.mjs
2. Modify navigate(viewId) to also set window.location.hash = '#' + viewId
3. On init, read window.location.hash — if it matches a valid view, navigate to it
4. Add window.addEventListener('hashchange', ...) to handle browser back/forward
5. Default to #chat if no hash or invalid hash
6. Valid views: chat, activity, skills, knowledge, workspaces, settings
7. Keep backwards compatibility — existing calls to navigate() still work

Commit: "Add hash-based routing for view persistence across refreshes"`
      },
      {
        id: '3.4',
        name: 'Focus mode',
        prompt: PREAMBLE + `Implement focus mode for distraction-free interaction.

TASKS:
1. Read public/js/app.mjs — it already has setupKeyboardShortcuts with Cmd+Shift+F for focus mode
2. Read public/js/store.mjs — it has ui.focusMode state
3. Read public/css/layout.css — check if focus mode CSS exists

4. In public/css/layout.css, add focus mode styles:
   .focus-mode .sidebar { transform: translateX(-100%); opacity: 0; pointer-events: none; }
   .focus-mode .right-panel { transform: translateX(100%); opacity: 0; pointer-events: none; }
   .focus-mode .dock { transform: translateY(100%); opacity: 0; }
   .focus-mode .main-canvas { grid-column: 1 / -1; }
   All transitions: 0.3s ease

5. In app.mjs, ensure the store subscription for focusMode adds/removes 'focus-mode' class on #app
6. Add a visible focus mode toggle button — a small expand/collapse icon in the chat header
7. Pressing Escape should exit focus mode (already handled if wired to SET_FOCUS_MODE false)
8. The avatar should still be visible in focus mode (centered above chat)

Commit: "Implement focus mode with keyboard shortcut and toggle button"`
      },
      {
        id: '3.5',
        name: 'Keyboard shortcuts help modal',
        prompt: PREAMBLE + `Add a keyboard shortcuts help modal.

TASKS:
1. Read public/js/app.mjs setupKeyboardShortcuts() to find all current shortcuts
2. In app.mjs, add a '?' key listener (when not typing in input) that shows shortcuts modal
3. Use the existing modal component (public/js/components/modal.mjs) to show:
   Title: "Keyboard Shortcuts"
   Grid of: shortcut key → description
   Known shortcuts: Esc (exit focus/close), Cmd+K (focus chat), Cmd+\\ (toggle sidebar), Cmd+Shift+F (focus mode), ? (this help)
4. Style the modal content with a simple two-column grid

Commit: "Add keyboard shortcuts help modal triggered by ? key"`
      },
      {
        id: '3.6',
        name: 'Loading skeleton screens',
        prompt: PREAMBLE + `Add skeleton loading states to replace "Loading..." text.

TASKS:
1. Add skeleton CSS to public/css/animations.css:
   .skeleton { background: linear-gradient(90deg, var(--glass-bg) 25%, var(--glass-border) 50%, var(--glass-bg) 75%); background-size: 200% 100%; animation: skeleton-pulse 1.5s ease-in-out infinite; border-radius: var(--radius-sm); }
   @keyframes skeleton-pulse { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
   .skeleton-text { height: 1em; margin-bottom: 0.5em; }
   .skeleton-avatar { width: 40px; height: 40px; border-radius: 50%; }
   .skeleton-card { height: 80px; margin-bottom: 0.75em; }

2. In chat.mjs — while streaming, show a skeleton block instead of "..." for the loading indicator
3. In activity-feed.mjs — show 3 skeleton cards while loading initial data
4. In knowledge-explorer.mjs — show skeleton cards while search results load

Keep changes minimal — just add skeleton class to existing loading placeholders.

Commit: "Add skeleton loading animations for chat, activity, and knowledge views"`
      }
    ]
  },

  // ═══════════════════════════════════════════════════════════════
  // PHASE 4: MULTI-AGENT INTELLIGENCE
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 4,
    name: 'Multi-Agent Intelligence',
    parallel: false,
    tasks: [
      {
        id: '4.1',
        name: 'REFINED INSTRUCTIONS pipeline',
        prompt: PREAMBLE + `Implement the REFINED INSTRUCTIONS multi-agent pipeline for high-quality outputs.

This is a key differentiator — when users ask for complex builds, the agent uses specialized sub-prompts.

TASKS:
1. Create refined-instructions.mjs in the project root:

   The module exports:
   - generateRefinedInstructions(userRequest, { askLLM, pickModel }) → string
     Uses a "Communicator" system prompt to extract full intent and produce a REFINED INSTRUCTIONS block
   - refineInstructions(rawInstructions, { askLLM, pickModel }) → string
     Uses a "Refiner" system prompt to polish and strengthen the instructions
   - reviewOutput(instructions, output, { askLLM, pickModel }) → { approved: boolean, notes: string }
     Uses a "Reviewer" system prompt to evaluate quality

   The REFINED INSTRUCTIONS format uses markers:
   === REFINED INSTRUCTIONS START ===
   [narrative instructions in plain English]
   Please create exactly this.
   === REFINED INSTRUCTIONS END ===

2. System prompts for each role (embed as template strings):
   - Communicator: warm, extracts intent, asks about audience/style/features, produces REFINED INSTRUCTIONS
   - Refiner: sharpens ambiguity, adds missing details (responsive, accessibility, empty states), eliminates redundancy
   - Reviewer: evaluates completeness, lists requirements met/unmet, suggests fixes

3. In skills/build-page.mjs and skills/create-file.mjs:
   - Import { generateRefinedInstructions, refineInstructions } from '../refined-instructions.mjs'
   - Before generating content, run the user's request through Communicator → Refiner pipeline
   - Use the refined instructions as the prompt for the actual generation
   - This makes build-page output dramatically better quality

4. Keep it simple — the pipeline is called internally by skills, not as a separate UI flow

Commit: "Add REFINED INSTRUCTIONS multi-agent pipeline for high-quality builds"`
      },
      {
        id: '4.2',
        name: 'Daily meta-reflection cycle',
        prompt: PREAMBLE + `Implement the daily meta-reflection cycle for continuous self-improvement.

TASKS:
1. Create meta-reflection.mjs in the project root:

   export async function runDailyReflection({ askLLM, pickModel, addEvent, db, getConfig })
   - Query last 24h of events from DB
   - Count: messages processed, skills run, files created, errors encountered
   - Build a reflection prompt including:
     - Activity summary
     - Error patterns
     - User feedback (thumbs up/down from events)
     - Current capability scores from evolution.yaml
   - Ask LLM (balanced tier) to produce a structured reflection:
     - What went well
     - What could improve
     - 1-3 specific actionable suggestions
   - Store reflection as event: addEvent('reflection', 'agent', reflectionText)
   - Update evolution.yaml capability scores if the LLM suggests adjustments

   export function shouldRunReflection(config, db)
   - Check config.cognitive.dailyReflection !== false
   - Check last reflection event was > 20 hours ago
   - Return boolean

2. In agent.mjs main loop, after idle processing:
   - if (shouldRunReflection(config, db)) await runDailyReflection(...)
   - Wrap in try/catch — reflection failure should never crash the loop

3. Add config default: cognitive.dailyReflection: true

Commit: "Add daily meta-reflection cycle for continuous self-improvement"`
      },
      {
        id: '4.3',
        name: 'App Forge multi-agent skill',
        prompt: PREAMBLE + `Create the App Forge skill — a multi-stage pipeline for building complete apps.

TASKS:
1. Create skills/app-forge.mjs:

   export const name = 'app-forge';
   export const description = 'Build a complete application from a description using a multi-stage pipeline';

   export async function execute(ctx) {
     The pipeline stages:

     STAGE 1 — PLAN:
     - Use ctx.askLLM with the 'powerful' tier to analyze ctx.args (user's app description)
     - Produce a JSON plan: { name, description, files: [{ path, description, type }] }
     - Log: ctx.addEvent('skill', 'agent', 'App Forge: Planning...', { stage: 'plan' })

     STAGE 2 — BUILD:
     - For each file in the plan, use ctx.askLLM to generate complete content
     - If refined-instructions.mjs exists, use refineInstructions for each file's prompt
     - Write via ctx.writeOutputFile(file.path, content)
     - Log progress for each file

     STAGE 3 — PUBLISH:
     - Generate an index.html that links to all created files
     - Include nice styling, navigation, and overview
     - Write via ctx.writeOutputFile('index.html', indexContent)

     Return { success: true, filesCreated: plan.files.length }
   }

2. In skills.mjs:
   - Add 'app-forge' to FILE_CREATING_SKILLS
   - Add 'app-forge' to USER_ONLY_SKILLS (never run autonomously)

3. In the skill detection logic (wherever detectAndQueueSkill is):
   - Add detection patterns for app-forge: "build me an app", "create an application", "build a complete", "make me a website with"

Commit: "Add App Forge skill: multi-stage SDLC pipeline for app building"`
      },
      {
        id: '4.4',
        name: 'Creative skills: diagram + mockup',
        prompt: PREAMBLE + `Add creative skills for diagrams and UI mockups.

TASKS:
1. Create skills/diagram.mjs:
   - name: 'diagram'
   - description: 'Generate visual diagrams from descriptions'
   - execute(ctx): Ask LLM to produce a self-contained HTML file with embedded Mermaid.js
     - Include: <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
     - The diagram content is generated from ctx.args
     - Support: flowcharts, sequence diagrams, mind maps, ER diagrams
     - Write via ctx.writeOutputFile('diagram-{timestamp}.html', content)
     - Return { success: true }

2. Create skills/mockup.mjs:
   - name: 'mockup'
   - description: 'Generate interactive UI mockups from descriptions'
   - execute(ctx): Ask LLM to produce a self-contained HTML wireframe
     - Use simple CSS for wireframe style (grey boxes, bordered rectangles, placeholder text)
     - Make it interactive: clickable nav, form elements that respond
     - Write via ctx.writeOutputFile('mockup-{timestamp}.html', content)
     - Return { success: true }

3. Add both to FILE_CREATING_SKILLS in skills.mjs
4. Add detection patterns in skill detection for "draw a diagram", "create a mockup", "wireframe", "flowchart"

Commit: "Add diagram and mockup creative skills"`
      },
      {
        id: '4.5',
        name: 'Scheduled tasks',
        prompt: PREAMBLE + `Add scheduled task support for recurring agent actions.

TASKS:
1. Create scheduler.mjs:
   - Stores schedules in config.schedules array
   - Each: { id: uuid, name, skill, interval: 'hourly'|'daily'|'weekly'|minutes, lastRun: timestamp, enabled: boolean }
   - intervalToMs(interval): hourly→3600000, daily→86400000, weekly→604800000, number→number*60000
   - checkSchedules(config, { runSkill, addEvent }): for each enabled schedule, if now - lastRun > intervalMs, run it
   - After running, update lastRun in config via updateConfig

2. Add to config.mjs DEFAULTS: schedules: []

3. Add API endpoints in server.mjs:
   GET /api/schedules → return config.schedules || []
   POST /api/schedules → validate { name, skill, interval }, add to config.schedules with uuid, save
   PUT /api/schedules/:id → update schedule fields
   DELETE /api/schedules/:id → remove from config.schedules, save

4. In agent.mjs main loop, near the start: call checkSchedules() with appropriate context

5. Add to public/js/components/settings.mjs:
   - New section "Scheduled Tasks" (after Cognitive section)
   - List current schedules with name, skill, interval, enabled toggle, delete button
   - "Add Schedule" button opens inline form: name input, skill dropdown (from palette), interval dropdown
   - Save calls api.post('/api/schedules', ...)
   - Add api helpers to public/js/api.mjs: getSchedules, createSchedule, updateSchedule, deleteSchedule

Commit: "Add scheduled task system with API and settings UI"`
      },
      {
        id: '4.6',
        name: 'Adaptive personalization from feedback',
        prompt: PREAMBLE + `Add adaptive personalization that learns from message feedback.

TASKS:
1. Read memory.mjs to understand the memory system
2. Read persona.mjs to understand how personality affects prompts

3. Create a function in persona.mjs: getAdaptiveContext(db)
   - Query events where type='feedback' from last 30 days
   - Count positive vs negative feedback
   - Look at the messages that got positive feedback — extract patterns:
     - Were they concise or detailed?
     - Did they include code?
     - Were they formal or casual?
   - Build a short context string: "User tends to prefer [concise/detailed] responses [with/without code examples]. They respond positively to [casual/formal] tone."
   - Cache the result (regenerate at most once per hour)

4. In the buildChatPrompt function (wherever it lives — agent.mjs or a prompt builder):
   - Add the adaptive context string to the system/personality section
   - This subtly steers the agent toward the user's demonstrated preferences

5. Keep it lightweight — don't over-engineer. A simple heuristic based on feedback counts is fine.

Commit: "Add adaptive personalization from user message feedback"`
      }
    ]
  },

  // ═══════════════════════════════════════════════════════════════
  // PHASE 5: ENHANCED KNOWLEDGE & MEMORY
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 5,
    name: 'Enhanced Knowledge & Memory',
    parallel: true,
    tasks: [
      {
        id: '5.1',
        name: 'Visual KG explorer with D3',
        prompt: PREAMBLE + `Add visual force-directed graph rendering to the Knowledge Explorer.

TASKS:
1. Read public/js/components/knowledge-explorer.mjs
2. Add a graph visualization tab/section that renders entities and relations as a force-directed graph
3. Use D3.js loaded from CDN: <script src="https://d3js.org/d3.v7.min.js"></script>
   - Add the script tag to public/index.html (with defer)
4. In knowledge-explorer.mjs, add a renderGraph(entities, relations) function:
   - Create SVG element inside the knowledge explorer view
   - Nodes = entities, colored by type (same colors as CSS badges)
   - Links = relations, with labels
   - Draggable nodes (d3.drag)
   - Click node → show details panel
   - Zoom and pan (d3.zoom)
5. Add a "Graph View" / "List View" toggle button at the top of the explorer
6. Lazy-load: only initialize D3 graph when user clicks "Graph View"
7. Handle empty state: "No entities yet. Chat with the agent or ingest content to build your knowledge graph."

Commit: "Add force-directed graph visualization to Knowledge Explorer"`
      },
      {
        id: '5.2',
        name: 'Knowledge import/export',
        prompt: PREAMBLE + `Add knowledge graph import and export functionality.

TASKS:
1. Read knowledge-graph.mjs
2. Add server endpoints:
   GET /api/kg/export → query all entities and relations, return as JSON { entities: [...], relations: [...] }
   POST /api/kg/import → accept JSON body with entities and relations arrays, upsert into DB

3. In knowledge-explorer.mjs, add Export and Import buttons:
   - Export: fetch /api/kg/export, download as knowledge-graph-{date}.json
   - Import: file input, read JSON, POST to /api/kg/import, refresh view

4. Add api helpers to api.mjs: exportKG(), importKG(data)

Commit: "Add knowledge graph import/export functionality"`
      },
      {
        id: '5.3',
        name: 'Procedural memory for skill patterns',
        prompt: PREAMBLE + `Add procedural memory that remembers successful skill execution patterns.

TASKS:
1. Read db.mjs — understand the table structure
2. Add a new table: procedural_memory (id, skill_name, input_pattern, approach, outcome, quality_score, created_at)
   - Add the CREATE TABLE in db.mjs initialization

3. In skills.mjs, after a skill completes successfully:
   - Store a procedural memory entry: { skill_name, input_pattern: first 200 chars of args, approach: 'standard', outcome: 'success', quality_score: 1.0 }

4. In skills.mjs, before running a skill:
   - Query procedural_memory for similar past executions (same skill_name, similar input)
   - If found, add context to the skill's prompt: "Previous successful approach for similar request: {approach}"
   - This helps the agent learn what works

5. Add GET /api/memories/procedural endpoint in server.mjs for UI visibility

Commit: "Add procedural memory for learning from past skill executions"`
      }
    ]
  },

  // ═══════════════════════════════════════════════════════════════
  // PHASE 6: DESKTOP INTEGRATION
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 6,
    name: 'Desktop Integration',
    parallel: true,
    tasks: [
      {
        id: '6.1',
        name: 'Native notifications',
        prompt: PREAMBLE + `Add desktop and browser notifications for key agent events.

TASKS:
1. Read electron/main.mjs and electron/preload.cjs
2. In electron/main.mjs:
   - Import { Notification } from 'electron'
   - Listen for WebSocket messages from the agent server (skill_complete, error events)
   - Show native Notification for: task completion, file creation, errors
   - Add config: notifications: { enabled: true }

3. For browser (non-Electron) fallback in public/js/app.mjs:
   - On first user interaction, request Notification.permission
   - In eventBus listener for SKILL_COMPLETE and error events, show browser Notification
   - Only if document.hidden (don't notify when user is looking at the app)

4. Add notifications toggle in settings.mjs under General section
5. Add to config.mjs DEFAULTS: notifications: { enabled: true }

Commit: "Add native desktop and browser notifications for agent events"`
      },
      {
        id: '6.2',
        name: 'Auto-update checker',
        prompt: PREAMBLE + `Add an auto-update version checker.

TASKS:
1. Create electron/auto-updater.mjs:
   - export async function checkForUpdates(config)
   - Fetch version.json from deployed site URL (read from config.siteUrl or default)
   - Compare remote version with local version.json
   - If remote is newer, return { available: true, version, changelog }
   - Store lastUpdateCheck timestamp in config
   - Only check once per 24 hours

2. In electron/main.mjs:
   - Call checkForUpdates() after app ready
   - If update available, show Notification with "Update available: v{version}"
   - Add tray menu item "Check for Updates" that calls checkForUpdates(force=true)

3. For web UI: in public/js/app.mjs loadSecondaryData():
   - Fetch /api/version, compare with a hardcoded or cached expected version
   - If mismatch, show a subtle banner: "A new version is available"
   - Add GET /api/update-check endpoint in server.mjs that does the version comparison server-side

Commit: "Add auto-update checker with notification for new versions"`
      },
      {
        id: '6.3',
        name: 'File watcher auto-ingest',
        prompt: PREAMBLE + `Extend file watcher to auto-ingest files from configured folders.

TASKS:
1. Read file-watcher.mjs
2. Add config: ingestion.watchFolders: [] (array of absolute paths)
3. In file-watcher.mjs or a new module, watch the configured folders:
   - When a new file appears (not modified, specifically new), queue it for ingestion
   - Use the existing ingestion.mjs queueIngestion() function
   - Debounce: don't re-ingest files already in the ingestion queue
   - Support: .txt, .md, .html, .pdf, .json, .csv files
4. Add UI in settings.mjs Ingestion section:
   - List of watched folders with remove button
   - "Add Folder" button (in Electron: uses desktopBridge.pickWorkspaceFolder; in browser: text input)
5. Broadcast a notification when auto-ingest completes

Commit: "Add file watcher auto-ingest for configured folders"`
      }
    ]
  },

  // ═══════════════════════════════════════════════════════════════
  // PHASE 7: RELIABILITY & TESTING
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 7,
    name: 'Reliability & Testing',
    parallel: true,
    tasks: [
      {
        id: '7.1',
        name: 'Fix existing test failures',
        prompt: PREAMBLE + `Run all existing tests and fix any failures.

TASKS:
1. Run each test individually. For each test file in tests/:
   node tests/test-config.mjs
   node tests/test-db.mjs
   node tests/test-ui-store.mjs
   node tests/test-session-store.mjs
   node tests/test-workspace-manager.mjs
   node tests/test-speech.mjs
   node tests/test-skills-context.mjs
   node tests/test-persona.mjs
   node tests/test-fetch-utils.mjs
   node tests/test-file-watcher.mjs
   node tests/test-hooks.mjs
   node tests/test-executor.mjs
   node tests/test-bash-tool.mjs
   ... (all files in tests/)

2. For each FAILING test:
   - Read the test file
   - Read the source module it tests
   - Determine if the bug is in the test or the source
   - Fix the source if it's a real bug; fix the test if the test expectations are wrong

3. For tests requiring external services (API keys, running servers):
   - Add a check at the top: if (!process.env.REQUIRED_KEY) { console.log('[SKIP] requires X'); process.exit(0); }

4. After fixing, run all tests again to verify they pass

Commit: "Fix failing tests across test suite"`
      },
      {
        id: '7.2',
        name: 'Health check + graceful shutdown',
        prompt: PREAMBLE + `Add health check endpoint and graceful shutdown.

TASKS:
1. Read server.mjs and agent.mjs

2. Add GET /api/health endpoint (NO auth required):
   - Read version.json
   - Return: {
       status: 'ok',
       version: versionJson.version,
       uptime: process.uptime(),
       memory: { rss, heapUsed, heapTotal } from process.memoryUsage(),
       timestamp: Date.now()
     }
   - Add before the auth middleware so it's accessible without a token

3. Add graceful shutdown to agent.mjs:
   - Listen for SIGTERM and SIGINT
   - Set a 'shuttingDown' flag to prevent new loop iterations
   - Close WebSocket connections (broadcast a 'shutdown' message)
   - Close the database connection
   - Log a shutdown event
   - process.exit(0)

4. In server.mjs, handle the case where the server is shutting down:
   - Return 503 for new requests during shutdown

Commit: "Add health check endpoint and graceful shutdown handling"`
      },
      {
        id: '7.3',
        name: 'Logging module',
        prompt: PREAMBLE + `Add a structured logging module.

TASKS:
1. Create logger.mjs in the project root:
   - Log levels: debug, info, warn, error (configurable via config.logLevel, default: 'info')
   - Each log entry: [ISO-timestamp] [LEVEL] [module] message
   - Write to both console and a log file (data/agent.log)
   - Log rotation: if file > 10MB, rename to agent.log.1 and start fresh
   - export function createLogger(moduleName) → returns { debug, info, warn, error }

2. DON'T refactor all existing console.log calls — just create the module and export it
   Future code will gradually adopt it

3. Add to config.mjs DEFAULTS: logLevel: 'info'

Commit: "Add structured logging module with file output and rotation"`
      }
    ]
  },

  // ═══════════════════════════════════════════════════════════════
  // PHASE 8: DOCUMENTATION
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 8,
    name: 'Documentation',
    parallel: true,
    tasks: [
      {
        id: '8.1',
        name: 'In-app help system',
        prompt: PREAMBLE + `Add an in-app help component.

TASKS:
1. Create public/js/components/help.mjs:
   - export function init() — listen for eventBus NOTIFICATION events of type 'help'
   - export function showHelp(topic) — shows a modal with help content
   - Help topics stored as a const object:
     'getting-started': overview of the app
     'chat': how to use chat, voice, file uploads
     'skills': what skills are and how to trigger them
     'knowledge': how the knowledge graph works
     'workspaces': managing multiple workspaces
     'voice': how voice input works
     'keyboard': list of keyboard shortcuts
   - Modal uses existing modal component
   - Content rendered as markdown (use existing renderMarkdown utility)

2. Add a "?" help button in the sidebar footer (or dock)
3. Clicking it shows a topic picker, then the help content

Commit: "Add in-app help system with topic-based documentation"`
      },
      {
        id: '8.2',
        name: 'Update CLAUDE.md',
        prompt: PREAMBLE + `Update CLAUDE.md to reflect the current state of the codebase.

TASKS:
1. Read CLAUDE.md thoroughly
2. Read version.json for current version
3. Update these sections:
   - Version references (update to current)
   - Skills list: add publish-site, app-forge, diagram, mockup
   - Config keys: add autonomy, schedules, notifications, cognitive.dailyReflection
   - API endpoints: add /api/health, /api/version, /api/schedules, /api/voice/transcribe, /api/kg/export, /api/kg/import
   - UI components: add onboarding, preview-panel, help
   - Module dependency graph: add scheduler.mjs, refined-instructions.mjs, meta-reflection.mjs, logger.mjs
4. Add a Troubleshooting section at the end:
   - "App won't start" → check Node.js version, npm install
   - "API key not working" → verify key format, check Settings → LLM
   - "Voice not working" → Electron needs mic permission, check Settings → Voice
   - "How to reset" → delete config.json and restart
5. Do NOT restructure existing sections — only update outdated content

Commit: "Update CLAUDE.md to reflect v5.0 features and architecture"`
      }
    ]
  },

  // ═══════════════════════════════════════════════════════════════
  // PHASE 9: PERFORMANCE & RELEASE
  // ═══════════════════════════════════════════════════════════════
  {
    phase: 9,
    name: 'Performance & Release',
    parallel: false,
    tasks: [
      {
        id: '9.1',
        name: 'Database optimization',
        prompt: PREAMBLE + `Optimize database queries and add caching.

TASKS:
1. Read db.mjs — find the schema initialization
2. Add indexes (in the init function, after table creation):
   CREATE INDEX IF NOT EXISTS idx_events_type_source ON events(type, source);
   CREATE INDEX IF NOT EXISTS idx_events_created ON events(created_at);
   CREATE INDEX IF NOT EXISTS idx_kg_entities_type ON kg_entities(type);
   CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
   CREATE INDEX IF NOT EXISTS idx_tasks_created ON tasks(created_at);

3. Add event archival function:
   export function archiveOldEvents(db, keepDays = 30)
   - Count events. If > 10000, move events older than keepDays to events_archive table
   - Create events_archive table with same schema if not exists
   - INSERT INTO events_archive SELECT * FROM events WHERE created_at < ?
   - DELETE FROM events WHERE created_at < ?

4. In server.mjs, add Cache-Control headers for static files:
   app.use(express.static('public', { maxAge: '1h' }))
   For workspace-files: maxAge: 0 (always fresh)

Commit: "Add database indexes, event archival, and static asset caching"`
      },
      {
        id: '9.2',
        name: 'Version bump and release prep',
        prompt: PREAMBLE + `Bump version to 5.0.0 and prepare the release.

TASKS:
1. Read version.json
2. Update:
   - version: "5.0.0"
   - released: today's date (YYYY-MM-DD format)
   - changelog: comprehensive list of changes made across all phases:
     - Zero-config onboarding wizard
     - REFINED INSTRUCTIONS multi-agent pipeline
     - App Forge SDLC skill
     - Daily meta-reflection cycle
     - Creative skills (diagram, mockup)
     - Adaptive personalization
     - Scheduled tasks
     - Focus mode
     - Hash-based routing
     - Chat UX (drag-drop, code copy, export, feedback)
     - Knowledge graph visualization
     - KG import/export
     - Procedural memory
     - Native notifications
     - Auto-update checker
     - Health check endpoint
     - Graceful shutdown
     - Structured logging
     - In-app help
     - All bug fixes from Phase 1
     - Test suite fixes
     - Database optimizations
3. Update settings.mjs if it still references old version anywhere
4. Read PLAN.md and note that all phases are complete

Commit: "Bump to v5.0.0 — Business Ready release

Features: onboarding wizard, multi-agent pipeline, App Forge, daily reflection,
creative skills, adaptive personalization, scheduled tasks, focus mode, KG visualization,
notifications, auto-update, health check, structured logging, and 20+ bug fixes."`
      }
    ]
  }
];

// ── Main execution logic ────────────────────────────────────────

async function runTask(task, { cwd = ROOT } = {}) {
  log(`\n${'='.repeat(70)}`);
  log(`TASK ${task.id}: ${task.name}`);
  log(`${'='.repeat(70)}`);

  const startTime = Date.now();
  try {
    const output = await runClaude(task.prompt, { cwd, timeout: TASK_TIMEOUT });
    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
    log(`Task ${task.id} completed in ${elapsed}s (${output.length} chars output)`);

    // Save output to task-specific log
    const taskLogFile = join(LOG_DIR, `task-${task.id.replace(/\./g, '-')}.log`);
    try { writeFileSync(taskLogFile, output); } catch {}

    return { success: true, output };
  } catch (err) {
    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
    logError(`Task ${task.id} failed after ${elapsed}s: ${err.message}`);
    return { success: false, error: err.message };
  }
}

async function runPhaseParallel(phase, state) {
  const pendingTasks = phase.tasks.filter(t => !isTaskComplete(state, phase.phase, t.id));
  if (pendingTasks.length === 0) {
    log(`Phase ${phase.phase} already complete — all tasks done`);
    return;
  }

  if (!PARALLEL || pendingTasks.length <= 1) {
    // Sequential fallback
    for (const task of pendingTasks) {
      const result = await runTask(task);
      if (result.success) {
        markTaskComplete(state, phase.phase, task.id);
      } else {
        recordError(state, phase.phase, task.id, result.error);
        logWarn(`Task ${task.id} failed — continuing to next task`);
      }
    }
    return;
  }

  // Parallel execution with worktrees
  log(`Running ${pendingTasks.length} tasks in parallel worktrees`);

  const worktreeJobs = [];
  for (const task of pendingTasks) {
    const wtName = `p${phase.phase}-t${task.id.replace(/\./g, '-')}`;
    const wtPath = createWorktree(wtName);
    if (!wtPath) {
      logError(`Skipping task ${task.id} — worktree creation failed`);
      continue;
    }
    worktreeJobs.push({ task, wtName, wtPath, promise: runTask(task, { cwd: wtPath }) });
  }

  const results = await Promise.allSettled(worktreeJobs.map(async (j) => ({
    ...j, result: await j.promise
  })));

  // Merge back
  for (const settled of results) {
    if (settled.status === 'rejected') continue;
    const { task, wtName, wtPath, result } = settled.value;

    if (result.success) {
      try {
        const st = execSync('git status --porcelain', { cwd: wtPath, encoding: 'utf-8' }).trim();
        if (st) {
          execSync('git add -A', { cwd: wtPath });
          const msg = `Phase ${task.id}: ${task.name}`;
          execSync(`git commit -m '${msg.replace(/'/g, "'\\''")}'`, { cwd: wtPath });
        }
      } catch {}

      if (mergeWorktree(wtName)) {
        markTaskComplete(state, phase.phase, task.id);
      }
    } else {
      recordError(state, phase.phase, task.id, result.error);
    }

    removeWorktree(wtName);
  }
}

async function runPhaseSequential(phase, state) {
  for (const task of phase.tasks) {
    if (isTaskComplete(state, phase.phase, task.id)) {
      log(`Task ${task.id} already complete — skipping`);
      continue;
    }

    const result = await runTask(task);
    if (result.success) {
      markTaskComplete(state, phase.phase, task.id);
    } else {
      recordError(state, phase.phase, task.id, result.error);
      logWarn(`Task ${task.id} failed — continuing`);
    }
  }
}

async function main() {
  const startTime = Date.now();

  log('\n' + '█'.repeat(70));
  log('  AUTO-DEV: Gergy v5.0 — Full Development Automation');
  log('  Running ALL phases unattended');
  log('█'.repeat(70));
  log(`Config: model=${MODEL} | budget=$${MAX_BUDGET}/task | timeout=${TASK_TIMEOUT/1000}s | parallel=${PARALLEL}`);
  log(`Total phases: ${PHASES.length} | Total tasks: ${PHASES.reduce((n, p) => n + p.tasks.length, 0)}`);

  // Prerequisites
  try {
    execSync('which claude', { stdio: 'pipe' });
  } catch {
    logError('Claude Code CLI not found. Install: npm install -g @anthropic-ai/claude-code');
    process.exit(1);
  }

  // Ensure we're on main and clean
  try {
    const branch = execSync('git branch --show-current', { cwd: ROOT, encoding: 'utf-8' }).trim();
    log(`Git branch: ${branch}`);
    const status = gitStatus();
    if (status) {
      log(`Working tree has uncommitted changes — committing before starting`);
      gitCommit('Auto-dev: save uncommitted work before automation run');
    }
  } catch (err) {
    logWarn(`Git check failed: ${err.message}`);
  }

  const state = loadState();
  const startPhase = flags.startPhase || state.currentPhase || 1;

  log(`\nStarting from Phase ${startPhase}`);
  log(`Previously completed: ${state.completedTasks.length} tasks`);
  log('');

  for (const phase of PHASES) {
    if (phase.phase < startPhase) continue;

    log('\n' + '━'.repeat(70));
    log(`  PHASE ${phase.phase}: ${phase.name.toUpperCase()}`);
    log(`  Tasks: ${phase.tasks.length} | Mode: ${phase.parallel ? 'PARALLEL' : 'SEQUENTIAL'}`);
    log('━'.repeat(70));

    const phaseStart = Date.now();

    if (phase.parallel) {
      await runPhaseParallel(phase, state);
    } else {
      await runPhaseSequential(phase, state);
    }

    // Commit any uncommitted work from this phase
    if (!DRY_RUN) {
      gitCommit(`Complete Phase ${phase.phase}: ${phase.name}`);
    }

    state.currentPhase = phase.phase + 1;
    state.currentTask = 1;
    saveState(state);

    const phaseElapsed = ((Date.now() - phaseStart) / 1000 / 60).toFixed(1);
    log(`\nPhase ${phase.phase} complete in ${phaseElapsed} minutes`);
  }

  const totalElapsed = ((Date.now() - startTime) / 1000 / 60).toFixed(1);

  log('\n' + '█'.repeat(70));
  log('  ALL PHASES COMPLETE');
  log(`  Total time: ${totalElapsed} minutes`);
  log(`  Tasks completed: ${state.completedTasks.length}`);
  log(`  Errors: ${state.errors.length}`);
  if (state.errors.length > 0) {
    log('\n  Failed tasks:');
    for (const err of state.errors) {
      log(`    Phase ${err.phase} Task ${err.task}: ${err.error.slice(0, 100)}`);
    }
  }
  log('█'.repeat(70));
  log('\nGergy v5.0 development automation complete!');
}

main().catch((err) => {
  logError(`Fatal error: ${err.message}`);
  console.error(err);
  process.exit(1);
});
