/**
 * Issue Command - Unified JSONL storage with CLI & API compatibility
 * Storage: issues.jsonl + solutions/{issue-id}.jsonl + queue.json
 * Commands: init, list, status, task, bind, queue, next, done, retry
 */
import chalk from 'chalk';
import { execSync } from 'child_process';
import inquirer from 'inquirer';
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, statSync } from 'fs';
import { join, resolve } from 'path';
import { EXEC_TIMEOUTS } from '../utils/exec-constants.js';
function isExecTimeoutError(error) {
    const err = error;
    const code = err?.code ?? err?.errno;
    if (code === 'ETIMEDOUT')
        return true;
    const message = typeof err?.message === 'string' ? err.message : '';
    return message.includes('ETIMEDOUT');
}
// Handle EPIPE errors gracefully
process.stdout.on('error', (err) => {
    if (err.code === 'EPIPE') {
        process.exit(0);
    }
    throw err;
});
const ISSUES_DIR = '.workflow/issues';
// ============ Storage Layer (JSONL) ============
/**
 * Normalize path for comparison (handles Windows case sensitivity)
 */
function normalizePath(p) {
    const normalized = resolve(p);
    // Windows: normalize to lowercase for comparison
    return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
}
/**
 * Try to resolve main repo from .git file (worktree link file)
 * .git file format: "gitdir: /path/to/main/.git/worktrees/name"
 */
function resolveMainRepoFromGitFile(gitFilePath) {
    try {
        const content = readFileSync(gitFilePath, 'utf-8').trim();
        // Parse "gitdir: /path/to/.git/worktrees/name"
        const match = content.match(/^gitdir:\s*(.+)$/);
        if (match) {
            const gitDir = match[1];
            // Navigate from .git/worktrees/name to .git to repo root
            // Pattern: /main/.git/worktrees/wt-name -> /main/.git -> /main
            const worktreesMatch = gitDir.match(/^(.+)[/\\]\.git[/\\]worktrees[/\\]/);
            if (worktreesMatch) {
                return worktreesMatch[1];
            }
        }
    }
    catch {
        // Failed to read or parse .git file
    }
    return null;
}
/**
 * Get the main repository root, even when running from a worktree.
 * This ensures .workflow/issues/ is always accessed from the main repo.
 */
function getProjectRoot() {
    // First, try to detect if we're in a git worktree using git commands
    try {
        // Get the common git directory (points to main repo's .git)
        const gitCommonDir = execSync('git rev-parse --git-common-dir', {
            encoding: 'utf-8',
            stdio: ['pipe', 'pipe', 'pipe'],
            timeout: EXEC_TIMEOUTS.GIT_QUICK,
        }).trim();
        // Get the current git directory
        const gitDir = execSync('git rev-parse --git-dir', {
            encoding: 'utf-8',
            stdio: ['pipe', 'pipe', 'pipe'],
            timeout: EXEC_TIMEOUTS.GIT_QUICK,
        }).trim();
        // Normalize paths for comparison (Windows case insensitive)
        const normalizedCommon = normalizePath(gitCommonDir);
        const normalizedGit = normalizePath(gitDir);
        // If gitDir != gitCommonDir, we're in a worktree
        if (normalizedGit !== normalizedCommon && gitDir !== '.git') {
            // We're in a worktree - resolve to main repo
            const absoluteCommonDir = resolve(process.cwd(), gitCommonDir);
            // .git directory's parent is the repo root
            const mainRepoRoot = resolve(absoluteCommonDir, '..');
            // Verify .workflow or .git exists in main repo
            if (existsSync(join(mainRepoRoot, '.workflow')) || existsSync(join(mainRepoRoot, '.git'))) {
                return mainRepoRoot;
            }
        }
    }
    catch (err) {
        if (isExecTimeoutError(err)) {
            console.warn(`[issue] git rev-parse timed out after ${EXEC_TIMEOUTS.GIT_QUICK}ms; falling back to filesystem detection`);
        }
        // Git command failed - fall through to manual detection
    }
    // Standard detection with worktree file support: walk up to find .workflow or .git
    let dir = process.cwd();
    while (dir !== resolve(dir, '..')) {
        const gitPath = join(dir, '.git');
        // Check if .git is a file (worktree link) rather than directory
        if (existsSync(gitPath)) {
            try {
                const gitStat = statSync(gitPath);
                if (gitStat.isFile()) {
                    // .git is a file - this is a worktree, try to resolve main repo
                    const mainRepo = resolveMainRepoFromGitFile(gitPath);
                    if (mainRepo && existsSync(join(mainRepo, '.workflow'))) {
                        return mainRepo;
                    }
                    // If main repo doesn't have .workflow, fall back to current worktree
                }
            }
            catch {
                // stat failed, continue with normal logic
            }
        }
        if (existsSync(join(dir, '.workflow')) || existsSync(gitPath)) {
            return dir;
        }
        dir = resolve(dir, '..');
    }
    return process.cwd();
}
function getIssuesDir() {
    return join(getProjectRoot(), ISSUES_DIR);
}
function ensureIssuesDir() {
    const dir = getIssuesDir();
    if (!existsSync(dir)) {
        mkdirSync(dir, { recursive: true });
    }
}
// ============ Issues JSONL ============
export function readIssues() {
    const path = join(getIssuesDir(), 'issues.jsonl');
    if (!existsSync(path))
        return [];
    try {
        return readFileSync(path, 'utf-8')
            .split('\n')
            .filter(line => line.trim())
            .map(line => JSON.parse(line));
    }
    catch {
        return [];
    }
}
export function writeIssues(issues) {
    ensureIssuesDir();
    const path = join(getIssuesDir(), 'issues.jsonl');
    // Always add trailing newline for proper JSONL format
    const content = issues.map(i => JSON.stringify(i)).join('\n');
    writeFileSync(path, content ? content + '\n' : '', 'utf-8');
}
function findIssue(issueId) {
    return readIssues().find(i => i.id === issueId);
}
// ============ Issue History JSONL ============
function readIssueHistory() {
    const path = join(getIssuesDir(), 'issue-history.jsonl');
    if (!existsSync(path))
        return [];
    try {
        return readFileSync(path, 'utf-8')
            .split('\n')
            .filter(line => line.trim())
            .map(line => JSON.parse(line));
    }
    catch {
        return [];
    }
}
function appendIssueHistory(issue) {
    ensureIssuesDir();
    const path = join(getIssuesDir(), 'issue-history.jsonl');
    const line = JSON.stringify(issue) + '\n';
    // Append to history file
    if (existsSync(path)) {
        const content = readFileSync(path, 'utf-8');
        // Ensure proper newline before appending
        const needsNewline = content.length > 0 && !content.endsWith('\n');
        writeFileSync(path, (needsNewline ? '\n' : '') + line, { flag: 'a' });
    }
    else {
        writeFileSync(path, line, 'utf-8');
    }
}
/**
 * Move completed issue from issues.jsonl to issue-history.jsonl
 */
function moveIssueToHistory(issueId) {
    const issues = readIssues();
    const idx = issues.findIndex(i => i.id === issueId);
    if (idx === -1)
        return false;
    const issue = issues[idx];
    if (issue.status !== 'completed')
        return false;
    // Append to history
    appendIssueHistory(issue);
    // Remove from active issues
    issues.splice(idx, 1);
    writeIssues(issues);
    return true;
}
function updateIssue(issueId, updates) {
    const issues = readIssues();
    const idx = issues.findIndex(i => i.id === issueId);
    if (idx === -1)
        return false;
    issues[idx] = { ...issues[idx], ...updates, updated_at: new Date().toISOString() };
    writeIssues(issues);
    // Auto-move to history when completed
    if (updates.status === 'completed') {
        moveIssueToHistory(issueId);
    }
    return true;
}
/**
 * Generate auto-increment issue ID: ISS-YYYYMMDD-NNN
 */
function generateIssueId(existingIssues = []) {
    const today = new Date();
    const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '');
    const prefix = `ISS-${dateStr}-`;
    const todayPattern = new RegExp(`^ISS-${dateStr}-(\\d{3})$`);
    let maxSeq = 0;
    for (const issue of existingIssues) {
        const match = issue.id.match(todayPattern);
        if (match)
            maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
    }
    return `${prefix}${String(maxSeq + 1).padStart(3, '0')}`;
}
/**
 * Create a new issue with proper JSONL handling
 * Auto-generates ID if not provided
 */
function createIssue(data) {
    const issues = readIssues();
    const issueId = data.id || generateIssueId(issues);
    if (issues.some(i => i.id === issueId)) {
        throw new Error(`Issue "${issueId}" already exists`);
    }
    const newIssue = {
        id: issueId,
        title: data.title || issueId,
        status: data.status || 'registered',
        priority: data.priority || 3,
        context: data.context || '',
        source: data.source,
        source_url: data.source_url,
        tags: data.tags,
        expected_behavior: data.expected_behavior,
        actual_behavior: data.actual_behavior,
        affected_components: data.affected_components,
        feedback: data.feedback,
        bound_solution_id: data.bound_solution_id || null,
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString()
    };
    issues.push(newIssue);
    writeIssues(issues);
    return newIssue;
}
// ============ Solutions JSONL ============
function getSolutionsPath(issueId) {
    return join(getIssuesDir(), 'solutions', `${issueId}.jsonl`);
}
export function readSolutions(issueId) {
    const path = getSolutionsPath(issueId);
    if (!existsSync(path))
        return [];
    try {
        return readFileSync(path, 'utf-8')
            .split('\n')
            .filter(line => line.trim())
            .map(line => JSON.parse(line));
    }
    catch {
        return [];
    }
}
export function writeSolutions(issueId, solutions) {
    const dir = join(getIssuesDir(), 'solutions');
    if (!existsSync(dir))
        mkdirSync(dir, { recursive: true });
    // Always add trailing newline for proper JSONL format
    const content = solutions.map(s => JSON.stringify(s)).join('\n');
    writeFileSync(getSolutionsPath(issueId), content ? content + '\n' : '', 'utf-8');
}
function findSolution(issueId, solutionId) {
    return readSolutions(issueId).find(s => s.id === solutionId);
}
function getBoundSolution(issueId) {
    return readSolutions(issueId).find(s => s.is_bound);
}
/**
 * Generate solution ID in format: SOL-{issue-id}-{seq}
 * @param issueId - The issue ID to include in the solution ID
 * @param existingSolutions - Existing solutions to calculate next sequence number
 * @returns Solution ID like "SOL-GH-123-1" or "SOL-ISS-20251229-001-2"
 */
function generateSolutionId(issueId, existingSolutions = []) {
    // Find the highest existing sequence number for this issue
    const pattern = new RegExp(`^SOL-${issueId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)$`);
    let maxSeq = 0;
    for (const sol of existingSolutions) {
        const match = sol.id.match(pattern);
        if (match) {
            maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
        }
    }
    return `SOL-${issueId}-${maxSeq + 1}`;
}
/**
 * Create a new solution with proper JSONL handling
 * Auto-generates ID if not provided
 */
function createSolution(issueId, data) {
    const issue = findIssue(issueId);
    if (!issue) {
        throw new Error(`Issue "${issueId}" not found`);
    }
    const solutions = readSolutions(issueId);
    const solutionId = data.id || generateSolutionId(issueId, solutions);
    if (solutions.some(s => s.id === solutionId)) {
        throw new Error(`Solution "${solutionId}" already exists`);
    }
    const newSolution = {
        id: solutionId,
        description: data.description || '',
        approach: data.approach || '',
        tasks: data.tasks || [],
        exploration_context: data.exploration_context,
        analysis: data.analysis,
        score: data.score,
        is_bound: false,
        created_at: new Date().toISOString()
    };
    solutions.push(newSolution);
    writeSolutions(issueId, solutions);
    return newSolution;
}
// ============ Queue Management (Multi-Queue) ============
function getQueuesDir() {
    return join(getIssuesDir(), 'queues');
}
function ensureQueuesDir() {
    const dir = getQueuesDir();
    if (!existsSync(dir)) {
        mkdirSync(dir, { recursive: true });
    }
}
function readQueueIndex() {
    const path = join(getQueuesDir(), 'index.json');
    if (!existsSync(path)) {
        return { active_queue_id: null, queues: [] };
    }
    return JSON.parse(readFileSync(path, 'utf-8'));
}
function writeQueueIndex(index) {
    ensureQueuesDir();
    writeFileSync(join(getQueuesDir(), 'index.json'), JSON.stringify(index, null, 2), 'utf-8');
}
function generateQueueFileId() {
    const now = new Date();
    const ts = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
    return `QUE-${ts}`;
}
export function readQueue(queueId) {
    const index = readQueueIndex();
    const targetId = queueId || index.active_queue_id;
    if (!targetId)
        return null;
    const path = join(getQueuesDir(), `${targetId}.json`);
    if (!existsSync(path))
        return null;
    return JSON.parse(readFileSync(path, 'utf-8'));
}
function readActiveQueue() {
    const queue = readQueue();
    if (queue)
        return queue;
    // Return empty queue structure if no active queue
    return createEmptyQueue();
}
function createEmptyQueue() {
    return {
        id: generateQueueFileId(),
        status: 'active',
        issue_ids: [],
        tasks: [],
        conflicts: [],
        _metadata: {
            version: '2.1',
            total_tasks: 0,
            pending_count: 0,
            executing_count: 0,
            completed_count: 0,
            failed_count: 0,
            updated_at: new Date().toISOString()
        }
    };
}
/**
 * Merge items from source queue into target queue
 * - Skips duplicate items (same issue_id + solution_id)
 * - Re-generates item IDs for merged items
 * - Marks source queue as 'merged' with metadata (or deletes if deleteSource=true)
 * - Updates queue index
 */
function mergeQueues(target, source, options) {
    const sourceItems = source.solutions || source.tasks || [];
    const targetItems = target.solutions || target.tasks || [];
    if (sourceItems.length === 0) {
        return { success: false, itemsMerged: 0, totalItems: targetItems.length, skippedDuplicates: 0, reason: 'Source queue is empty' };
    }
    // Ensure target has solutions array
    if (!target.solutions) {
        target.solutions = [];
    }
    let itemsMerged = 0;
    let skippedDuplicates = 0;
    for (const sourceItem of sourceItems) {
        // Skip if already exists in target (same issue_id + solution_id)
        const exists = target.solutions.some(t => t.issue_id === sourceItem.issue_id && t.solution_id === sourceItem.solution_id);
        if (exists) {
            skippedDuplicates++;
            continue;
        }
        // Add issue to target's issue_ids if not present
        if (!target.issue_ids.includes(sourceItem.issue_id)) {
            target.issue_ids.push(sourceItem.issue_id);
        }
        // Clone and add item with new item_id
        const newItem = {
            ...sourceItem,
            item_id: generateQueueItemId(target, 'solution'),
            execution_order: target.solutions.length + 1
        };
        target.solutions.push(newItem);
        itemsMerged++;
    }
    // Merge conflicts if any
    if (source.conflicts && source.conflicts.length > 0) {
        if (!target.conflicts)
            target.conflicts = [];
        target.conflicts.push(...source.conflicts);
    }
    // Write updated target queue
    writeQueue(target);
    // Handle source queue: delete or mark as merged
    const index = readQueueIndex();
    if (options?.deleteSource) {
        // Delete source queue file and remove from index
        const queuePath = join(getQueuesDir(), `${source.id}.json`);
        if (existsSync(queuePath)) {
            unlinkSync(queuePath);
        }
        index.queues = index.queues.filter(q => q.id !== source.id);
    }
    else {
        // Mark source queue as merged
        source.status = 'merged';
        if (!source._metadata) {
            source._metadata = {
                version: '2.1',
                total_tasks: 0,
                pending_count: 0,
                executing_count: 0,
                completed_count: 0,
                failed_count: 0,
                updated_at: new Date().toISOString()
            };
        }
        source._metadata.merged_into = target.id;
        source._metadata.merged_at = new Date().toISOString();
        writeQueue(source);
        const sourceEntry = index.queues.find(q => q.id === source.id);
        if (sourceEntry) {
            sourceEntry.status = 'merged';
        }
    }
    // Update target entry in index
    const targetEntry = index.queues.find(q => q.id === target.id);
    if (targetEntry) {
        targetEntry.total_solutions = target.solutions.length;
        targetEntry.completed_solutions = target.solutions.filter(s => s.status === 'completed').length;
        targetEntry.issue_ids = target.issue_ids;
    }
    writeQueueIndex(index);
    return {
        success: itemsMerged > 0,
        itemsMerged,
        totalItems: target.solutions.length,
        skippedDuplicates,
        reason: itemsMerged === 0 ? 'All items already exist in target queue' : undefined
    };
}
// ============ Multi-Queue Helper Functions ============
/**
 * Find which queue contains a given item ID
 * Supports both simple (S-1) and qualified (QUE-xxx:S-1) formats
 */
function findItemQueue(itemId) {
    // Check if qualified format (QUE-xxx:S-1)
    const qualifiedMatch = itemId.match(/^(QUE-[^:]+):(.+)$/);
    if (qualifiedMatch) {
        const [, queueId, actualItemId] = qualifiedMatch;
        const queue = readQueue(queueId);
        if (!queue)
            return null;
        const items = queue.solutions || queue.tasks || [];
        const itemIndex = items.findIndex(i => i.item_id === actualItemId);
        if (itemIndex === -1)
            return null;
        return { queue, item: items[itemIndex], itemIndex };
    }
    // Search all queues for unqualified item ID
    const index = readQueueIndex();
    const activeQueueIds = index.active_queue_ids || (index.active_queue_id ? [index.active_queue_id] : []);
    // Search active queues first
    for (const queueId of activeQueueIds) {
        const queue = readQueue(queueId);
        if (!queue)
            continue;
        const items = queue.solutions || queue.tasks || [];
        const itemIndex = items.findIndex(i => i.item_id === itemId);
        if (itemIndex >= 0) {
            return { queue, item: items[itemIndex], itemIndex };
        }
    }
    // Search all other queues
    for (const queueEntry of index.queues) {
        if (activeQueueIds.includes(queueEntry.id))
            continue;
        const queue = readQueue(queueEntry.id);
        if (!queue)
            continue;
        const items = queue.solutions || queue.tasks || [];
        const itemIndex = items.findIndex(i => i.item_id === itemId);
        if (itemIndex >= 0) {
            return { queue, item: items[itemIndex], itemIndex };
        }
    }
    return null;
}
/**
 * Get all active queues ordered by priority (lower = higher priority)
 * Falls back to creation date order
 */
function getActiveQueues() {
    const index = readQueueIndex();
    const activeIds = index.active_queue_ids || (index.active_queue_id ? [index.active_queue_id] : []);
    const queues = [];
    for (const queueId of activeIds) {
        const queue = readQueue(queueId);
        if (queue && queue.status === 'active') {
            queues.push(queue);
        }
    }
    // Sort by priority field in index (lower = higher priority)
    const priorityMap = new Map();
    for (const entry of index.queues) {
        priorityMap.set(entry.id, entry.priority ?? Number.MAX_SAFE_INTEGER);
    }
    queues.sort((a, b) => {
        const pa = priorityMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
        const pb = priorityMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
        if (pa !== pb)
            return pa - pb;
        // Fall back to creation date (from queue ID)
        return a.id.localeCompare(b.id);
    });
    return queues;
}
/**
 * Parse failure reason into structured FailureDetail
 * Detects JSON format vs plain string
 */
function parseFailureReason(reason) {
    const timestamp = new Date().toISOString();
    // Try to parse as JSON first
    if (reason.trim().startsWith('{')) {
        try {
            const parsed = JSON.parse(reason);
            return {
                task_id: parsed.task_id,
                error_type: parsed.error_type || 'unknown',
                message: parsed.message || reason,
                stack_trace: parsed.stack_trace,
                timestamp
            };
        }
        catch {
            // Not valid JSON, treat as plain message
        }
    }
    // Plain string message
    return {
        error_type: 'execution_error',
        message: reason,
        timestamp
    };
}
export function writeQueue(queue) {
    ensureQueuesDir();
    // Support both old (tasks) and new (solutions) queue format
    const items = queue.solutions || queue.tasks || [];
    const isSolutionQueue = !!queue.solutions;
    // Ensure _metadata exists (support queues with 'metadata' field from external sources)
    if (!queue._metadata) {
        const extMeta = queue.metadata;
        queue._metadata = {
            version: '2.0',
            total_tasks: extMeta?.total_tasks || items.length,
            pending_count: items.filter(q => q.status === 'pending').length,
            executing_count: items.filter(q => q.status === 'executing').length,
            completed_count: items.filter(q => q.status === 'completed').length,
            failed_count: items.filter(q => q.status === 'failed').length,
            updated_at: new Date().toISOString()
        };
    }
    // Update metadata counts
    queue._metadata.total_tasks = items.length;
    queue._metadata.pending_count = items.filter(q => q.status === 'pending').length;
    queue._metadata.executing_count = items.filter(q => q.status === 'executing').length;
    queue._metadata.completed_count = items.filter(q => q.status === 'completed').length;
    queue._metadata.failed_count = items.filter(q => q.status === 'failed').length;
    queue._metadata.updated_at = new Date().toISOString();
    // Write queue file
    const path = join(getQueuesDir(), `${queue.id}.json`);
    writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');
    // Update index
    const index = readQueueIndex();
    const existingIdx = index.queues.findIndex(q => q.id === queue.id);
    // Derive issue_ids from solutions if not present
    const issueIds = queue.issue_ids || (isSolutionQueue
        ? [...new Set(items.map(item => item.issue_id))]
        : []);
    const indexEntry = {
        id: queue.id,
        status: queue.status,
        issue_ids: issueIds,
        created_at: queue.id.replace('QUE-', '').replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, '$1-$2-$3T$4:$5:$6Z'), // Derive from ID
        completed_at: queue.status === 'completed' ? new Date().toISOString() : undefined
    };
    // Add format-specific counts
    if (isSolutionQueue) {
        indexEntry.total_solutions = items.length;
        indexEntry.completed_solutions = queue._metadata.completed_count;
    }
    else {
        indexEntry.total_tasks = items.length;
        indexEntry.completed_tasks = queue._metadata.completed_count;
    }
    if (existingIdx >= 0) {
        index.queues[existingIdx] = indexEntry;
    }
    else {
        index.queues.unshift(indexEntry);
    }
    if (queue.status === 'active') {
        index.active_queue_id = queue.id;
    }
    writeQueueIndex(index);
}
function generateQueueItemId(queue, level = 'solution') {
    const prefix = level === 'solution' ? 'S' : 'T';
    const items = level === 'solution' ? (queue.solutions || []) : (queue.tasks || []);
    const pattern = new RegExp(`^${prefix}-(\\d+)$`);
    const maxNum = items.reduce((max, q) => {
        const match = q.item_id.match(pattern);
        return match ? Math.max(max, parseInt(match[1])) : max;
    }, 0);
    return `${prefix}-${maxNum + 1}`;
}
// ============ Commands ============
/**
 * create - Create issue from JSON data
 * Usage: ccw issue create --data '{"title":"...", "context":"..."}'
 *        echo '{"title":"..."}' | ccw issue create
 * Output: JSON with created issue (includes auto-generated ID)
 */
async function createAction(options) {
    let jsonData = options.data;
    // Support stdin pipe input (avoids shell escaping issues)
    if (!jsonData && !process.stdin.isTTY) {
        try {
            jsonData = readFileSync(0, 'utf-8').trim();
        }
        catch {
            // stdin not available or empty
        }
    }
    if (!jsonData) {
        console.error(chalk.red('JSON data required'));
        console.error(chalk.gray('Usage: ccw issue create --data \'{"title":"...", "context":"..."}\''));
        console.error(chalk.gray('       echo \'{"title":"..."}\' | ccw issue create'));
        process.exit(1);
    }
    try {
        const data = JSON.parse(jsonData);
        const issue = createIssue(data);
        console.log(JSON.stringify(issue, null, 2));
    }
    catch (err) {
        console.error(chalk.red(err.message));
        process.exit(1);
    }
}
/**
 * pull - Pull issues from GitHub
 * Usage: ccw issue pull [--state open|closed|all] [--limit N] [--labels label1,label2]
 */
async function pullAction(options) {
    try {
        // Check if gh CLI is available
        try {
            execSync('gh --version', { stdio: 'ignore', timeout: EXEC_TIMEOUTS.GIT_QUICK });
        }
        catch {
            console.error(chalk.red('GitHub CLI (gh) is not installed or not in PATH'));
            console.error(chalk.gray('Install from: https://cli.github.com/'));
            process.exit(1);
        }
        // Build gh command with options
        const state = options.state || 'open';
        const limit = options.limit || 100;
        let ghCommand = `gh issue list --state ${state} --limit ${limit} --json number,title,body,labels,url,state`;
        if (options.labels) {
            ghCommand += ` --label "${options.labels}"`;
        }
        console.log(chalk.cyan(`Fetching issues from GitHub (state: ${state}, limit: ${limit})...`));
        // Fetch issues from GitHub
        const ghOutput = execSync(ghCommand, {
            encoding: 'utf-8',
            stdio: ['pipe', 'pipe', 'pipe'],
            timeout: EXEC_TIMEOUTS.PROCESS_SPAWN,
        }).trim();
        if (!ghOutput) {
            console.log(chalk.yellow('No issues found on GitHub'));
            return;
        }
        const ghIssues = JSON.parse(ghOutput);
        const existingIssues = readIssues();
        let imported = 0;
        let skipped = 0;
        let updated = 0;
        for (const ghIssue of ghIssues) {
            const issueId = `GH-${ghIssue.number}`;
            const existingIssue = existingIssues.find(i => i.id === issueId);
            // Prepare issue data
            const issueData = {
                id: issueId,
                title: ghIssue.title,
                status: ghIssue.state === 'OPEN' ? 'registered' : 'completed',
                priority: 3, // Default priority
                context: ghIssue.body?.substring(0, 500) || ghIssue.title,
                source: 'github',
                source_url: ghIssue.url,
                tags: ghIssue.labels?.map((l) => l.name) || [],
            };
            if (existingIssue) {
                // Update existing issue if state changed
                if (existingIssue.source_url === ghIssue.url) {
                    // Check if status needs updating
                    const newStatus = ghIssue.state === 'OPEN' ? 'registered' : 'completed';
                    if (existingIssue.status !== newStatus || existingIssue.title !== ghIssue.title) {
                        existingIssue.title = ghIssue.title;
                        existingIssue.status = newStatus;
                        existingIssue.updated_at = new Date().toISOString();
                        updated++;
                    }
                    else {
                        skipped++;
                    }
                }
                else {
                    skipped++;
                }
            }
            else {
                // Create new issue
                try {
                    createIssue(issueData);
                    imported++;
                }
                catch (err) {
                    console.error(chalk.red(`Failed to import issue #${ghIssue.number}: ${err.message}`));
                }
            }
        }
        // Save updates if any
        if (updated > 0) {
            writeIssues(existingIssues);
        }
        console.log(chalk.green(`\n✓ GitHub sync complete:`));
        console.log(chalk.gray(`  - Imported: ${imported} new issues`));
        console.log(chalk.gray(`  - Updated: ${updated} existing issues`));
        console.log(chalk.gray(`  - Skipped: ${skipped} unchanged issues`));
        if (options.json) {
            console.log(JSON.stringify({ imported, updated, skipped, total: ghIssues.length }));
        }
    }
    catch (err) {
        console.error(chalk.red(`Failed to pull issues from GitHub: ${err.message}`));
        process.exit(1);
    }
}
/**
 * solution - Create or read solutions
 * Create: ccw issue solution <issue-id> --data '{"tasks":[...]}'
 * Read:   ccw issue solution <issue-id> [--brief] [--solution-id <id>]
 * Brief:  Returns { solution_id, files_touched[], task_count } for each solution
 */
async function solutionAction(issueId, options) {
    if (!issueId) {
        console.error(chalk.red('Issue ID required'));
        console.error(chalk.gray('Usage: ccw issue solution <issue-id> [--brief] [--solution-id <id>]'));
        console.error(chalk.gray('       ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
        process.exit(1);
    }
    let jsonData = options.data;
    // Support stdin pipe input (avoids shell escaping issues)
    if (!jsonData && !process.stdin.isTTY) {
        try {
            jsonData = readFileSync(0, 'utf-8').trim();
        }
        catch {
            // stdin not available or empty
        }
    }
    // CREATE mode: if --data provided
    if (jsonData) {
        try {
            const data = JSON.parse(jsonData);
            const solution = createSolution(issueId, data);
            console.log(JSON.stringify(solution, null, 2));
        }
        catch (err) {
            console.error(chalk.red(err.message));
            process.exit(1);
        }
        return;
    }
    // READ mode: list solutions for issue
    const issue = findIssue(issueId);
    if (!issue) {
        console.error(chalk.red(`Issue "${issueId}" not found`));
        process.exit(1);
    }
    const solutions = readSolutions(issueId);
    if (solutions.length === 0) {
        if (options.json || options.brief) {
            console.log('[]');
        }
        else {
            console.log(chalk.yellow(`No solutions found for ${issueId}`));
        }
        return;
    }
    // Filter by solution-id if specified
    let targetSolutions = solutions;
    if (options.solutionId) {
        targetSolutions = solutions.filter(s => s.id === options.solutionId);
        if (targetSolutions.length === 0) {
            console.error(chalk.red(`Solution "${options.solutionId}" not found`));
            process.exit(1);
        }
    }
    // Brief mode: extract files_touched from modification_points
    if (options.brief) {
        const briefSolutions = targetSolutions.map(sol => {
            const filesTouched = new Set();
            for (const task of sol.tasks) {
                if (task.modification_points) {
                    for (const mp of task.modification_points) {
                        if (mp.file)
                            filesTouched.add(mp.file);
                    }
                }
            }
            return {
                solution_id: sol.id,
                is_bound: sol.is_bound,
                task_count: sol.tasks.length,
                files_touched: Array.from(filesTouched)
            };
        });
        console.log(JSON.stringify(briefSolutions, null, 2));
        return;
    }
    // JSON mode: full solutions
    if (options.json) {
        console.log(JSON.stringify(targetSolutions, null, 2));
        return;
    }
    // Human-readable output
    console.log(chalk.bold.cyan(`\nSolutions for ${issueId}:\n`));
    for (const sol of targetSolutions) {
        const marker = sol.is_bound ? chalk.green('◉ BOUND') : chalk.gray('○');
        console.log(`${marker} ${sol.id}`);
        console.log(chalk.gray(`  Tasks: ${sol.tasks.length}`));
        if (sol.description) {
            console.log(chalk.gray(`  ${sol.description.substring(0, 80)}...`));
        }
        console.log();
    }
}
/**
 * init - Initialize a new issue (manual ID)
 */
async function initAction(issueId, options) {
    if (!issueId) {
        console.error(chalk.red('Issue ID is required'));
        console.error(chalk.gray('Usage: ccw issue init <issue-id> [--title "..."]'));
        process.exit(1);
    }
    const existing = findIssue(issueId);
    if (existing && !options.force) {
        console.error(chalk.red(`Issue "${issueId}" already exists`));
        console.error(chalk.gray('Use --force to reinitialize'));
        process.exit(1);
    }
    const issues = readIssues().filter(i => i.id !== issueId);
    const newIssue = {
        id: issueId,
        title: options.title || issueId,
        status: 'registered',
        priority: options.priority ? parseInt(options.priority) : 3,
        context: options.description || '',
        bound_solution_id: null,
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString()
    };
    issues.push(newIssue);
    writeIssues(issues);
    console.log(chalk.green(`✓ Issue "${issueId}" initialized`));
    console.log(chalk.gray(`  Next: ccw issue task ${issueId} --title "Task title"`));
}
/**
 * list - List issues or tasks
 */
async function listAction(issueId, options) {
    if (!issueId) {
        // List all issues
        let issues = readIssues();
        // Filter by status if specified
        if (options.status) {
            const statuses = options.status.split(',').map(s => s.trim());
            issues = issues.filter(i => statuses.includes(i.status));
        }
        // Brief mode: minimal fields only (id, title, status, priority, tags, bound_solution_id)
        if (options.brief) {
            const briefIssues = issues.map(i => ({
                id: i.id,
                title: i.title,
                status: i.status,
                priority: i.priority,
                tags: i.tags || [],
                bound_solution_id: i.bound_solution_id
            }));
            console.log(JSON.stringify(briefIssues, null, 2));
            return;
        }
        if (options.json) {
            console.log(JSON.stringify(issues, null, 2));
            return;
        }
        if (issues.length === 0) {
            console.log(chalk.yellow('No issues found'));
            console.log(chalk.gray('Create one with: ccw issue init <issue-id>'));
            return;
        }
        console.log(chalk.bold.cyan('\nIssues\n'));
        console.log(chalk.gray('ID'.padEnd(20) + 'Status'.padEnd(15) + 'Solutions'.padEnd(12) + 'Title'));
        console.log(chalk.gray('-'.repeat(70)));
        for (const issue of issues) {
            const statusColor = {
                'registered': chalk.gray,
                'planning': chalk.blue,
                'planned': chalk.cyan,
                'queued': chalk.yellow,
                'executing': chalk.yellow,
                'completed': chalk.green,
                'failed': chalk.red,
                'paused': chalk.magenta
            }[issue.status] || chalk.white;
            const solutionCount = readSolutions(issue.id).length;
            const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${solutionCount}`;
            console.log(issue.id.padEnd(20) +
                statusColor(issue.status.padEnd(15)) +
                bound.padEnd(12) +
                (issue.title || '').substring(0, 30));
        }
        return;
    }
    // List tasks in bound solution
    const issue = findIssue(issueId);
    if (!issue) {
        console.error(chalk.red(`Issue "${issueId}" not found`));
        process.exit(1);
    }
    const solution = getBoundSolution(issueId);
    const tasks = solution?.tasks || [];
    if (options.json) {
        console.log(JSON.stringify({ issue, solution, tasks }, null, 2));
        return;
    }
    console.log(chalk.bold.cyan(`\nIssue: ${issueId}\n`));
    console.log(`Title: ${issue.title}`);
    console.log(`Status: ${issue.status}`);
    console.log(`Bound: ${issue.bound_solution_id || 'none'}`);
    console.log();
    if (tasks.length === 0) {
        console.log(chalk.yellow('No tasks (bind a solution first)'));
        return;
    }
    console.log(chalk.gray('ID'.padEnd(8) + 'Action'.padEnd(12) + 'Scope'.padEnd(20) + 'Title'));
    console.log(chalk.gray('-'.repeat(70)));
    for (const task of tasks) {
        console.log(task.id.padEnd(8) +
            task.action.padEnd(12) +
            task.scope.substring(0, 18).padEnd(20) +
            task.title.substring(0, 30));
    }
}
/**
 * history - List completed issues from history
 */
async function historyAction(options) {
    const history = readIssueHistory();
    // Brief mode: minimal fields only
    if (options.brief) {
        const briefHistory = history.map(i => ({
            id: i.id,
            title: i.title,
            status: i.status,
            completed_at: i.completed_at
        }));
        console.log(JSON.stringify(briefHistory, null, 2));
        return;
    }
    if (options.json) {
        console.log(JSON.stringify(history, null, 2));
        return;
    }
    if (history.length === 0) {
        console.log(chalk.yellow('No completed issues in history'));
        return;
    }
    console.log(chalk.bold.cyan('\nIssue History (Completed)\n'));
    console.log(chalk.gray('ID'.padEnd(25) + 'Completed At'.padEnd(22) + 'Title'));
    console.log(chalk.gray('-'.repeat(80)));
    for (const issue of history) {
        const completedAt = issue.completed_at
            ? new Date(issue.completed_at).toLocaleString()
            : 'N/A';
        console.log(chalk.green(issue.id.padEnd(25)) +
            completedAt.padEnd(22) +
            (issue.title || '').substring(0, 35));
    }
    console.log(chalk.gray(`\nTotal: ${history.length} completed issues`));
}
/**
 * status - Show detailed status
 */
async function statusAction(issueId, options) {
    if (!issueId) {
        // Show queue status
        const queue = readActiveQueue();
        const issues = readIssues();
        const index = readQueueIndex();
        if (options.json) {
            // Return full queue for programmatic access
            console.log(JSON.stringify(queue, null, 2));
            return;
        }
        console.log(chalk.bold.cyan('\nSystem Status\n'));
        console.log(`Issues: ${issues.length}`);
        console.log(`Queues: ${index.queues.length} (Active: ${index.active_queue_id || 'none'})`);
        console.log(`Active Queue: ${queue._metadata.total_tasks} tasks`);
        console.log(`  Pending: ${queue._metadata.pending_count}`);
        console.log(`  Executing: ${queue._metadata.executing_count}`);
        console.log(`  Completed: ${queue._metadata.completed_count}`);
        console.log(`  Failed: ${queue._metadata.failed_count}`);
        return;
    }
    const issue = findIssue(issueId);
    if (!issue) {
        console.error(chalk.red(`Issue "${issueId}" not found`));
        process.exit(1);
    }
    const solutions = readSolutions(issueId);
    const boundSol = solutions.find(s => s.is_bound);
    if (options.json) {
        console.log(JSON.stringify({ issue, solutions, bound: boundSol }, null, 2));
        return;
    }
    console.log(chalk.bold.cyan(`\nIssue: ${issueId}\n`));
    console.log(`Title: ${issue.title}`);
    console.log(`Status: ${issue.status}`);
    console.log(`Priority: ${issue.priority}`);
    console.log(`Created: ${issue.created_at}`);
    console.log(`Updated: ${issue.updated_at}`);
    if (issue.context) {
        console.log();
        console.log(chalk.bold('Context:'));
        console.log(issue.context.substring(0, 200));
    }
    console.log();
    console.log(chalk.bold(`Solutions (${solutions.length}):`));
    for (const sol of solutions) {
        const marker = sol.is_bound ? chalk.green('◉') : chalk.gray('○');
        console.log(`  ${marker} ${sol.id}: ${sol.tasks.length} tasks`);
    }
}
/**
 * task - Add or update task (simplified - mainly for manual task management)
 */
async function taskAction(issueId, taskId, options) {
    if (!issueId) {
        console.error(chalk.red('Issue ID is required'));
        console.error(chalk.gray('Usage: ccw issue task <issue-id> [task-id] --title "..."'));
        process.exit(1);
    }
    const issue = findIssue(issueId);
    if (!issue) {
        console.error(chalk.red(`Issue "${issueId}" not found`));
        process.exit(1);
    }
    const solutions = readSolutions(issueId);
    let boundIdx = solutions.findIndex(s => s.is_bound);
    // Create default solution if none bound
    if (boundIdx === -1) {
        const newSol = {
            id: generateSolutionId(issueId, solutions),
            description: 'Manual tasks',
            tasks: [],
            is_bound: true,
            created_at: new Date().toISOString(),
            bound_at: new Date().toISOString()
        };
        solutions.push(newSol);
        boundIdx = solutions.length - 1;
        updateIssue(issueId, { bound_solution_id: newSol.id, status: 'planned' });
    }
    const solution = solutions[boundIdx];
    if (taskId) {
        // Update existing task
        const taskIdx = solution.tasks.findIndex(t => t.id === taskId);
        if (taskIdx === -1) {
            console.error(chalk.red(`Task "${taskId}" not found`));
            process.exit(1);
        }
        if (options.title)
            solution.tasks[taskIdx].title = options.title;
        if (options.status)
            solution.tasks[taskIdx].status = options.status;
        writeSolutions(issueId, solutions);
        console.log(chalk.green(`✓ Task ${taskId} updated`));
    }
    else {
        // Add new task
        if (!options.title) {
            console.error(chalk.red('Task title is required (--title)'));
            process.exit(1);
        }
        const newTaskId = `T${solution.tasks.length + 1}`;
        const newTask = {
            id: newTaskId,
            title: options.title,
            scope: '',
            action: 'Implement',
            description: options.description || options.title,
            implementation: [],
            test: {
                unit: [],
                commands: ['npm test']
            },
            regression: ['npm test'],
            acceptance: {
                criteria: ['Task completed successfully'],
                verification: ['Manual verification']
            },
            commit: {
                type: 'feat',
                scope: 'core',
                message_template: `feat(core): ${options.title}`
            },
            depends_on: []
        };
        solution.tasks.push(newTask);
        writeSolutions(issueId, solutions);
        console.log(chalk.green(`✓ Task ${newTaskId} added to ${issueId}`));
    }
}
/**
 * update - Update issue fields (status, priority, title, etc.)
 * --from-queue: Sync statuses from active queue (auto-update queued issues)
 */
async function updateAction(issueId, options) {
    // Handle --from-queue: Sync statuses from queue
    if (options.fromQueue) {
        // Determine queue ID: string value = specific queue, true = active queue
        const queueId = typeof options.fromQueue === 'string' ? options.fromQueue : undefined;
        const queue = queueId ? readQueue(queueId) : readActiveQueue();
        if (!queue) {
            if (options.json) {
                console.log(JSON.stringify({ success: false, message: `Queue not found: ${queueId}`, queued: [], unplanned: [] }));
            }
            else {
                console.log(chalk.red(`Queue not found: ${queueId}`));
            }
            return;
        }
        const items = queue.solutions || queue.tasks || [];
        const allIssues = readIssues();
        if (!queue.id || items.length === 0) {
            if (options.json) {
                console.log(JSON.stringify({ success: false, message: 'No active queue', queued: [], unplanned: [] }));
            }
            else {
                console.log(chalk.yellow('No active queue to sync from'));
            }
            return;
        }
        // Get issue IDs from queue
        const queuedIssueIds = new Set(items.map(item => item.issue_id));
        const now = new Date().toISOString();
        // Track updates
        const updated = [];
        const unplanned = [];
        // Update queued issues
        for (const issueId of queuedIssueIds) {
            const issue = allIssues.find(i => i.id === issueId);
            if (issue && issue.status !== 'queued' && issue.status !== 'executing' && issue.status !== 'completed') {
                updateIssue(issueId, { status: 'queued', queued_at: now });
                updated.push(issueId);
            }
        }
        // Find planned issues NOT in queue
        for (const issue of allIssues) {
            if (issue.status === 'planned' && issue.bound_solution_id && !queuedIssueIds.has(issue.id)) {
                unplanned.push(issue.id);
            }
        }
        if (options.json) {
            console.log(JSON.stringify({
                success: true,
                queue_id: queue.id,
                queued: updated,
                queued_count: updated.length,
                unplanned: unplanned,
                unplanned_count: unplanned.length
            }, null, 2));
        }
        else {
            console.log(chalk.green(`✓ Synced from queue ${queue.id}`));
            console.log(chalk.gray(`  Updated to 'queued': ${updated.length} issues`));
            if (updated.length > 0) {
                updated.forEach(id => console.log(chalk.gray(`    - ${id}`)));
            }
            if (unplanned.length > 0) {
                console.log(chalk.yellow(`  Planned but NOT in queue: ${unplanned.length} issues`));
                unplanned.forEach(id => console.log(chalk.yellow(`    - ${id}`)));
            }
        }
        return;
    }
    // Standard single-issue update
    if (!issueId) {
        console.error(chalk.red('Issue ID is required'));
        console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status>'));
        console.error(chalk.gray('       ccw issue update --from-queue [queue-id]  (sync from queue)'));
        process.exit(1);
    }
    const issue = findIssue(issueId);
    if (!issue) {
        console.error(chalk.red(`Issue "${issueId}" not found`));
        process.exit(1);
    }
    const updates = {};
    if (options.status) {
        const validStatuses = ['registered', 'planning', 'planned', 'queued', 'executing', 'completed', 'failed', 'paused'];
        if (!validStatuses.includes(options.status)) {
            console.error(chalk.red(`Invalid status: ${options.status}`));
            console.error(chalk.gray(`Valid: ${validStatuses.join(', ')}`));
            process.exit(1);
        }
        updates.status = options.status;
        // Auto-set timestamps based on status
        if (options.status === 'planned')
            updates.planned_at = new Date().toISOString();
        if (options.status === 'queued')
            updates.queued_at = new Date().toISOString();
        if (options.status === 'completed')
            updates.completed_at = new Date().toISOString();
    }
    if (options.priority) {
        updates.priority = parseInt(options.priority);
    }
    if (options.title) {
        updates.title = options.title;
    }
    if (options.description) {
        updates.context = options.description;
    }
    if (Object.keys(updates).length === 0) {
        console.error(chalk.yellow('No updates specified'));
        console.error(chalk.gray('Use --status, --priority, --title, or --description'));
        return;
    }
    updateIssue(issueId, updates);
    if (options.json) {
        console.log(JSON.stringify({ success: true, issue_id: issueId, updates }));
    }
    else {
        console.log(chalk.green(`✓ Issue "${issueId}" updated`));
        Object.entries(updates).forEach(([k, v]) => {
            console.log(chalk.gray(`  ${k}: ${v}`));
        });
    }
}
/**
 * bind - Register and/or bind a solution
 */
async function bindAction(issueId, solutionId, options) {
    if (!issueId) {
        console.error(chalk.red('Issue ID is required'));
        console.error(chalk.gray('Usage: ccw issue bind <issue-id> [solution-id] [--solution <path>]'));
        process.exit(1);
    }
    const issue = findIssue(issueId);
    if (!issue) {
        console.error(chalk.red(`Issue "${issueId}" not found`));
        process.exit(1);
    }
    let solutions = readSolutions(issueId);
    // Register new solution from file if provided
    if (options.solution) {
        try {
            const content = readFileSync(options.solution, 'utf-8');
            const data = JSON.parse(content);
            // Priority: CLI arg > file content ID > generate new (SOL-{issue-id}-{seq})
            const newSol = {
                id: solutionId || data.id || generateSolutionId(issueId, solutions),
                description: data.description || data.approach_name || 'Imported solution',
                tasks: data.tasks || [],
                exploration_context: data.exploration_context,
                analysis: data.analysis,
                score: data.score,
                is_bound: false,
                created_at: new Date().toISOString()
            };
            solutions.push(newSol);
            solutionId = newSol.id;
            console.log(chalk.green(`✓ Solution ${solutionId} registered (${newSol.tasks.length} tasks)`));
        }
        catch (e) {
            console.error(chalk.red(`Failed to read solution file: ${options.solution}`));
            process.exit(1);
        }
    }
    if (!solutionId) {
        // List available solutions
        if (solutions.length === 0) {
            console.log(chalk.yellow('No solutions available'));
            console.log(chalk.gray('Register one: ccw issue bind <issue-id> --solution <path>'));
            return;
        }
        console.log(chalk.bold.cyan(`\nSolutions for ${issueId}:\n`));
        for (const sol of solutions) {
            const marker = sol.is_bound ? chalk.green('◉') : chalk.gray('○');
            console.log(`  ${marker} ${sol.id}: ${sol.tasks.length} tasks - ${sol.description || ''}`);
        }
        return;
    }
    // Bind the specified solution
    const solIdx = solutions.findIndex(s => s.id === solutionId);
    if (solIdx === -1) {
        console.error(chalk.red(`Solution "${solutionId}" not found`));
        process.exit(1);
    }
    // Unbind all, bind selected
    solutions = solutions.map(s => ({ ...s, is_bound: false }));
    solutions[solIdx].is_bound = true;
    solutions[solIdx].bound_at = new Date().toISOString();
    writeSolutions(issueId, solutions);
    updateIssue(issueId, {
        bound_solution_id: solutionId,
        status: 'planned',
        planned_at: new Date().toISOString()
    });
    console.log(chalk.green(`✓ Solution ${solutionId} bound to ${issueId}`));
}
/**
 * queue - Queue management (list / add / history)
 */
async function queueAction(subAction, issueId, options) {
    // List all queues (history)
    if (subAction === 'list' || subAction === 'history') {
        const index = readQueueIndex();
        // Brief mode: minimal queue index info
        if (options.brief) {
            const briefIndex = {
                active_queue_id: index.active_queue_id,
                queues: index.queues.map(q => ({
                    id: q.id,
                    status: q.status,
                    issue_ids: q.issue_ids,
                    total_solutions: q.total_solutions,
                    completed_solutions: q.completed_solutions
                }))
            };
            console.log(JSON.stringify(briefIndex, null, 2));
            return;
        }
        if (options.json) {
            console.log(JSON.stringify(index, null, 2));
            return;
        }
        console.log(chalk.bold.cyan('\nQueue History\n'));
        console.log(chalk.gray(`Active: ${index.active_queue_id || 'none'}`));
        console.log();
        if (index.queues.length === 0) {
            console.log(chalk.yellow('No queues found'));
            console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
            return;
        }
        console.log(chalk.gray('ID'.padEnd(22) + 'Status'.padEnd(12) + 'Tasks'.padEnd(10) + 'Issues'));
        console.log(chalk.gray('-'.repeat(70)));
        for (const q of index.queues) {
            const statusColor = {
                'active': chalk.green,
                'completed': chalk.cyan,
                'archived': chalk.gray,
                'failed': chalk.red
            }[q.status] || chalk.white;
            const marker = q.id === index.active_queue_id ? '→ ' : '  ';
            console.log(marker +
                q.id.padEnd(20) +
                statusColor(q.status.padEnd(12)) +
                `${q.completed_tasks}/${q.total_tasks}`.padEnd(10) +
                q.issue_ids.join(', '));
        }
        return;
    }
    // Switch active queue
    if (subAction === 'switch' && issueId) {
        const queueId = issueId; // issueId is actually queue ID here
        const targetQueue = readQueue(queueId);
        if (!targetQueue) {
            console.error(chalk.red(`Queue "${queueId}" not found`));
            process.exit(1);
        }
        const index = readQueueIndex();
        index.active_queue_id = queueId;
        // Also update active_queue_ids for multi-queue support
        index.active_queue_ids = [queueId];
        writeQueueIndex(index);
        console.log(chalk.green(`✓ Switched to queue ${queueId}`));
        return;
    }
    // Set queue priority (lower = higher priority)
    if (subAction === 'priority' && issueId) {
        const queueId = issueId;
        const priority = parseInt(options.priority || '0');
        if (isNaN(priority)) {
            console.error(chalk.red('Invalid priority value (must be a number)'));
            process.exit(1);
        }
        const index = readQueueIndex();
        const queueEntry = index.queues.find(q => q.id === queueId);
        if (!queueEntry) {
            console.error(chalk.red(`Queue "${queueId}" not found`));
            process.exit(1);
        }
        queueEntry.priority = priority;
        writeQueueIndex(index);
        console.log(chalk.green(`✓ Queue ${queueId} priority set to ${priority}`));
        return;
    }
    // Activate multiple queues at once
    if (subAction === 'activate' && issueId) {
        const queueIds = issueId.split(',').map(id => id.trim());
        const index = readQueueIndex();
        // Validate all queue IDs
        for (const queueId of queueIds) {
            if (!index.queues.some(q => q.id === queueId)) {
                console.error(chalk.red(`Queue "${queueId}" not found`));
                process.exit(1);
            }
        }
        index.active_queue_ids = queueIds;
        index.active_queue_id = queueIds[0] || null; // Backward compat
        writeQueueIndex(index);
        console.log(chalk.green(`✓ Activated ${queueIds.length} queue(s): ${queueIds.join(', ')}`));
        return;
    }
    // DAG - Return dependency graph for parallel execution planning (solution-level)
    if (subAction === 'dag') {
        let queue;
        // Use explicit queue if provided via --queue or issueId, otherwise use active queue
        if (options.queue) {
            const targetQueue = readQueue(options.queue);
            if (!targetQueue) {
                console.log(JSON.stringify({ error: `Queue ${options.queue} not found`, nodes: [], edges: [], groups: [] }));
                return;
            }
            queue = targetQueue;
        }
        else if (issueId && issueId.startsWith('QUE-')) {
            const targetQueue = readQueue(issueId);
            if (!targetQueue) {
                console.log(JSON.stringify({ error: `Queue ${issueId} not found`, nodes: [], edges: [], groups: [] }));
                return;
            }
            queue = targetQueue;
        }
        else {
            queue = readActiveQueue();
        }
        // Support both old (tasks) and new (solutions) queue format
        const items = queue.solutions || queue.tasks || [];
        if (!queue.id || items.length === 0) {
            console.log(JSON.stringify({ error: 'No active queue', nodes: [], edges: [], groups: [] }));
            return;
        }
        // Build DAG nodes (solution-level)
        const completedIds = new Set(items.filter(t => t.status === 'completed').map(t => t.item_id));
        const failedIds = new Set(items.filter(t => t.status === 'failed').map(t => t.item_id));
        const nodes = items.map(item => ({
            id: item.item_id,
            issue_id: item.issue_id,
            solution_id: item.solution_id,
            status: item.status,
            priority: item.semantic_priority,
            depends_on: item.depends_on || [],
            task_count: item.task_count || 1,
            files_touched: item.files_touched || [],
            // Calculate if ready (dependencies satisfied)
            ready: item.status === 'pending' && (item.depends_on || []).every(d => completedIds.has(d)),
            blocked_by: (item.depends_on || []).filter(d => !completedIds.has(d) && !failedIds.has(d))
        }));
        // Build edges for visualization
        const edges = items.flatMap(item => (item.depends_on || []).map(dep => ({ from: dep, to: item.item_id })));
        // Group ready items by execution_group
        const readyItems = nodes.filter(n => n.ready || n.status === 'executing');
        const groups = {};
        for (const item of items) {
            if (readyItems.some(r => r.id === item.item_id)) {
                const group = item.execution_group || 'P1';
                if (!groups[group])
                    groups[group] = [];
                groups[group].push(item.item_id);
            }
        }
        // Calculate parallel batches - prefer execution_groups from queue if available
        const parallelBatches = [];
        const readyItemIds = new Set(readyItems.map(t => t.id));
        // Check if queue has pre-assigned execution_groups
        if (queue.execution_groups && queue.execution_groups.length > 0) {
            // Use agent-assigned execution groups
            for (const group of queue.execution_groups) {
                const groupItems = (group.solutions || group.tasks || [])
                    .filter((id) => readyItemIds.has(id));
                if (groupItems.length > 0) {
                    if (group.type === 'parallel') {
                        // All items in parallel group can run together
                        parallelBatches.push(groupItems);
                    }
                    else {
                        // Sequential group: each item is its own batch
                        for (const itemId of groupItems) {
                            parallelBatches.push([itemId]);
                        }
                    }
                }
            }
        }
        else {
            // Fallback: calculate parallel batches from file conflicts
            const remainingReady = new Set(readyItemIds);
            while (remainingReady.size > 0) {
                const batch = [];
                const batchFiles = new Set();
                for (const itemId of Array.from(remainingReady)) {
                    const item = items.find(t => t.item_id === itemId);
                    if (!item)
                        continue;
                    // Get all files touched by this solution
                    let solutionFiles = item.files_touched || [];
                    // If not in queue item, fetch from solution definition
                    if (solutionFiles.length === 0) {
                        const solution = findSolution(item.issue_id, item.solution_id);
                        if (solution?.tasks) {
                            for (const task of solution.tasks) {
                                for (const mp of task.modification_points || []) {
                                    solutionFiles.push(mp.file);
                                }
                            }
                        }
                    }
                    const hasConflict = solutionFiles.some(f => batchFiles.has(f));
                    if (!hasConflict) {
                        batch.push(itemId);
                        solutionFiles.forEach(f => batchFiles.add(f));
                    }
                }
                if (batch.length === 0) {
                    // Fallback: take one at a time if all conflict
                    const first = Array.from(remainingReady)[0];
                    batch.push(first);
                }
                parallelBatches.push(batch);
                batch.forEach(id => remainingReady.delete(id));
            }
        }
        console.log(JSON.stringify({
            queue_id: queue.id,
            total: nodes.length,
            ready_count: readyItems.length,
            completed_count: completedIds.size,
            nodes,
            edges,
            groups: Object.entries(groups).map(([id, solutions]) => ({ id, solutions })),
            parallel_batches: parallelBatches,
            _summary: {
                can_parallel: parallelBatches[0]?.length || 0,
                batches_needed: parallelBatches.length
            }
        }, null, 2));
        return;
    }
    // Merge queues: ccw issue queue merge <source-id> --queue <target-id>
    if (subAction === 'merge' && issueId) {
        const sourceQueueId = issueId; // issueId is actually source queue ID here
        const targetQueueId = options.queue; // --queue option
        if (!targetQueueId) {
            console.error(chalk.red('Target queue ID required'));
            console.error(chalk.gray('Usage: ccw issue queue merge <source-id> --queue <target-id>'));
            process.exit(1);
        }
        const sourceQueue = readQueue(sourceQueueId);
        const targetQueue = readQueue(targetQueueId);
        if (!sourceQueue) {
            console.error(chalk.red(`Source queue "${sourceQueueId}" not found`));
            process.exit(1);
        }
        if (!targetQueue) {
            console.error(chalk.red(`Target queue "${targetQueueId}" not found`));
            process.exit(1);
        }
        // mergeQueues marks source as 'merged' and updates index
        const result = mergeQueues(targetQueue, sourceQueue);
        if (options.json) {
            console.log(JSON.stringify({
                success: result.success,
                sourceQueueId,
                targetQueueId,
                itemsMerged: result.itemsMerged,
                skippedDuplicates: result.skippedDuplicates,
                totalItems: result.totalItems,
                reason: result.reason
            }, null, 2));
        }
        else {
            if (result.success) {
                console.log(chalk.green(`✓ Merged ${result.itemsMerged} items from ${sourceQueueId} into ${targetQueueId}`));
                if (result.skippedDuplicates > 0) {
                    console.log(chalk.gray(`  Skipped ${result.skippedDuplicates} duplicate items`));
                }
                console.log(chalk.gray(`  Total items in target: ${result.totalItems}`));
                console.log(chalk.gray(`  Source queue ${sourceQueueId} marked as 'merged'`));
            }
            else {
                console.log(chalk.yellow(`⚠ Merge skipped: ${result.reason}`));
            }
        }
        return;
    }
    // Archive current queue
    if (subAction === 'archive') {
        const queue = readActiveQueue();
        const items = queue.solutions || queue.tasks || [];
        if (!queue.id || items.length === 0) {
            console.log(chalk.yellow('No active queue to archive'));
            return;
        }
        queue.status = 'archived';
        writeQueue(queue);
        const index = readQueueIndex();
        index.active_queue_id = null;
        writeQueueIndex(index);
        console.log(chalk.green(`✓ Archived queue ${queue.id}`));
        return;
    }
    // Delete queue from history
    if ((subAction === 'clear' || subAction === 'delete') && issueId) {
        const queueId = issueId; // issueId is actually queue ID here
        const queuePath = join(getQueuesDir(), `${queueId}.json`);
        if (!existsSync(queuePath)) {
            console.error(chalk.red(`Queue "${queueId}" not found`));
            process.exit(1);
        }
        if (!options.force) {
            const { proceed } = await inquirer.prompt([{
                    type: 'confirm',
                    name: 'proceed',
                    message: `Delete queue ${queueId}? This action cannot be undone.`,
                    default: false
                }]);
            if (!proceed) {
                console.log(chalk.yellow('Queue deletion cancelled'));
                return;
            }
        }
        // Remove from index
        const index = readQueueIndex();
        index.queues = index.queues.filter(q => q.id !== queueId);
        if (index.active_queue_id === queueId) {
            index.active_queue_id = null;
        }
        writeQueueIndex(index);
        // Delete queue file
        unlinkSync(queuePath);
        console.log(chalk.green(`✓ Deleted queue ${queueId}`));
        return;
    }
    // Add issue solution to queue (solution-level granularity)
    if (subAction === 'add' && issueId) {
        const issue = findIssue(issueId);
        if (!issue) {
            console.error(chalk.red(`Issue "${issueId}" not found`));
            process.exit(1);
        }
        const solution = getBoundSolution(issueId);
        if (!solution) {
            console.error(chalk.red(`No bound solution for "${issueId}"`));
            console.error(chalk.gray('First bind a solution: ccw issue bind <issue-id> <solution-id>'));
            process.exit(1);
        }
        // Step 1: Create new queue (temporary, not active yet)
        const newQueue = createEmptyQueue();
        newQueue.solutions = [];
        // Add issue to queue's issue list
        newQueue.issue_ids.push(issueId);
        // Collect all files touched by this solution
        const filesTouched = new Set();
        for (const task of solution.tasks || []) {
            for (const mp of task.modification_points || []) {
                filesTouched.add(mp.file);
            }
        }
        // Create solution-level queue item (S-N)
        newQueue.solutions.push({
            item_id: generateQueueItemId(newQueue, 'solution'),
            issue_id: issueId,
            solution_id: solution.id,
            status: 'pending',
            execution_order: 1,
            execution_group: 'P1',
            depends_on: [],
            semantic_priority: 0.5,
            task_count: solution.tasks?.length || 0,
            files_touched: Array.from(filesTouched)
        });
        // Step 2: Write temporary queue file
        writeQueue(newQueue);
        updateIssue(issueId, { status: 'queued', queued_at: new Date().toISOString() });
        console.log(chalk.green(`✓ Created temporary queue ${newQueue.id}`));
        console.log(chalk.gray(`  Solution ${solution.id} (${solution.tasks?.length || 0} tasks)`));
        // Step 3: Check for existing active queue
        const existingQueue = readQueue();
        const hasActiveQueue = existingQueue && existingQueue.status === 'active' &&
            (existingQueue.solutions?.length || existingQueue.tasks?.length || 0) > 0;
        if (!hasActiveQueue || options.force) {
            // No active queue or force flag - set new queue as active
            const index = readQueueIndex();
            index.active_queue_id = newQueue.id;
            writeQueueIndex(index);
            console.log(chalk.green(`✓ Queue ${newQueue.id} activated`));
            return;
        }
        // Step 4: Active queue exists - prompt user
        const existingItems = existingQueue.solutions || existingQueue.tasks || [];
        console.log();
        console.log(chalk.cyan(`Active queue exists: ${existingQueue.id}`));
        console.log(chalk.gray(`  Issues: ${existingQueue.issue_ids.join(', ')}`));
        console.log(chalk.gray(`  Items: ${existingItems.length} (${existingItems.filter(i => i.status === 'completed').length} completed)`));
        console.log();
        const { action } = await inquirer.prompt([{
                type: 'list',
                name: 'action',
                message: 'How would you like to proceed?',
                choices: [
                    { name: 'Merge into existing queue', value: 'merge_to_existing' },
                    { name: 'Use new queue', value: 'use_new' },
                    { name: 'Cancel', value: 'cancel' }
                ]
            }]);
        // Step 5: Execute user choice
        if (action === 'cancel') {
            // Delete temporary queue
            const queuePath = join(getQueuesDir(), `${newQueue.id}.json`);
            unlinkSync(queuePath);
            console.log(chalk.yellow(`✓ New queue deleted, keeping ${existingQueue.id} active`));
            return;
        }
        if (action === 'use_new') {
            // Switch to new queue
            const index = readQueueIndex();
            index.active_queue_id = newQueue.id;
            writeQueueIndex(index);
            console.log(chalk.green(`✓ Switched to new queue ${newQueue.id}`));
            console.log(chalk.gray(`  Previous queue ${existingQueue.id} remains in history`));
            return;
        }
        if (action === 'merge_to_existing') {
            // Merge new → existing, delete temporary queue
            const mergeResult = mergeQueues(existingQueue, newQueue, { deleteSource: true });
            console.log(chalk.green(`✓ Merged ${mergeResult.itemsMerged} items into ${existingQueue.id}`));
            if (mergeResult.skippedDuplicates > 0) {
                console.log(chalk.gray(`  Skipped ${mergeResult.skippedDuplicates} duplicate items`));
            }
            console.log(chalk.gray(`  Temporary queue ${newQueue.id} deleted`));
            return;
        }
        return;
    }
    // Show current queue
    const queue = readActiveQueue();
    // Brief mode: minimal queue info (id, issue_ids, item summaries)
    if (options.brief) {
        const items = queue.solutions || queue.tasks || [];
        const briefQueue = {
            id: queue.id,
            issue_ids: queue.issue_ids || [],
            total: items.length,
            pending: items.filter(i => i.status === 'pending').length,
            executing: items.filter(i => i.status === 'executing').length,
            completed: items.filter(i => i.status === 'completed').length,
            items: items.map(i => ({
                item_id: i.item_id,
                issue_id: i.issue_id,
                solution_id: i.solution_id,
                status: i.status,
                task_count: i.task_count
            }))
        };
        console.log(JSON.stringify(briefQueue, null, 2));
        return;
    }
    if (options.json) {
        console.log(JSON.stringify(queue, null, 2));
        return;
    }
    console.log(chalk.bold.cyan('\nActive Queue\n'));
    // Support both solution-level and task-level queues
    const items = queue.solutions || queue.tasks || [];
    const isSolutionLevel = !!(queue.solutions && queue.solutions.length > 0);
    if (!queue.id || items.length === 0) {
        console.log(chalk.yellow('No active queue'));
        console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
        console.log(chalk.gray('Or list history: ccw issue queue list'));
        return;
    }
    console.log(chalk.gray(`Queue: ${queue.id}`));
    console.log(chalk.gray(`Issues: ${queue.issue_ids.join(', ')}`));
    console.log(chalk.gray(`Total: ${items.length} | Pending: ${items.filter(i => i.status === 'pending').length} | Executing: ${items.filter(i => i.status === 'executing').length} | Completed: ${items.filter(i => i.status === 'completed').length}`));
    console.log();
    if (isSolutionLevel) {
        console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'));
    }
    else {
        console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'));
    }
    console.log(chalk.gray('-'.repeat(48)));
    for (const item of items) {
        const statusColor = {
            'pending': chalk.gray,
            'ready': chalk.cyan,
            'executing': chalk.yellow,
            'completed': chalk.green,
            'failed': chalk.red,
            'blocked': chalk.magenta
        }[item.status] || chalk.white;
        const thirdCol = isSolutionLevel
            ? String(item.task_count || 0).padEnd(8)
            : (item.task_id || '-').padEnd(8);
        let line = item.item_id.padEnd(10) +
            item.issue_id.substring(0, 13).padEnd(15) +
            thirdCol +
            statusColor(item.status);
        // Show failure reason for failed items
        if (item.status === 'failed') {
            const reason = item.failure_details?.message || item.failure_reason;
            if (reason) {
                // Truncate to 40 chars for display
                const shortReason = reason.length > 40 ? reason.substring(0, 37) + '...' : reason;
                line += chalk.gray(` [${shortReason}]`);
            }
            // Show retry count if there's failure history
            if (item.failure_history && item.failure_history.length > 0) {
                line += chalk.gray(` (${item.failure_history.length} retry)`);
            }
        }
        console.log(line);
    }
}
/**
 * next - Get next ready task for execution (JSON output)
 * Accepts optional item_id to fetch a specific task directly
 */
async function nextAction(itemId, options) {
    let queue;
    let items;
    // Determine which queue(s) to use
    if (options.queue) {
        // Explicit queue specified
        const targetQueue = readQueue(options.queue);
        if (!targetQueue) {
            console.log(JSON.stringify({ status: 'error', message: `Queue ${options.queue} not found` }));
            return;
        }
        queue = targetQueue;
        items = queue.solutions || queue.tasks || [];
    }
    else {
        // Multi-queue: iterate active queues in priority order (serialized execution)
        const activeQueues = getActiveQueues();
        if (activeQueues.length === 0) {
            console.log(JSON.stringify({ status: 'empty', message: 'No active queues' }));
            return;
        }
        // Find first queue with incomplete items (serialized: complete Q1 before Q2)
        let foundQueue = null;
        for (const q of activeQueues) {
            const queueItems = q.solutions || q.tasks || [];
            const hasIncomplete = queueItems.some(i => i.status === 'pending' || i.status === 'executing');
            if (hasIncomplete) {
                foundQueue = q;
                break;
            }
        }
        if (!foundQueue) {
            console.log(JSON.stringify({ status: 'empty', message: 'All queues completed' }));
            return;
        }
        queue = foundQueue;
        items = queue.solutions || queue.tasks || [];
    }
    let nextItem;
    let isResume = false;
    // If specific item_id provided, fetch that item directly
    if (itemId) {
        nextItem = items.find(t => t.item_id === itemId);
        if (!nextItem) {
            console.log(JSON.stringify({ status: 'error', message: `Item ${itemId} not found in queue ${queue.id}` }));
            return;
        }
        if (nextItem.status === 'completed') {
            console.log(JSON.stringify({ status: 'completed', message: `Item ${itemId} already completed` }));
            return;
        }
        if (nextItem.status === 'failed') {
            console.log(JSON.stringify({ status: 'failed', message: `Item ${itemId} failed, use retry to reset` }));
            return;
        }
        isResume = nextItem.status === 'executing';
    }
    else {
        // Auto-select: Priority 1 - executing, Priority 2 - ready pending
        const executingItems = items.filter(item => item.status === 'executing');
        const pendingItems = items.filter(item => {
            if (item.status !== 'pending')
                return false;
            return (item.depends_on || []).every(depId => {
                const dep = items.find(q => q.item_id === depId);
                return !dep || dep.status === 'completed';
            });
        });
        const readyItems = [...executingItems, ...pendingItems];
        if (readyItems.length === 0) {
            console.log(JSON.stringify({
                status: 'empty',
                message: 'No ready items',
                queue_id: queue.id,
                queue_status: queue._metadata
            }, null, 2));
            return;
        }
        readyItems.sort((a, b) => a.execution_order - b.execution_order);
        nextItem = readyItems[0];
        isResume = nextItem.status === 'executing';
    }
    // Load FULL solution with all tasks
    const solution = findSolution(nextItem.issue_id, nextItem.solution_id);
    if (!solution) {
        console.log(JSON.stringify({ status: 'error', message: 'Solution not found' }));
        process.exit(1);
    }
    // Only update status if not already executing
    if (!isResume) {
        const idx = items.findIndex(q => q.item_id === nextItem.item_id);
        items[idx].status = 'executing';
        items[idx].started_at = new Date().toISOString();
        // Write back to correct array
        if (queue.solutions) {
            queue.solutions = items;
        }
        else {
            queue.tasks = items;
        }
        writeQueue(queue);
        updateIssue(nextItem.issue_id, { status: 'executing' });
    }
    // Calculate queue stats
    const stats = {
        total: items.length,
        completed: items.filter(q => q.status === 'completed').length,
        failed: items.filter(q => q.status === 'failed').length,
        executing: items.filter(q => q.status === 'executing').length,
        pending: items.filter(q => q.status === 'pending').length
    };
    const remaining = stats.pending + stats.executing;
    // Calculate total estimated time for all tasks
    const totalMinutes = solution.tasks?.reduce((sum, t) => sum + (t.estimated_minutes || 30), 0) || 30;
    console.log(JSON.stringify({
        queue_id: queue.id,
        item_id: nextItem.item_id,
        issue_id: nextItem.issue_id,
        solution_id: nextItem.solution_id,
        // Return full solution object with all tasks
        solution: {
            id: solution.id,
            approach: solution.approach,
            tasks: solution.tasks || [],
            exploration_context: solution.exploration_context || {}
        },
        resumed: isResume,
        resume_note: isResume ? `Resuming interrupted item (started: ${nextItem.started_at})` : undefined,
        execution_hints: {
            task_count: solution.tasks?.length || 0,
            estimated_minutes: totalMinutes
        },
        queue_progress: {
            completed: stats.completed,
            remaining: remaining,
            total: stats.total,
            progress: `${stats.completed}/${stats.total}`
        }
    }, null, 2));
}
/**
 * detail - Get task details by item_id (READ-ONLY, does NOT change status)
 * Used for parallel execution: orchestrator gets dag, then dispatches with detail <id>
 */
async function detailAction(itemId, options) {
    if (!itemId) {
        console.log(JSON.stringify({ status: 'error', message: 'item_id is required' }));
        return;
    }
    let queue;
    let queueItem;
    // Use explicit queue if provided, otherwise auto-detect
    if (options.queue) {
        const targetQueue = readQueue(options.queue);
        if (!targetQueue) {
            console.log(JSON.stringify({ status: 'error', message: `Queue ${options.queue} not found` }));
            return;
        }
        queue = targetQueue;
        const items = queue.solutions || queue.tasks || [];
        queueItem = items.find(t => t.item_id === itemId);
    }
    else {
        // Auto-detect queue from item ID
        const found = findItemQueue(itemId);
        if (found) {
            queue = found.queue;
            queueItem = found.item;
        }
        else {
            console.log(JSON.stringify({ status: 'error', message: `Item ${itemId} not found in any queue` }));
            return;
        }
    }
    if (!queueItem) {
        console.log(JSON.stringify({ status: 'error', message: `Item ${itemId} not found` }));
        return;
    }
    // Load FULL solution with all tasks
    const solution = findSolution(queueItem.issue_id, queueItem.solution_id);
    if (!solution) {
        console.log(JSON.stringify({ status: 'error', message: 'Solution not found' }));
        return;
    }
    // Calculate total estimated time for all tasks
    const totalMinutes = solution.tasks?.reduce((sum, t) => sum + (t.estimated_minutes || 30), 0) || 30;
    // Return FULL SOLUTION with all tasks (READ-ONLY - no status update)
    console.log(JSON.stringify({
        queue_id: queue.id,
        item_id: queueItem.item_id,
        issue_id: queueItem.issue_id,
        solution_id: queueItem.solution_id,
        status: queueItem.status,
        // Return full solution object with all tasks
        solution: {
            id: solution.id,
            approach: solution.approach,
            tasks: solution.tasks || [],
            exploration_context: solution.exploration_context || {}
        },
        execution_hints: {
            task_count: solution.tasks?.length || 0,
            estimated_minutes: totalMinutes
        }
    }, null, 2));
}
/**
 * done - Mark task completed or failed
 */
async function doneAction(queueItemId, options) {
    if (!queueItemId) {
        console.error(chalk.red('Item ID is required'));
        console.error(chalk.gray('Usage: ccw issue done <item-id> [--fail] [--reason "..."] [--queue <queue-id>]'));
        process.exit(1);
    }
    let queue;
    let items;
    let idx;
    // Use explicit queue if provided, otherwise auto-detect
    if (options.queue) {
        const targetQueue = readQueue(options.queue);
        if (!targetQueue) {
            console.error(chalk.red(`Queue "${options.queue}" not found`));
            process.exit(1);
        }
        queue = targetQueue;
        items = queue.solutions || queue.tasks || [];
        idx = items.findIndex(q => q.item_id === queueItemId);
        if (idx === -1) {
            console.error(chalk.red(`Queue item "${queueItemId}" not found in queue ${options.queue}`));
            process.exit(1);
        }
    }
    else {
        // Auto-detect queue from item ID
        const found = findItemQueue(queueItemId);
        if (!found) {
            console.error(chalk.red(`Queue item "${queueItemId}" not found in any queue`));
            process.exit(1);
        }
        queue = found.queue;
        items = queue.solutions || queue.tasks || [];
        idx = found.itemIndex;
    }
    const isFail = options.fail;
    items[idx].status = isFail ? 'failed' : 'completed';
    items[idx].completed_at = new Date().toISOString();
    if (isFail) {
        const reason = options.reason || 'Unknown failure';
        items[idx].failure_reason = reason; // Backward compat
        items[idx].failure_details = parseFailureReason(reason); // Structured failure
    }
    else if (options.result) {
        try {
            items[idx].result = JSON.parse(options.result);
        }
        catch {
            console.warn(chalk.yellow('Warning: Could not parse result JSON'));
        }
    }
    // Update issue status (solution = issue in new model)
    const issueId = items[idx].issue_id;
    if (isFail) {
        updateIssue(issueId, { status: 'failed' });
        console.log(chalk.red(`✗ ${queueItemId} failed`));
    }
    else {
        updateIssue(issueId, { status: 'completed', completed_at: new Date().toISOString() });
        console.log(chalk.green(`✓ ${queueItemId} completed`));
        console.log(chalk.green(`✓ Issue ${issueId} completed`));
    }
    // Check if entire queue is complete
    const allQueueComplete = items.every(q => q.status === 'completed');
    const anyQueueFailed = items.some(q => q.status === 'failed');
    if (allQueueComplete) {
        queue.status = 'completed';
        console.log(chalk.green(`\n✓ Queue ${queue.id} completed (all solutions done)`));
    }
    else if (anyQueueFailed && items.every(q => q.status === 'completed' || q.status === 'failed')) {
        queue.status = 'failed';
        console.log(chalk.yellow(`\n⚠ Queue ${queue.id} has failed solutions`));
    }
    // Write back to queue (update the correct array)
    if (queue.solutions) {
        queue.solutions = items;
    }
    else {
        queue.tasks = items;
    }
    writeQueue(queue);
}
/**
 * retry - Reset failed items to pending for re-execution
 * Syncs failure details to Issue.feedback for planning phase
 */
async function retryAction(issueId, options) {
    let queues;
    // Use explicit queue if provided, otherwise use all active queues
    if (options.queue) {
        const targetQueue = readQueue(options.queue);
        if (!targetQueue) {
            console.log(chalk.red(`Queue "${options.queue}" not found`));
            return;
        }
        queues = [targetQueue];
    }
    else {
        queues = getActiveQueues();
    }
    if (queues.length === 0) {
        console.log(chalk.yellow('No active queues'));
        return;
    }
    let totalUpdated = 0;
    const updatedIssues = new Set();
    for (const queue of queues) {
        const items = queue.solutions || queue.tasks || [];
        let queueUpdated = 0;
        for (const item of items) {
            // Retry failed items only
            if (item.status === 'failed') {
                if (!issueId || item.issue_id === issueId) {
                    // Sync failure details to Issue.feedback (persistent for planning phase)
                    if (item.failure_details && item.issue_id) {
                        const issue = findIssue(item.issue_id);
                        if (issue) {
                            if (!issue.feedback) {
                                issue.feedback = [];
                            }
                            // Add failure to feedback history
                            issue.feedback.push({
                                type: 'failure',
                                stage: 'execute',
                                content: JSON.stringify({
                                    solution_id: item.solution_id,
                                    task_id: item.failure_details.task_id,
                                    error_type: item.failure_details.error_type,
                                    message: item.failure_details.message,
                                    stack_trace: item.failure_details.stack_trace,
                                    queue_id: queue.id,
                                    item_id: item.item_id
                                }),
                                created_at: item.failure_details.timestamp
                            });
                            // Keep issue status as 'failed' (or optionally 'pending_replan')
                            // This signals to planning phase that this issue had failures
                            updateIssue(item.issue_id, {
                                status: 'failed',
                                updated_at: new Date().toISOString()
                            });
                            updatedIssues.add(item.issue_id);
                        }
                    }
                    // Preserve failure history before resetting
                    if (item.failure_details) {
                        if (!item.failure_history) {
                            item.failure_history = [];
                        }
                        item.failure_history.push(item.failure_details);
                    }
                    // Reset QueueItem for retry (but Issue status remains 'failed')
                    item.status = 'pending';
                    item.failure_reason = undefined;
                    item.failure_details = undefined;
                    item.started_at = undefined;
                    item.completed_at = undefined;
                    queueUpdated++;
                }
            }
        }
        if (queueUpdated > 0) {
            // Reset queue status if it was failed
            if (queue.status === 'failed') {
                queue.status = 'active';
            }
            // Write back to queue
            if (queue.solutions) {
                queue.solutions = items;
            }
            else {
                queue.tasks = items;
            }
            writeQueue(queue);
            totalUpdated += queueUpdated;
        }
    }
    if (totalUpdated === 0) {
        console.log(chalk.yellow('No failed items to retry'));
        return;
    }
    console.log(chalk.green(`✓ Reset ${totalUpdated} item(s) to pending (failure history preserved)`));
    if (updatedIssues.size > 0) {
        console.log(chalk.cyan(`✓ Synced failure details to ${updatedIssues.size} issue(s) for planning phase`));
    }
}
// ============ Main Entry ============
export async function issueCommand(subcommand, args, options) {
    const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
    switch (subcommand) {
        case 'create':
            await createAction(options);
            break;
        case 'pull':
            await pullAction(options);
            break;
        case 'solution':
            await solutionAction(argsArray[0], options);
            break;
        case 'init':
            await initAction(argsArray[0], options);
            break;
        case 'list':
            await listAction(argsArray[0], options);
            break;
        case 'history':
            await historyAction(options);
            break;
        case 'status':
            await statusAction(argsArray[0], options);
            break;
        case 'task':
            await taskAction(argsArray[0], argsArray[1], options);
            break;
        case 'bind':
            await bindAction(argsArray[0], argsArray[1], options);
            break;
        case 'update':
            await updateAction(argsArray[0], options);
            break;
        case 'queue':
            await queueAction(argsArray[0], argsArray[1], options);
            break;
        case 'next':
            await nextAction(argsArray[0], options);
            break;
        case 'detail':
            await detailAction(argsArray[0], options);
            break;
        case 'done':
            await doneAction(argsArray[0], options);
            break;
        case 'retry':
            await retryAction(argsArray[0], options);
            break;
        // Legacy aliases
        case 'register':
            console.log(chalk.yellow('Deprecated: use "ccw issue bind <issue-id> --solution <path>"'));
            await bindAction(argsArray[0], undefined, options);
            break;
        case 'complete':
            await doneAction(argsArray[0], options);
            break;
        case 'fail':
            await doneAction(argsArray[0], { ...options, fail: true });
            break;
        default:
            console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
            console.log(chalk.bold('Core Commands:'));
            console.log(chalk.gray('  create --data \'{"title":"..."}\'    Create issue (auto-generates ID)'));
            console.log(chalk.gray('  pull [--state open|closed|all]     Pull issues from GitHub'));
            console.log(chalk.gray('       [--limit N] [--labels label1,label2]'));
            console.log(chalk.gray('  init <issue-id>                    Initialize new issue (manual ID)'));
            console.log(chalk.gray('  list [issue-id]                    List issues or tasks'));
            console.log(chalk.gray('  history                            List completed issues (from history)'));
            console.log(chalk.gray('  status [issue-id]                  Show detailed status'));
            console.log(chalk.gray('  solution <id>                      List solutions for issue'));
            console.log(chalk.gray('  solution <id> --brief              Brief: solution_id, files_touched, task_count'));
            console.log(chalk.gray('  solution <id> --data \'{...}\'       Create solution (auto-generates ID)'));
            console.log(chalk.gray('  bind <issue-id> [sol-id]           Bind solution'));
            console.log(chalk.gray('  update <issue-id> --status <s>     Update issue status'));
            console.log(chalk.gray('  update --from-queue [queue-id]     Sync statuses from queue (default: active)'));
            console.log();
            console.log(chalk.bold('Queue Commands:'));
            console.log(chalk.gray('  queue                              Show active queue'));
            console.log(chalk.gray('  queue list                         List all queues (history)'));
            console.log(chalk.gray('  queue add <issue-id>               Add issue to active queue (or create new)'));
            console.log(chalk.gray('  queue switch <queue-id>            Switch active queue'));
            console.log(chalk.gray('  queue activate <q1,q2,...>         Activate multiple queues (comma-separated)'));
            console.log(chalk.gray('  queue priority <queue-id>          Set queue priority (--priority N, lower=higher)'));
            console.log(chalk.gray('  queue dag [--queue <id>]           Get dependency graph (JSON) for parallel execution'));
            console.log(chalk.gray('  queue archive                      Archive current queue'));
            console.log(chalk.gray('  queue delete <queue-id>            Delete queue from history'));
            console.log(chalk.gray('  retry [issue-id] [--queue <id>]    Retry failed tasks'));
            console.log();
            console.log(chalk.bold('Execution Endpoints:'));
            console.log(chalk.gray('  next [item-id] [--queue <id>]      Get & mark task executing (JSON)'));
            console.log(chalk.gray('  detail <item-id> [--queue <id>]    Get task details (READ-ONLY, for parallel)'));
            console.log(chalk.gray('  done <item-id> [--queue <id>]      Mark task completed'));
            console.log(chalk.gray('  done <item-id> --fail --reason "." Mark task failed with reason (supports JSON)'));
            console.log();
            console.log(chalk.bold('Options:'));
            console.log(chalk.gray('  --title <title>                    Issue/task title'));
            console.log(chalk.gray('  --status <status>                  Filter by status (comma-separated)'));
            console.log(chalk.gray('  --brief                            Brief JSON output (minimal fields)'));
            console.log(chalk.gray('  --solution <path>                  Solution JSON file'));
            console.log(chalk.gray('  --result <json>                    Execution result'));
            console.log(chalk.gray('  --reason <text>                    Failure reason (string or JSON)'));
            console.log(chalk.gray('  --queue <queue-id>                 Target queue for multi-queue operations'));
            console.log(chalk.gray('  --priority <n>                     Queue priority (lower = higher)'));
            console.log(chalk.gray('  --json                             JSON output'));
            console.log(chalk.gray('  --force                            Force operation'));
            console.log(chalk.gray('  --state <state>                    GitHub issue state (open/closed/all)'));
            console.log(chalk.gray('  --limit <n>                        Max issues to pull from GitHub'));
            console.log(chalk.gray('  --labels <labels>                  Filter by GitHub labels (comma-separated)'));
            console.log();
            console.log(chalk.bold('Storage:'));
            console.log(chalk.gray('  .workflow/issues/issues.jsonl         Active issues'));
            console.log(chalk.gray('  .workflow/issues/issue-history.jsonl  Completed issues'));
            console.log(chalk.gray('  .workflow/issues/solutions/*.jsonl Solutions per issue'));
            console.log(chalk.gray('  .workflow/issues/queues/           Queue files (multi-queue)'));
            console.log(chalk.gray('  .workflow/issues/queues/index.json Queue index'));
    }
}
//# sourceMappingURL=issue.js.map