#!/usr/bin/env bash
#===============================================================================
# Loki Mode CLI Wrapper
# Command-line interface for Loki Mode
#
# Installation:
#   ln -sf ~/.claude/skills/loki-mode/autonomy/loki /usr/local/bin/loki
#
# Usage:
#   loki start [PRD]      - Start Loki Mode (optionally with PRD)
#   loki stop             - Stop execution immediately
#   loki cleanup          - Kill orphaned processes from crashed sessions
#   loki pause            - Pause after current session
#   loki resume           - Resume paused execution
#   loki status           - Show current status
#   loki dashboard        - Open dashboard in browser
#   loki import           - Import GitHub issues
#   loki github [cmd]     - GitHub integration (sync|export|pr|status)
#   loki help             - Show this help
#===============================================================================

set -euo pipefail

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'

# v7.4.5 (BUG-15 bash route): honor NO_COLOR convention (https://no-color.org/)
# Mirrors the equivalent fix in loki-ts/src/util/colors.ts. Required for parity
# when the shim falls through to the bash route (e.g., when bun is missing).
if [ -n "${NO_COLOR:-}" ]; then
    RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; DIM=''; NC=''
fi

# v7.4.10: opt-in debug tracer. When LOKI_DEBUG is set (any non-empty value)
# every loki_debug call emits "[loki-debug] <ISO-timestamp> <msg>" to stderr.
# When unset it is a true no-op (no syscalls, zero observable output).
loki_debug() {
    if [ -z "${LOKI_DEBUG:-}" ]; then return 0; fi
    local ts
    ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "?")
    printf '[loki-debug] %s %s\n' "$ts" "$*" >&2
}

# Logging functions (portable across bash/zsh)
log_info()  { echo -e "${GREEN}[INFO]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_debug() { echo -e "${CYAN}[DEBUG]${NC} $*"; }

# Source TUI library if available (spinners, progress bars, tables, diffs)
_LOKI_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$_LOKI_SCRIPT_DIR/tui.sh" ]; then
    # shellcheck source=tui.sh
    source "$_LOKI_SCRIPT_DIR/tui.sh"
fi

# Crash-reporting helpers (provides loki_collection_enabled, used by
# cmd_telemetry status and cmd_crash). Self-guarded against double-source.
if [ -f "$_LOKI_SCRIPT_DIR/crash.sh" ]; then
    # shellcheck source=crash.sh
    source "$_LOKI_SCRIPT_DIR/crash.sh"
fi

# Resolve the script's real path (handles symlinks)
resolve_script_path() {
    local script="$1"
    # Use realpath if available, otherwise fall back to readlink
    if command -v realpath &> /dev/null; then
        realpath "$script" 2>/dev/null || echo "$script"
    elif command -v readlink &> /dev/null; then
        # macOS readlink doesn't have -f, use a loop
        while [ -L "$script" ]; do
            local dir=$(dirname "$script")
            script=$(readlink "$script")
            [[ "$script" != /* ]] && script="$dir/$script"
        done
        echo "$script"
    else
        echo "$script"
    fi
}

# Find the skill installation
find_skill_dir() {
    local script_path
    script_path=$(resolve_script_path "$0")
    local script_dir
    script_dir=$(dirname "$script_path")

    # Check script's own parent first (most likely current), then installed skill, then cwd
    local script_parent
    script_parent="$(cd "$script_dir/.." 2>/dev/null && pwd)"
    local dirs=(
        "$script_parent"
        "$HOME/.claude/skills/loki-mode"
        "."
    )

    for dir in "${dirs[@]}"; do
        if [ -f "$dir/SKILL.md" ] && [ -f "$dir/autonomy/run.sh" ]; then
            echo "$dir"
            return 0
        fi
    done

    echo ""
    return 1
}

# Use || true to prevent set -e from exiting before error message
SKILL_DIR=$(find_skill_dir) || true
if [ -z "$SKILL_DIR" ]; then
    # Check if installation exists but is incomplete (missing run.sh)
    _check_dir1="$HOME/.claude/skills/loki-mode"
    _check_dir2="$(dirname "$(resolve_script_path "$0")")/.."
    _incomplete_install=""

    for _dir in "$_check_dir1" "$_check_dir2"; do
        if [ -f "$_dir/SKILL.md" ] && [ ! -f "$_dir/autonomy/run.sh" ]; then
            _incomplete_install="$_dir"
            break
        fi
    done

    if [ -n "$_incomplete_install" ]; then
        echo -e "${RED}Error: Loki Mode installation is incomplete${NC}"
        echo "Missing: $_incomplete_install/autonomy/run.sh"
        echo ""
        echo -e "${YELLOW}Fix with:${NC}"
        echo "  npm uninstall -g loki-mode && npm install -g loki-mode"
        echo ""
        echo "This can happen when npm cache is corrupted."
    else
        echo -e "${RED}Error: Could not find Loki Mode installation${NC}"
        echo "Expected at: ~/.claude/skills/loki-mode"
        echo "Or install via: npm install -g loki-mode"
    fi
    exit 1
fi

RUN_SH="$SKILL_DIR/autonomy/run.sh"
SANDBOX_SH="$SKILL_DIR/autonomy/sandbox.sh"
EMIT_SH="$SKILL_DIR/events/emit.sh"
LEARNING_EMIT_SH="$SKILL_DIR/learning/emit.sh"
LOKI_DIR="${LOKI_DIR:-.loki}"
export LOKI_DIR

# v7.7.34: launch the autonomous runner as a SESSION/PROCESS-GROUP LEADER so the
# whole tree (orchestrator + the claude/codex/aider agent pipeline + monitors)
# shares one process group and can be killed atomically with `kill -- -PGID`.
# Without this, stopping the orchestrator pid orphans the agent child (it
# reparents to init and keeps editing files), which is the "dashboard says
# stopped but it keeps running" bug. macOS has no `setsid` binary, so prefer the
# real binary (Linux/Docker), then perl POSIX::setsid, then python3 os.setsid,
# then degrade to a plain exec (the cwd+sentinel agent sweep is the backstop).
# This execs (replaces the current process); callers use it exactly like `exec`.
#
# IMPORTANT: setsid detaches the new session from the controlling terminal, so a
# foreground INTERACTIVE `loki start` would then ignore Ctrl+C (breaking the
# documented pause/exit-on-Ctrl+C UX). In an interactive shell, job control
# ALREADY places the runner in its own process group, so the agent child shares
# it and group-kill on stop works WITHOUT setsid. We therefore only create a new
# session when stdin is NOT a tty (scripts / CI / detached launches), where the
# runner would otherwise inherit the caller's group and escape group-kill.
# Interactive runs keep Ctrl+C. Opt out entirely with LOKI_NO_NEW_SESSION=1;
# force it on (e.g. for testing) with LOKI_FORCE_NEW_SESSION=1.
_loki_new_session_exec() {
    if [ "${LOKI_NO_NEW_SESSION:-}" = "1" ]; then
        exec "$@"
    elif [ -t 0 ] && [ "${LOKI_FORCE_NEW_SESSION:-}" != "1" ]; then
        # Interactive terminal: keep the controlling tty so Ctrl+C still works.
        # We do NOT setsid here, so the runner may SHARE the interactive shell's
        # process group. Therefore we do NOT export LOKI_OWN_SESSION, and the
        # runner will NOT record loki.pgid -- group-kill is unsafe on a shared
        # shell group (it could kill the user's shell). Stop falls back to the
        # cwd + [LOKI-AUTONOMY-AGENT] sentinel sweep, which is safe and reaps the
        # agent by identity rather than by group.
        exec "$@"
    elif command -v setsid >/dev/null 2>&1; then
        LOKI_OWN_SESSION=1 exec setsid "$@"
    elif command -v perl >/dev/null 2>&1; then
        LOKI_OWN_SESSION=1 exec perl -e 'use POSIX qw(setsid); setsid(); exec @ARGV or exit 127;' "$@"
    elif command -v python3 >/dev/null 2>&1; then
        LOKI_OWN_SESSION=1 exec python3 -c 'import os,sys; os.setsid(); os.execvp(sys.argv[1], sys.argv[1:])' "$@"
    else
        exec "$@"
    fi
}

# Anonymous usage telemetry
PROJECT_DIR="$SKILL_DIR"
_TELEMETRY_SCRIPT="$SKILL_DIR/autonomy/telemetry.sh"
if [ -f "$_TELEMETRY_SCRIPT" ]; then
    source "$_TELEMETRY_SCRIPT"
fi

# Get version from VERSION file
get_version() {
    if [ -f "$SKILL_DIR/VERSION" ]; then
        cat "$SKILL_DIR/VERSION"
    else
        echo "unknown"
    fi
}

# Ensure dashboard Python venv with all deps installed.
# Uses ~/.loki/dashboard-venv (persistent, writable, survives npm/brew upgrades).
# Sets DASHBOARD_PYTHON to the venv python3 path on success.
# Returns 0 on success, 1 on failure.
ensure_dashboard_venv() {
    local dashboard_venv="$HOME/.loki/dashboard-venv"
    DASHBOARD_PYTHON="python3"

    # Use existing venv python if available
    if [ -x "${dashboard_venv}/bin/python3" ]; then
        DASHBOARD_PYTHON="${dashboard_venv}/bin/python3"
    fi

    # Check all required imports
    if "$DASHBOARD_PYTHON" -c "import fastapi; import sqlalchemy; import aiosqlite" 2>/dev/null; then
        return 0
    fi

    echo -e "${YELLOW}Setting up dashboard virtualenv...${NC}"

    # Create venv if missing or broken
    if ! [ -x "${dashboard_venv}/bin/python3" ]; then
        # Remove broken venv if exists
        if [ -d "$dashboard_venv" ]; then
            echo -e "${YELLOW}Removing broken dashboard venv...${NC}"
            rm -rf "$dashboard_venv"
        fi
        mkdir -p "$HOME/.loki"
        python3 -m venv "$dashboard_venv" 2>/dev/null || python3.13 -m venv "$dashboard_venv" 2>/dev/null || {
            echo -e "${RED}Failed to create dashboard virtualenv${NC}"
            echo "You may need to install python3-venv:"
            echo "  sudo apt install python3-venv    (Debian/Ubuntu)"
            echo "  brew install python3              (macOS)"
            return 1
        }
    fi

    DASHBOARD_PYTHON="${dashboard_venv}/bin/python3"
    echo -e "${YELLOW}Installing dashboard dependencies...${NC}"

    # Try pinned requirements first, then unpinned fallback
    local req_file="${SKILL_DIR}/dashboard/requirements.txt"
    local installed=false
    if [ -f "$req_file" ]; then
        if "${dashboard_venv}/bin/pip" install -r "$req_file" 2>&1 | tail -1; then
            installed=true
        else
            echo -e "${YELLOW}Pinned deps failed, trying unpinned...${NC}"
        fi
    fi

    if [ "$installed" = false ]; then
        # Install without greenlet first (it may need a C compiler).
        # SQLAlchemy only requires greenlet for sync-in-async; aiosqlite is pure async.
        if ! "${dashboard_venv}/bin/pip" install fastapi uvicorn pydantic websockets sqlalchemy aiosqlite httpx pexpect watchdog 2>&1 | tail -1; then
            echo -e "${RED}Failed to install dashboard dependencies${NC}"
            echo "Try manually: ${dashboard_venv}/bin/pip install fastapi uvicorn sqlalchemy aiosqlite"
            return 1
        fi
        # Try greenlet separately (optional, needs C compiler on some platforms)
        "${dashboard_venv}/bin/pip" install greenlet 2>/dev/null || true
    fi

    # Verify imports work after install
    if ! "$DASHBOARD_PYTHON" -c "import fastapi; import sqlalchemy; import aiosqlite" 2>/dev/null; then
        echo -e "${RED}Dashboard dependencies installed but imports still fail${NC}"
        echo "Try removing the venv and retrying:"
        echo "  rm -rf ${dashboard_venv}"
        echo "  loki dashboard start"
        return 1
    fi

    return 0
}

# Check if jq is available (called by functions that need it)
require_jq() {
    if ! command -v jq &> /dev/null; then
        echo -e "${RED}Error: jq is required but not installed.${NC}"
        echo "Install with:"
        echo "  brew install jq    (macOS)"
        echo "  apt install jq     (Debian/Ubuntu)"
        echo "  yum install jq     (RHEL/CentOS)"
        return 1
    fi
}

# Emit event (non-blocking)
# Usage: emit_event <type> <source> <action> [key=value ...]
emit_event() {
    if [ -f "$EMIT_SH" ]; then
        # Run in background to be non-blocking
        ("$EMIT_SH" "$@" >/dev/null 2>&1 &)
    fi
}

# Emit learning signal (non-blocking) - SYN-018
# Usage: emit_learning_signal <signal_type> [options]
# Signal types: user_preference, error_pattern, success_pattern, tool_efficiency, workflow_pattern
# See learning/emit.sh for full option documentation
emit_learning_signal() {
    if [ -f "$LEARNING_EMIT_SH" ]; then
        # Run in background to be non-blocking
        (LOKI_DIR="$LOKI_DIR" LOKI_SKILL_DIR="$SKILL_DIR" "$LEARNING_EMIT_SH" "$@" >/dev/null 2>&1 &)
    fi
}

# Track session timing for efficiency signals
SESSION_START_TIME=""
record_session_start() {
    SESSION_START_TIME=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
}

# Calculate session duration in milliseconds
get_session_duration_ms() {
    if [ -n "$SESSION_START_TIME" ]; then
        local end_time
        end_time=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
        echo $((end_time - SESSION_START_TIME))
    else
        echo "0"
    fi
}

# Load memory context at startup (SYN-008)
# Retrieves task-aware memories and writes to .loki/state/memory-context.json
# Note: Uses different file than get_relevant_learnings() in run.sh which writes to relevant-learnings.json
# Usage: load_memory_context [prd_path]
load_memory_context() {
    local prd_path="${1:-}"
    local output_file="$LOKI_DIR/state/memory-context.json"
    local temp_file="$LOKI_DIR/state/.memory-context.json.tmp"

    # Skip if explicitly disabled
    if [[ "${LOKI_SKIP_MEMORY:-}" == "true" ]]; then
        return 0
    fi

    # v7.7.23 privacy opt-out (excellence bar 6): honor a per-project
    # .loki/config.json {"memory": {"disabled": true}} flag. Lets a user
    # disable ALL memory capture/retrieval for a sensitive project without
    # setting an env var on every invocation.
    if [ -f "$LOKI_DIR/config.json" ]; then
        # v7.7.23 council fix (Opus 2): FAIL-CLOSED. A config.json that
        # exists but cannot be parsed prints 'true' (suppress memory) so
        # a JSON typo on a sensitive project does not silently re-enable
        # retrieval. Only the no-config case (outer -f guard false)
        # proceeds with memory on.
        local _mem_disabled
        _mem_disabled=$(python3 -c "
import json, sys
try:
    d = json.load(open('$LOKI_DIR/config.json'))
    print('true' if d.get('memory', {}).get('disabled') is True else 'false')
except Exception:
    print('true')  # malformed config -> fail closed (suppress memory)
" 2>/dev/null || echo "true")
        if [ "$_mem_disabled" = "true" ]; then
            return 0
        fi
    fi

    # Check if python3 is available (required for memory system)
    if ! command -v python3 &> /dev/null; then
        echo -e "${YELLOW}Warning: python3 not found - memory context loading disabled${NC}" >&2
        return 0
    fi

    # Check if memory system is available
    if [ ! -d "$LOKI_DIR/memory" ]; then
        return 0
    fi

    # Ensure state directory exists
    mkdir -p "$LOKI_DIR/state"

    # Extract context from PRD if available (first 500 chars)
    # Use base64 encoding to safely pass PRD content to Python (prevents command injection)
    local prd_context_b64=""
    if [ -n "$prd_path" ] && [ -f "$prd_path" ]; then
        prd_context_b64=$(head -c 500 "$prd_path" 2>/dev/null | base64)
    fi

    # Call Python memory retrieval with safe parameter passing via environment variables
    # Export environment variables for Python script (cleaner than shell variable expansion)
    local result
    _LOKI_SKILL_DIR="$SKILL_DIR" _LOKI_DIR="$LOKI_DIR" _LOKI_PRD_CONTEXT_B64="$prd_context_b64" \
    result=$(python3 << 'PYEOF' 2>/dev/null
import sys
import os
import json
import base64

# Get parameters from environment
skill_dir = os.environ.get('_LOKI_SKILL_DIR', '')
loki_dir = os.environ.get('_LOKI_DIR', '')
prd_context_b64 = os.environ.get('_LOKI_PRD_CONTEXT_B64', '')

# Decode PRD context safely
prd_context = ''
if prd_context_b64:
    try:
        prd_context = base64.b64decode(prd_context_b64).decode('utf-8', errors='replace')
    except Exception:
        prd_context = ''

sys.path.insert(0, skill_dir)
try:
    from memory.retrieval import MemoryRetrieval
    from memory.storage import MemoryStorage

    storage = MemoryStorage(f'{loki_dir}/memory')
    retriever = MemoryRetrieval(storage)

    # Build context for task-aware retrieval
    context = {
        'goal': prd_context if prd_context else 'Analyze codebase and improve',
        'phase': 'startup'
    }

    # Retrieve relevant memories
    results = retriever.retrieve_task_aware(context, top_k=5, token_budget=2000)

    # Format output
    output = {
        'timestamp': __import__('datetime').datetime.now().isoformat(),
        'prd_context': prd_context[:200] if prd_context else None,
        'memory_count': len(results),
        'memories': []
    }

    for r in results:
        memory_item = {
            'source': r.get('_source', 'unknown'),
            'score': round(r.get('_weighted_score', r.get('_score', 0)), 3),
            'summary': r.get('summary', r.get('pattern', r.get('goal', '')))[:200],
            'id': r.get('id', '')
        }
        output['memories'].append(memory_item)

    # v7.7.20: wake the previously-dead cross-project + knowledge-graph
    # code. When local episodic retrieval is sparse (< 5 hits), augment
    # with patterns mined from OTHER projects' .loki/memory/ stores. This
    # is the cross-project learning the diagnosis flagged as dead code
    # (zero call sites). Best-effort: any failure is swallowed and the
    # local results stand alone. Opt out with LOKI_SKIP_CROSS_PROJECT=true.
    output['cross_project'] = []
    if os.environ.get('LOKI_SKIP_CROSS_PROJECT', '').lower() not in ('true', '1', 'yes'):
        try:
            from memory.knowledge_graph import OrganizationKnowledgeGraph
            kg = OrganizationKnowledgeGraph()
            query_text = context.get('goal', '') or 'general'
            patterns = kg.query_patterns(query_text, max_results=3)
            for p in patterns:
                output['cross_project'].append({
                    'source': 'knowledge-graph',
                    'pattern': str(p.get('description', p.get('pattern', p)))[:200]
                              if isinstance(p, dict) else str(p)[:200],
                    'score': round(float(p.get('score', 0.0)), 3) if isinstance(p, dict) else 0.0,
                })
        except Exception:
            pass  # cross-project augmentation is best-effort
    output['cross_project_count'] = len(output['cross_project'])

    print(json.dumps(output, indent=2))
except ImportError:
    print('{"error": "Memory module not available", "memory_count": 0, "memories": []}')
except Exception as e:
    print(json.dumps({"error": str(e), "memory_count": 0, "memories": []}))
PYEOF
    )

    # Write to output file using atomic write (temp file + rename)
    if [ -n "$result" ]; then
        echo "$result" > "$temp_file" && mv "$temp_file" "$output_file"

        # Emit event
        local memory_count=$(echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('memory_count', 0))" 2>/dev/null || echo "0")
        emit_event memory cli load "count=$memory_count" "prd_provided=$([[ -n "$prd_path" ]] && echo 'true' || echo 'false')"

        # Print summary
        if [ "$memory_count" -gt 0 ]; then
            echo -e "${CYAN}Memory:${NC} Loaded $memory_count relevant learnings"
        fi
    else
        # Write empty result using atomic write
        echo '{"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'", "memory_count": 0, "memories": [], "error": "retrieval_failed"}' > "$temp_file" && mv "$temp_file" "$output_file"
    fi

    return 0
}

# Show help
show_help() {
    local version=$(get_version)
    echo -e "${BOLD}Loki Mode v$version${NC}"
    echo ""
    echo "Usage: loki <command> [options]"
    echo ""
    echo "New here? Try one of these first:"
    echo "  loki doctor               Check your setup is ready (instant)"
    echo "  loki quick \"add a health endpoint\"   One small task, start to finish"
    echo "  loki demo                 Build a sample todo app end to end (real run)"
    echo "  loki start ./prd.md       Build from a spec (PRD file, GitHub issue, or no arg)"
    echo "  Docs: https://github.com/asklokesh/loki-mode  |  Report a problem: loki crash"
    echo ""
    echo "Commands:"
    echo "  start [PRD|ISSUE] Start Loki Mode (PRD file, issue ref, or no arg)"
    echo "  run <issue>      (deprecated) Alias for 'loki start <issue-ref>'"
    echo "  quick \"task\"     Quick single-task mode (lightweight, 3 iterations max)"
    echo "  monitor [path]   Monitor Docker Compose services with auto-fix (v6.67.0)"
    echo "  demo             Build a sample todo app end to end (real run)"
    echo "  init [name]      Project scaffolding with 21 PRD templates"
    echo "  issue <url|num>  (deprecated) Use 'loki start <issue-ref>' instead"
    echo "  watch [prd]      Auto-rerun on PRD file changes (v6.33.0)"
    echo "  export <format>  Export session data: json|markdown|csv|timeline (v6.0.0)"
    echo "  stop             Stop execution immediately"
    echo "  cleanup          Kill orphaned processes from crashed sessions"
    echo "  pause            Pause after current session"
    echo "  resume           Resume paused execution"
    echo "  status [--json]  Show current status (--json for machine-readable)"
    echo "  stats [flags]    Session statistics (--json, --efficiency)"
    echo "  kpis [--json]    Accuracy + efficiency KPIs (v7.5.28+) [Phase K]"
    echo "  logs             Show recent log output"
    echo "  dashboard [cmd]  Dashboard server (start|stop|status|url|open)"
    echo "  web [cmd]        Web app UI (start|stop|status) -- serves web-app/dist/"
    echo "  provider [cmd]   Manage AI provider (show|set|list|info)"
    echo "  serve            Start dashboard/API server (alias for api start)"
    echo "  api [cmd]        Dashboard/API server (start|stop|status)"
    echo "  sandbox [cmd]    Docker sandbox (start|stop|status|logs|shell|build)"
    echo "  notify [cmd]     Send notifications (test|slack|discord|webhook|status)"
    echo "  telemetry [cmd]  OpenTelemetry management (status|enable|disable|stop|start)"
    echo "  import           Import GitHub issues as tasks"
    echo "  github [cmd]     GitHub integration (sync|export|pr|status)"
    echo "  config [cmd]     Manage configuration (show|init|edit|path|set|get)"
    echo "  completions [bash|zsh]  Output shell completion scripts"
    echo "  memory [cmd]     Cross-project learnings (list|show|search|stats)"
    echo "  compound [cmd]   Knowledge compounding (list|show|search|run|stats)"
    echo "  council [cmd]    Completion council (status|verdicts|convergence|force-review|report)"
    echo "  checkpoint|cp    Save/restore session checkpoints"
    echo "  projects         Multi-project registry management"
    echo "  audit [cmd]      Agent audit log and quality scanning (log|scan)"
    echo "  heal <path>      Legacy system healing (archaeology, stabilize, modernize)"
    echo "  review [opts]    Standalone code review with quality gates (diff, staged, PR, files)"
    echo "  optimize         Optimize prompts based on session history"
    echo "  enterprise       Enterprise feature management (tokens, OIDC)"
    echo "  metrics [opts]   Session productivity report (--json, --last N, --save, --share)"
    echo "  cost [opts]      Transparent cost view: per-run/project spend + budget (--json, --last N)"
    echo "  trust [--json]   Visible trust trajectory: council/gate pass-rate + interventions over runs [R4]"
    echo "  dogfood          Show self-development statistics"
    echo "  secrets [cmd]    API key status and validation (status|validate)"
    echo "  reset [target]   Reset session state (all|retries|failed)"
    echo "  doctor [--json]  Check system prerequisites and skill symlinks"
    echo "  setup-skill      Create skill symlinks for all providers"
    echo "  self-update      Upgrade loki via current manager (use --to bun|npm|brew to switch)"
    echo "  watchdog [cmd]   Process health monitoring (status|help)"
    echo "  worktree [cmd]   Parallel worktree management (list|merge|clean|status)"
    echo "  agent [cmd]      Agent type dispatch (list|info|run|start|review)"
    echo "  remote [PRD]     Start remote session (connect from phone/browser, Claude Pro/Max)"
  echo "  trigger          Event-driven autonomous execution (schedules, webhooks)"
    echo "  failover [cmd]   Cross-provider auto-failover (status|--enable|--test|--chain)"
    echo "  onboard [path]   Analyze a repo and generate CLAUDE.md (structure, conventions, commands)"
    echo '  explain [path]   Analyze any codebase and explain its architecture in plain English'
    echo "  docs [cmd]       Generate, update, check project documentation"
    echo "  magic [cmd]      Spec-driven component generation (MagicModules + MoMoA)"
    echo "  plan <PRD>       Dry-run PRD analysis: complexity, cost, and execution plan"
    echo "  ci [opts]        CI/CD quality gate integration (--pr, --report, --github-comment)"
    echo "  test [opts]      AI-powered test generation (--file, --dir, --changed, --dry-run)"
    echo "  context [cmd]    Context window management (show|files|tools|add|clear)"
    echo "  code [cmd]       Codebase intelligence (overview|symbols|deps|hotspots|diff|search)"
    echo "  report [opts]    Session report generator (--format text|markdown|html, --output)"
    echo "  share [opts]     Share session report as GitHub Gist (--private, --format)"
    echo "  proof [cmd]      Inspect/share proof-of-run artifacts (list|show|open|share)"
    echo "  bench [cmd]      Head-to-head benchmark harness (run|vs|list|verify|report)"
    echo "  version          Show version"
    echo "  help             Show this help"
    echo ""
    echo "Options for 'start':"
    echo "  --provider NAME  AI provider: claude (default), codex, cline, aider"
    echo "  --parallel       Enable parallel mode with git worktrees"
    echo "  --bg, --background  Run in background mode"
    echo "  --simple         Force simple complexity tier (3 phases)"
    echo "  --complex        Force complex complexity tier (8 phases)"
    echo "  --github         Enable GitHub issue import"
    echo "  --no-dashboard   Disable web dashboard"
    echo "  --sandbox        Run in Docker sandbox for isolation"
    echo "  --skip-memory    Skip loading memory context at startup"
    echo "  --compliance PRESET  Enable compliance mode (default|healthcare|fintech|government)"
    echo "  --budget USD     Set cost budget limit (display in dashboard/status)"
    echo "  --bmad-project PATH  Use BMAD Method project artifacts as input"
    echo "  --openspec PATH      Use OpenSpec change directory as input"
    echo ""
    echo "Options for 'run' (v6.0.0):"
    echo "  --dry-run          Preview generated PRD without starting"
    echo "  --no-start         Generate PRD but don't start execution"
    echo "  --output FILE      Save PRD to custom path"
    echo "  --provider NAME    AI provider: claude (default), codex, cline, aider"
    echo "  --parallel         Enable parallel mode with git worktrees"
    echo "  --budget USD       Set cost budget limit"
    echo ""
    echo "Progressive Isolation (for 'run'):"
    echo "  --worktree, -w     Git worktree isolation (separate branch)"
    echo "  --pr               Auto-create PR after completion (implies --worktree)"
    echo "  --ship             Auto-merge after PR (implies --pr)"
    echo "  --detach, -d       Run in background (implies --worktree)"
    echo ""
    echo "Examples:"
    echo ""
    echo "  # Starting a session"
    echo "  loki start ./prd.md           # Start with PRD file"
    echo "  loki start owner/repo#123     # GitHub issue (auto-fetches)"
    echo "  loki start PROJ-456           # Jira issue"
    echo "  loki start --bg ./prd.md      # Start in background"
    echo "  loki start --parallel ./prd.md   # Parallel mode (git worktrees)"
    echo "  loki quick \"add dark mode\"  # Single-task mode (3 iters max)"
    echo "  loki demo                     # Build a sample todo app end to end"
    echo "  loki init -t saas-starter     # Scaffold from template"
    echo "  loki template install <src>   # Install a community PRD template"
    echo ""
    echo "  # Session ops + observability"
    echo "  loki status [--json]          # Current status"
    echo "  loki stats --efficiency       # Token + cost stats"
    echo "  loki kpis [--json]            # Accuracy + efficiency KPI snapshot"
    echo "  loki doctor [--json]          # System prereq + skill symlinks"
    echo "  loki logs                     # Tail recent log output"
    echo "  loki export json|markdown|csv|timeline   # Export session"
    echo "  loki assets export team.tgz   # Export shareable team assets (redacted)"
    echo "  loki cleanup                  # Kill orphaned processes"
    echo ""
    echo "  # Providers + model routing"
    echo "  loki provider list            # Show 4 providers (claude/codex/cline/aider)"
    echo "  loki provider set codex       # Switch active provider"
    echo "  # OpenRouter / Ollama routing (Phase I v7.5.25+):"
    echo "  export ANTHROPIC_BASE_URL=https://openrouter.ai/api/v1 \\\\"
    echo "         LOKI_MODEL_OVERRIDE=anthropic/claude-sonnet-4.5"
    echo "  loki start ./prd.md           # Routes to OpenRouter via Claude Code"
    echo ""
    echo "  # Cross-project context (Phase F v7.5.23+)"
    echo "  # Drop .loki/app.json with {schema_version:1,app_id:myapp,members:[ui,api]}"
    echo "  # at the parent of related repos for shared CLAUDE.md + memory."
    echo ""
    echo "  # Memory + learnings"
    echo "  loki memory list              # All learnings"
    echo "  loki memory search <query>    # Search across learnings"
    echo "  loki memory consolidate       # Run episodic-to-semantic pipeline"
    echo ""
    echo "  # Config + dashboard"
    echo "  loki config set maxTier sonnet   # Cap model cost"
    echo "  loki dashboard start          # Web dashboard at localhost:57374"
    echo "  loki watch                    # Auto-rerun on PRD changes"
    echo "  loki remote                   # Remote session (phone/browser)"
    echo ""
    echo "Phase A-J features (v7.5.18 - v7.5.28) are default-on. See CHANGELOG."
    echo ""
    echo "Environment Variables:"
    echo "  Opt-outs: LOKI_HOOK_EVENTS=off, LOKI_DYNAMIC_PROMPT_SECTIONS=keep,"
    echo "  LOKI_MEMORY_BASE_PATH (shared memory dir for app graph)"
    echo "  See: $RUN_SH (header comments) for full list."
}

# Detect argument type for unified `loki start` (v6.84.0)
# Returns one of: prd | issue | empty | unknown
# Logic:
#   - empty arg -> "empty"
#   - file path ending in .md/.json/.txt AND exists on disk -> "prd"
#   - file path ending in .md/.json/.txt (doesn't exist) -> "prd" (let downstream handle)
#   - URL matching github.com/gitlab.com/atlassian.net/dev.azure.com/visualstudio.com issues -> "issue"
#   - PROJ-123 style Jira key -> "issue"
#   - owner/repo#123 -> "issue"
#   - #NUM or bare NUM -> "issue" (GitHub issue in current repo)
#   - Anything else that looks like a filesystem path -> "prd"
#   - Fallback -> "unknown" (caller treats as PRD path for back-compat)
detect_arg_type() {
    local arg="${1:-}"

    # Empty arg
    if [ -z "$arg" ]; then
        echo "empty"
        return 0
    fi

    # Known PRD extensions - treat as PRD first (highest priority).
    # This handles edge case where a file is literally named "123.md" or
    # sits in a dir that matches another pattern.
    case "$arg" in
        *.md|*.json|*.txt|*.yaml|*.yml)
            echo "prd"
            return 0
            ;;
    esac

    # Issue URLs -- order matters: check explicit tracker hosts first
    if [[ "$arg" =~ github\.com/[^/]+/[^/]+/issues/[0-9]+ ]]; then
        echo "issue"
        return 0
    fi
    if [[ "$arg" =~ gitlab\.com/.+/-/issues/[0-9]+ ]] || [[ "$arg" =~ gitlab\..+/-/issues/[0-9]+ ]]; then
        echo "issue"
        return 0
    fi
    if [[ "$arg" =~ \.atlassian\.net/browse/[A-Z]+-[0-9]+ ]] || [[ "$arg" =~ jira\..+/browse/[A-Z]+-[0-9]+ ]]; then
        echo "issue"
        return 0
    fi
    if [[ "$arg" =~ dev\.azure\.com/.+/_workitems/edit/[0-9]+ ]] || [[ "$arg" =~ visualstudio\.com/.+/_workitems/edit/[0-9]+ ]]; then
        echo "issue"
        return 0
    fi

    # owner/repo#123
    if [[ "$arg" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+$ ]]; then
        echo "issue"
        return 0
    fi

    # Jira project key: PROJ-123
    if [[ "$arg" =~ ^[A-Z][A-Z0-9]+-[0-9]+$ ]]; then
        echo "issue"
        return 0
    fi

    # #123 or bare number -- treat as GitHub issue in current repo,
    # BUT only if a file with that literal name does NOT exist on disk
    # (ambiguity resolution: files-on-disk win over issue-number heuristic).
    if [[ "$arg" =~ ^#?[0-9]+$ ]]; then
        if [ -e "$arg" ]; then
            # File on disk wins -- treat as PRD
            echo "prd"
        else
            echo "issue"
        fi
        return 0
    fi

    # Path-like (contains / or starts with . or /) -> treat as PRD
    if [[ "$arg" == */* ]] || [[ "$arg" == .* ]] || [[ "$arg" == /* ]]; then
        echo "prd"
        return 0
    fi

    # Existing file on disk -> PRD
    if [ -e "$arg" ]; then
        echo "prd"
        return 0
    fi

    # R7 (zero-config first run): a one-line brief. An arg that contains
    # whitespace and matched none of the file/issue/path patterns above is a
    # natural-language brief (e.g. "build a todo app"), NOT a PRD path. This is
    # additive: a single-token arg with no whitespace still falls through to
    # "unknown" (PRD-path back-compat) below. The `--brief` flag is the
    # deterministic escape hatch for the rare single-word brief.
    if [[ "$arg" == *[[:space:]]* ]]; then
        echo "brief"
        return 0
    fi

    # Fallback: unknown. Caller treats as PRD for backward compat.
    echo "unknown"
}

# Start Loki Mode
cmd_start() {
    # First-run welcome (once): open the branded welcome page on the very
    # first start. Writes ~/.loki/.welcomed and never repeats; non-blocking,
    # and never auto-opens a browser in CI or non-interactive shells.
    cmd_welcome_maybe_firstrun 2>/dev/null || true
    local args=()
    local prd_file=""
    local provider=""
    local brief_text=""           # R7: explicit one-line brief (--brief "...")
    local bmad_project_path=""
    local openspec_change_path=""
    local mirofish_url=""
    local mirofish_docker_image=""
    local mirofish_bg=false
    local mirofish_disabled=false
    local no_plan=false  # v6.81.1: --no-plan opts out of auto-plan display

    # v6.84.0: unified entry point -- explicit mode overrides + issue-mode args
    local explicit_mode=""            # "prd" | "issue" | "" (auto-detect)
    local positional_arg=""           # raw positional arg for mode detection
    local issue_ref_explicit=""       # --issue value (when explicit_mode=issue)
    local issue_mode_args=()          # captured issue-mode flags (--dry-run, --pr, etc.)
    local issue_dry_run=false
    local issue_no_start=false
    local issue_output_file=""
    local issue_use_worktree=false
    local issue_create_pr=false
    local issue_auto_merge=false
    local issue_run_detached=false

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki start${NC} - Unified entry point for Loki Mode (v6.84.0)"
                echo ""
                echo "Usage: loki start [PRD|ISSUE-REF] [options]"
                echo ""
                echo "Modes (auto-detected from input):"
                echo "  PRD mode    - path ending in .md/.json/.txt/.yaml -> build from PRD"
                echo "  ISSUE mode  - GitHub/GitLab/Jira/Azure DevOps URL, owner/repo#N,"
                echo "                PROJ-123, #123, or bare number -> generate PRD from issue"
                echo "  BRIEF mode  - a quoted one-line description (with spaces) -> fast"
                echo "                zero-config first run; visible artifact + proof quickly"
                echo "  no input    - analyze current directory, auto-generate PRD"
                echo ""
                echo "Arguments:"
                echo "  PRD                   Path to PRD file (.md/.json/.txt/.yaml)"
                echo "  ISSUE-REF             Issue URL, number, or tracker key (see above)"
                echo ""
                echo "Explicit mode flags (override auto-detection):"
                echo "  --prd FILE            Force PRD mode with FILE"
                echo "  --issue URL|NUM       Force issue mode with URL or number"
                echo "  --brief \"TEXT\"        Force zero-config brief mode (fast first run)"
                echo ""
                echo "Options:"
                echo "  --provider NAME       AI provider: claude (default), codex, cline, aider"
                echo "  --parallel            Enable parallel mode with git worktrees"
                echo "  --bg, --background    Run in background mode"
                echo "  --simple              Force simple complexity tier (3 phases)"
                echo "  --complex             Force complex complexity tier (8 phases)"
                echo "  --github              Enable GitHub issue import"
                echo "  --no-dashboard        Disable web dashboard"
                echo "  --api                 Start dashboard API server alongside the build"
                echo "  --sandbox             Run in Docker sandbox"
                echo "  --skip-memory         Skip loading memory context at startup"
                echo "  --compliance PRESET   Enable compliance mode (default|healthcare|fintech|government)"
                echo "  --budget USD          Cost budget limit (auto-pause when exceeded)"
                echo "  --bmad-project PATH   Use BMAD Method project artifacts as input"
                echo "  --openspec PATH       Use OpenSpec change directory as input"
                echo "  --mirofish [URL]      Enable MiroFish market validation (default: localhost:5001)"
                echo "  --mirofish-docker IMG Auto-start MiroFish from Docker image"
                echo "  --mirofish-rounds N   Simulation rounds (default: 100)"
                echo "  --mirofish-timeout S  Max pipeline wait in seconds (default: 3600)"
                echo "  --mirofish-bg         Run MiroFish pipeline in background"
                echo "  --no-mirofish         Disable MiroFish even if env var is set"
                echo "  --no-plan             Skip auto-shown PRD analysis at startup"
                echo "  --brief \"TEXT\"        Zero-config fast first run from a one-line brief"
                echo "  --yes, -y             Skip confirmation prompts (auto-confirm)"
                echo ""
                echo "Issue-mode options (only used when input is an ISSUE-REF):"
                echo "  --dry-run             Preview generated PRD without starting"
                echo "  --no-start            Generate PRD but don't start execution"
                echo "  --output FILE         Save generated PRD to custom path"
                echo "  --worktree, -w        Git worktree isolation (separate branch)"
                echo "  --pr                  Worktree + auto-create PR"
                echo "  --ship                Worktree + PR + auto-merge"
                echo "  --detach, -d          Run in background"
                echo ""
                echo "Environment Variables:"
                echo "  LOKI_PRD_FILE         Path to PRD file (alternative to positional arg)"
                echo "  LOKI_AUTO_CONFIRM     Set to 'true'/'false' to control prompts (takes precedence over CI)"
                echo "  CI                    Fallback: auto-confirms when 'true' and LOKI_AUTO_CONFIRM is unset"
                echo "  LOKI_MAX_ITERATIONS   Max iteration count"
                echo "  LOKI_BUDGET_LIMIT     Cost budget limit in USD"
                echo ""
                echo "Examples:"
                echo "  loki start                              # Interactive, analyze current dir"
                echo "  loki start \"build a todo app\"           # BRIEF mode (zero-config fast run)"
                echo "  loki start --brief \"snake\"              # BRIEF mode (single-word escape hatch)"
                echo "  loki start ./prd.md                     # PRD mode"
                echo "  loki start https://github.com/o/r/issues/42  # ISSUE mode (GitHub)"
                echo "  loki start 123                          # ISSUE mode (current repo GitHub issue)"
                echo "  loki start PROJ-456                     # ISSUE mode (Jira)"
                echo "  loki start owner/repo#789               # ISSUE mode (GitHub specific repo)"
                echo "  loki start --prd ./prd.md --parallel    # Explicit PRD, parallel mode"
                echo "  loki start --issue 123 --ship           # Explicit issue, full automation"
                echo "  loki start --provider codex             # Use OpenAI Codex CLI"
                echo "  loki start --bmad-project ./my-project  # Start from BMAD artifacts"
                echo "  loki start --openspec ./openspec/changes/my-feature  # From OpenSpec change"
                echo "  loki start --yes                        # Skip confirmation prompt"
                echo "  LOKI_PRD_FILE=./prd.md loki start       # PRD via env var"
                echo ""
                echo "Note: 'loki run' is a deprecated alias for 'loki start' with an issue ref."
                exit 0
                ;;
            --provider)
                if [[ -n "${2:-}" ]]; then
                    if [[ "$2" == --* ]]; then
                        echo -e "${RED}Missing value for --provider flag${NC}"
                        exit 1
                    fi
                    provider="$2"
                    args+=("--provider" "$provider")
                    shift 2
                else
                    echo -e "${RED}--provider requires a value (claude, codex, cline, aider)${NC}"
                    exit 1
                fi
                ;;
            --provider=*)
                provider="${1#*=}"
                args+=("--provider" "$provider")
                shift
                ;;
            --aider-model)
                if [[ -n "${2:-}" ]]; then
                    export LOKI_AIDER_MODEL="$2"
                    shift 2
                else
                    echo -e "${RED}--aider-model requires a value${NC}"
                    exit 1
                fi
                ;;
            --aider-model=*)
                export LOKI_AIDER_MODEL="${1#*=}"
                shift
                ;;
            --aider-flags)
                if [[ -n "${2:-}" ]]; then
                    export LOKI_AIDER_FLAGS="$2"
                    shift 2
                else
                    echo -e "${RED}--aider-flags requires a value${NC}"
                    exit 1
                fi
                ;;
            --aider-flags=*)
                export LOKI_AIDER_FLAGS="${1#*=}"
                shift
                ;;
            --cline-model)
                if [[ -n "${2:-}" ]]; then
                    export LOKI_CLINE_MODEL="$2"
                    shift 2
                else
                    echo -e "${RED}--cline-model requires a value${NC}"
                    exit 1
                fi
                ;;
            --cline-model=*)
                export LOKI_CLINE_MODEL="${1#*=}"
                shift
                ;;
            --parallel)
                args+=("--parallel")
                shift
                ;;
            --regen-prd|--regenerate-prd|--regen)
                # v7.8.1: force a fresh generated PRD on a no-PRD run, overriding
                # the staleness-aware reuse (decide_generated_prd_action in
                # run.sh reads LOKI_PRD_REGEN). Exported so the runner sees it.
                export LOKI_PRD_REGEN=1
                shift
                ;;
            --bg|--background)
                args+=("--bg")
                shift
                ;;
            --simple)
                export LOKI_COMPLEXITY=simple
                shift
                ;;
            --complex)
                export LOKI_COMPLEXITY=complex
                shift
                ;;
            --github)
                export LOKI_GITHUB_IMPORT=true
                shift
                ;;
            --no-dashboard)
                export LOKI_DASHBOARD=false
                shift
                ;;
            --api)
                export LOKI_START_API=true
                shift
                ;;
            --sandbox)
                export LOKI_SANDBOX_MODE=true
                shift
                ;;
            --skip-memory)
                export LOKI_SKIP_MEMORY=true
                shift
                ;;
            --compliance)
                if [[ -n "${2:-}" ]]; then
                    export LOKI_COMPLIANCE_PRESET="$2"
                    echo -e "${CYAN}Compliance mode: $2${NC}"
                    shift 2
                else
                    echo -e "${RED}--compliance requires a preset name (default, healthcare, fintech, government)${NC}"
                    exit 1
                fi
                ;;
            --compliance=*)
                local compliance_val="${1#*=}"
                export LOKI_COMPLIANCE_PRESET="$compliance_val"
                echo -e "${CYAN}Compliance mode: $compliance_val${NC}"
                shift
                ;;
            --yes|-y)
                export LOKI_AUTO_CONFIRM=true
                shift
                ;;
            --bmad-project)
                if [[ -n "${2:-}" ]]; then
                    bmad_project_path="$2"
                    shift 2
                else
                    echo -e "${RED}--bmad-project requires a path to a BMAD project directory${NC}"
                    exit 1
                fi
                ;;
            --bmad-project=*)
                bmad_project_path="${1#*=}"
                shift
                ;;
            --openspec)
                if [[ -n "${2:-}" ]]; then
                    openspec_change_path="$2"
                    shift 2
                else
                    echo -e "${RED}--openspec requires a path to an OpenSpec change directory${NC}"
                    exit 1
                fi
                ;;
            --openspec=*)
                openspec_change_path="${1#*=}"
                shift
                ;;
            --mirofish)
                if [[ -n "${2:-}" ]] && [[ "${2:-}" != --* ]]; then
                    mirofish_url="$2"
                    shift 2
                else
                    mirofish_url="http://localhost:5001"
                    shift
                fi
                ;;
            --mirofish=*)
                mirofish_url="${1#*=}"
                shift
                ;;
            --mirofish-docker)
                if [[ -n "${2:-}" ]]; then
                    mirofish_docker_image="$2"
                    mirofish_url="${mirofish_url:-http://localhost:5001}"
                    shift 2
                else
                    echo -e "${RED}--mirofish-docker requires a Docker image name${NC}"
                    exit 1
                fi
                ;;
            --mirofish-docker=*)
                mirofish_docker_image="${1#*=}"
                mirofish_url="${mirofish_url:-http://localhost:5001}"
                shift
                ;;
            --mirofish-rounds)
                if [[ -n "${2:-}" ]]; then
                    export LOKI_MIROFISH_ROUNDS="$2"
                    shift 2
                else
                    echo -e "${RED}--mirofish-rounds requires a number${NC}"
                    exit 1
                fi
                ;;
            --mirofish-rounds=*)
                export LOKI_MIROFISH_ROUNDS="${1#*=}"
                shift
                ;;
            --mirofish-timeout)
                if [[ -n "${2:-}" ]]; then
                    export LOKI_MIROFISH_TIMEOUT="$2"
                    shift 2
                else
                    echo -e "${RED}--mirofish-timeout requires seconds${NC}"
                    exit 1
                fi
                ;;
            --mirofish-timeout=*)
                export LOKI_MIROFISH_TIMEOUT="${1#*=}"
                shift
                ;;
            --mirofish-bg)
                mirofish_bg=true
                shift
                ;;
            --no-mirofish)
                mirofish_disabled=true
                shift
                ;;
            --no-plan)
                no_plan=true
                shift
                ;;
            --brief)
                # R7: explicit one-line brief (escape hatch for single-word
                # briefs that detect_arg_type would otherwise treat as a PRD
                # path). Forces the zero-config first-run brief sub-path.
                if [[ -n "${2:-}" ]] && [[ "${2:-}" != --* ]]; then
                    brief_text="$2"
                    shift 2
                else
                    echo -e "${RED}--brief requires a one-line description (e.g., --brief \"build a todo app\")${NC}"
                    exit 1
                fi
                ;;
            --brief=*)
                brief_text="${1#*=}"
                shift
                ;;
            --budget)
                if [[ -n "${2:-}" ]]; then
                    if ! echo "$2" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
                        echo -e "${RED}--budget requires a numeric USD amount (e.g., --budget 5.00)${NC}"
                        exit 1
                    fi
                    export LOKI_BUDGET_LIMIT="$2"
                    shift 2
                else
                    echo -e "${RED}--budget requires a USD amount (e.g., --budget 5.00)${NC}"
                    exit 1
                fi
                ;;
            --budget=*)
                local budget_val="${1#*=}"
                if ! echo "$budget_val" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
                    echo -e "${RED}--budget requires a numeric USD amount (e.g., --budget=5.00)${NC}"
                    exit 1
                fi
                export LOKI_BUDGET_LIMIT="$budget_val"
                shift
                ;;
            # v6.84.0: explicit mode flags (override auto-detection)
            --prd)
                if [[ -n "${2:-}" ]] && [[ "${2:-}" != --* ]]; then
                    explicit_mode="prd"
                    prd_file="$2"
                    shift 2
                else
                    echo -e "${RED}--prd requires a file path${NC}"
                    exit 1
                fi
                ;;
            --prd=*)
                explicit_mode="prd"
                prd_file="${1#*=}"
                shift
                ;;
            --issue)
                if [[ -n "${2:-}" ]] && [[ "${2:-}" != --* ]]; then
                    explicit_mode="issue"
                    issue_ref_explicit="$2"
                    shift 2
                else
                    echo -e "${RED}--issue requires a URL, number, or issue key${NC}"
                    exit 1
                fi
                ;;
            --issue=*)
                explicit_mode="issue"
                issue_ref_explicit="${1#*=}"
                shift
                ;;
            # v6.84.0: issue-mode flags (only meaningful when input is an issue)
            --dry-run)
                issue_dry_run=true
                issue_mode_args+=("--dry-run")
                shift
                ;;
            --no-start)
                issue_no_start=true
                issue_mode_args+=("--no-start")
                shift
                ;;
            --output)
                if [[ -n "${2:-}" ]]; then
                    issue_output_file="$2"
                    issue_mode_args+=("--output" "$2")
                    shift 2
                else
                    echo -e "${RED}--output requires a file path${NC}"
                    exit 1
                fi
                ;;
            --output=*)
                issue_output_file="${1#*=}"
                issue_mode_args+=("--output=${1#*=}")
                shift
                ;;
            --worktree|-w)
                issue_use_worktree=true
                issue_mode_args+=("--worktree")
                shift
                ;;
            --pr)
                issue_use_worktree=true
                issue_create_pr=true
                issue_mode_args+=("--pr")
                shift
                ;;
            --ship)
                issue_use_worktree=true
                issue_create_pr=true
                issue_auto_merge=true
                issue_mode_args+=("--ship")
                shift
                ;;
            --detach|-d)
                issue_use_worktree=true
                issue_run_detached=true
                issue_mode_args+=("--detach")
                shift
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                exit 1
                ;;
            *)
                # Capture first positional arg for unified mode detection.
                # Additional positional args are ignored (same as prior behavior).
                if [ -z "$positional_arg" ]; then
                    positional_arg="$1"
                fi
                shift
                ;;
        esac
    done

    # Clear any stale raw-brief marker from a PRIOR run before we decide the
    # mode of THIS run. Only the brief branch (below) re-writes it. Without this,
    # a brief run leaves .loki/state/brief.txt behind and a later non-brief run
    # (PRD or codebase-analysis) would have its proof mislabel source="brief"
    # with the old one-liner, since proof-generator reads brief.txt first.
    rm -f "$LOKI_DIR/state/brief.txt" 2>/dev/null || true

    # v6.84.0: Unified dispatch based on explicit flags or auto-detection
    # Precedence: --brief > --issue > --prd > positional auto-detect >
    #             LOKI_PRD_FILE env
    local detected_type=""
    if [ -n "$brief_text" ]; then
        # R7: explicit --brief always forces the zero-config brief sub-path.
        detected_type="brief"
    elif [ -n "$explicit_mode" ]; then
        detected_type="$explicit_mode"
    elif [ -n "$positional_arg" ]; then
        detected_type=$(detect_arg_type "$positional_arg")
        # R7: a positional one-line brief (whitespace, not a file/issue) maps to
        # the brief sub-path; capture the text from the positional arg.
        if [ "$detected_type" = "brief" ]; then
            brief_text="$positional_arg"
        fi
    elif [ -n "${LOKI_PRD_FILE:-}" ]; then
        detected_type="prd"
        prd_file="$LOKI_PRD_FILE"
    else
        detected_type="empty"
    fi

    # ISSUE mode: delegate to cmd_run's code path. To avoid duplicating ~450
    # lines of issue-fetching + PR-creation logic, we re-dispatch through
    # cmd_run with reconstructed args. cmd_run then calls cmd_start again
    # with the generated PRD file and --no-plan (see line ~4431).
    if [ "$detected_type" = "issue" ]; then
        local issue_ref
        if [ "$explicit_mode" = "issue" ]; then
            issue_ref="$issue_ref_explicit"
        else
            issue_ref="$positional_arg"
        fi
        # Reconstruct args for cmd_run: issue ref + all options we captured.
        # args[] already has --provider, --parallel, --bg, etc.
        local run_args=("$issue_ref")
        # Pass through issue-mode flags
        for a in "${issue_mode_args[@]+"${issue_mode_args[@]}"}"; do
            run_args+=("$a")
        done
        # Pass through generic options that cmd_run understands
        for a in "${args[@]+"${args[@]}"}"; do
            run_args+=("$a")
        done
        # Pass through --no-plan if set
        if [ "$no_plan" = "true" ]; then
            run_args+=("--no-plan")
        fi
        # Mark this invocation as unified (so cmd_run skips deprecation notice)
        export LOKI_UNIFIED_START=1
        cmd_run "${run_args[@]}"
        return $?
    fi

    # R7 (zero-config first run): BRIEF mode. The user gave a one-line brief
    # (`loki start "build a todo app"` or `loki start --brief "..."`). Synthesize
    # a forward-looking PRD, switch to the lightweight TTFV profile (honest fast:
    # capped iterations, council off, simple tier, heavy phases off), and mark
    # LOKI_TTFV=1 so run.sh prints the "what next / go deeper" framing at the
    # end. Then fall through to the normal flow, which appends prd_file to args
    # and execs run.sh. This is additive -- nothing here changes the PRD/issue
    # paths above.
    if [ "$detected_type" = "brief" ]; then
        local version
        version=$(get_version)
        local _ttfv_max_iter="${LOKI_MAX_ITERATIONS:-3}"
        mkdir -p "$LOKI_DIR" 2>/dev/null || true
        local brief_prd="$LOKI_DIR/brief-prd-$$.md"
        synthesize_brief_prd "$brief_prd" "$brief_text"
        prd_file="$brief_prd"

        # Persist the raw one-liner so the proof-of-run can show the actual brief
        # the user typed ("Built and verified from: <brief>") instead of falling
        # back to "codebase-analysis / No brief recorded". The synthesized PRD is
        # kept distinct (brief-prd-$$.md); the raw brief is the stronger, more
        # honest shareable artifact. proof-generator.py::_collect_spec reads this
        # as a fallback when no PRD_PATH / generated-prd.md is present.
        mkdir -p "$LOKI_DIR/state" 2>/dev/null || true
        printf '%s' "$brief_text" > "$LOKI_DIR/state/brief.txt" 2>/dev/null || true

        # Apply the shared lightweight profile and flag the TTFV first-run path.
        # The signal value ("brief") drives the end-of-run wording in run.sh so
        # the message matches what actually ran (lightweight, council off).
        set_ttfv_lightweight_profile "$_ttfv_max_iter"
        export LOKI_TTFV=brief
        # Skip the heavy auto-plan analysis -- the brief path is the fast path,
        # and we already printed the upfront framing below.
        no_plan=true

        echo -e "${BOLD}Loki Mode v$version - Zero-config first run${NC}"
        echo ""
        echo -e "${CYAN}Brief:${NC} $brief_text"
        echo -e "${DIM}Fast first pass: $_ttfv_max_iter iterations max, council off, simple tier.${NC}"
        echo -e "${DIM}You will get a runnable artifact and a proof-of-run quickly.${NC}"
        echo -e "${DIM}Go deeper later with: loki start (full RARV-C depth).${NC}"
        echo -e "${GREEN}Brief PRD written${NC} to $brief_prd"
        echo ""
    fi

    # R7: existing-repo no-arg path also gets the TTFV "what next" framing at the
    # end of the run. Execution here is UNCHANGED (full-depth no-PRD codebase
    # analysis + generated-PRD-reuse); we only add accurate end-of-run framing.
    # The signal value ("repo") drives the full-depth wording in run.sh so the
    # message does not falsely claim a lightweight pass.
    if [ "$detected_type" = "empty" ] && [ -z "${LOKI_TTFV:-}" ]; then
        export LOKI_TTFV=repo
    fi

    # PRD mode: positional arg is the PRD file (if not already set via --prd)
    if [ "$detected_type" = "prd" ] && [ -z "$prd_file" ]; then
        prd_file="$positional_arg"
    fi
    # Unknown type: fall back to PRD (legacy back-compat)
    if [ "$detected_type" = "unknown" ] && [ -z "$prd_file" ]; then
        prd_file="$positional_arg"
    fi

    # R8 fix (v7.0.0): if a PRD path was specified (positional or --prd) and
    # the file does NOT exist, fail fast with a clear error. This prevents the
    # "silently accepted, then run as no-PRD" failure mode.
    if [ -n "$prd_file" ] && [ "$detected_type" != "empty" ]; then
        case "$prd_file" in
            *.md|*.json|*.txt|*.yaml|*.yml)
                if [ ! -f "$prd_file" ]; then
                    echo "Error: PRD file not found: $prd_file" >&2
                    echo "Hint: provide a path to an existing .md/.json/.txt/.yaml/.yml file, or run 'loki start' with no args to analyze the current directory." >&2
                    exit 1
                fi
                ;;
        esac
    fi

    # Support LOKI_PRD_FILE environment variable as fallback
    if [ -z "$prd_file" ] && [ -n "${LOKI_PRD_FILE:-}" ]; then
        prd_file="$LOKI_PRD_FILE"
    fi

    # v6.84.1 (R8 Gap 1): fail fast if a PRD-like path was provided but the file
    # does not exist on disk. Previously we silently fell through and let the
    # "no PRD specified" branch take over, which misled users who typo'd a path.
    # Only applies when an explicit path was given (positional, --prd, or env);
    # the no-arg "generate from codebase" path is still supported below.
    # BMAD/OpenSpec adapters run later and may set prd_file to a normalized
    # output that doesn't exist yet -- they're guarded by bmad_project_path /
    # openspec_change_path, so skip this check when either is set.
    if [ -n "$prd_file" ] && [ -z "$bmad_project_path" ] && [ -z "$openspec_change_path" ]; then
        case "$prd_file" in
            *.md|*.json|*.txt|*.yaml|*.yml)
                if [ ! -f "$prd_file" ]; then
                    echo -e "${RED}Error: PRD file not found: $prd_file${NC}" >&2
                    exit 1
                fi
                ;;
        esac
    fi

    # Mutual exclusivity: --openspec and --bmad-project cannot be used together
    if [[ -n "${openspec_change_path:-}" ]] && [[ -n "${bmad_project_path:-}" ]]; then
        echo -e "${RED}Error: --openspec and --bmad-project are mutually exclusive. Use one or the other.${NC}"
        exit 1
    fi

    # BMAD project validation and adapter execution
    if [[ -n "$bmad_project_path" ]]; then
        # Resolve to absolute path
        if [[ ! "$bmad_project_path" = /* ]]; then
            local original_bmad_path="$bmad_project_path"
            bmad_project_path="$(cd "$bmad_project_path" 2>/dev/null && pwd)" || {
                echo -e "${RED}Error: BMAD project path does not exist: $original_bmad_path${NC}"
                exit 1
            }
        fi

        # Validate path is a directory
        if [[ ! -d "$bmad_project_path" ]]; then
            echo -e "${RED}Error: BMAD project path is not a directory: $bmad_project_path${NC}"
            exit 1
        fi

        # Check for BMAD artifacts (_bmad-output/ or common BMAD markers)
        if [[ ! -d "$bmad_project_path/_bmad-output" ]]; then
            echo -e "${YELLOW}Warning: No _bmad-output/ directory found in $bmad_project_path${NC}"
            echo "Expected a BMAD Method project with _bmad-output/ artifacts."
            echo -e "Continue anyway? [y/N] \\c"
            local _auto="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
            if [[ "$_auto" == "true" ]]; then
                echo "y (auto-confirmed)"
            else
                read -r _bmad_confirm
                if [[ ! "$_bmad_confirm" =~ ^[Yy] ]]; then
                    echo "Aborted."
                    exit 0
                fi
            fi
        fi

        # Export for run.sh to access
        export BMAD_PROJECT_PATH="$bmad_project_path"

        # Ensure .loki directory exists for adapter output
        mkdir -p "$LOKI_DIR"

        # Run the BMAD adapter to normalize artifacts
        echo -e "${CYAN}Running BMAD adapter...${NC}"
        local adapter_script="$(dirname "$(resolve_script_path "$0")")/bmad-adapter.py"
        if [[ ! -f "$adapter_script" ]]; then
            echo -e "${RED}Error: BMAD adapter not found at $adapter_script${NC}"
            echo "Please ensure autonomy/bmad-adapter.py exists."
            exit 1
        fi

        if ! python3 "$adapter_script" "$bmad_project_path" --output-dir "$LOKI_DIR" --validate; then
            echo -e "${RED}Error: BMAD adapter failed. Check the project artifacts.${NC}"
            exit 1
        fi
        echo -e "${GREEN}BMAD artifacts normalized successfully.${NC}"

        # If no explicit PRD was provided, use the normalized BMAD PRD
        if [[ -z "$prd_file" ]] && [[ -f "$LOKI_DIR/bmad-prd-normalized.md" ]]; then
            prd_file="$LOKI_DIR/bmad-prd-normalized.md"
            echo -e "${CYAN}Using normalized BMAD PRD: $prd_file${NC}"
        fi
    fi

    # OpenSpec change directory validation and adapter execution
    if [[ -n "$openspec_change_path" ]]; then
        # Resolve to absolute path
        if [[ ! "$openspec_change_path" = /* ]]; then
            local original_openspec_path="$openspec_change_path"
            openspec_change_path="$(cd "$openspec_change_path" 2>/dev/null && pwd)" || {
                echo -e "${RED}Error: OpenSpec change path does not exist: $original_openspec_path${NC}"
                exit 1
            }
        fi

        # Validate path is a directory
        if [[ ! -d "$openspec_change_path" ]]; then
            echo -e "${RED}Error: OpenSpec change path is not a directory: $openspec_change_path${NC}"
            exit 1
        fi

        # Check for required OpenSpec artifacts
        if [[ ! -f "$openspec_change_path/proposal.md" ]]; then
            echo -e "${RED}Error: No proposal.md found in $openspec_change_path${NC}"
            echo "Expected an OpenSpec change directory with proposal.md and specs/."
            exit 1
        fi

        # Export for run.sh to access
        export OPENSPEC_CHANGE_PATH="$openspec_change_path"

        # Ensure .loki directory exists for adapter output
        mkdir -p "$LOKI_DIR"

        # Run the OpenSpec adapter to normalize artifacts
        echo -e "${CYAN}Running OpenSpec adapter...${NC}"
        local adapter_script="$(dirname "$(resolve_script_path "$0")")/openspec-adapter.py"
        if [[ ! -f "$adapter_script" ]]; then
            echo -e "${RED}Error: OpenSpec adapter not found at $adapter_script${NC}"
            echo "Please ensure autonomy/openspec-adapter.py exists."
            exit 1
        fi

        # Validate first
        if ! python3 "$adapter_script" "$openspec_change_path" --validate; then
            echo -e "${RED}Error: OpenSpec adapter validation failed. Check the change artifacts.${NC}"
            exit 1
        fi

        # Generate output files
        if ! python3 "$adapter_script" "$openspec_change_path" --output-dir "$LOKI_DIR"; then
            echo -e "${RED}Error: OpenSpec adapter failed to generate output files.${NC}"
            exit 1
        fi
        echo -e "${GREEN}OpenSpec artifacts normalized successfully.${NC}"

        # If no explicit PRD was provided, use the normalized OpenSpec PRD
        if [[ -z "$prd_file" ]] && [[ -f "$LOKI_DIR/openspec-prd-normalized.md" ]]; then
            prd_file="$LOKI_DIR/openspec-prd-normalized.md"
            echo -e "${CYAN}Using normalized OpenSpec PRD: $prd_file${NC}"
        fi
    fi

    # MiroFish market validation (optional, non-blocking)
    if [[ -z "$mirofish_url" ]] && [[ -n "${LOKI_MIROFISH_URL:-}" ]] && [[ "$mirofish_disabled" != "true" ]]; then
        mirofish_url="$LOKI_MIROFISH_URL"
    fi

    if [[ -n "$mirofish_url" ]] && [[ "$mirofish_disabled" != "true" ]]; then
        export MIROFISH_URL="$mirofish_url"
        mkdir -p "$LOKI_DIR"

        local mf_adapter="$(dirname "$(resolve_script_path "$0")")/mirofish-adapter.py"
        if [[ ! -f "$mf_adapter" ]]; then
            echo -e "${RED}Error: MiroFish adapter not found at $mf_adapter${NC}"
            exit 1
        fi

        # Docker auto-start if requested
        if [[ -n "$mirofish_docker_image" ]]; then
            echo -e "${CYAN}Starting MiroFish container...${NC}"
            if ! python3 "$mf_adapter" --docker-start --docker-image "$mirofish_docker_image" \
                    ${LOKI_MIROFISH_PORT:+--port "$LOKI_MIROFISH_PORT"}; then
                echo -e "${RED}Error: Failed to start MiroFish container${NC}"
                exit 1
            fi
        fi

        # Health check
        if ! python3 "$mf_adapter" --health --url "$mirofish_url" 2>/dev/null; then
            echo -e "${YELLOW}Warning: MiroFish not reachable at $mirofish_url${NC}"
            echo "Start MiroFish with: loki start --mirofish-docker <image>"
            echo -e "Continue without market validation? [y/N] \\c"
            local _auto="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
            if [[ "$_auto" == "true" ]]; then
                echo "y (auto-confirmed)"
            else
                read -r _mf_confirm
                if [[ ! "$_mf_confirm" =~ ^[Yy] ]]; then
                    exit 0
                fi
            fi
            unset MIROFISH_URL
        else
            echo -e "${GREEN}MiroFish reachable at $mirofish_url${NC}"

            # Need a PRD to run MiroFish
            local _mf_prd="${prd_file:-}"
            if [[ -z "$_mf_prd" ]]; then
                echo -e "${YELLOW}Warning: No PRD file -- skipping MiroFish (needs PRD for simulation seed)${NC}"
                unset MIROFISH_URL
            else
                # Validate PRD can be parsed for MiroFish
                if python3 "$mf_adapter" "$_mf_prd" --validate --url "$mirofish_url" 2>/dev/null; then
                    echo -e "${GREEN}PRD validated for MiroFish simulation${NC}"
                    # Run with --background (adapter forks internally, returns immediately)
                    python3 "$mf_adapter" "$_mf_prd" --output-dir "$LOKI_DIR" \
                        --url "$mirofish_url" --background \
                        ${LOKI_MIROFISH_ROUNDS:+--max-rounds "$LOKI_MIROFISH_ROUNDS"} \
                        ${LOKI_MIROFISH_TIMEOUT:+--timeout "$LOKI_MIROFISH_TIMEOUT"}
                    # PID is tracked in .loki/mirofish/pipeline-state.json by the adapter
                    echo -e "${CYAN}MiroFish pipeline started in background. Check status: loki mirofish status${NC}"
                else
                    echo -e "${YELLOW}Warning: PRD validation for MiroFish failed. Continuing without.${NC}"
                    unset MIROFISH_URL
                fi
            fi
        fi
    fi

    if [ -n "$prd_file" ]; then
        args+=("$prd_file")
    else
        # No PRD file specified -- warn and confirm before starting
        # Auto-confirm in CI environments or when LOKI_AUTO_CONFIRM is set
        # LOKI_AUTO_CONFIRM takes precedence when explicitly set;
        # fall back to CI env var only when LOKI_AUTO_CONFIRM is unset.
        # v7.7.13 fix (user-reported Docker bug): also auto-confirm when stdin
        # is not a TTY. Without this, `docker run --rm asklokesh/loki-mode
        # start` showed the prompt then exited because the user didn't pass
        # `-it` to docker, so stdin was closed and `read` returned EOF with
        # an empty string; the script kept going but the user only saw the
        # prompt before the next bug fired. Now non-TTY callers get a clean
        # auto-confirm path identical to CI.
        local _auto_confirm="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
        if [[ "$_auto_confirm" != "true" ]] && [ ! -t 0 ]; then
            _auto_confirm="true"
            echo -e "${YELLOW}Warning: No PRD file specified, stdin not a TTY (e.g. docker run without -it). Auto-confirming.${NC}"
            echo -e "${YELLOW}Tip: pass docker run -it ... or set LOKI_AUTO_CONFIRM=true to suppress this warning.${NC}"
        fi
        if [[ "$_auto_confirm" == "true" ]]; then
            echo -e "${YELLOW}Warning: No PRD file specified. Auto-confirming (CI mode).${NC}"
        else
            echo -e "${YELLOW}No PRD file specified.${NC}"
            echo "Loki Mode will analyze the existing codebase and generate"
            echo "a PRD automatically. No requirements document needed."
            echo ""
            # v7.5.3 UX: rephrased + default flipped from N to Y. The pre-v7.5.3
            # "Continue? [y/N]" with default-N caused users to accidentally
            # cancel by hitting Enter after reading the explanation.
            echo -e "Generate PRD from codebase and start? [Y/n] \c"
            read -r confirm
            if [[ "$confirm" =~ ^[Nn] ]]; then
                echo "Aborted. Usage: loki start <path-to-prd.md>"
                exit 0
            fi
        fi
    fi

    # v6.81.1: auto-show PRD analysis so users see complexity / cost / time
    # up front without needing a separate `loki plan` invocation. Skipped via
    # --no-plan or in non-TTY contexts (CI/pipe). No-op when PRD is absent.
    if [ -n "$prd_file" ]; then
        maybe_show_auto_plan "$prd_file" "$no_plan" || true
    fi

    # Load saved provider if not specified on command line
    if [ -z "$provider" ]; then
        if [ -f "$LOKI_DIR/state/provider" ]; then
            provider=$(cat "$LOKI_DIR/state/provider" 2>/dev/null)
            if [ -n "$provider" ]; then
                args+=("--provider" "$provider")
            fi
        fi
    fi

    # v7.5.12 Gap B: Stale-PID detection. Hard-kill (Ctrl+C followed by SIGKILL or
    # `loki stop`) can leave .loki/loki.pid + .loki/session.lock orphaned. The
    # next `loki start` then refuses to launch -- or worse, run.sh's downstream
    # cleanup may treat the stale pid as live. Detect-and-clean here, BEFORE
    # exec, so the user gets one clear log line instead of mysterious silent
    # behavior.
    local _start_loki_dir="${LOKI_DIR:-.loki}"
    local _start_pid_file="$_start_loki_dir/loki.pid"
    if [ -f "$_start_pid_file" ]; then
        local _existing_pid
        _existing_pid=$(cat "$_start_pid_file" 2>/dev/null | tr -dc '0-9')
        if [ -n "$_existing_pid" ] && kill -0 "$_existing_pid" 2>/dev/null; then
            echo -e "${RED}Error: another loki instance is running (pid $_existing_pid).${NC}" >&2
            echo -e "${YELLOW}Run 'loki stop' first, then retry 'loki start'.${NC}" >&2
            exit 1
        fi
        # PID is stale (file present but process gone). Log + remove + continue.
        echo -e "${YELLOW}Removing stale pid file ($_start_pid_file, pid=${_existing_pid:-empty} not alive)${NC}" >&2
        rm -f "$_start_pid_file" 2>/dev/null || true
        rm -f "$_start_loki_dir/session.lock" 2>/dev/null || true
    fi

    # Determine effective provider for display
    local effective_provider="${provider:-${LOKI_PROVIDER:-claude}}"

    # Handle sandbox mode - delegate to sandbox.sh
    # Skip if we're already inside a sandbox (IS_SANDBOX=1 or /.dockerenv exists)
    if [[ "${LOKI_SANDBOX_MODE:-}" == "true" ]] && [[ "${IS_SANDBOX:-}" != "1" ]] && [[ ! -f /.dockerenv ]]; then
        if [ ! -f "$SANDBOX_SH" ]; then
            echo -e "${RED}Error: sandbox.sh not found at $SANDBOX_SH${NC}"
            exit 1
        fi

        echo -e "${GREEN}Starting Loki Mode in sandbox...${NC}"
        # Load memory context before sandbox start (errors don't block startup)
        load_memory_context "$prd_file" || true
        emit_event session cli start "provider=$effective_provider" "sandbox=true" "prd_path=${prd_file:-}"
        # v7.7.13 fix: safe empty-array expansion (see line 1551 comment).
        exec "$SANDBOX_SH" start ${args[@]+"${args[@]}"}
    fi

    # Load memory context before starting (errors don't block startup)
    load_memory_context "$prd_file" || true

    # Show provider info
    echo -e "${GREEN}Starting Loki Mode...${NC}"
    echo -e "${CYAN}Provider:${NC} $effective_provider"
    if [ -f "$LOKI_DIR/state/provider" ]; then
        echo -e "${DIM}  (saved for this project - change with: loki provider set <name>)${NC}"
    else
        echo -e "${DIM}  (default - save for project with: loki provider set <name>)${NC}"
    fi
    echo ""

    # Emit session start event
    emit_event session cli start "provider=$effective_provider" "prd_path=${prd_file:-}"

    # Record session start time for efficiency tracking
    record_session_start

    # UT2-13: Write cli-provider marker when --provider was explicitly passed.
    # This lets cmd_status_json report provider_source="cli" for the lifetime
    # of this session (24-hour heuristic). Atomic write via .tmp + mv.
    # v7.7.11 (council fix): validate provider against canonical set BEFORE
    # writing so a typo like `--provider xyz` does not pollute status output
    # with bogus state for 24h. Also write current PID alongside so the reader
    # can drop the marker when the originating process is no longer alive
    # (SIGKILL / crash paths that skip our cleanup hooks).
    case "$provider" in
        claude|codex|cline|aider)
            mkdir -p "$LOKI_DIR/state" 2>/dev/null || true
            printf '%s:%s:%s\n' "$provider" "$(date +%s)" "$$" \
                > "$LOKI_DIR/state/.cli-provider.tmp" 2>/dev/null \
                && mv "$LOKI_DIR/state/.cli-provider.tmp" \
                      "$LOKI_DIR/state/cli-provider" 2>/dev/null || true
            ;;
        '') : ;;  # no --provider passed
        *)  log_warn "UT2-13: skipped cli-provider marker (unknown provider '$provider')" ;;
    esac

    # Emit learning signal: user preference for provider selection (SYN-018)
    if [ -n "$provider" ]; then
        # User explicitly chose a provider
        emit_learning_signal user_preference \
            --source cli \
            --action "provider_selection" \
            --key "provider" \
            --value "$provider" \
            --rejected '["'"$([ "$provider" != "claude" ] && echo "claude" || echo "codex")'", "'"$([ "$provider" != "cline" ] && echo "cline" || echo "aider")'"]' \
            --confidence 0.95
    fi

    # Emit learning signal: workflow pattern for session start
    emit_learning_signal workflow_pattern \
        --source cli \
        --action "session_start" \
        --workflow-name "loki_session" \
        --steps '["init", "load_memory", "start_runner"]' \
        --outcome success \
        --context "{\"provider\":\"$effective_provider\",\"prd_path\":\"${prd_file:-}\"}"

    # Pre-flight: check that the provider CLI is installed
    if ! command -v "$effective_provider" &>/dev/null; then
        echo -e "${RED}Error: Provider CLI '$effective_provider' is not installed.${NC}"
        echo ""
        echo "Install it first:"
        case "$effective_provider" in
            claude) echo "  npm install -g @anthropic-ai/claude-code" ;;
            codex)  echo "  npm install -g @openai/codex" ;;
            cline)  echo "  npm install -g @anthropic-ai/cline" ;;
            aider)  echo "  pip install aider-chat" ;;
            *)      echo "  Check the provider documentation for installation." ;;
        esac
        echo ""
        echo "Check your environment: loki doctor"
        exit 1
    fi

    # --api flag: start the dashboard API server before the build.
    # cmd_dashboard_start already daemonizes the server (nohup ... &) and runs
    # its own readiness probe, so run it in a contained SUBSHELL (not a
    # double-background) and surface the outcome. The old
    # `cmd_dashboard_start 2>/dev/null &` swallowed all output AND double-
    # backgrounded the already-daemonized server, so a port-in-use or startup
    # failure was invisible and the user got no dashboard and no error. The
    # subshell also contains cmd_dashboard_start's internal `exit 1` so a
    # dashboard failure can never abort the build.
    if [ "${LOKI_START_API:-false}" = "true" ]; then
        local dash_port="${LOKI_DASHBOARD_PORT:-57374}"
        echo -e "${GREEN}Starting dashboard API on port $dash_port...${NC}"
        ( cmd_dashboard_start ) || echo -e "${YELLOW}Dashboard API did not start (see message above); continuing the build without it.${NC}"
    fi

    # Phase F (v7.5.23): cross-project context discovery. Walks ONE parent
    # level looking for sibling repos sharing a `.loki/app.json` marker.
    # Exports LOKI_PROJECT_GRAPH_{ROOT,APP_ID,MEMBERS} which survive exec
    # into run.sh. Silent no-op when no graph is found (backward compat).
    local _pg_helper="$SKILL_DIR/autonomy/lib/project-graph.sh"
    if [ -f "$_pg_helper" ]; then
        # shellcheck disable=SC1090
        . "$_pg_helper"
        local _pg_target="${LOKI_TARGET_DIR:-$(pwd)}"
        loki_project_graph_discover "$_pg_target" || true
    fi

    # v7.7.13 fix (user-reported bug): `"${args[@]}"` triggers "unbound
    # variable" under bash 3.2 (macOS default) + `set -u` when args is empty.
    # User report: `loki start` with no PRD on /Users/lokesh/git/anonima
    # crashed at this line. Safe expansion guards against empty arrays.
    _loki_new_session_exec "$RUN_SH" ${args[@]+"${args[@]}"}
}

# Check if session is running
# Usage: is_session_running [session_id]
# Without args: returns true if ANY session (global or per-session) is running
# With session_id: returns true only if that specific session is running
is_session_running() {
    local target_session="${1:-}"

    if [ -n "$target_session" ]; then
        # Check specific session
        local session_pid_file="$LOKI_DIR/sessions/$target_session/loki.pid"
        if [ -f "$session_pid_file" ]; then
            local pid
            pid=$(cat "$session_pid_file" 2>/dev/null)
            if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
                return 0
            fi
        fi
        # Also check legacy per-run PID file (run-<number>.pid)
        local run_pid_file="$LOKI_DIR/run-${target_session}.pid"
        if [ -f "$run_pid_file" ]; then
            local pid
            pid=$(cat "$run_pid_file" 2>/dev/null)
            if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
                return 0
            fi
        fi
        return 1
    fi

    # Check global PID files (loki.pid from run.sh, run.pid legacy)
    local pid_files=("$LOKI_DIR/loki.pid" "$LOKI_DIR/run.pid")
    for pid_file in "${pid_files[@]}"; do
        if [ -f "$pid_file" ]; then
            local pid
            pid=$(cat "$pid_file" 2>/dev/null)
            if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
                return 0
            fi
        fi
    done

    # Check per-session PIDs
    if [ -d "$LOKI_DIR/sessions" ]; then
        for session_dir in "$LOKI_DIR/sessions"/*/; do
            [ -d "$session_dir" ] || continue
            local pid_file="${session_dir}loki.pid"
            if [ -f "$pid_file" ]; then
                local pid
                pid=$(cat "$pid_file" 2>/dev/null)
                if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
                    return 0
                fi
            fi
        done
    fi
    return 1
}

# List all running session IDs
list_running_sessions() {
    local sessions=()
    # Check global session
    if [ -f "$LOKI_DIR/loki.pid" ]; then
        local pid
        pid=$(cat "$LOKI_DIR/loki.pid" 2>/dev/null)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            sessions+=("global:$pid")
        fi
    fi
    # Check per-session PIDs
    if [ -d "$LOKI_DIR/sessions" ]; then
        for session_dir in "$LOKI_DIR/sessions"/*/; do
            [ -d "$session_dir" ] || continue
            local sid pid_file pid
            sid=$(basename "$session_dir")
            pid_file="${session_dir}loki.pid"
            if [ -f "$pid_file" ]; then
                pid=$(cat "$pid_file" 2>/dev/null)
                if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
                    sessions+=("$sid:$pid")
                fi
            fi
        done
    fi
    # Check legacy run-*.pid files
    for run_pid_file in "$LOKI_DIR"/run-*.pid; do
        [ -f "$run_pid_file" ] || continue
        local sid pid
        sid=$(basename "$run_pid_file" .pid)
        sid="${sid#run-}"
        pid=$(cat "$run_pid_file" 2>/dev/null)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            # Avoid duplicates (if also in sessions/)
            local dup=false
            for s in ${sessions[@]+"${sessions[@]}"}; do
                if [[ "$s" == "$sid:"* ]]; then dup=true; break; fi
            done
            if ! $dup; then
                sessions+=("$sid:$pid")
            fi
        fi
    done
    printf '%s\n' ${sessions[@]+"${sessions[@]}"}
}

# Kill a PID with SIGTERM, wait, then SIGKILL if needed
_kill_pid() {
    local pid="$1"
    pkill -P "$pid" 2>/dev/null || true
    kill "$pid" 2>/dev/null || true
    sleep 1
    if kill -0 "$pid" 2>/dev/null; then
        pkill -9 -P "$pid" 2>/dev/null || true
        kill -9 "$pid" 2>/dev/null || true
    fi
}

# Stop a specific session by its session ID
_stop_session_by_id() {
    local sid="$1"
    local session_dir="$LOKI_DIR/sessions/$sid"
    local pid_file="$session_dir/loki.pid"

    if [ -f "$pid_file" ]; then
        local pid
        pid=$(cat "$pid_file" 2>/dev/null)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            _kill_pid "$pid"
            echo -e "${RED}Stopped session $sid (PID: $pid)${NC}"
        fi
        rm -f "$pid_file"
    fi

    # Also check legacy run-<id>.pid
    local run_pid_file="$LOKI_DIR/run-${sid}.pid"
    if [ -f "$run_pid_file" ]; then
        local pid
        pid=$(cat "$run_pid_file" 2>/dev/null)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            _kill_pid "$pid"
        fi
        rm -f "$run_pid_file"
    fi

    # Clean up session lock file
    rm -f "$session_dir/session.lock" 2>/dev/null
}

# Stop a specific session or all sessions
# Usage: loki stop [session_id]
# Without args: stops all running sessions
# With session_id: stops only that specific session
cmd_stop() {
    local target_session=""
    local stop_all=false

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki stop${NC} - Stop running sessions (v7.7.30)"
                echo ""
                echo "Usage: loki stop [session-id] [--all]"
                echo ""
                echo "  loki stop          Stop ONLY the current folder's session."
                echo "                     Other folders keep running. Marks this"
                echo "                     project stopped in the dashboard registry."
                echo "  loki stop 52       Stop only session #52 in this folder."
                echo "  loki stop --all    Stop EVERY loki runner on this machine"
                echo "                     (legacy machine-wide behavior)."
                echo ""
                echo "The shared dashboard stays up if other projects are still"
                echo "running; it is stopped only when no project remains (or --all)."
                echo ""
                echo "Use 'loki status' to see this folder's sessions."
                exit 0
                ;;
            --all)
                stop_all=true
                shift
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki stop --help' for usage."
                exit 1
                ;;
            *)
                target_session="$1"
                shift
                ;;
        esac
    done

    if [ ! -d "$LOKI_DIR" ]; then
        echo -e "${YELLOW}No .loki directory found.${NC}"
        echo "No active session to stop."
        emit_learning_signal error_pattern \
            --source cli \
            --action "cmd_stop" \
            --error-type "SessionNotFound" \
            --error-message "No .loki directory found" \
            --recovery-steps '["Run loki start first"]' \
            --confidence 0.7
        exit 0
    fi

    # Stop a specific session by ID
    if [ -n "$target_session" ]; then
        if is_session_running "$target_session"; then
            _stop_session_by_id "$target_session"
            echo "Stopped session: $target_session"
        else
            echo "No active session found"
        fi
        return 0
    fi

    # No session ID given -- stop all running sessions
    if is_session_running; then
        # Stop per-session PIDs first
        if [ -d "$LOKI_DIR/sessions" ]; then
            for session_dir in "$LOKI_DIR/sessions"/*/; do
                [ -d "$session_dir" ] || continue
                local sid
                sid=$(basename "$session_dir")
                if is_session_running "$sid"; then
                    _stop_session_by_id "$sid"
                fi
            done
        fi
        # Stop legacy run-*.pid sessions
        for run_pid_file in "$LOKI_DIR"/run-*.pid; do
            [ -f "$run_pid_file" ] || continue
            local pid
            pid=$(cat "$run_pid_file" 2>/dev/null)
            if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
                _kill_pid "$pid"
                local sid
                sid=$(basename "$run_pid_file" .pid)
                echo -e "${RED}Stopped session ${sid#run-} (PID: $pid)${NC}"
            fi
            rm -f "$run_pid_file"
        done

        # Stop global session
        touch "$LOKI_DIR/STOP"

        # v7.7.34: group-kill first. The autonomous agent (claude/codex/aider)
        # shares the orchestrator's process group (the runner is launched as a
        # session leader), so signaling the whole group reaps the orchestrator
        # AND the agent child atomically. Killing only the orchestrator pid lets
        # the agent reparent to init and keep editing files -- the reported bug.
        # Guards: only a numeric pgid > 1 that is NOT this shell's own group.
        local _stop_pgid_file
        for _stop_pgid_file in "$LOKI_DIR/loki.pgid" "$LOKI_DIR/run.pgid"; do
            [ -f "$_stop_pgid_file" ] || continue
            local _spgid
            _spgid=$(cat "$_stop_pgid_file" 2>/dev/null | tr -d ' ')
            case "$_spgid" in ''|*[!0-9]*) continue ;; esac
            [ "$_spgid" -gt 1 ] 2>/dev/null || continue
            local _my_pgid
            _my_pgid=$(ps -o pgid= -p $$ 2>/dev/null | tr -d ' ')
            [ "$_spgid" = "$_my_pgid" ] && continue   # never kill our own group
            # Collect protected pids (dashboard, app-runner, registered pids) so
            # a group-kill never takes down the dashboard if it happens to share
            # the orchestrator group. Mirrors the dashboard Python route.
            local _protected=" "
            local _pf
            if [ -d "$LOKI_DIR/pids" ]; then
                for _pf in "$LOKI_DIR/pids"/*.json; do
                    [ -f "$_pf" ] || continue
                    _protected="${_protected}$(basename "$_pf" .json) "
                done
            fi
            for _pf in "$LOKI_DIR/dashboard/dashboard.pid" "${HOME}/.loki/dashboard/dashboard.pid"; do
                [ -f "$_pf" ] && _protected="${_protected}$(cat "$_pf" 2>/dev/null | tr -d ' ') "
            done
            # Does any protected pid share this group?
            local _conflict=0 _gp
            for _gp in $(ps -axo pid=,pgid= 2>/dev/null | awk -v g="$_spgid" '$2==g{print $1}'); do
                case "$_protected" in *" $_gp "*) _conflict=1; break ;; esac
            done
            if [ "$_conflict" = "1" ]; then
                # Per-pid kill of group members EXCLUDING protected pids.
                for _gp in $(ps -axo pid=,pgid= 2>/dev/null | awk -v g="$_spgid" '$2==g{print $1}'); do
                    case "$_protected" in *" $_gp "*) continue ;; esac
                    [ "$_gp" = "$$" ] && continue
                    kill -TERM "$_gp" 2>/dev/null || true
                done
                sleep 1
                for _gp in $(ps -axo pid=,pgid= 2>/dev/null | awk -v g="$_spgid" '$2==g{print $1}'); do
                    case "$_protected" in *" $_gp "*) continue ;; esac
                    [ "$_gp" = "$$" ] && continue
                    kill -KILL "$_gp" 2>/dev/null || true
                done
            else
                kill -TERM -- -"$_spgid" 2>/dev/null || true
                sleep 1
                kill -KILL -- -"$_spgid" 2>/dev/null || true
            fi
            rm -f "$_stop_pgid_file" 2>/dev/null || true
        done

        local killed_pid=""
        for pid_file in "$LOKI_DIR/loki.pid" "$LOKI_DIR/run.pid"; do
            if [ -f "$pid_file" ]; then
                local pid
                pid=$(cat "$pid_file" 2>/dev/null)
                if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
                    _kill_pid "$pid"
                    killed_pid="$pid"
                fi
                rm -f "$pid_file"
            fi
        done

        # Mark session.json as stopped (skill-invoked sessions)
        # BUG-ST-008: Atomic session.json update via temp file + mv (matches run.sh)
        if [ -f "$LOKI_DIR/session.json" ]; then
            _LOKI_SESSION_FILE="$LOKI_DIR/session.json" python3 -c "
import json, os, tempfile
sf = os.environ['_LOKI_SESSION_FILE']
try:
    with open(sf) as f:
        d = json.load(f)
    d['status'] = 'stopped'
    sd = os.path.dirname(sf)
    fd, tmp = tempfile.mkstemp(dir=sd, suffix='.json')
    with os.fdopen(fd, 'w') as f:
        json.dump(d, f)
    os.replace(tmp, sf)
except (json.JSONDecodeError, OSError): pass
" 2>/dev/null || true
        fi

        # v7.7.30: mark THIS project stopped in the shared dashboard registry
        # so the multi-project switcher reflects it. Best-effort, never fatal.
        # Honors LOKI_SKIP_PROJECT_REGISTRY (matches run.sh registration).
        if [ -z "${LOKI_SKIP_PROJECT_REGISTRY:-}" ] && command -v python3 >/dev/null 2>&1; then
            LOKI_REG_TARGET="$(pwd)" LOKI_REG_SKILL="${SKILL_DIR:-$PROJECT_DIR}" \
            python3 - <<'PYSTOP' >/dev/null 2>&1 || true
import os, sys
sys.path.insert(0, os.environ.get("LOKI_REG_SKILL", "."))
try:
    from dashboard import registry
    registry.mark_project_stopped(os.path.abspath(os.environ["LOKI_REG_TARGET"]))
except Exception:
    pass
PYSTOP
        fi

        # Clean up control files
        rm -f "$LOKI_DIR/STOP" "$LOKI_DIR/PAUSE" "$LOKI_DIR/PAUSED.md" 2>/dev/null

        # Kill dashboard if running.
        # v7.7.30: the project-local dashboard (.loki/dashboard) belongs solely
        # to this folder and is ALWAYS killed. The shared standalone dashboard
        # (~/.loki/dashboard) is killed ONLY when no OTHER registered project is
        # still running, so we never tear down a dashboard other folders need.
        # For --all, the shared dashboard is always killed (tear everything down).
        local _kill_shared_dash=false
        if [ "$stop_all" = true ]; then
            _kill_shared_dash=true
        elif command -v python3 >/dev/null 2>&1; then
            # Query the registry (after mark_project_stopped excluded this
            # project) for any OTHER project whose pid is still alive.
            local _dash_decision
            _dash_decision=$(LOKI_REG_SKILL="${SKILL_DIR:-$PROJECT_DIR}" \
                python3 - <<'PYDASH' 2>/dev/null || true
import os, sys
sys.path.insert(0, os.environ.get("LOKI_REG_SKILL", "."))
try:
    from dashboard import registry
    alive = 0
    for p in registry.list_projects(include_inactive=True):
        pid = p.get("pid")
        if isinstance(pid, int) and pid > 0:
            try:
                os.kill(pid, 0)
                alive += 1
            except OSError:
                pass
    print("CLEAR" if alive == 0 else "KEEP")
except Exception:
    print("CLEAR")
PYDASH
            )
            [ "$_dash_decision" = "CLEAR" ] && _kill_shared_dash=true
        else
            # python3 unavailable: fall back to legacy behavior (kill shared)
            # to avoid leaking the shared dashboard on minimal systems.
            _kill_shared_dash=true
        fi

        local _dash_pidf
        local _dash_targets=("$LOKI_DIR/dashboard/dashboard.pid")
        [ "$_kill_shared_dash" = true ] && _dash_targets+=("${HOME}/.loki/dashboard/dashboard.pid")
        for _dash_pidf in "${_dash_targets[@]}"; do
            if [ -f "$_dash_pidf" ]; then
                local dash_pid
                dash_pid=$(cat "$_dash_pidf" 2>/dev/null)
                if [ -n "$dash_pid" ] && kill -0 "$dash_pid" 2>/dev/null; then
                    kill "$dash_pid" 2>/dev/null || true
                    sleep 0.5
                    kill -9 "$dash_pid" 2>/dev/null || true
                fi
                rm -f "$_dash_pidf"
            fi
        done

        # Kill any remaining registered processes (2s graceful window matches run.sh)
        if [ -d "$LOKI_DIR/pids" ]; then
            for entry_file in "$LOKI_DIR/pids"/*.json; do
                [ -f "$entry_file" ] || continue
                local reg_pid
                reg_pid=$(basename "$entry_file" .json)
                case "$reg_pid" in ''|*[!0-9]*) continue ;; esac
                if kill -0 "$reg_pid" 2>/dev/null; then
                    kill "$reg_pid" 2>/dev/null || true
                    local w=0
                    while [ $w -lt 4 ] && kill -0 "$reg_pid" 2>/dev/null; do
                        sleep 0.5
                        w=$((w + 1))
                    done
                    if kill -0 "$reg_pid" 2>/dev/null; then
                        kill -9 "$reg_pid" 2>/dev/null || true
                    fi
                fi
                rm -f "$entry_file"
            done
        fi

        # Emit session stop event
        emit_event session cli stop "reason=user_requested"
        # Emit success pattern for clean stop (SYN-018)
        emit_learning_signal success_pattern \
            --source cli \
            --action "cmd_stop" \
            --pattern-name "session_stop" \
            --action-sequence '["check_session", "send_stop_signal"]' \
            --outcome success \
            --confidence 0.9

        if [ -n "$killed_pid" ]; then
            echo -e "${RED}Stopped Loki Mode (PID: $killed_pid)${NC}"
        else
            echo -e "${RED}STOP signal sent. Execution will halt immediately.${NC}"
        fi
    else
        echo -e "${YELLOW}No active session running.${NC}"
        if [ -f "$LOKI_DIR/STOP" ]; then
            echo "STOP signal already present."
        else
            echo "Start a session with: loki start"
        fi
    fi

    # v7.7.30: --all preserves the legacy machine-wide kill. It runs even when
    # the current folder has no live session (the "clean everything" use case),
    # reaping every folder's orphaned loki-run-* temp script (SIGTERM, SIGKILL).
    if [ "$stop_all" = true ]; then
        pkill -f "loki-run-" 2>/dev/null || true
        sleep 0.5
        pkill -9 -f "loki-run-" 2>/dev/null || true
        echo -e "${RED}--all: signalled all loki-run-* processes on this machine.${NC}"
    fi
}

# Kill orphaned processes from crashed sessions
cmd_cleanup() {
    # v7.6.2 B-13 fix: --help must print help, not run cleanup as a side effect.
    case "${1:-}" in
        --help|-h|help)
            echo -e "${BOLD}Loki Mode -- cleanup orphaned processes${NC}"
            echo ""
            echo "Usage: loki cleanup [options]"
            echo ""
            echo "Scans \$LOKI_DIR/pids/ for stale PID registrations from prior runs"
            echo "and kills processes whose PID file points at a dead process."
            echo ""
            echo "Options:"
            echo "  --help, -h   Show this help and exit"
            echo ""
            echo "Side effects: kills processes registered under .loki/pids/."
            return 0
            ;;
    esac
    local pids_dir="$LOKI_DIR/pids"
    local killed=0
    local stale=0

    if [ ! -d "$pids_dir" ]; then
        echo "No PID registry found. Nothing to clean up."
        exit 0
    fi

    echo -e "${BOLD}Scanning for orphaned processes...${NC}"

    for entry_file in "$pids_dir"/*.json; do
        [ -f "$entry_file" ] || continue
        local pid
        pid=$(basename "$entry_file" .json)
        case "$pid" in
            ''|*[!0-9]*) continue ;;
        esac

        local label=""
        local ppid_val=""
        # Parse JSON fields (python3 with shell fallback)
        if command -v python3 >/dev/null 2>&1; then
            label=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('label','unknown'))" "$entry_file" 2>/dev/null) || label="unknown"
            ppid_val=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('ppid',''))" "$entry_file" 2>/dev/null) || true
        else
            label=$(sed 's/.*"label":"//' "$entry_file" 2>/dev/null | sed 's/".*//' | head -1) || label="unknown"
            ppid_val=$(sed 's/.*"ppid"://' "$entry_file" 2>/dev/null | sed 's/[,}].*//' | head -1) || true
        fi

        if kill -0 "$pid" 2>/dev/null; then
            # Process is alive - check if parent is dead (orphan)
            local is_orphan=false
            # Validate ppid_val is numeric before using with kill
            case "$ppid_val" in ''|*[!0-9]*) ppid_val="" ;; esac
            if [ -n "$ppid_val" ] && ! kill -0 "$ppid_val" 2>/dev/null; then
                is_orphan=true
            fi

            if [ "$is_orphan" = true ] || [ "${1:-}" = "--force" ]; then
                echo -e "  ${RED}Killing${NC} PID=$pid label=$label (parent $ppid_val dead)"
                kill "$pid" 2>/dev/null || true
                sleep 0.5
                if kill -0 "$pid" 2>/dev/null; then
                    kill -9 "$pid" 2>/dev/null || true
                fi
                rm -f "$entry_file"
                killed=$((killed + 1))
            else
                echo -e "  ${GREEN}Alive${NC}  PID=$pid label=$label (parent $ppid_val alive)"
            fi
        else
            # Process is dead - clean up stale entry
            rm -f "$entry_file"
            stale=$((stale + 1))
        fi
    done

    echo ""
    echo "Results: $killed orphan(s) killed, $stale stale entries cleaned"

    # Also kill orphaned loki-run temp scripts
    local temp_killed=0
    if pgrep -f "loki-run-" >/dev/null 2>&1; then
        if ! is_session_running; then
            echo "Killing orphaned loki-run temp scripts..."
            pkill -f "loki-run-" 2>/dev/null || true
            sleep 0.5
            pkill -9 -f "loki-run-" 2>/dev/null || true
            temp_killed=1
        fi
    fi

    if [ $killed -eq 0 ] && [ $stale -eq 0 ] && [ $temp_killed -eq 0 ]; then
        echo -e "${GREEN}System is clean. No orphans found.${NC}"
    fi
}

# Pause after current session
cmd_pause() {
    # v7.6.2 B-13 fix: --help must print help, not act on session state.
    case "${1:-}" in
        --help|-h|help)
            echo -e "${BOLD}Loki Mode -- pause active session${NC}"
            echo ""
            echo "Usage: loki pause [options]"
            echo ""
            echo "Marks the active session as paused. The runner detects this on"
            echo "the next iteration boundary and halts cleanly until 'loki resume'."
            echo ""
            echo "Options:"
            echo "  --help, -h   Show this help and exit"
            echo ""
            echo "See also: loki resume, loki stop"
            return 0
            ;;
    esac
    if [ ! -d "$LOKI_DIR" ]; then
        echo -e "${YELLOW}No .loki directory found.${NC}"
        echo "No active session to pause."
        exit 0
    fi

    if is_session_running; then
        # Warn if running in perpetual mode where PAUSE is auto-cleared (#84)
        local current_mode="${LOKI_AUTONOMY_MODE:-perpetual}"
        local perpetual_flag="${LOKI_PERPETUAL_MODE:-false}"
        if [ "$current_mode" = "perpetual" ] || [ "$perpetual_flag" = "true" ]; then
            echo -e "${YELLOW}Warning: Session is running in perpetual mode.${NC}"
            echo -e "${YELLOW}PAUSE signals are auto-cleared in perpetual mode and will be ignored.${NC}"
            echo -e "Use ${CYAN}loki stop${NC} to halt a perpetual session, or switch autonomy mode first:"
            echo -e "  ${DIM}loki config set autonomy_mode checkpoint${NC}"
        fi
        touch "$LOKI_DIR/PAUSE"
        # Emit session pause event
        emit_event session cli pause "reason=user_requested"
        # Emit user preference for pause action (SYN-018)
        emit_learning_signal user_preference \
            --source cli \
            --action "session_control" \
            --key "control_action" \
            --value "pause" \
            --rejected '["stop", "continue"]' \
            --confidence 0.9
        echo -e "${YELLOW}PAUSE signal sent. Execution will pause after current task.${NC}"
        echo -e "${DIM}Resume with: loki resume${NC}"
    else
        echo -e "${YELLOW}No active session running.${NC}"
        if [ -f "$LOKI_DIR/PAUSE" ]; then
            echo "PAUSE signal already present."
        else
            echo "Start a session with: loki start"
        fi
    fi
}

# Resume paused execution
cmd_resume() {
    # v7.6.2 B-13 fix: --help must print help, not act on session state.
    case "${1:-}" in
        --help|-h|help)
            echo -e "${BOLD}Loki Mode -- resume paused session${NC}"
            echo ""
            echo "Usage: loki resume [options]"
            echo ""
            echo "Clears the paused flag and lets the autonomous runner continue"
            echo "from where 'loki pause' stopped it."
            echo ""
            echo "Options:"
            echo "  --help, -h   Show this help and exit"
            echo ""
            echo "See also: loki pause, loki start"
            return 0
            ;;
    esac
    if [ ! -d "$LOKI_DIR" ]; then
        echo -e "${YELLOW}No .loki directory found.${NC}"
        echo "No session to resume. Start a session with: loki start"
        exit 0
    fi

    # Check if there is anything to resume before touching signal files
    local has_pause_signal=false
    local has_stop_signal=false
    [ -f "$LOKI_DIR/PAUSE" ] && has_pause_signal=true
    [ -f "$LOKI_DIR/STOP" ] && has_stop_signal=true
    local session_running=false
    is_session_running && session_running=true

    # If nothing is paused/stopped and no session is running, exit early
    if ! $has_pause_signal && ! $has_stop_signal && ! $session_running; then
        echo -e "${YELLOW}No session to resume.${NC}"
        echo "Start a session with: loki start"
        exit 0
    fi

    # Clear the signal file if one exists
    local removed_signal=""
    if $has_pause_signal; then
        rm -f "$LOKI_DIR/PAUSE"
        removed_signal="PAUSE"
    elif $has_stop_signal; then
        rm -f "$LOKI_DIR/STOP"
        removed_signal="STOP"
    fi

    if [ -n "$removed_signal" ]; then
        # Emit session resume event
        emit_event session cli resume "cleared_signal=$removed_signal"
        # Emit success pattern for resume (SYN-018)
        emit_learning_signal success_pattern \
            --source cli \
            --action "session_resume" \
            --pattern-name "resume_after_$removed_signal" \
            --action-sequence '["check_signals", "clear_signal", "resume"]' \
            --outcome success \
            --confidence 0.85
        if $session_running; then
            echo -e "${GREEN}$removed_signal signal cleared. Session will resume automatically.${NC}"
        else
            echo -e "${GREEN}$removed_signal signal cleared.${NC}"
            echo "Session is not running. Start with: loki start"
        fi
    else
        # Session is running but no signals to clear
        echo -e "${CYAN}Session is running normally. Nothing to resume.${NC}"
    fi
}

# Show current status
cmd_status() {
    # Check for flags
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --json) cmd_status_json; return $? ;;
            --help|-h) echo "Usage: loki status [--json]"; return 0 ;;
            *)
                echo -e "${RED}Unknown flag: $1${NC}"
                echo "Usage: loki status [--json]"
                return 1
                ;;
        esac
    done

    require_jq || return 1

    if [ ! -d "$LOKI_DIR" ]; then
        echo -e "${BOLD}Loki Mode Status${NC}"
        echo ""
        echo -e "${YELLOW}No active session found.${NC}"
        echo "Loki Mode has not been initialized in this directory."
        echo ""
        echo "To start a session:"
        echo "  loki start <prd>              - Start with a PRD file"
        echo "  loki start                    - Start without a PRD"
        echo ""
        echo -e "${DIM}Current directory: $(pwd)${NC}"
        return 0
    fi

    echo -e "${BOLD}Loki Mode Status${NC}"
    echo ""

    # Show current provider
    local saved_provider=""
    if [ -f "$LOKI_DIR/state/provider" ]; then
        saved_provider=$(cat "$LOKI_DIR/state/provider" 2>/dev/null)
    fi
    local current_provider="${saved_provider:-${LOKI_PROVIDER:-claude}}"
    # Show provider with capability
    local capability="full features"
    case "$current_provider" in
        codex|aider)
            capability="degraded mode"
            ;;
        cline)
            capability="near-full mode"
            ;;
    esac
    echo -e "${CYAN}Provider:${NC} $current_provider ($capability)"
    echo -e "${DIM}  Switch with: loki provider set <claude|codex|cline|aider>${NC}"
    echo ""

    # Show running sessions (v6.4.0 - concurrent session support)
    local running_sessions=()
    while IFS= read -r line; do
        [ -n "$line" ] && running_sessions+=("$line")
    done < <(list_running_sessions 2>/dev/null)

    if [ ${#running_sessions[@]} -gt 0 ]; then
        echo -e "${GREEN}Active Sessions: ${#running_sessions[@]}${NC}"
        for entry in "${running_sessions[@]}"; do
            local sid="${entry%%:*}"
            local spid="${entry#*:}"
            if [ "$sid" = "global" ]; then
                echo -e "  ${CYAN}[global]${NC} PID $spid"
            else
                echo -e "  ${CYAN}[#$sid]${NC} PID $spid"
            fi
        done
        echo ""
        echo -e "${DIM}  Stop specific: loki stop <session-id>${NC}"
        echo -e "${DIM}  Stop all:      loki stop${NC}"
        echo ""
    fi

    # Check for signals first (more prominent)
    if [ -f "$LOKI_DIR/PAUSE" ]; then
        echo -e "${YELLOW}Status: PAUSED${NC}"
        echo -e "${DIM}  Resume with: loki resume${NC}"
        echo ""
    elif [ -f "$LOKI_DIR/STOP" ]; then
        echo -e "${RED}Status: STOPPED${NC}"
        echo -e "${DIM}  Clear with: loki resume${NC}"
        echo ""
    fi

    # Check status file
    if [ -f "$LOKI_DIR/STATUS.txt" ]; then
        echo -e "${CYAN}Session Info:${NC}"
        cat "$LOKI_DIR/STATUS.txt"
        echo ""
    fi

    # Check orchestrator state
    if [ -f "$LOKI_DIR/state/orchestrator.json" ]; then
        echo -e "${CYAN}Orchestrator State:${NC}"
        jq -r '.currentPhase // "unknown"' "$LOKI_DIR/state/orchestrator.json" 2>/dev/null || echo "unknown"
    fi

    # Check pending tasks
    if [ -f "$LOKI_DIR/queue/pending.json" ]; then
        local task_count=$(jq 'if type == "array" then length elif .tasks then .tasks | length else 0 end' "$LOKI_DIR/queue/pending.json" 2>/dev/null || echo "0")
        echo -e "${CYAN}Pending Tasks:${NC} $task_count"
    fi

    # Check budget with visual gauge
    if [ -f "$LOKI_DIR/metrics/budget.json" ]; then
        local budget_limit budget_used budget_remaining
        budget_limit=$(LOKI_BUDGET_FILE="$LOKI_DIR/metrics/budget.json" python3 -c "import json,os; d=json.load(open(os.environ['LOKI_BUDGET_FILE'])); print(d.get('budget_limit', 0))" 2>/dev/null || echo "0")
        budget_used=$(LOKI_BUDGET_FILE="$LOKI_DIR/metrics/budget.json" python3 -c "import json,os; d=json.load(open(os.environ['LOKI_BUDGET_FILE'])); print(round(d.get('budget_used', 0), 2))" 2>/dev/null || echo "0")
        if [ "$budget_limit" != "0" ]; then
            echo -e "${CYAN}Budget:${NC} \$$budget_used / \$$budget_limit"
            # Show budget gauge if TUI is loaded
            if type context_gauge &>/dev/null; then
                local used_cents remaining_cents limit_cents
                used_cents=$(echo "scale=0; $budget_used * 100" | bc 2>/dev/null || echo "0")
                limit_cents=$(echo "scale=0; $budget_limit * 100" | bc 2>/dev/null || echo "100")
                # Render as integer cents for the gauge
                used_cents=${used_cents%.*}
                limit_cents=${limit_cents%.*}
                [ -z "$used_cents" ] && used_cents=0
                [ -z "$limit_cents" ] && limit_cents=100
                context_gauge "$used_cents" "$limit_cents" "Budget"
            fi
        else
            echo -e "${CYAN}Cost:${NC} \$$budget_used (no limit)"
        fi
    fi

    # Context window usage (token tracking)
    if [ -f "$LOKI_DIR/state/context-usage.json" ]; then
        local ctx_used ctx_total
        ctx_total=$(_LOKI_CTX_FILE="$LOKI_DIR/state/context-usage.json" python3 -c "import json, os; d=json.load(open(os.environ['_LOKI_CTX_FILE'])); print(d.get('window_size', 200000))" 2>/dev/null || echo "200000")
        ctx_used=$(_LOKI_CTX_FILE="$LOKI_DIR/state/context-usage.json" python3 -c "import json, os; d=json.load(open(os.environ['_LOKI_CTX_FILE'])); print(d.get('used_tokens', 0))" 2>/dev/null || echo "0")
        if type context_gauge &>/dev/null; then
            context_gauge "$ctx_used" "$ctx_total" "Context"
        else
            local ctx_pct=0
            if [ "$ctx_total" -gt 0 ] 2>/dev/null; then
                ctx_pct=$((ctx_used * 100 / ctx_total))
            fi
            echo -e "${CYAN}Context:${NC} ${ctx_pct}% (${ctx_used} / ${ctx_total} tokens)"
        fi
    fi

    # Check dashboard
    if [ -f "$LOKI_DIR/dashboard/dashboard.pid" ]; then
        local pid=$(cat "$LOKI_DIR/dashboard/dashboard.pid" 2>/dev/null)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            local port=${LOKI_DASHBOARD_PORT:-57374}
            echo -e "${CYAN}Dashboard:${NC} http://127.0.0.1:$port/"
        fi
    fi

    echo ""
    echo -e "${DIM}  Tip: loki context show   - detailed token breakdown${NC}"
    echo -e "${DIM}  Tip: loki code overview   - codebase intelligence${NC}"
}

# JSON output for loki status --json
cmd_status_json() {
    local skill_dir="$SKILL_DIR"
    local loki_dir="$LOKI_DIR"
    local dashboard_port="${LOKI_DASHBOARD_PORT:-57374}"
    local env_provider="${LOKI_PROVIDER:-claude}"

    python3 -c "
import json, os, sys, time

skill_dir = sys.argv[1]
loki_dir = sys.argv[2]
dashboard_port = sys.argv[3]
env_provider = sys.argv[4]
result = {}

# UT2-13: Helper -- read cli-provider marker written by --provider flag dispatch.
# v7.7.11 (council fix): file format is now '<provider>:<unix_ts>:<pid>' so we
# can drop the marker when the originating process is gone (SIGKILL / crash
# paths that bypassed our cleanup hooks). Also validates provider name against
# canonical set so a typo writing the file can't pollute status output.
# Legacy format '<provider>:<unix_ts>' still parses (no PID liveness check).
# Returns (provider_name, True) when valid; (None, False) otherwise.
def _read_cli_provider(loki_dir):
    cli_file = os.path.join(loki_dir, 'state', 'cli-provider')
    if not os.path.isfile(cli_file):
        return None, False
    try:
        content = open(cli_file).read().strip()
        parts = content.split(':')
        if len(parts) < 2:
            return None, False
        prov = parts[0]
        ts = int(parts[1])
        if prov not in ('claude', 'codex', 'cline', 'aider'):
            return None, False
        if time.time() - ts > 86400:
            return None, False
        # If a PID is present, drop the marker when that process is gone.
        if len(parts) >= 3:
            try:
                pid = int(parts[2])
                if pid > 0:
                    os.kill(pid, 0)
            except (ValueError, ProcessLookupError, PermissionError):
                # PID missing/dead/cross-uid -> treat as stale.
                return None, False
        return prov, True
    except Exception:
        return None, False

# Version
version_file = os.path.join(skill_dir, 'VERSION')
if os.path.isfile(version_file):
    with open(version_file) as f:
        result['version'] = f.read().strip()
else:
    result['version'] = 'unknown'

# Check if session exists
if not os.path.isdir(loki_dir):
    result['status'] = 'inactive'
    result['phase'] = None
    result['iteration'] = 0
    # UT2-13: CLI check first (inactive path -- loki_dir may not exist yet).
    _cli_prov, _cli_set = _read_cli_provider(loki_dir)
    if _cli_set:
        result['provider'] = _cli_prov
        result['provider_source'] = 'cli'
    elif os.environ.get('LOKI_PROVIDER', ''):
        result['provider'] = env_provider
        result['provider_source'] = 'env'
    else:
        result['provider'] = 'claude'
        result['provider_source'] = 'default'
    result['dashboard_url'] = None
    result['pid'] = None
    result['elapsed_time'] = 0
    result['task_counts'] = {'total': 0, 'completed': 0, 'failed': 0, 'pending': 0}
    result['phase1'] = {
        'findings_iters': 0,
        'learnings_count': 0,
        'escalations_count': 0,
        'pause_signal': False,
        'gate_failure_counts': {},
    }
    print(json.dumps(result, indent=2))
    sys.exit(0)

# Status from signals and session.json
if os.path.isfile(os.path.join(loki_dir, 'PAUSE')):
    result['status'] = 'paused'
elif os.path.isfile(os.path.join(loki_dir, 'STOP')):
    result['status'] = 'stopped'
else:
    session_file = os.path.join(loki_dir, 'session.json')
    if os.path.isfile(session_file):
        try:
            with open(session_file) as f:
                session = json.load(f)
            result['status'] = session.get('status', 'unknown')
        except Exception:
            result['status'] = 'unknown'
    else:
        result['status'] = 'unknown'

# Phase and iteration from dashboard-state.json
ds_file = os.path.join(loki_dir, 'dashboard-state.json')
if os.path.isfile(ds_file):
    try:
        with open(ds_file) as f:
            ds = json.load(f)
        result['phase'] = ds.get('phase', ds.get('currentPhase'))
        result['iteration'] = ds.get('iteration', ds.get('currentIteration', 0))
    except Exception:
        result['phase'] = None
        result['iteration'] = 0
else:
    orch_file = os.path.join(loki_dir, 'state', 'orchestrator.json')
    if os.path.isfile(orch_file):
        try:
            with open(orch_file) as f:
                orch = json.load(f)
            result['phase'] = orch.get('currentPhase')
            result['iteration'] = orch.get('currentIteration', 0)
        except Exception:
            result['phase'] = None
            result['iteration'] = 0
    else:
        result['phase'] = None
        result['iteration'] = 0

# Provider + provider_source (v7.7.2 B-5 clarity: surface WHY this
# value won the resolution race: 'cli' > 'saved' > 'env' > 'default').
# UT2-13: cli source is checked FIRST -- overrides saved/env/default.
_cli_prov, _cli_set = _read_cli_provider(loki_dir)
provider_file = os.path.join(loki_dir, 'state', 'provider')
if os.path.isfile(provider_file):
    try:
        with open(provider_file) as f:
            saved = f.read().strip()
    except OSError:
        saved = ''
else:
    saved = ''
env_set = os.environ.get('LOKI_PROVIDER', '') != ''
if _cli_set:
    result['provider'] = _cli_prov
    result['provider_source'] = 'cli'
elif saved:
    result['provider'] = saved
    result['provider_source'] = 'saved'
elif env_set:
    result['provider'] = env_provider
    result['provider_source'] = 'env'
else:
    result['provider'] = 'claude'
    result['provider_source'] = 'default'

# PID
pid_file = os.path.join(loki_dir, 'loki.pid')
if os.path.isfile(pid_file):
    try:
        with open(pid_file) as f:
            result['pid'] = int(f.read().strip())
    except (ValueError, Exception):
        result['pid'] = None
else:
    result['pid'] = None

# Elapsed time from session.json
session_file = os.path.join(loki_dir, 'session.json')
if os.path.isfile(session_file):
    try:
        with open(session_file) as f:
            session = json.load(f)
        start_time = session.get('start_time', session.get('startTime'))
        if start_time:
            if isinstance(start_time, (int, float)):
                result['elapsed_time'] = int(time.time() - start_time)
            else:
                from datetime import datetime
                dt = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
                result['elapsed_time'] = int(time.time() - dt.timestamp())
        else:
            result['elapsed_time'] = 0
    except Exception:
        result['elapsed_time'] = 0
else:
    result['elapsed_time'] = 0

# Dashboard URL. Check both the project-local in-build dashboard and the
# standalone dashboard (~/.loki/dashboard) and honor saved scheme/host/port
# side-files, so --json reports the right URL regardless of which started it.
_dash_candidates = [
    os.path.join(loki_dir, 'dashboard', 'dashboard.pid'),
    os.path.expanduser(os.path.join('~', '.loki', 'dashboard', 'dashboard.pid')),
]
dashboard_pid_file = next((p for p in _dash_candidates if os.path.isfile(p)), _dash_candidates[0])
dashboard_url = None
if os.path.isfile(dashboard_pid_file):
    try:
        with open(dashboard_pid_file) as f:
            dpid = int(f.read().strip())
        os.kill(dpid, 0)
        _dd = os.path.dirname(dashboard_pid_file)
        def _side(name, default):
            p = os.path.join(_dd, name)
            try:
                return open(p).read().strip() if os.path.isfile(p) else default
            except OSError:
                return default
        _scheme = _side('scheme', 'http')
        _host = _side('host', '127.0.0.1')
        _port = _side('port', str(dashboard_port))
        if _host == '0.0.0.0':
            _host = '127.0.0.1'
        dashboard_url = _scheme + '://' + _host + ':' + _port + '/'
    except (ProcessLookupError, PermissionError, ValueError, Exception):
        pass
result['dashboard_url'] = dashboard_url

# Task counts from queue files
task_counts = {'total': 0, 'completed': 0, 'failed': 0, 'pending': 0}
queue_dir = os.path.join(loki_dir, 'queue')
if os.path.isdir(queue_dir):
    for name, key in [('pending.json', 'pending'), ('completed.json', 'completed'), ('failed.json', 'failed')]:
        fpath = os.path.join(queue_dir, name)
        if os.path.isfile(fpath):
            try:
                with open(fpath) as f:
                    data = json.load(f)
                if isinstance(data, list):
                    task_counts[key] = len(data)
                elif isinstance(data, dict) and 'tasks' in data:
                    task_counts[key] = len(data['tasks'])
            except Exception:
                pass
    task_counts['total'] = task_counts['pending'] + task_counts['completed'] + task_counts['failed']
result['task_counts'] = task_counts

# v7.5.5 (#204): Phase 1 (RARV-C closure) artifact summary -- mirror of the
# Bun route in loki-ts/src/commands/status.ts STATUS_JSON_PY. Keep in sync.
phase1 = {
    'findings_iters': 0,
    'learnings_count': 0,
    'escalations_count': 0,
    'pause_signal': False,
    'gate_failure_counts': {},
}
state_dir = os.path.join(loki_dir, 'state')
if os.path.isdir(state_dir):
    try:
        phase1['findings_iters'] = sum(
            1 for n in os.listdir(state_dir)
            if n.startswith('findings-') and n.endswith('.json')
        )
    except Exception:
        pass
    learnings_file = os.path.join(state_dir, 'relevant-learnings.json')
    if os.path.isfile(learnings_file):
        try:
            with open(learnings_file) as f:
                learnings = json.load(f)
            if isinstance(learnings, list):
                phase1['learnings_count'] = len(learnings)
            elif isinstance(learnings, dict):
                entries = learnings.get('entries')
                if isinstance(entries, list):
                    phase1['learnings_count'] = len(entries)
        except Exception:
            pass
escalations_dir = os.path.join(loki_dir, 'escalations')
if os.path.isdir(escalations_dir):
    try:
        phase1['escalations_count'] = sum(
            1 for n in os.listdir(escalations_dir) if n.endswith('.md')
        )
    except Exception:
        pass
phase1['pause_signal'] = os.path.isfile(os.path.join(loki_dir, 'PAUSE'))
gate_count_file = os.path.join(loki_dir, 'quality', 'gate-failure-count.json')
if os.path.isfile(gate_count_file):
    try:
        with open(gate_count_file) as f:
            gc = json.load(f)
        if isinstance(gc, dict):
            phase1['gate_failure_counts'] = {
                k: v for k, v in gc.items() if isinstance(v, (int, float))
            }
    except Exception:
        pass
result['phase1'] = phase1

print(json.dumps(result, indent=2))
" "$skill_dir" "$loki_dir" "$dashboard_port" "$env_provider"

    if [ $? -ne 0 ]; then
        echo '{"error": "Failed to generate JSON status. Ensure python3 is available."}' >&2
        return 1
    fi
}

# Session statistics
cmd_stats() {
    # v7.6.2 B-13 fix: --help must print help, not render the stats panel.
    case "${1:-}" in
        --help|-h|help)
            echo -e "${BOLD}Loki Mode -- session statistics${NC}"
            echo ""
            echo "Usage: loki stats [options]"
            echo ""
            echo "Prints session-level statistics: tasks completed, iterations,"
            echo "tokens used, cost, council votes, gate verdicts."
            echo ""
            echo "Options:"
            echo "  --json          JSON output (machine-readable)"
            echo "  --efficiency    Include cost/token efficiency breakdown"
            echo "  --help, -h      Show this help and exit"
            echo ""
            echo "See also: loki kpis, loki status"
            return 0
            ;;
    esac
    local show_json=false
    local show_efficiency=false

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --json) show_json=true; shift ;;
            --efficiency) show_efficiency=true; shift ;;
            *) shift ;;
        esac
    done

    if [ ! -d "$LOKI_DIR" ]; then
        if [ "$show_json" = true ]; then
            echo '{"error": "No active session"}'
        else
            echo -e "${YELLOW}No active session found.${NC}"
            echo "Start a session with: loki start <prd>"
        fi
        return 0
    fi

    local loki_dir="$LOKI_DIR"
    local eff_flag="$show_efficiency"
    local json_flag="$show_json"

    python3 -c "
import json, os, sys, glob

loki_dir = sys.argv[1]
show_efficiency = sys.argv[2] == 'true'
show_json = sys.argv[3] == 'true'

# --- Gather data ---

# Session state (orchestrator)
phase = 'N/A'
iteration_count = 0
orch_file = os.path.join(loki_dir, 'state', 'orchestrator.json')
if os.path.isfile(orch_file):
    try:
        with open(orch_file) as f:
            orch = json.load(f)
        phase = orch.get('currentPhase', 'N/A')
        iteration_count = orch.get('currentIteration', 0)
    except Exception:
        pass

# Per-iteration metrics
eff_dir = os.path.join(loki_dir, 'metrics', 'efficiency')
iterations = []
if os.path.isdir(eff_dir):
    for path in sorted(glob.glob(os.path.join(eff_dir, 'iteration-*.json'))):
        try:
            with open(path) as f:
                iterations.append(json.load(f))
        except Exception:
            pass

# If we have iteration files, use their count (more accurate)
if iterations:
    iteration_count = max(iteration_count, len(iterations))

total_input = sum(it.get('input_tokens', 0) for it in iterations)
total_output = sum(it.get('output_tokens', 0) for it in iterations)
total_tokens = total_input + total_output
total_cost = sum(it.get('cost_usd', 0) for it in iterations)
total_duration = sum(it.get('duration_seconds', 0) for it in iterations)

# Budget
budget_limit = 0
budget_used = 0
budget_file = os.path.join(loki_dir, 'metrics', 'budget.json')
if os.path.isfile(budget_file):
    try:
        with open(budget_file) as f:
            bd = json.load(f)
        budget_limit = bd.get('budget_limit', 0)
        budget_used = bd.get('budget_used', 0)
    except Exception:
        pass

# Quality gates
gates_passed = 0
gates_total = 0
gates_file = os.path.join(loki_dir, 'state', 'quality-gates.json')
if os.path.isfile(gates_file):
    try:
        with open(gates_file) as f:
            gates = json.load(f)
        if isinstance(gates, dict):
            for k, v in gates.items():
                if isinstance(v, dict):
                    gates_total += 1
                    if v.get('passed') or v.get('status') == 'passed':
                        gates_passed += 1
                elif isinstance(v, bool):
                    gates_total += 1
                    if v:
                        gates_passed += 1
        elif isinstance(gates, list):
            for g in gates:
                gates_total += 1
                if isinstance(g, dict) and (g.get('passed') or g.get('status') == 'passed'):
                    gates_passed += 1
                elif g is True:
                    gates_passed += 1
    except Exception:
        pass

# Gate failures
gate_failures = {}
gf_file = os.path.join(loki_dir, 'quality', 'gate-failure-count.json')
if os.path.isfile(gf_file):
    try:
        with open(gf_file) as f:
            gate_failures = json.load(f)
        if not isinstance(gate_failures, dict):
            gate_failures = {}
    except Exception:
        pass

# Code reviews
reviews_total = 0
reviews_approved = 0
reviews_revision = 0
quality_dir = os.path.join(loki_dir, 'quality')
if os.path.isdir(quality_dir):
    for fname in os.listdir(quality_dir):
        if not fname.endswith('.json') or fname == 'gate-failure-count.json':
            continue
        fpath = os.path.join(quality_dir, fname)
        try:
            with open(fpath) as f:
                rev = json.load(f)
            if isinstance(rev, dict) and ('verdict' in rev or 'approved' in rev or 'reviewers' in rev):
                reviews_total += 1
                verdict = rev.get('verdict', '').lower()
                if rev.get('approved') or verdict in ('approved', 'approve', 'pass'):
                    reviews_approved += 1
                elif verdict in ('revision', 'revise', 'changes_requested', 'reject'):
                    reviews_revision += 1
        except Exception:
            pass

# --- Format duration helper ---
def fmt_duration(secs):
    secs = int(secs)
    if secs < 60:
        return f'{secs}s'
    hours = secs // 3600
    mins = (secs % 3600) // 60
    secs_rem = secs % 60
    if hours > 0:
        return f'{hours}h {mins:02d}m'
    return f'{mins}m {secs_rem:02d}s'

def fmt_number(n):
    return f'{n:,}'

# --- JSON output ---
if show_json:
    output = {
        'session': {
            'iterations': iteration_count,
            'duration_seconds': total_duration,
            'phase': phase
        },
        'tokens': {
            'input': total_input,
            'output': total_output,
            'total': total_tokens,
            'cost_usd': round(total_cost, 2)
        },
        'quality': {
            'gates_passed': gates_passed,
            'gates_total': gates_total,
            'reviews_total': reviews_total,
            'reviews_approved': reviews_approved,
            'reviews_revision': reviews_revision,
            'gate_failures': gate_failures
        },
        'efficiency': {
            'avg_tokens_per_iteration': round(total_tokens / iteration_count) if iteration_count > 0 else 0,
            'avg_cost_per_iteration': round(total_cost / iteration_count, 2) if iteration_count > 0 else 0,
            'avg_duration_per_iteration': round(total_duration / iteration_count, 1) if iteration_count > 0 else 0
        },
        'budget': {
            'used': round(budget_used, 2),
            'limit': budget_limit,
            'percent': round((budget_used / budget_limit) * 100, 1) if budget_limit > 0 else 0
        }
    }
    if show_efficiency:
        output['iterations'] = []
        for i, it in enumerate(iterations, 1):
            output['iterations'].append({
                'number': i,
                'input_tokens': it.get('input_tokens', 0),
                'output_tokens': it.get('output_tokens', 0),
                'cost_usd': round(it.get('cost_usd', 0), 2),
                'duration_seconds': it.get('duration_seconds', 0)
            })
    print(json.dumps(output, indent=2))
    sys.exit(0)

# --- Text output ---
print('Loki Mode Session Statistics')
print('============================')
print()

# Session
print('Session')
print(f'  Iterations completed: {iteration_count}')
print(f'  Duration: {fmt_duration(total_duration)}')
print(f'  Current phase: {phase}')
print()

# Token Usage
print('Token Usage')
if iterations:
    print(f'  Input tokens:  {fmt_number(total_input)}')
    print(f'  Output tokens: {fmt_number(total_output)}')
    print(f'  Total tokens:  {fmt_number(total_tokens)}')
    print(f'  Estimated cost: \${total_cost:.2f}')
else:
    print('  N/A (no iteration metrics found)')
print()

# Quality Gates
print('Quality Gates')
if gates_total > 0:
    pct = round((gates_passed / gates_total) * 100) if gates_total > 0 else 0
    print(f'  Gates passed: {gates_passed}/{gates_total} ({pct}%)')
else:
    print('  Gates passed: N/A')
if reviews_total > 0:
    parts = []
    if reviews_approved > 0:
        parts.append(f'{reviews_approved} approved')
    if reviews_revision > 0:
        parts.append(f'{reviews_revision} revision requested')
    detail = ', '.join(parts) if parts else 'N/A'
    print(f'  Code reviews: {reviews_total} ({detail})')
if gate_failures:
    failure_parts = [f'{k} ({v})' for k, v in gate_failures.items() if v > 0]
    if failure_parts:
        sep = ', '
        print(f'  Gate failures: {sep.join(failure_parts)}')
print()

# Efficiency
print('Efficiency')
if iteration_count > 0 and iterations:
    avg_tokens = round(total_tokens / iteration_count)
    avg_cost = total_cost / iteration_count
    avg_dur = total_duration / iteration_count
    print(f'  Avg tokens/iteration: {fmt_number(avg_tokens)}')
    print(f'  Avg cost/iteration: \${avg_cost:.2f}')
    print(f'  Avg duration/iteration: {fmt_duration(avg_dur)}')
else:
    print('  N/A (no iteration metrics found)')
print()

# Budget
print('Budget')
if budget_limit > 0:
    pct = round((budget_used / budget_limit) * 100, 1)
    print(f'  Used: \${budget_used:.2f} / \${budget_limit:.2f} ({pct}%)')
elif budget_used > 0:
    print(f'  Used: \${budget_used:.2f} (no limit set)')
else:
    print('  N/A')

# Per-iteration breakdown
if show_efficiency and iterations:
    print()
    print('Per-Iteration Breakdown')
    for i, it in enumerate(iterations, 1):
        inp = fmt_number(it.get('input_tokens', 0))
        out = fmt_number(it.get('output_tokens', 0))
        cost = it.get('cost_usd', 0)
        dur = fmt_duration(it.get('duration_seconds', 0))
        print(f'  #{i:<3} input: {inp:<10} output: {out:<10} cost: \${cost:.2f}  time: {dur}')
" "$loki_dir" "$eff_flag" "$json_flag"
}

# Provider management
cmd_provider() {
    local subcommand="${1:-show}"
    shift || true

    case "$subcommand" in
        show|current)
            cmd_provider_show "$@"
            ;;
        set)
            cmd_provider_set "$@"
            ;;
        list)
            cmd_provider_list
            ;;
        info)
            cmd_provider_info "$@"
            ;;
        models)
            cmd_provider_models
            ;;
        *)
            echo -e "${BOLD}Loki Mode Provider Management${NC}"
            echo ""
            echo "Usage: loki provider <command>"
            echo ""
            echo "Commands:"
            echo "  show     Show current provider (default)"
            echo "  set      Set provider for this project"
            echo "  list     List available providers"
            echo "  info     Show provider details"
            echo "  models   Show resolved model configuration for all providers"
            echo ""
            echo "Precedence (v7.7.2 -- highest wins):"
            echo "  1. loki start --provider NAME         CLI flag (per-invocation)"
            echo "  2. .loki/state/provider               saved value (per-project)"
            echo "  3. LOKI_PROVIDER env var              shell session default"
            echo "  4. claude                             built-in default"
            echo ""
            echo "  Note: 'loki status' reflects the SAVED provider, not the env var."
            echo "  Run 'loki provider set <name>' to make a project-level choice persist."
            echo ""
            echo "Examples:"
            echo "  loki provider show"
            echo "  loki provider set claude"
            echo "  loki provider set codex"
            echo "  loki provider list"
            echo "  loki provider info codex"
            echo "  loki provider models"
            ;;
    esac
}

cmd_provider_show() {
    local positional_arg="${1:-}"

    local saved_provider=""
    if [ -f "$LOKI_DIR/state/provider" ]; then
        saved_provider=$(cat "$LOKI_DIR/state/provider" 2>/dev/null)
    fi

    local current="${positional_arg:-${saved_provider:-${LOKI_PROVIDER:-claude}}}"

    echo -e "${BOLD}Current Provider${NC}"
    echo ""
    echo -e "${CYAN}Provider:${NC} $current"

    # Show capability status
    case "$current" in
        claude)
            echo -e "${GREEN}Status:${NC}   Full features (subagents, parallel, MCP)"
            ;;
        cline)
            echo -e "${GREEN}Status:${NC}   Near-full mode (subagents, MCP, 12+ providers)"
            ;;
        codex|aider)
            echo -e "${YELLOW}Status:${NC}   Degraded mode (sequential only)"
            ;;
    esac

    if [ -n "$saved_provider" ]; then
        echo -e "${DIM}(saved in .loki/state/provider)${NC}"
    else
        echo -e "${DIM}(default - not explicitly set)${NC}"
    fi

    echo ""
    echo -e "Switch provider: ${CYAN}loki provider set <name>${NC}"
    echo -e "Available:       ${CYAN}loki provider list${NC}"
}

cmd_provider_set() {
    local new_provider="${1:-}"

    if [ -z "$new_provider" ]; then
        echo -e "${RED}Error: Provider name required${NC}"
        echo "Usage: loki provider set <claude|codex|cline|aider>"
        exit 1
    fi

    # Validate provider
    case "$new_provider" in
        gemini)
            echo -e "${RED}Error: Provider 'gemini' is deprecated as of v7.5.18 and has been removed.${NC}"
            echo "Active providers: claude, codex, cline, aider"
            exit 1
            ;;
        claude|codex|cline|aider)
            ;;
        *)
            echo -e "${RED}Error: Invalid provider '$new_provider'${NC}"
            echo "Valid providers: claude, codex, cline, aider"
            exit 1
            ;;
    esac

    # Check if CLI is installed
    if ! command -v "$new_provider" &> /dev/null; then
        echo -e "${YELLOW}Warning: '$new_provider' CLI not found in PATH${NC}"
        echo ""
        case "$new_provider" in
            claude)
                echo "Install: npm install -g @anthropic-ai/claude-code"
                ;;
            codex)
                echo "Install: npm install -g @openai/codex"
                ;;
            cline)
                echo "Install: npm install -g cline"
                ;;
            aider)
                echo "Install: pip install aider-chat"
                ;;
        esac
        echo ""
        echo -e "${DIM}Setting provider anyway...${NC}"
    fi

    # Save provider
    mkdir -p "$LOKI_DIR/state"
    echo "$new_provider" > "$LOKI_DIR/state/provider"

    echo -e "${GREEN}Provider set to: $new_provider${NC}"
    echo ""
    echo "This will be used for all future runs in this project."
    echo "Override temporarily with: loki start --provider <name>"
}

cmd_provider_list() {
    echo -e "${BOLD}Available Providers${NC}"
    echo ""

    local saved_provider=""
    if [ -f "$LOKI_DIR/state/provider" ]; then
        saved_provider=$(cat "$LOKI_DIR/state/provider" 2>/dev/null)
    fi
    local current="${saved_provider:-${LOKI_PROVIDER:-claude}}"

    # Check which CLIs are installed
    local claude_status="${RED}not installed${NC}"
    local codex_status="${RED}not installed${NC}"
    local cline_status="${RED}not installed${NC}"
    local aider_status="${RED}not installed${NC}"

    if command -v claude &> /dev/null; then
        claude_status="${GREEN}installed${NC}"
    fi
    if command -v codex &> /dev/null; then
        codex_status="${GREEN}installed${NC}"
    fi
    if command -v cline &> /dev/null; then
        cline_status="${GREEN}installed${NC}"
    fi
    if command -v aider &> /dev/null; then
        aider_status="${GREEN}installed${NC}"
    fi

    # Display providers
    local marker=""
    [ "$current" = "claude" ] && marker=" ${CYAN}(current)${NC}"
    echo -e "  claude  - Claude Code (Anthropic)     $claude_status$marker"

    marker=""
    [ "$current" = "codex" ] && marker=" ${CYAN}(current)${NC}"
    echo -e "  codex   - Codex CLI (OpenAI)          $codex_status$marker"

    marker=""
    [ "$current" = "cline" ] && marker=" ${CYAN}(current)${NC}"
    echo -e "  cline   - Cline (multi-provider)      $cline_status$marker"

    marker=""
    [ "$current" = "aider" ] && marker=" ${CYAN}(current)${NC}"
    echo -e "  aider   - Aider (terminal pair prog)  $aider_status$marker"

    echo ""
    echo -e "Set provider: ${CYAN}loki provider set <name>${NC}"
}

cmd_provider_info() {
    local provider="${1:-${LOKI_PROVIDER:-claude}}"

    echo -e "${BOLD}Provider: $provider${NC}"
    echo ""

    case "$provider" in
        claude)
            echo "Name:        Claude Code"
            echo "Vendor:      Anthropic"
            echo "CLI:         claude"
            echo "Flag:        --dangerously-skip-permissions"
            echo ""
            echo "Features:"
            echo "  - Full autonomous mode"
            echo "  - Task tool for subagents"
            echo "  - Parallel execution"
            echo "  - MCP server support"
            echo ""
            echo "Status:      Full features"
            ;;
        codex)
            echo "Name:        Codex CLI"
            echo "Vendor:      OpenAI"
            echo "CLI:         codex"
            echo "Flag:        --full-auto"
            echo ""
            echo "Features:"
            echo "  - Autonomous mode"
            echo "  - Sequential only (no subagents)"
            echo ""
            echo "Status:      Degraded mode"
            ;;
        cline)
            echo "Name:        Cline CLI (Multi-Provider)"
            echo "Vendor:      Cline (supports 12+ providers)"
            echo "CLI:         cline"
            echo "Flag:        -y (YOLO mode)"
            echo ""
            echo "Features:"
            echo "  - Autonomous mode"
            echo "  - Subagent support"
            echo "  - MCP server support"
            echo "  - 12+ model providers"
            echo ""
            echo "Status:      Near-full mode (Tier 2)"
            ;;
        aider)
            echo "Name:        Aider (18+ Providers)"
            echo "Vendor:      Aider (supports 18+ providers)"
            echo "CLI:         aider"
            echo "Flag:        --yes-always"
            echo ""
            echo "Features:"
            echo "  - Autonomous mode"
            echo "  - 18+ model providers"
            echo "  - Architect mode (dual model)"
            echo "  - Built-in lint/test integration"
            echo ""
            echo "Status:      Degraded mode (Tier 3)"
            ;;
        *)
            echo -e "${RED}Unknown provider: $provider${NC}"
            exit 1
            ;;
    esac
}

# Show resolved model configuration for all providers with source attribution
cmd_provider_models() {
    local script_dir
    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

    echo -e "${BOLD}Model Configuration (resolved):${NC}"
    echo ""

    local providers=("claude" "codex" "cline" "aider")
    for provider in "${providers[@]}"; do
        local provider_file="$script_dir/providers/${provider}.sh"
        [ -f "$provider_file" ] || continue

        echo -e "  ${BOLD}Provider: $provider${NC}"

        # Source in subshell to get resolved values
        local planning dev fast
        planning=$(bash -c "source '$provider_file' 2>/dev/null; echo \$PROVIDER_MODEL_PLANNING")
        dev=$(bash -c "source '$provider_file' 2>/dev/null; echo \$PROVIDER_MODEL_DEVELOPMENT")
        fast=$(bash -c "source '$provider_file' 2>/dev/null; echo \$PROVIDER_MODEL_FAST")

        # Determine source for each tier
        local provider_upper
        provider_upper=$(echo "$provider" | tr '[:lower:]' '[:upper:]')

        for tier in planning development fast; do
            local tier_upper
            tier_upper=$(echo "$tier" | tr '[:lower:]' '[:upper:]')
            local source="default"

            # Single-model providers (aider, cline) only chain through their own env var
            # and LOKI_MODEL_DEVELOPMENT -- they ignore per-tier and generic tier vars
            if [ "$provider" = "aider" ]; then
                if [ -n "${LOKI_AIDER_MODEL+x}" ]; then
                    source="LOKI_AIDER_MODEL"
                elif [ -n "${LOKI_MODEL_DEVELOPMENT+x}" ]; then
                    source="LOKI_MODEL_DEVELOPMENT"
                fi
            elif [ "$provider" = "cline" ]; then
                if [ -n "${LOKI_CLINE_MODEL+x}" ]; then
                    source="LOKI_CLINE_MODEL"
                elif [ -n "${LOKI_MODEL_DEVELOPMENT+x}" ]; then
                    source="LOKI_MODEL_DEVELOPMENT"
                fi
            elif [ "$provider" = "codex" ]; then
                # Codex uses single LOKI_CODEX_MODEL or generic tier vars
                if [ -n "${LOKI_CODEX_MODEL+x}" ]; then
                    source="LOKI_CODEX_MODEL"
                else
                    local generic_var="LOKI_MODEL_${tier_upper}"
                    eval "local gval=\${$generic_var+x}"
                    if [ -n "$gval" ]; then
                        source="$generic_var"
                    fi
                fi
            else
                # Multi-tier providers (claude): check provider-specific per-tier, then generic
                local provider_var="LOKI_${provider_upper}_MODEL_${tier_upper}"
                local generic_var="LOKI_MODEL_${tier_upper}"

                eval "local pval=\${$provider_var+x}"
                if [ -n "$pval" ]; then
                    source="$provider_var"
                else
                    eval "local gval=\${$generic_var+x}"
                    if [ -n "$gval" ]; then
                        source="$generic_var"
                    fi
                fi
            fi

            local value
            case "$tier" in
                planning)    value="$planning" ;;
                development) value="$dev" ;;
                fast)        value="$fast" ;;
            esac

            printf "    %-12s %-30s (source: %s)\n" "${tier^}:" "$value" "$source"
        done

        # Show extra info per provider
        if [ "$provider" = "codex" ]; then
            local effort_p effort_d effort_f
            effort_p=$(bash -c "source '$provider_file' 2>/dev/null; echo \$PROVIDER_EFFORT_PLANNING")
            effort_d=$(bash -c "source '$provider_file' 2>/dev/null; echo \$PROVIDER_EFFORT_DEVELOPMENT")
            effort_f=$(bash -c "source '$provider_file' 2>/dev/null; echo \$PROVIDER_EFFORT_FAST")
            echo "    Effort:      planning=$effort_p, development=$effort_d, fast=$effort_f"
        fi
        echo ""
    done
}

# Standalone dashboard server management (`loki dashboard`).
# State lives at a FIXED, machine-global ~/.loki/dashboard so that
# stop|status|open find the running server from ANY working directory.
# (The relative ${LOKI_DIR}/dashboard path made these commands silently
# fail from a different cwd than `start`, orphaning the server.) The
# separate in-build dashboard started by run.sh during `loki start` stays
# project-local and is unaffected by this constant.
DASHBOARD_PID_DIR="${HOME}/.loki/dashboard"
DASHBOARD_PID_FILE="${DASHBOARD_PID_DIR}/dashboard.pid"
DASHBOARD_DEFAULT_PORT=57374
# Default bind host: 0.0.0.0 inside a container (so a -p port map reaches
# the server; 127.0.0.1 would be unreachable from the host), else loopback
# for host safety. Detect Docker via /.dockerenv or LOKI_SANDBOX_MODE.
if [ -f /.dockerenv ] || [ "${LOKI_SANDBOX_MODE:-}" = "true" ] || [ -n "${LOKI_IN_CONTAINER:-}" ]; then
    DASHBOARD_DEFAULT_HOST="0.0.0.0"
else
    DASHBOARD_DEFAULT_HOST="127.0.0.1"
fi

cmd_dashboard() {
    local subcommand="${1:-}"

    # If no subcommand provided, show help (backward compatible with old behavior)
    if [ -z "$subcommand" ]; then
        cmd_dashboard_help
        exit 0
    fi

    shift || true

    case "$subcommand" in
        start)
            cmd_dashboard_start "$@"
            ;;
        stop)
            cmd_dashboard_stop
            ;;
        status)
            cmd_dashboard_status
            ;;
        url)
            cmd_dashboard_url "$@"
            ;;
        open)
            cmd_dashboard_open
            ;;
        --help|-h|help)
            cmd_dashboard_help
            ;;
        *)
            echo -e "${RED}Unknown dashboard command: $subcommand${NC}"
            echo "Run 'loki dashboard help' for usage."
            exit 1
            ;;
    esac
}

cmd_dashboard_help() {
    echo -e "${BOLD}Loki Mode Dashboard Server${NC}"
    echo ""
    echo "Usage: loki dashboard <command> [options]"
    echo ""
    echo "Note: 'loki dashboard' is the operations/observability UI (port ${DASHBOARD_DEFAULT_PORT})."
    echo "      It is NOT the same as 'loki web' (Purple Lab, port ${PURPLE_LAB_DEFAULT_PORT}, where you input PRDs)."
    echo "      Use 'loki dashboard' to monitor agents, tasks, costs, council, escalations."
    echo "      Use 'loki web' to submit a PRD and watch agents build."
    echo ""
    echo "Commands:"
    echo "  start    Start the dashboard server"
    echo "  stop     Stop the dashboard server"
    echo "  status   Show server status and URL"
    echo "  url      Get the dashboard URL"
    echo "  open     Open dashboard in browser"
    echo "  help     Show this help"
    echo ""
    echo "Options for 'start':"
    echo "  --port PORT        Port to listen on (default: $DASHBOARD_DEFAULT_PORT)"
    echo "  --host HOST        Host to bind to (default: $DASHBOARD_DEFAULT_HOST)"
    echo "  --tls-cert PATH    Path to PEM certificate (enables HTTPS)"
    echo "  --tls-key PATH     Path to PEM private key (enables HTTPS)"
    echo ""
    echo "Options for 'url':"
    echo "  --format FMT   Output format: text (default) or json"
    echo ""
    echo "Environment:"
    echo "  LOKI_DASHBOARD_PORT  Default port (overrides $DASHBOARD_DEFAULT_PORT)"
    echo "  LOKI_DASHBOARD_HOST  Default host (overrides $DASHBOARD_DEFAULT_HOST)"
    echo "  LOKI_TLS_CERT        Path to PEM certificate (enables HTTPS)"
    echo "  LOKI_TLS_KEY         Path to PEM private key (enables HTTPS)"
    echo ""
    echo "Examples:"
    echo "  loki dashboard start                  # Start on default port"
    echo "  loki dashboard start --port 8080      # Start on port 8080"
    echo "  loki dashboard start --tls-cert cert.pem --tls-key key.pem"
    echo "  loki dashboard status                 # Check if running"
    echo "  loki dashboard url --format json      # Get URL as JSON"
    echo "  loki dashboard open                   # Open in browser"
    echo "  loki dashboard stop                   # Stop server"
}

cmd_dashboard_start() {
    local port="${LOKI_DASHBOARD_PORT:-$DASHBOARD_DEFAULT_PORT}"
    local host="${LOKI_DASHBOARD_HOST:-$DASHBOARD_DEFAULT_HOST}"
    local tls_cert="${LOKI_TLS_CERT:-}"
    local tls_key="${LOKI_TLS_KEY:-}"

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --port)
                if [[ -n "${2:-}" ]]; then
                    port="$2"
                    shift 2
                else
                    echo -e "${RED}--port requires a value${NC}"
                    exit 1
                fi
                ;;
            --port=*)
                port="${1#*=}"
                shift
                ;;
            --host)
                if [[ -n "${2:-}" ]]; then
                    host="$2"
                    shift 2
                else
                    echo -e "${RED}--host requires a value${NC}"
                    exit 1
                fi
                ;;
            --host=*)
                host="${1#*=}"
                shift
                ;;
            --tls-cert)
                if [[ -n "${2:-}" ]]; then
                    tls_cert="$2"
                    shift 2
                else
                    echo -e "${RED}--tls-cert requires a path${NC}"
                    exit 1
                fi
                ;;
            --tls-cert=*)
                tls_cert="${1#*=}"
                shift
                ;;
            --tls-key)
                if [[ -n "${2:-}" ]]; then
                    tls_key="$2"
                    shift 2
                else
                    echo -e "${RED}--tls-key requires a path${NC}"
                    exit 1
                fi
                ;;
            --tls-key=*)
                tls_key="${1#*=}"
                shift
                ;;
            --help|-h)
                cmd_dashboard_help
                exit 0
                ;;
            *)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki dashboard start --help' for usage."
                exit 1
                ;;
        esac
    done

    # Validate port is numeric
    if [[ ! "$port" =~ ^[0-9]+$ ]]; then
        echo -e "${RED}Invalid port: $port${NC}"
        echo "Port must be a number."
        exit 1
    fi

    # Check if Python is available
    if ! command -v python &> /dev/null && ! command -v python3 &> /dev/null; then
        echo -e "${RED}Error: Python not found${NC}"
        echo "Python is required for the dashboard server."
        echo "Install with: brew install python"
        exit 1
    fi

    # Set up dashboard venv and python command
    if ! ensure_dashboard_venv; then
        echo -e "${RED}Cannot start dashboard without Python dependencies${NC}"
        return 1
    fi
    local python_cmd="$DASHBOARD_PYTHON"

    # Check if already running
    if [ -f "$DASHBOARD_PID_FILE" ]; then
        local existing_pid
        existing_pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)
        if [ -n "$existing_pid" ] && kill -0 "$existing_pid" 2>/dev/null; then
            echo -e "${YELLOW}Dashboard server already running (PID: $existing_pid)${NC}"
            echo ""
            cmd_dashboard_status
            exit 0
        fi
        # Stale PID file, remove it
        rm -f "$DASHBOARD_PID_FILE"
    fi

    # Check if port is already in use
    if command -v lsof &> /dev/null; then
        if lsof -i ":$port" &> /dev/null; then
            echo -e "${RED}Error: Port $port is already in use${NC}"
            echo ""
            echo "Check what's using the port:"
            echo "  lsof -i :$port"
            echo ""
            echo "Or use a different port:"
            echo "  loki dashboard start --port 8080"
            exit 1
        fi
    fi

    # Ensure PID directory exists
    mkdir -p "$DASHBOARD_PID_DIR"

    # Create log directory
    local log_dir="${DASHBOARD_PID_DIR}/logs"
    mkdir -p "$log_dir"
    local log_file="${log_dir}/dashboard.log"

    # Determine URL scheme based on TLS
    local url_scheme="http"
    local tls_info=""
    if [ -n "$tls_cert" ] && [ -n "$tls_key" ]; then
        url_scheme="https"
        tls_info=" (TLS enabled)"
    fi

    echo -e "${GREEN}Starting dashboard server...${NC}"
    echo -e "${CYAN}Host:${NC} $host"
    echo -e "${CYAN}Port:${NC} $port"
    if [ -n "$tls_info" ]; then
        echo -e "${CYAN}TLS:${NC}  enabled"
    fi
    echo ""

    # Start the dashboard server in background
    # LOKI_SKILL_DIR tells server.py where to find static files
    LOKI_DIR="$LOKI_DIR" LOKI_SKILL_DIR="$SKILL_DIR" PYTHONPATH="$SKILL_DIR" \
        LOKI_DASHBOARD_HOST="$host" LOKI_DASHBOARD_PORT="$port" \
        LOKI_TLS_CERT="$tls_cert" LOKI_TLS_KEY="$tls_key" \
        nohup "$python_cmd" -m dashboard.server > "$log_file" 2>&1 &
    local new_pid=$!

    # Save PID
    echo "$new_pid" > "$DASHBOARD_PID_FILE"

    # Also save port and host for status command
    echo "$port" > "${DASHBOARD_PID_DIR}/port"
    echo "$host" > "${DASHBOARD_PID_DIR}/host"
    echo "$url_scheme" > "${DASHBOARD_PID_DIR}/scheme"

    # Wait for the dashboard to become ready (up to 10 seconds). Probe the
    # UNAUTHENTICATED /health endpoint over the ACTUAL scheme: /api/status
    # 401s under LOKI_ENTERPRISE_AUTH, and the scheme is https when TLS is on,
    # so the old hardcoded http://.../api/status probe always failed against a
    # healthy TLS or auth-enabled server. Use a loopback-reachable host when
    # bound to 0.0.0.0, and -k to tolerate self-signed certs.
    local health_host="$host"; [ "$health_host" = "0.0.0.0" ] && health_host="127.0.0.1"
    local health_curl_opts="-sf"; [ "$url_scheme" = "https" ] && health_curl_opts="-sfk"
    local health_retries=20
    local health_interval=0.5
    local health_ok=false
    while [[ $health_retries -gt 0 ]]; do
        if curl $health_curl_opts "${url_scheme}://${health_host}:${port}/health" >/dev/null 2>&1; then
            health_ok=true
            break
        fi
        # Also check if the process died
        if ! kill -0 "$new_pid" 2>/dev/null; then
            break
        fi
        sleep "$health_interval"
        health_retries=$((health_retries - 1))
    done
    if ! $health_ok && kill -0 "$new_pid" 2>/dev/null; then
        echo -e "${YELLOW}Warning: Dashboard started but health check did not respond within timeout${NC}"
    fi

    if kill -0 "$new_pid" 2>/dev/null; then
        local url="${url_scheme}://${host}:${port}"
        echo -e "${GREEN}Dashboard server started${NC}"
        echo ""
        echo "  PID:  $new_pid"
        echo "  URL:  $url"
        echo "  Logs: $log_file"
        echo ""
        echo -e "Open in browser: ${CYAN}loki dashboard open${NC}"
        echo -e "Check status:    ${CYAN}loki dashboard status${NC}"
        echo -e "Stop server:     ${CYAN}loki dashboard stop${NC}"
    else
        rm -f "$DASHBOARD_PID_FILE"
        echo -e "${RED}Failed to start dashboard server${NC}"
        echo ""
        echo "Check the log file for errors:"
        echo "  cat $log_file"
        exit 1
    fi
}

cmd_dashboard_stop() {
    if [ ! -f "$DASHBOARD_PID_FILE" ]; then
        echo -e "${YELLOW}Dashboard server is not running${NC}"
        echo "(No PID file found at $DASHBOARD_PID_FILE)"
        exit 0
    fi

    local pid
    pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)

    if [ -z "$pid" ]; then
        rm -f "$DASHBOARD_PID_FILE"
        echo -e "${YELLOW}Dashboard server is not running (empty PID file)${NC}"
        exit 0
    fi

    if kill -0 "$pid" 2>/dev/null; then
        echo -e "${GREEN}Stopping dashboard server (PID: $pid)...${NC}"
        kill "$pid" 2>/dev/null

        # Wait for process to terminate
        local wait_count=0
        while kill -0 "$pid" 2>/dev/null && [ $wait_count -lt 10 ]; do
            sleep 0.5
            wait_count=$((wait_count + 1))
        done

        # Force kill if still running
        if kill -0 "$pid" 2>/dev/null; then
            echo -e "${YELLOW}Process did not terminate gracefully, force killing...${NC}"
            kill -9 "$pid" 2>/dev/null
        fi

        rm -f "$DASHBOARD_PID_FILE"
        echo -e "${GREEN}Dashboard server stopped${NC}"
    else
        rm -f "$DASHBOARD_PID_FILE"
        echo -e "${YELLOW}Dashboard server was not running (stale PID file removed)${NC}"
    fi
}

cmd_dashboard_status() {
    echo -e "${BOLD}Dashboard Server Status${NC}"
    echo ""

    if [ ! -f "$DASHBOARD_PID_FILE" ]; then
        echo -e "${YELLOW}Status: Not running${NC}"
        echo ""
        echo -e "Start with: ${CYAN}loki dashboard start${NC}"
        return 0
    fi

    local pid
    pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)

    if [ -z "$pid" ]; then
        echo -e "${YELLOW}Status: Not running (empty PID file)${NC}"
        rm -f "$DASHBOARD_PID_FILE"
        return 0
    fi

    if kill -0 "$pid" 2>/dev/null; then
        # Read saved host, port, and scheme
        local host="${DASHBOARD_DEFAULT_HOST}"
        local port="${DASHBOARD_DEFAULT_PORT}"
        local scheme="http"

        if [ -f "${DASHBOARD_PID_DIR}/host" ]; then
            host=$(cat "${DASHBOARD_PID_DIR}/host" 2>/dev/null)
        fi
        if [ -f "${DASHBOARD_PID_DIR}/port" ]; then
            port=$(cat "${DASHBOARD_PID_DIR}/port" 2>/dev/null)
        fi
        if [ -f "${DASHBOARD_PID_DIR}/scheme" ]; then
            scheme=$(cat "${DASHBOARD_PID_DIR}/scheme" 2>/dev/null)
        fi

        local url="${scheme}://${host}:${port}"

        echo -e "${GREEN}Status: Running${NC}"
        echo ""
        echo "  PID:  $pid"
        echo "  Host: $host"
        echo "  Port: $port"
        if [ "$scheme" = "https" ]; then
            echo "  TLS:  enabled"
        fi
        echo "  URL:  $url"
        echo ""

        # Check if server is responding
        local curl_flags="-s --connect-timeout 2"
        if [ "$scheme" = "https" ]; then
            curl_flags="$curl_flags -k"
        fi
        if command -v curl &> /dev/null; then
            if curl $curl_flags "$url" &> /dev/null; then
                echo -e "${GREEN}Server is responding${NC}"
            else
                echo -e "${YELLOW}Server process running but not responding on $url${NC}"
                echo "The server may still be starting up."
            fi
        fi

        echo ""
        echo -e "Open in browser: ${CYAN}loki dashboard open${NC}"
        echo -e "Stop server:     ${CYAN}loki dashboard stop${NC}"
    else
        echo -e "${YELLOW}Status: Not running (stale PID file)${NC}"
        rm -f "$DASHBOARD_PID_FILE"
        echo ""
        echo -e "Start with: ${CYAN}loki dashboard start${NC}"
    fi
}

cmd_dashboard_url() {
    local format="text"

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --format)
                if [[ -n "${2:-}" ]]; then
                    format="$2"
                    shift 2
                else
                    echo -e "${RED}--format requires a value (text or json)${NC}"
                    exit 1
                fi
                ;;
            --format=*)
                format="${1#*=}"
                shift
                ;;
            --help|-h)
                echo "Usage: loki dashboard url [--format text|json]"
                exit 0
                ;;
            *)
                echo -e "${RED}Unknown option: $1${NC}"
                exit 1
                ;;
        esac
    done

    # Validate format
    if [[ "$format" != "text" && "$format" != "json" ]]; then
        echo -e "${RED}Invalid format: $format${NC}"
        echo "Valid formats: text, json"
        exit 1
    fi

    # Check if running
    if [ ! -f "$DASHBOARD_PID_FILE" ]; then
        if [[ "$format" == "json" ]]; then
            echo '{"running":false,"error":"Dashboard server is not running"}'
        else
            echo -e "${YELLOW}Dashboard server is not running${NC}"
            echo "Start with: loki dashboard start"
        fi
        exit 1
    fi

    local pid
    pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)

    if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
        rm -f "$DASHBOARD_PID_FILE"
        if [[ "$format" == "json" ]]; then
            echo '{"running":false,"error":"Dashboard server is not running"}'
        else
            echo -e "${YELLOW}Dashboard server is not running${NC}"
            echo "Start with: loki dashboard start"
        fi
        exit 1
    fi

    # Read saved host, port, and scheme
    local host="${DASHBOARD_DEFAULT_HOST}"
    local port="${DASHBOARD_DEFAULT_PORT}"
    local scheme="http"

    if [ -f "${DASHBOARD_PID_DIR}/host" ]; then
        host=$(cat "${DASHBOARD_PID_DIR}/host" 2>/dev/null)
    fi
    if [ -f "${DASHBOARD_PID_DIR}/port" ]; then
        port=$(cat "${DASHBOARD_PID_DIR}/port" 2>/dev/null)
    fi
    if [ -f "${DASHBOARD_PID_DIR}/scheme" ]; then
        scheme=$(cat "${DASHBOARD_PID_DIR}/scheme" 2>/dev/null)
    fi

    local url="${scheme}://${host}:${port}"

    if [[ "$format" == "json" ]]; then
        echo "{\"running\":true,\"url\":\"$url\",\"scheme\":\"$scheme\",\"host\":\"$host\",\"port\":$port,\"pid\":$pid}"
    else
        echo "$url"
    fi
}

cmd_dashboard_open() {
    # Check if server is running
    if [ ! -f "$DASHBOARD_PID_FILE" ]; then
        echo -e "${YELLOW}Dashboard server is not running${NC}"
        echo ""
        echo -e "Start with: ${CYAN}loki dashboard start${NC}"
        exit 1
    fi

    local pid
    pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)

    if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
        rm -f "$DASHBOARD_PID_FILE"
        echo -e "${YELLOW}Dashboard server is not running${NC}"
        echo ""
        echo -e "Start with: ${CYAN}loki dashboard start${NC}"
        exit 1
    fi

    # Read saved host, port, and scheme
    local host="${DASHBOARD_DEFAULT_HOST}"
    local port="${DASHBOARD_DEFAULT_PORT}"
    local scheme="http"

    if [ -f "${DASHBOARD_PID_DIR}/host" ]; then
        host=$(cat "${DASHBOARD_PID_DIR}/host" 2>/dev/null)
    fi
    if [ -f "${DASHBOARD_PID_DIR}/port" ]; then
        port=$(cat "${DASHBOARD_PID_DIR}/port" 2>/dev/null)
    fi
    if [ -f "${DASHBOARD_PID_DIR}/scheme" ]; then
        scheme=$(cat "${DASHBOARD_PID_DIR}/scheme" 2>/dev/null)
    fi

    local url="${scheme}://${host}:${port}"

    echo -e "${GREEN}Opening dashboard: $url${NC}"

    # Open in browser (cross-platform)
    if command -v open &> /dev/null; then
        open "$url"
    elif command -v xdg-open &> /dev/null; then
        xdg-open "$url"
    elif command -v start &> /dev/null; then
        start "$url"
    else
        echo ""
        echo "Could not detect browser opener."
        echo "Please open in browser: $url"
    fi
}

# Welcome opener (the "magic opener"). Shows a branded welcome page in the
# browser, or a terminal welcome when no browser is available (headless,
# Docker, CI). Honors telemetry opt-out (LOKI_TELEMETRY_DISABLED / DO_NOT_TRACK):
# when opted out, the page is loaded with ?telemetry=off so its form is
# disabled and no analytics are ever sent.
WELCOME_MARKER="${HOME}/.loki/.welcomed"

_loki_telemetry_off() {
    [ "${LOKI_TELEMETRY_DISABLED:-}" = "true" ] && return 0
    [ "${DO_NOT_TRACK:-}" = "1" ] && return 0
    return 1
}

cmd_welcome_terminal() {
    local ver="${1:-}"
    echo ""
    echo -e "  ${BOLD}Loki Mode${NC} ${DIM}by Autonomi${NC}${ver:+  ${DIM}v${ver}${NC}}"
    echo ""
    echo -e "  ${BOLD}Describe it. Walk away. Get working, tested software.${NC}"
    echo ""
    echo -e "  Loki takes a spec (PRD, GitHub issue, or one-line brief) to a deployed"
    echo -e "  product via the RARV-C closure loop, until the work is actually done."
    echo ""
    echo -e "  ${CYAN}Closure loop + council${NC}   Every change is verified by a unanimous council."
    echo -e "  ${CYAN}Cross-project memory${NC}     Lessons compound, so agents stop repeating mistakes."
    echo -e "  ${CYAN}Provider-agnostic${NC}        Claude, Codex, Cline, or Aider. Your keys, your infra."
    echo ""
    echo -e "  Quick start:  ${BOLD}loki start ./prd.md${NC}"
    echo -e "  Docs:         ${BOLD}https://www.autonomi.dev/docs${NC}"
    echo ""
    if _loki_telemetry_off; then
        echo -e "  ${DIM}Analytics are off for this session. Nothing is sent.${NC}"
    else
        echo -e "  ${DIM}Anonymous usage analytics help us improve the product. We never${NC}"
        echo -e "  ${DIM}collect prompts, PRDs, or code. Opt out: LOKI_TELEMETRY_DISABLED=true${NC}"
    fi
    echo ""
}

cmd_welcome() {
    local ver=""
    [ -f "${SKILL_DIR}/VERSION" ] && ver="$(cat "${SKILL_DIR}/VERSION" 2>/dev/null | tr -d '[:space:]')"

    local welcome_file="${SKILL_DIR}/assets/welcome/welcome.html"

    # Build the file URL with params: version, opt-out, and the existing
    # anonymous telemetry distinct-id (so browser + install share one id).
    local q="v=${ver}"
    if _loki_telemetry_off; then
        q="${q}&telemetry=off"
    else
        local idfile="${HOME}/.loki-telemetry-id"
        [ -f "$idfile" ] && q="${q}&did=$(cat "$idfile" 2>/dev/null | tr -d '[:space:]')"
    fi

    # Headless / Docker / CI / no-browser: print the terminal welcome only.
    local has_browser=""
    if command -v open >/dev/null 2>&1; then has_browser="open"
    elif command -v xdg-open >/dev/null 2>&1; then has_browser="xdg-open"
    elif command -v start >/dev/null 2>&1; then has_browser="start"
    fi

    if [ -z "$has_browser" ] || [ ! -f "$welcome_file" ] || [ -n "${CI:-}" ] || [ ! -t 1 ]; then
        cmd_welcome_terminal "$ver"
        [ -f "$welcome_file" ] && echo -e "  ${DIM}Full welcome page: ${welcome_file}${NC}" && echo ""
        return 0
    fi

    echo -e "${GREEN}Opening the Loki Mode welcome page in your browser...${NC}"
    "$has_browser" "file://${welcome_file}?${q}" >/dev/null 2>&1 || cmd_welcome_terminal "$ver"
}

# First-run hook: open the welcome once, then never again. Browser only when
# interactive + not CI + not opted out; otherwise nothing (no noisy auto-print
# during a real run). Marker lives at ~/.loki/.welcomed.
cmd_welcome_maybe_firstrun() {
    [ -f "$WELCOME_MARKER" ] && return 0
    mkdir -p "$(dirname "$WELCOME_MARKER")" 2>/dev/null || true
    : > "$WELCOME_MARKER" 2>/dev/null || true
    # Only auto-open on an interactive, non-CI terminal. cmd_welcome itself
    # re-checks for a browser and falls back to the terminal welcome, so we
    # gate purely on interactivity + CI here (grouped to avoid &&/|| precedence
    # ambiguity). Backgrounded so it never delays the start.
    if [ -t 1 ] && [ -z "${CI:-}" ]; then
        (cmd_welcome >/dev/null 2>&1 &) || true
    fi
}

# Web app management
# Purple Lab -- standalone product web UI for Loki Mode.
# Runs on port 57375 (separate from dashboard at 57374).
# Backend: web-app/server.py (FastAPI), Frontend: web-app/dist/

PURPLE_LAB_DEFAULT_PORT=57375
PURPLE_LAB_DEFAULT_HOST="127.0.0.1"
# Use HOME-based path so stop works from any CWD
PURPLE_LAB_STATE_DIR="${HOME}/.loki/purple-lab"
PURPLE_LAB_PID_FILE="${PURPLE_LAB_STATE_DIR}/purple-lab.pid"

cmd_web() {
    local subcommand="${1:-start}"

    case "$subcommand" in
        start)
            shift || true
            cmd_web_start "$@"
            ;;
        stop)
            cmd_web_stop
            ;;
        status)
            cmd_web_status
            ;;
        logs)
            shift || true
            local log_lines="${1:-100}"
            local log_file="${PURPLE_LAB_STATE_DIR}/logs/purple-lab.log"
            if [ ! -f "$log_file" ]; then
                log_file="${LOKI_DIR}/purple-lab/logs/purple-lab.log"
            fi
            if [ ! -f "$log_file" ]; then
                echo -e "${YELLOW}No Purple Lab log file found${NC}"
                return 0
            fi
            echo -e "${BOLD}Purple Lab Logs (last $log_lines lines)${NC}"
            echo -e "${DIM}Use 'loki web logs <N>' to show more/fewer lines${NC}"
            echo ""
            tail -n "$log_lines" "$log_file"
            ;;
        --help|-h|help)
            cmd_web_help
            ;;
        *)
            # Treat unknown args as options to start (e.g., loki web --no-open)
            shift
            cmd_web_start "$subcommand" "$@"
            ;;
    esac
}

cmd_web_help() {
    echo -e "${BOLD}Purple Lab -- Loki Mode Web UI${NC}"
    echo ""
    echo "Usage: loki web [command] [options]"
    echo ""
    echo "Note: 'loki web' is Purple Lab (the PRD-input/build-watch UI, port ${PURPLE_LAB_DEFAULT_PORT})."
    echo "      It is NOT the same as 'loki dashboard' (operations UI, port ${DASHBOARD_DEFAULT_PORT})."
    echo "      Use 'loki web' to submit a PRD and watch agents build it."
    echo "      Use 'loki dashboard' to monitor running agents, tasks, costs, council, escalations."
    echo ""
    echo "Commands:"
    echo "  start    Start Purple Lab (default)"
    echo "  stop     Stop Purple Lab server"
    echo "  status   Show Purple Lab server status"
    echo "  logs     Show Purple Lab server logs"
    echo "  help     Show this help"
    echo ""
    echo "Options (for start):"
    echo "  --no-open      Don't open browser automatically"
    echo "  --port PORT    Use custom port (default: ${PURPLE_LAB_DEFAULT_PORT})"
    echo ""
    echo "Purple Lab is the product UI where you input PRDs and watch agents build."
    echo "It runs its own backend (web-app/server.py) on port ${PURPLE_LAB_DEFAULT_PORT}."
    echo ""
    echo "Examples:"
    echo "  loki web                  Start Purple Lab and open in browser"
    echo "  loki web --no-open        Start without opening browser"
    echo "  loki web stop             Stop the Purple Lab server"
    echo ""
    echo "Note (since v7.5.30):"
    echo "  Purple Lab is also embedded in the Dashboard as a Lab sidebar entry."
    echo "  Run 'loki dashboard start' and click 'Lab' to use the same UI"
    echo "  without spawning a second port. 'loki web' standalone remains"
    echo "  supported (Rule 0); both modes serve the same React bundle."
}

cmd_web_start() {
    local open_browser=true
    local port="${PURPLE_LAB_DEFAULT_PORT}"
    local prd_file=""

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --no-open)
                open_browser=false
                shift
                ;;
            --port)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}--port requires a port number${NC}"
                    exit 1
                fi
                port="$2"
                shift 2
                ;;
            --prd)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}--prd requires a file path${NC}"
                    exit 1
                fi
                prd_file="$2"
                shift 2
                ;;
            --help|-h)
                cmd_web_help
                exit 0
                ;;
            *)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki web --help' for usage."
                exit 1
                ;;
        esac
    done

    # Validate port: must be numeric and in valid range
    if [[ ! "$port" =~ ^[0-9]+$ ]]; then
        echo -e "${RED}Error: Port must be a number, got '$port'${NC}"
        exit 1
    fi
    if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
        echo -e "${RED}Error: Port must be between 1 and 65535, got $port${NC}"
        exit 1
    fi
    # Port validation moved below (after PID file check) so we can offer to kill orphans

    # Validate --prd file if provided
    if [ -n "$prd_file" ]; then
        if [ ! -f "$prd_file" ]; then
            echo -e "${RED}Error: PRD file not found: $prd_file${NC}"
            exit 1
        fi
        export PURPLE_LAB_PRD
        PURPLE_LAB_PRD=$(cat "$prd_file")
    fi

    # Check that web-app dist exists
    local web_dist="${SKILL_DIR}/web-app/dist"
    if [ ! -d "$web_dist" ] || [ ! -f "$web_dist/index.html" ]; then
        echo -e "${RED}Error: Web app not built${NC}"
        echo "Expected files at: $web_dist"
        echo ""
        echo "Build the web app first:"
        echo "  cd web-app && npm install && npm run build"
        exit 1
    fi

    # Check that server.py exists
    local server_py="${SKILL_DIR}/web-app/server.py"
    if [ ! -f "$server_py" ]; then
        echo -e "${RED}Error: Purple Lab server not found at $server_py${NC}"
        exit 1
    fi

    # Ensure Python venv with FastAPI is available
    if ! ensure_dashboard_venv; then
        echo -e "${RED}Error: Failed to set up Python environment${NC}"
        exit 1
    fi

    # Check if already running
    mkdir -p "${LOKI_DIR}/purple-lab"
    if [ -f "$PURPLE_LAB_PID_FILE" ]; then
        local existing_pid
        existing_pid=$(cat "$PURPLE_LAB_PID_FILE" 2>/dev/null)
        if [ -n "$existing_pid" ] && kill -0 "$existing_pid" 2>/dev/null; then
            echo -e "${GREEN}Purple Lab already running (PID: $existing_pid)${NC}"
            local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
            echo -e "Open: ${CYAN}$url${NC}"
            if [ "$open_browser" = true ]; then
                if command -v open &> /dev/null; then
                    open "$url"
                elif command -v xdg-open &> /dev/null; then
                    xdg-open "$url"
                fi
            fi
            return 0
        fi
    fi

    # Check if port is already in use -- try to clean up orphaned processes
    if lsof -i:"$port" -sTCP:LISTEN &> /dev/null; then
        local blocking_pid
        blocking_pid=$(lsof -ti:"$port" -sTCP:LISTEN 2>/dev/null | head -1)
        if [ -n "$blocking_pid" ]; then
            echo -e "${YELLOW}Port $port in use by PID $blocking_pid -- stopping it...${NC}"
            kill "$blocking_pid" 2>/dev/null
            sleep 1
            if kill -0 "$blocking_pid" 2>/dev/null; then
                kill -9 "$blocking_pid" 2>/dev/null
                sleep 1
            fi
            # Verify port is now free
            if lsof -i:"$port" -sTCP:LISTEN &> /dev/null; then
                echo -e "${RED}Error: Could not free port $port${NC}"
                echo "Use --port to specify a different port, or manually kill the process."
                exit 1
            fi
            echo -e "${GREEN}Port $port freed.${NC}"
        fi
    fi

    # Start the server
    local log_dir="${LOKI_DIR}/purple-lab/logs"
    mkdir -p "$log_dir"
    local log_file="$log_dir/purple-lab.log"

    echo -e "${CYAN}Starting Purple Lab...${NC}"

    PURPLE_LAB_PORT="$port" PURPLE_LAB_HOST="${PURPLE_LAB_DEFAULT_HOST}" \
    LOKI_DIR="$LOKI_DIR" LOKI_SKILL_DIR="$SKILL_DIR" PYTHONPATH="$SKILL_DIR" \
    nohup "$DASHBOARD_PYTHON" "$server_py" > "$log_file" 2>&1 &
    local pid=$!

    mkdir -p "$PURPLE_LAB_STATE_DIR"
    echo "$pid" > "$PURPLE_LAB_PID_FILE"
    echo "$port" > "${PURPLE_LAB_STATE_DIR}/port"
    # Also write to local .loki for backward compat
    mkdir -p "${LOKI_DIR}/purple-lab"
    echo "$pid" > "${LOKI_DIR}/purple-lab/purple-lab.pid"
    echo "$port" > "${LOKI_DIR}/purple-lab/port"

    # Wait for server to be ready
    local retries=0
    local server_ready=false
    while [ $retries -lt 15 ]; do
        if curl -s "http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/api/session/status" > /dev/null 2>&1; then
            server_ready=true
            break
        fi
        sleep 0.5
        retries=$((retries + 1))
    done

    if ! kill -0 "$pid" 2>/dev/null; then
        echo -e "${RED}Error: Purple Lab failed to start${NC}"
        echo "Check logs at: $log_file"
        rm -f "$PURPLE_LAB_PID_FILE"
        exit 1
    fi

    local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"

    if [ "$server_ready" = true ]; then
        echo -e "${GREEN}Purple Lab running at: $url${NC} (PID: $pid)"
    else
        echo -e "${YELLOW}Purple Lab starting at: $url${NC} (PID: $pid)"
        echo "Server may still be loading. Refresh the browser if it does not load immediately."
    fi
    echo -e "Logs: $log_file"
    echo -e "Stop with: ${CYAN}loki web stop${NC}"

    # Open browser only after server is confirmed ready
    if [ "$open_browser" = true ]; then
        if [ "$server_ready" = true ]; then
            echo ""
            if command -v open &> /dev/null; then
                open "$url"
            elif command -v xdg-open &> /dev/null; then
                xdg-open "$url"
            elif command -v start &> /dev/null; then
                start "$url"
            else
                echo "Please open in browser: $url"
            fi
        else
            echo ""
            echo -e "${YELLOW}Browser not opened because server is not ready yet.${NC}"
            echo "Open manually when ready: $url"
        fi
    fi
}

cmd_web_stop() {
    local stopped=false
    local port
    port=$(cat "${PURPLE_LAB_STATE_DIR}/port" 2>/dev/null || cat "${LOKI_DIR}/purple-lab/port" 2>/dev/null || echo "$PURPLE_LAB_DEFAULT_PORT")

    # Gracefully stop any active session before killing Purple Lab
    curl -s --max-time 3 -X POST "http://127.0.0.1:${port}/api/session/stop" >/dev/null 2>&1 || true
    sleep 1

    # Try PID file first (check global location, then local)
    local pid_file="$PURPLE_LAB_PID_FILE"
    if [ ! -f "$pid_file" ]; then
        pid_file="${LOKI_DIR}/purple-lab/purple-lab.pid"
    fi
    if [ -f "$pid_file" ]; then
        local pid
        pid=$(cat "$pid_file" 2>/dev/null)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            # Verify the PID belongs to a Purple Lab/Python process (PID recycling guard)
            local pid_cmd
            pid_cmd=$(ps -p "$pid" -o comm= 2>/dev/null || true)
            if [[ "$pid_cmd" == *python* ]] || [[ "$pid_cmd" == *uvicorn* ]] || [[ "$pid_cmd" == *Purple* ]]; then
                kill "$pid" 2>/dev/null
                sleep 1
                if kill -0 "$pid" 2>/dev/null; then
                    kill -9 "$pid" 2>/dev/null
                fi
                echo -e "${GREEN}Purple Lab stopped (PID: $pid)${NC}"
                stopped=true
            else
                echo -e "${YELLOW}PID $pid is not a Purple Lab process (found: $pid_cmd), skipping${NC}"
            fi
        fi
        rm -f "$PURPLE_LAB_PID_FILE"
    fi

    # Also kill any process on the port (catches orphans without PID file)
    local port_pid
    port_pid=$(lsof -ti:"$port" -sTCP:LISTEN 2>/dev/null | head -1 || true)
    if [ -n "$port_pid" ]; then
        kill "$port_pid" 2>/dev/null
        sleep 1
        if kill -0 "$port_pid" 2>/dev/null; then
            kill -9 "$port_pid" 2>/dev/null
        fi
        if [ "$stopped" = false ]; then
            echo -e "${GREEN}Purple Lab stopped (orphan PID: $port_pid on port $port)${NC}"
        fi
        stopped=true
    fi

    # Also kill any orphan web-app/server.py processes (catches processes from other CWDs)
    local orphan_pids
    orphan_pids=$(pgrep -f "web-app/server.py" 2>/dev/null || true)
    if [ -n "$orphan_pids" ]; then
        for opid in $orphan_pids; do
            if kill -0 "$opid" 2>/dev/null; then
                kill "$opid" 2>/dev/null
                sleep 1
                if kill -0 "$opid" 2>/dev/null; then
                    kill -9 "$opid" 2>/dev/null
                fi
                if [ "$stopped" = false ]; then
                    echo -e "${GREEN}Purple Lab stopped (orphan process PID: $opid)${NC}"
                fi
                stopped=true
            fi
        done
    fi

    if [ "$stopped" = false ]; then
        echo "Purple Lab is not running."
    fi

    # Clean PID files from current dir AND home dir (catches cross-CWD state)
    rm -f "$PURPLE_LAB_PID_FILE" "${LOKI_DIR}/purple-lab/port" 2>/dev/null || true
    rm -f "$HOME/.loki/purple-lab/purple-lab.pid" "$HOME/.loki/purple-lab/port" 2>/dev/null || true

    # Kill only processes that Purple Lab started (tracked in child-pids.json)
    # External loki sessions (started via "loki start" from terminal) are NOT touched
    local pids_file="${LOKI_DIR}/purple-lab/child-pids.json"
    if [ -f "$pids_file" ]; then
        local tracked_pids
        tracked_pids=$(LOKI_PIDS_FILE="$pids_file" python3 -c "import json, os; [print(p) for p in json.load(open(os.environ['LOKI_PIDS_FILE']))]" 2>/dev/null || true)
        if [ -n "$tracked_pids" ]; then
            echo "Cleaning up Purple Lab build processes..."
            for opid in $tracked_pids; do
                pkill -TERM -P "$opid" 2>/dev/null || true
                kill -TERM "$opid" 2>/dev/null || true
            done
            sleep 2
            for opid in $tracked_pids; do
                pkill -9 -P "$opid" 2>/dev/null || true
                kill -9 "$opid" 2>/dev/null || true
            done
            echo "Purple Lab build processes cleaned up."
        fi
        rm -f "$pids_file" 2>/dev/null || true
    fi

    # Global cleanup: kill ALL loki-related processes regardless of CWD
    # This ensures "loki web stop" from anywhere cleans up everything

    # Kill any dashboard server (by process name and by port)
    local dash_port="${LOKI_DASHBOARD_PORT:-57374}"
    local dash_pids
    dash_pids=$(pgrep -f "dashboard.server\|dashboard/server" 2>/dev/null || true)
    if [ -n "$dash_pids" ]; then
        for dpid in $dash_pids; do
            kill "$dpid" 2>/dev/null || true
        done
        sleep 1
        for dpid in $dash_pids; do
            kill -0 "$dpid" 2>/dev/null && kill -9 "$dpid" 2>/dev/null || true
        done
        echo "Loki Dashboard stopped"
    fi
    # Also kill by port in case pgrep missed it
    local dash_port_pid
    dash_port_pid=$(lsof -ti:"$dash_port" -sTCP:LISTEN 2>/dev/null | head -1 || true)
    if [ -n "$dash_port_pid" ]; then
        kill "$dash_port_pid" 2>/dev/null || true
        sleep 1
        kill -0 "$dash_port_pid" 2>/dev/null && kill -9 "$dash_port_pid" 2>/dev/null || true
    fi

    # Kill any loki run.sh orchestrator processes (status monitors, resource monitors)
    local run_pids
    run_pids=$(pgrep -f "loki-run-\|status-monitor\|resource-monitor" 2>/dev/null || true)
    if [ -n "$run_pids" ]; then
        for rpid in $run_pids; do
            kill "$rpid" 2>/dev/null || true
        done
        sleep 1
        for rpid in $run_pids; do
            kill -0 "$rpid" 2>/dev/null && kill -9 "$rpid" 2>/dev/null || true
        done
        if [ "$stopped" = true ]; then
            echo "Background processes cleaned up"
        fi
    fi

    # Clean up all PID files globally
    rm -f "${LOKI_DIR}/dashboard/dashboard.pid" 2>/dev/null || true
    rm -f "$HOME/.loki/dashboard/dashboard.pid" 2>/dev/null || true
    rm -f "${PURPLE_LAB_STATE_DIR}/purple-lab.pid" "${PURPLE_LAB_STATE_DIR}/port" 2>/dev/null || true
}

cmd_web_status() {
    local port
    port=$(cat "${PURPLE_LAB_STATE_DIR}/port" 2>/dev/null || cat "${LOKI_DIR}/purple-lab/port" 2>/dev/null || echo "$PURPLE_LAB_DEFAULT_PORT")

    # Resolve log file path (check home-based state dir first, then CWD-based)
    local log_path="${PURPLE_LAB_STATE_DIR}/logs/purple-lab.log"
    if [ ! -f "$log_path" ]; then
        log_path="${LOKI_DIR}/purple-lab/logs/purple-lab.log"
    fi

    # Check PID file
    if [ -f "$PURPLE_LAB_PID_FILE" ]; then
        local pid
        pid=$(cat "$PURPLE_LAB_PID_FILE" 2>/dev/null)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            echo -e "${GREEN}Purple Lab is running${NC}"
            echo "  PID:  $pid"
            echo "  URL:  http://${PURPLE_LAB_DEFAULT_HOST}:${port}"
            echo "  Logs: $log_path"
            return 0
        fi
        rm -f "$PURPLE_LAB_PID_FILE"
    fi

    # Fallback: check port directly (catches orphans)
    local port_pid
    port_pid=$(lsof -ti:"$port" -sTCP:LISTEN 2>/dev/null | head -1 || true)
    if [ -n "$port_pid" ]; then
        echo -e "${YELLOW}Purple Lab is running (orphan process)${NC}"
        echo "  PID:  $port_pid"
        echo "  Port: $port"
        echo "  Note: No PID file found. Stop with: loki web stop"
        return 0
    fi

    echo "Purple Lab is not running."
}

# Import GitHub issues
cmd_import() {
    # v7.6.2 B-13 fix: --help must print help, not start an import.
    case "${1:-}" in
        --help|-h|help)
            echo -e "${BOLD}Loki Mode -- import GitHub issues into the queue${NC}"
            echo ""
            echo "Usage: loki import [options]"
            echo ""
            echo "Imports issues from the current repo's GitHub project into"
            echo ".loki/queue/ so loki start picks them up as tasks."
            echo ""
            echo "Options:"
            echo "  --help, -h   Show this help and exit"
            echo ""
            echo "Side effects: makes GitHub API calls, writes to .loki/queue/."
            return 0
            ;;
    esac
    if [ ! -d "$LOKI_DIR" ]; then
        mkdir -p "$LOKI_DIR/queue"
    fi

    export LOKI_GITHUB_IMPORT=true

    # Check gh CLI
    if ! command -v gh &> /dev/null; then
        echo -e "${RED}Error: gh CLI not found. Install with: brew install gh${NC}"
        exit 1
    fi

    if ! gh auth status &> /dev/null; then
        echo -e "${RED}Error: gh CLI not authenticated. Run: gh auth login${NC}"
        exit 1
    fi

    echo -e "${GREEN}Importing GitHub issues...${NC}"
    # Source the functions from run.sh and call import
    source "$RUN_SH" 2>/dev/null || true
    if type import_github_issues &>/dev/null; then
        import_github_issues
    else
        echo -e "${YELLOW}Import function not available. Using fallback.${NC}"
        gh issue list --json number,title,url --limit 20
    fi
}

# GitHub integration management (v5.41.0)
cmd_github() {
    local subcmd="${1:-help}"
    shift 2>/dev/null || true

    case "$subcmd" in
        sync)
            # Sync completed tasks back to GitHub issues
            if [ ! -d "$LOKI_DIR" ]; then
                echo -e "${RED}No active Loki session found${NC}"
                exit 1
            fi

            if ! command -v gh &>/dev/null; then
                echo -e "${RED}Error: gh CLI not found. Install with: brew install gh${NC}"
                exit 1
            fi

            if ! gh auth status &>/dev/null; then
                echo -e "${RED}Error: gh CLI not authenticated. Run: gh auth login${NC}"
                exit 1
            fi

            export LOKI_GITHUB_SYNC=true
            source "$RUN_SH" 2>/dev/null || true

            echo -e "${GREEN}Syncing completed tasks to GitHub...${NC}"
            if type sync_github_completed_tasks &>/dev/null; then
                sync_github_completed_tasks
                echo -e "${GREEN}Sync complete.${NC}"

                # Show what was synced
                if [ -f "$LOKI_DIR/github/synced.log" ]; then
                    local count
                    count=$(wc -l < "$LOKI_DIR/github/synced.log" | tr -d ' ')
                    echo -e "${DIM}Total synced status updates: $count${NC}"
                fi
            else
                echo -e "${YELLOW}Sync function not available.${NC}"
            fi
            ;;

        export)
            # Export local tasks as GitHub issues
            if [ ! -d "$LOKI_DIR" ]; then
                echo -e "${RED}No active Loki session found${NC}"
                exit 1
            fi

            if ! command -v gh &>/dev/null; then
                echo -e "${RED}Error: gh CLI not found. Install with: brew install gh${NC}"
                exit 1
            fi

            echo -e "${GREEN}Exporting local tasks to GitHub issues...${NC}"
            source "$RUN_SH" 2>/dev/null || true
            if type export_tasks_to_github &>/dev/null; then
                export_tasks_to_github
                echo -e "${GREEN}Export complete.${NC}"
            else
                echo -e "${YELLOW}Export function not available.${NC}"
            fi
            ;;

        pr)
            # Create PR from completed work
            if ! command -v gh &>/dev/null; then
                echo -e "${RED}Error: gh CLI not found. Install with: brew install gh${NC}"
                exit 1
            fi

            local feature_name="${1:-Loki Mode changes}"
            export LOKI_GITHUB_PR=true
            source "$RUN_SH" 2>/dev/null || true

            echo -e "${GREEN}Creating pull request: $feature_name${NC}"
            if type create_github_pr &>/dev/null; then
                create_github_pr "$feature_name"
            else
                echo -e "${YELLOW}PR function not available.${NC}"
            fi
            ;;

        status)
            # Show GitHub integration status
            echo -e "${BOLD}GitHub Integration Status${NC}"
            echo ""

            # gh CLI
            if command -v gh &>/dev/null; then
                echo -e "  gh CLI:       ${GREEN}installed $(gh --version 2>/dev/null | head -1 | sed 's/gh version /v/')${NC}"
                if gh auth status &>/dev/null 2>&1; then
                    echo -e "  Auth:         ${GREEN}authenticated${NC}"
                else
                    echo -e "  Auth:         ${RED}not authenticated${NC}"
                fi
            else
                echo -e "  gh CLI:       ${RED}not installed${NC}"
            fi

            # Repo detection
            local repo=""
            repo=$(git remote get-url origin 2>/dev/null | sed 's|.*github.com[:/]||;s|\.git$||' || echo "")
            if [ -n "$repo" ]; then
                echo -e "  Repository:   ${GREEN}$repo${NC}"
            else
                echo -e "  Repository:   ${YELLOW}not detected${NC}"
            fi

            # Config flags
            echo ""
            echo -e "${BOLD}Configuration${NC}"
            echo -e "  LOKI_GITHUB_IMPORT:  ${LOKI_GITHUB_IMPORT:-false}"
            echo -e "  LOKI_GITHUB_SYNC:    ${LOKI_GITHUB_SYNC:-false}"
            echo -e "  LOKI_GITHUB_PR:      ${LOKI_GITHUB_PR:-false}"
            echo -e "  LOKI_GITHUB_LABELS:  ${LOKI_GITHUB_LABELS:-(all)}"
            echo -e "  LOKI_GITHUB_LIMIT:   ${LOKI_GITHUB_LIMIT:-100}"

            # Sync log
            if [ -f "$LOKI_DIR/github/synced.log" ]; then
                echo ""
                echo -e "${BOLD}Sync History${NC}"
                local total
                total=$(wc -l < "$LOKI_DIR/github/synced.log" | tr -d ' ')
                echo -e "  Total synced updates: $total"
                echo -e "  Recent:"
                tail -5 "$LOKI_DIR/github/synced.log" | sed 's/^/    /'
            fi

            # Imported tasks
            if [ -f "$LOKI_DIR/queue/pending.json" ]; then
                local gh_tasks
                gh_tasks=$(python3 -c "
import json
try:
    with open('$LOKI_DIR/queue/pending.json') as f:
        data = json.load(f)
    tasks = data.get('tasks', data) if isinstance(data, dict) else data
    gh = [t for t in tasks if t.get('source') == 'github']
    print(len(gh))
except: print(0)
" 2>/dev/null || echo "0")
                echo ""
                echo -e "${BOLD}Imported Issues${NC}"
                echo -e "  GitHub tasks in queue: $gh_tasks"
            fi
            ;;

        help|*)
            echo -e "${BOLD}loki github${NC} - GitHub integration management"
            echo ""
            echo "Commands:"
            echo "  status       Show GitHub integration status"
            echo "  sync         Sync completed task status back to GitHub issues"
            echo "  export       Export local tasks as new GitHub issues"
            echo "  pr [name]    Create pull request from completed work"
            echo ""
            echo "Environment Variables:"
            echo "  LOKI_GITHUB_IMPORT=true    Import open issues as tasks on start"
            echo "  LOKI_GITHUB_SYNC=true      Sync status back to issues during session"
            echo "  LOKI_GITHUB_PR=true        Create PR when session completes successfully"
            echo "  LOKI_GITHUB_LABELS=bug     Filter issues by label (comma-separated)"
            echo "  LOKI_GITHUB_MILESTONE=v2   Filter by milestone"
            echo "  LOKI_GITHUB_ASSIGNEE=me    Filter by assignee"
            echo "  LOKI_GITHUB_LIMIT=50       Max issues to import (default: 100)"
            echo "  LOKI_GITHUB_PR_LABEL=loki  Label to add to created PRs"
            echo ""
            echo "Examples:"
            echo "  loki github status"
            echo "  loki github sync"
            echo "  loki github export"
            echo "  loki github pr \"Add user authentication\""
            echo "  LOKI_GITHUB_SYNC=true loki start --github ./prd.md"
            ;;
    esac
}

# Parse GitHub issue using issue-parser.sh
cmd_issue_parse() {
    local issue_ref=""
    local format="yaml"
    local output_file=""
    local quiet=false

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki issue parse${NC} - Parse GitHub issue into structured format"
                echo ""
                echo "Usage: loki issue parse <issue-ref> [options]"
                echo ""
                echo "Issue Reference Formats:"
                echo "  https://github.com/owner/repo/issues/123"
                echo "  owner/repo#123"
                echo "  #123                  (uses current repo)"
                echo "  123                   (uses current repo)"
                echo ""
                echo "Options:"
                echo "  --format yaml|json    Output format (default: yaml)"
                echo "  --output FILE         Write to file instead of stdout"
                echo "  --quiet               Suppress info messages"
                echo ""
                echo "Examples:"
                echo "  loki issue parse 123"
                echo "  loki issue parse owner/repo#456 --format json"
                echo "  loki issue parse 789 --output issue.yaml"
                exit 0
                ;;
            --format)
                format="${2:-yaml}"
                if [ $# -ge 2 ]; then shift 2; else shift; fi
                ;;
            --format=*)
                format="${1#*=}"
                shift
                ;;
            --output|-o)
                output_file="${2:-}"
                if [ $# -ge 2 ]; then shift 2; else shift; fi
                ;;
            --output=*)
                output_file="${1#*=}"
                if [[ "$output_file" == *".."* ]]; then
                    echo -e "${RED}Error: Output path must not contain '..'${NC}"
                    exit 1
                fi
                shift
                ;;
            --quiet|-q)
                quiet=true
                shift
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki issue parse --help' for usage."
                exit 1
                ;;
            *)
                if [[ -z "$issue_ref" ]]; then
                    issue_ref="$1"
                fi
                shift
                ;;
        esac
    done

    if [[ -z "$issue_ref" ]]; then
        echo -e "${RED}Error: Issue reference required${NC}"
        echo "Usage: loki issue parse <issue-ref>"
        exit 1
    fi

    # Find and run issue-parser.sh
    local parser_script="$SKILL_DIR/autonomy/issue-parser.sh"
    if [[ ! -f "$parser_script" ]]; then
        echo -e "${RED}Error: issue-parser.sh not found at $parser_script${NC}"
        exit 1
    fi

    local args=("$issue_ref" "--format" "$format")
    [[ -n "$output_file" ]] && args+=("--output" "$output_file")
    [[ "$quiet" == "true" ]] && args+=("--quiet")

    "$parser_script" "${args[@]}"
}

# View parsed GitHub issue details
cmd_issue_view() {
    require_jq || return 1

    local issue_ref="${1:-}"

    # Handle --help flag
    if [[ "$issue_ref" == "--help" || "$issue_ref" == "-h" ]]; then
        echo -e "${BOLD}loki issue view${NC} - View parsed GitHub issue details"
        echo ""
        echo "Usage: loki issue view <issue-ref>"
        echo ""
        echo "Issue Reference Formats:"
        echo "  https://github.com/owner/repo/issues/123"
        echo "  owner/repo#123"
        echo "  #123                  (uses current repo)"
        echo "  123                   (uses current repo)"
        echo ""
        echo "Examples:"
        echo "  loki issue view 123"
        echo "  loki issue view owner/repo#456"
        exit 0
    fi

    if [[ -z "$issue_ref" ]]; then
        echo -e "${RED}Error: Issue reference required${NC}"
        echo "Usage: loki issue view <issue-ref>"
        exit 1
    fi

    # Find and run issue-parser.sh
    local parser_script="$SKILL_DIR/autonomy/issue-parser.sh"
    if [[ ! -f "$parser_script" ]]; then
        echo -e "${RED}Error: issue-parser.sh not found at $parser_script${NC}"
        exit 1
    fi

    # Parse and get JSON for easier extraction
    local parsed_json
    parsed_json=$("$parser_script" "$issue_ref" --format json --quiet 2>&1)
    local exit_code=$?

    if [[ $exit_code -ne 0 ]]; then
        echo "$parsed_json"
        exit $exit_code
    fi

    # Extract fields using jq
    local title type priority labels url problem acceptance technical raw_body
    title=$(echo "$parsed_json" | jq -r '.metadata.title // ""')
    type=$(echo "$parsed_json" | jq -r '.metadata.type // "task"')
    priority=$(echo "$parsed_json" | jq -r '.metadata.priority // "normal"')
    labels=$(echo "$parsed_json" | jq -r '.metadata.labels | join(", ") // ""')
    url=$(echo "$parsed_json" | jq -r '.source.url // ""')
    problem=$(echo "$parsed_json" | jq -r '.content.problem_statement // ""')
    acceptance=$(echo "$parsed_json" | jq -r '.content.acceptance_criteria // ""')
    technical=$(echo "$parsed_json" | jq -r '.content.technical_requirements // ""')
    raw_body=$(echo "$parsed_json" | jq -r '.content.raw_body // ""')

    # Display structured view
    echo -e "${BOLD}GitHub Issue Details${NC}"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    echo -e "${CYAN}Title:${NC}    $title"
    echo -e "${CYAN}Type:${NC}     $type"
    echo -e "${CYAN}Priority:${NC} $priority"
    echo -e "${CYAN}Labels:${NC}   $labels"
    echo -e "${CYAN}URL:${NC}      $url"
    echo ""

    # Problem statement
    if [[ -n "$problem" ]]; then
        echo -e "${BOLD}Problem Statement${NC}"
        echo "─────────────────────────────────────────────────────────────────────────"
        echo "$problem" | head -15
        echo ""
    fi

    # Acceptance criteria
    if [[ -n "$acceptance" && "$acceptance" != "null" ]]; then
        echo -e "${BOLD}Acceptance Criteria${NC}"
        echo "─────────────────────────────────────────────────────────────────────────"
        echo "$acceptance" | head -15
        echo ""
    fi

    # Technical requirements
    if [[ -n "$technical" && "$technical" != "null" ]]; then
        echo -e "${BOLD}Technical Requirements${NC}"
        echo "─────────────────────────────────────────────────────────────────────────"
        echo "$technical" | head -10
        echo ""
    fi

    # Show raw body preview if nothing else was extracted
    if [[ -z "$problem" && -z "$acceptance" && -z "$technical" && -n "$raw_body" ]]; then
        echo -e "${BOLD}Issue Body${NC}"
        echo "─────────────────────────────────────────────────────────────────────────"
        echo "$raw_body" | head -20
        echo ""
    fi

    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo -e "Next steps:"
    echo -e "  ${CYAN}loki issue parse $issue_ref --format json${NC}  # Get JSON output"
    echo -e "  ${CYAN}loki issue $issue_ref --start${NC}              # Generate PRD and start"
}

#===============================================================================
# loki run - Issue-driven engineering (v6.0.0)
# Primary entry point for issue-to-implementation workflow.
# Supports GitHub, GitLab, Jira, and Azure DevOps issues.
#===============================================================================

cmd_run() {
    # v6.84.0: `loki run` is now a deprecated alias for `loki start <issue-ref>`.
    # When invoked directly (not via the unified start dispatcher), emit a
    # deprecation notice and a telemetry event so we can track adoption of the
    # unified command. Skip when LOKI_UNIFIED_START=1 (cmd_start -> cmd_run).
    if [ "${LOKI_UNIFIED_START:-0}" != "1" ]; then
        # Only warn for real invocations, not --help (help is still useful)
        local _show_warn=true
        for _a in "$@"; do
            if [ "$_a" = "--help" ] || [ "$_a" = "-h" ]; then
                _show_warn=false
                break
            fi
        done
        if [ "$_show_warn" = "true" ]; then
            echo -e "${YELLOW}Notice: 'loki run' is deprecated. Use 'loki start <issue-ref>' instead.${NC}" >&2
            echo -e "${DIM}  All features (--worktree, --pr, --ship, --dry-run) work on 'loki start'.${NC}" >&2
            echo "" >&2
            # Emit telemetry event so we can measure adoption of the unified command.
            # Signature: emit_event <type> <source> <action> [key=value ...]
            emit_event cli_command_deprecated cli run_to_start \
                "old_command=run" \
                "new_command=start" \
                "version=6.84.0" \
                "argv=${*:-}"
        fi
    fi

    require_jq || return 1

    local issue_ref=""
    local dry_run=false
    local output_file=""
    local start_args=()
    local no_start=false
    local provider_override=""
    local use_worktree=false
    local create_pr=false
    local auto_merge=false
    local run_detached=false

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki run${NC} - Issue-driven engineering (DEPRECATED: use 'loki start')"
                echo ""
                echo "Usage: loki run <issue-ref> [options]"
                echo "       loki run <url-or-key> [options]"
                echo ""
                echo "Takes an issue from any supported tracker, generates a PRD,"
                echo "and starts Loki Mode to implement it autonomously."
                echo ""
                echo "Issue Reference Formats:"
                echo "  GitHub:       123, #123, owner/repo#123, https://github.com/..."
                echo "  GitLab:       https://gitlab.com/owner/repo/-/issues/42"
                echo "  Jira:         PROJ-123, https://org.atlassian.net/browse/PROJ-123"
                echo "  Azure DevOps: https://dev.azure.com/org/project/_workitems/edit/456"
                echo ""
                echo "Options:"
                echo "  --dry-run          Preview generated PRD without starting"
                echo "  --no-start         Generate PRD but don't start execution"
                echo "  --output FILE      Save PRD to custom path"
                echo "  --provider NAME    AI provider: claude (default), codex, cline, aider"
                echo "  --parallel         Enable parallel mode with git worktrees"
                echo "  --bg, --background Run in background mode"
                echo "  --simple           Force simple complexity tier"
                echo "  --complex          Force complex complexity tier"
                echo "  --no-dashboard     Disable web dashboard"
                echo "  --sandbox          Run in Docker sandbox"
                echo "  --no-plan          Skip auto-shown PRD analysis at startup"
                echo "  --budget USD       Set cost budget limit"
                echo ""
                echo "Progressive Isolation:"
                echo "  --worktree, -w     Git worktree isolation (separate branch)"
                echo "  --pr               Worktree + auto-create PR (implies --worktree)"
                echo "  --ship             Worktree + PR + auto-merge (implies --pr)"
                echo "  --detach, -d       Run in background (implies --worktree)"
                echo ""
                echo "  Cascade: --ship implies --pr implies --worktree"
                echo ""
                echo "Environment Variables:"
                echo "  JIRA_API_TOKEN     Jira API token (for Jira issues)"
                echo "  JIRA_URL           Jira base URL (for Jira issues)"
                echo "  JIRA_EMAIL         Jira user email (for Jira Cloud auth)"
                echo ""
                echo "Examples:"
                echo "  loki run 123                             # GitHub issue from current repo"
                echo "  loki run owner/repo#456                  # GitHub issue from specific repo"
                echo "  loki run PROJ-789                        # Jira issue"
                echo "  loki run https://gitlab.com/o/r/-/issues/42  # GitLab issue"
                echo "  loki run 123 --dry-run                   # Preview PRD"
                echo "  loki run 123 --parallel --provider codex # Parallel mode with Codex"
                echo "  loki run 123 --worktree                  # Isolated branch"
                echo "  loki run 123 --pr                        # Auto-create PR"
                echo "  loki run 123 --ship                      # Full automation: PR + merge"
                echo "  loki run 123 --ship -d                   # Background, full automation"
                exit 0
                ;;
            --dry-run)
                dry_run=true
                shift
                ;;
            --no-start)
                no_start=true
                shift
                ;;
            --output)
                if [[ -n "${2:-}" ]]; then
                    output_file="$2"
                    if [[ "$output_file" == *".."* ]]; then
                        echo -e "${RED}Error: Output path must not contain '..'${NC}"
                        exit 1
                    fi
                    shift 2
                else
                    echo -e "${RED}--output requires a file path${NC}"
                    exit 1
                fi
                ;;
            --output=*)
                output_file="${1#*=}"
                if [[ "$output_file" == *".."* ]]; then
                    echo -e "${RED}Error: Output path must not contain '..'${NC}"
                    exit 1
                fi
                shift
                ;;
            --provider)
                if [[ -n "${2:-}" ]]; then
                    provider_override="$2"
                    start_args+=("--provider" "$2")
                    # UT2-13: Record CLI provider at parse time (atomic write).
                    # v7.7.11 (council fix): validate against canonical set; include
                    # PID so reader can drop the marker when originating process is gone.
                    case "$2" in
                        claude|codex|cline|aider)
                            mkdir -p "${LOKI_DIR:-.loki}/state" 2>/dev/null || true
                            printf '%s:%s:%s\n' "$2" "$(date +%s)" "$$" \
                                > "${LOKI_DIR:-.loki}/state/.cli-provider.tmp" 2>/dev/null \
                                && mv "${LOKI_DIR:-.loki}/state/.cli-provider.tmp" \
                                      "${LOKI_DIR:-.loki}/state/cli-provider" 2>/dev/null || true
                            ;;
                    esac
                    shift 2
                else
                    echo -e "${RED}--provider requires a value${NC}"
                    exit 1
                fi
                ;;
            --provider=*)
                provider_override="${1#*=}"
                start_args+=("--provider" "$provider_override")
                # UT2-13: Record CLI provider at parse time (atomic write).
                # v7.7.11 (council fix): validate + include PID.
                case "$provider_override" in
                    claude|codex|cline|aider)
                        mkdir -p "${LOKI_DIR:-.loki}/state" 2>/dev/null || true
                        printf '%s:%s:%s\n' "$provider_override" "$(date +%s)" "$$" \
                            > "${LOKI_DIR:-.loki}/state/.cli-provider.tmp" 2>/dev/null \
                            && mv "${LOKI_DIR:-.loki}/state/.cli-provider.tmp" \
                                  "${LOKI_DIR:-.loki}/state/cli-provider" 2>/dev/null || true
                        ;;
                esac
                shift
                ;;
            --parallel|--bg|--background|--simple|--complex|--no-dashboard|--sandbox|--no-plan)
                start_args+=("$1")
                shift
                ;;
            --budget)
                if [[ -n "${2:-}" ]]; then
                    start_args+=("--budget" "$2")
                    shift 2
                else
                    echo -e "${RED}--budget requires a USD amount${NC}"
                    exit 1
                fi
                ;;
            --budget=*)
                start_args+=("--budget" "${1#*=}")
                shift
                ;;
            --worktree|-w)
                use_worktree=true
                shift
                ;;
            --pr)
                use_worktree=true
                create_pr=true
                shift
                ;;
            --ship)
                use_worktree=true
                create_pr=true
                auto_merge=true
                shift
                ;;
            --detach|-d)
                use_worktree=true
                run_detached=true
                shift
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki run --help' for usage."
                exit 1
                ;;
            *)
                if [[ -z "$issue_ref" ]]; then
                    issue_ref="$1"
                fi
                shift
                ;;
        esac
    done

    # Validate git prerequisites for --ship and --pr flags
    if $create_pr || $auto_merge; then
        if ! command -v git &>/dev/null; then
            echo -e "${RED}Error: --pr/--ship requires git but it is not installed.${NC}"
            return 1
        fi
        if ! git rev-parse --git-dir &>/dev/null 2>&1; then
            echo -e "${RED}Error: --pr/--ship requires a git repository but the current directory is not one.${NC}"
            return 1
        fi
        if [[ -z "$(git remote 2>/dev/null)" ]]; then
            echo -e "${RED}Error: --pr/--ship requires a git remote but none is configured.${NC}"
            return 1
        fi
    fi

    # Add --parallel once if worktree mode is enabled (not per-flag)
    if $use_worktree; then
        start_args+=("--parallel")
    fi

    if [[ -z "$issue_ref" ]]; then
        echo -e "${RED}Error: Issue reference required${NC}"
        echo ""
        echo "Usage: loki run <issue-ref> [options]"
        echo ""
        echo "Examples:"
        echo "  loki run 123                    # GitHub issue"
        echo "  loki run PROJ-456               # Jira issue"
        echo "  loki run owner/repo#789         # GitHub with specific repo"
        echo ""
        echo "Run 'loki run --help' for full usage."
        exit 1
    fi

    # Source issue provider abstraction
    local issue_providers_script="$SKILL_DIR/autonomy/issue-providers.sh"
    if [[ ! -f "$issue_providers_script" ]]; then
        echo -e "${RED}Error: issue-providers.sh not found at $issue_providers_script${NC}"
        exit 1
    fi
    source "$issue_providers_script"

    # Detect provider
    local issue_provider
    issue_provider=$(detect_issue_provider "$issue_ref")
    echo -e "${CYAN}Issue provider:${NC} ${issue_provider}"

    # Fetch the issue
    echo -e "${CYAN}Fetching issue...${NC}"
    local issue_json
    issue_json=$(fetch_issue "$issue_ref") || {
        echo -e "${RED}Failed to fetch issue${NC}"
        exit 1
    }

    # Extract fields for display
    local title number url
    title=$(echo "$issue_json" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('title',''))")
    number=$(echo "$issue_json" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('number',''))")
    url=$(echo "$issue_json" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('url',''))")

    echo -e "${GREEN}Issue #$number:${NC} $title"
    if [[ -n "$url" ]]; then
        echo -e "${DIM}$url${NC}"
    fi
    echo ""

    # Per-session locking (v6.4.0): export session ID so run.sh uses
    # per-session PID/lock files instead of the global lock. This allows
    # multiple concurrent `loki run` sessions (e.g., loki run 52 -d && loki run 54 -d).
    local session_id="${number:-$(date +%s)}"
    export LOKI_SESSION_ID="$session_id"

    # Progressive isolation: set up worktree branch naming
    if $use_worktree; then
        local branch_name="issue/${issue_provider}-${number:-$(date +%s)}"
        log_info "Progressive isolation: branch $branch_name"
        export LOKI_PARALLEL_MODE=true
        export LOKI_WORKTREE_BRANCH="$branch_name"
    fi

    # Generate PRD
    local prd_content
    prd_content=$(echo "$issue_json" | generate_prd_from_issue)

    # Detached mode: fork to background
    if $run_detached; then
        # Guard: prevent launching duplicate session
        if is_session_running "$session_id"; then
            echo -e "${RED}Error: Session '$session_id' is already running.${NC}"
            echo -e "Stop it first with: ${CYAN}loki stop $session_id${NC}"
            exit 1
        fi

        local log_file="$LOKI_DIR/logs/run-${number:-$(date +%s)}.log"
        mkdir -p "$(dirname "$log_file")"
        echo -e "${GREEN}Running detached. Logs: $log_file${NC}"
        echo -e "Check status: ${CYAN}loki status${NC}"

        # Write PRD first so background process can use it
        mkdir -p "$LOKI_DIR"
        local detach_prd="$LOKI_DIR/prd-issue-${number}.md"
        echo "$prd_content" > "$detach_prd"

        if [[ -z "${branch_name:-}" ]]; then
            branch_name="issue/detach-$(date +%s)"
        fi

        # Write a temp script to avoid shell injection via variable interpolation
        local run_script="$LOKI_DIR/scripts/run-${number:-detached}.sh"
        mkdir -p "$LOKI_DIR/scripts"
        local loki_cmd
        loki_cmd="$(command -v loki || echo "$0")"
        cat > "$run_script" << 'INNER_SCRIPT_EOF'
#!/usr/bin/env bash
set -euo pipefail
cd "$LOKI_RUN_DIR"
export LOKI_DETACHED=true
export LOKI_PARALLEL_MODE=true
"$LOKI_CMD" start "$LOKI_PRD_PATH" ${LOKI_START_ARGS:-}

# Post-completion: create PR if requested
if [[ "$LOKI_CREATE_PR" == "true" ]]; then
    branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
    if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
        git push origin "$branch_current" 2>/dev/null || true
        gh pr create --title "$LOKI_PR_TITLE" --body "Implemented by Loki Mode" --head "$branch_current" 2>/dev/null || true
    fi
fi
# Post-completion: auto-merge if requested
if [[ "$LOKI_AUTO_MERGE" == "true" ]]; then
    branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
    if gh pr merge "$branch_current" --squash --delete-branch 2>/dev/null; then
        if [[ -n "${LOKI_ISSUE_NUMBER:-}" ]]; then
            gh issue close "$LOKI_ISSUE_NUMBER" --comment "Resolved by Loki Mode" 2>/dev/null || true
        fi
    fi
fi
INNER_SCRIPT_EOF
        chmod +x "$run_script"

        # Pass all variables safely via environment
        LOKI_RUN_DIR="$(pwd)" \
        LOKI_CMD="$loki_cmd" \
        LOKI_SESSION_ID="$session_id" \
        LOKI_WORKTREE_BRANCH="$branch_name" \
        LOKI_PRD_PATH="$detach_prd" \
        LOKI_START_ARGS="${start_args[*]+"${start_args[*]}"}" \
        LOKI_CREATE_PR="$create_pr" \
        LOKI_AUTO_MERGE="$auto_merge" \
        LOKI_PR_TITLE="${title:-Implementation for issue ${issue_ref}}" \
        LOKI_ISSUE_NUMBER="${number:-}" \
        nohup bash "$run_script" > "$log_file" 2>&1 &

        local bg_pid=$!
        echo "$bg_pid" > "$LOKI_DIR/run-${number:-detached}.pid"
        echo "Background PID: $bg_pid"
        return 0
    fi

    # Handle dry-run
    if [[ "$dry_run" == "true" ]]; then
        echo -e "${BOLD}Generated PRD Preview:${NC}"
        echo "------------------------------------------------------------------------"
        echo "$prd_content"
        echo "------------------------------------------------------------------------"
        echo ""
        echo -e "${DIM}(dry-run mode - PRD not saved)${NC}"
        exit 0
    fi

    # Determine output file
    if [[ -z "$output_file" ]]; then
        mkdir -p "$LOKI_DIR"
        # Use provider-specific naming
        case "$issue_provider" in
            github|gitlab) output_file="$LOKI_DIR/prd-issue-$number.md" ;;
            jira)          output_file="$LOKI_DIR/prd-$number.md" ;;
            azure_devops)  output_file="$LOKI_DIR/prd-ado-$number.md" ;;
            *)             output_file="$LOKI_DIR/prd-issue-$number.md" ;;
        esac
    fi

    # Write PRD file
    echo "$prd_content" > "$output_file"
    echo -e "${GREEN}PRD generated:${NC} $output_file"

    # v6.81.1: auto-show PRD analysis once the issue-derived PRD is on disk,
    # so users see cost / complexity / time before execution begins. When the
    # user will continue into `cmd_start` below, cmd_start also has its own
    # auto-plan call -- but that second call respects --no-plan / TTY checks
    # and would typically re-emit the same analysis. We therefore pass
    # --no-plan along in start_args to prevent duplicate output. Passing
    # --no-plan here is safe because `cmd_run` itself already displayed the
    # plan.
    local _run_no_plan=false
    for _arg in "${start_args[@]+"${start_args[@]}"}"; do
        if [[ "$_arg" == "--no-plan" ]]; then
            _run_no_plan=true
            break
        fi
    done
    if [ -f "$output_file" ]; then
        maybe_show_auto_plan "$output_file" "$_run_no_plan" || true
    fi
    # Ensure cmd_start does not re-show the plan now that we just did.
    if [[ "$_run_no_plan" != "true" ]]; then
        start_args+=("--no-plan")
    fi

    # Start Loki Mode unless --no-start
    if [[ "$no_start" == "true" ]]; then
        echo ""
        echo "Next steps:"
        echo -e "  ${CYAN}loki start $output_file${NC}    # Start with generated PRD"
        echo -e "  ${CYAN}cat $output_file${NC}            # View PRD"
    else
        echo ""
        echo -e "${GREEN}Starting Loki Mode with generated PRD...${NC}"
        cmd_start "$output_file" ${start_args[@]+"${start_args[@]}"}

        # Progressive isolation: create PR
        if $create_pr; then
            echo ""
            echo -e "${GREEN}Creating pull request...${NC}"
            local pr_title="${title:-Implementation for issue ${issue_ref}}"
            local pr_body="Implemented by Loki Mode (autonomous agent)

Issue: ${issue_ref}
Provider: ${issue_provider}

## Changes
$(git log --oneline "main..HEAD" 2>/dev/null || echo "See diff")"

            case "${issue_provider:-github}" in
                github)
                    if command -v gh &>/dev/null; then
                        local branch_current
                        branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
                        if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
                            git push origin "$branch_current" 2>/dev/null || true
                            gh pr create --title "$pr_title" --body "$pr_body" --head "$branch_current" 2>/dev/null && \
                                echo -e "${GREEN}PR created${NC}" || \
                                echo -e "${YELLOW}PR creation failed${NC}"
                        fi
                    fi
                    ;;
                gitlab)
                    if command -v glab &>/dev/null; then
                        local branch_current
                        branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
                        if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
                            git push origin "$branch_current" 2>/dev/null || true
                            glab mr create --title "$pr_title" --description "$pr_body" --source-branch "$branch_current" 2>/dev/null && \
                                echo -e "${GREEN}MR created${NC}" || \
                                echo -e "${YELLOW}MR creation failed${NC}"
                        fi
                    fi
                    ;;
                *)
                    echo -e "${YELLOW}PR creation not supported for provider: $issue_provider${NC}"
                    ;;
            esac
        fi

        # Progressive isolation: auto-merge
        if $auto_merge; then
            echo -e "${GREEN}Auto-merging...${NC}"
            case "${issue_provider:-github}" in
                github)
                    local branch_current
                    branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
                    if gh pr merge "$branch_current" --squash --delete-branch 2>/dev/null; then
                        echo -e "${GREEN}PR merged and branch deleted${NC}"
                        if [[ -n "${number:-}" ]]; then
                            gh issue close "$number" --comment "Resolved by Loki Mode" 2>/dev/null || true
                        fi
                    else
                        echo -e "${YELLOW}Auto-merge failed. PR remains open.${NC}"
                    fi
                    ;;
                gitlab)
                    glab mr merge --squash --remove-source-branch 2>/dev/null && \
                        echo -e "${GREEN}MR merged and branch deleted${NC}" || \
                        echo -e "${YELLOW}Auto-merge failed. MR remains open.${NC}"
                    ;;
                *)
                    echo -e "${YELLOW}Auto-merge not supported for provider: $issue_provider${NC}"
                    ;;
            esac
        fi
    fi
}

# Generate PRD from GitHub issue (DEPRECATED in v6.0.0 - use 'loki run' instead)
cmd_issue() {
    # v6.0.0: Show deprecation notice
    echo -e "${YELLOW}[DEPRECATED] 'loki issue' is deprecated in v6.0.0. Use 'loki run' instead.${NC}" >&2
    echo -e "${YELLOW}  'loki run' supports GitHub, GitLab, Jira, and Azure DevOps issues.${NC}" >&2
    echo "" >&2

    require_jq || return 1

    local issue_ref=""
    local repo=""
    local number=""
    local start_loki=false
    local dry_run=false
    local output_file=""
    local start_args=()
    local subcommand=""

    # Check for subcommands first
    if [[ $# -gt 0 ]]; then
        case "$1" in
            parse)
                # Delegate to issue-parser.sh
                shift
                cmd_issue_parse "$@"
                return $?
                ;;
            view)
                # View parsed issue details
                shift
                cmd_issue_view "$@"
                return $?
                ;;
        esac
    fi

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki issue${NC} - Generate PRD from GitHub issue"
                echo ""
                echo "Usage: loki issue <url-or-number> [options]"
                echo "       loki issue parse <url-or-number> [--format yaml|json]"
                echo "       loki issue view <url-or-number>"
                echo "       loki issue --repo owner/repo --number 123 [options]"
                echo ""
                echo "Subcommands:"
                echo "  parse              Parse issue and output structured YAML/JSON"
                echo "  view               View parsed issue details"
                echo ""
                echo "Arguments:"
                echo "  <url-or-number>    GitHub issue URL or issue number"
                echo ""
                echo "Options:"
                echo "  --repo OWNER/REPO  Specify repository (default: auto-detect from git)"
                echo "  --number NUM       Specify issue number (alternative to positional arg)"
                echo "  --start            Start Loki Mode with the generated PRD"
                echo "  --dry-run          Preview generated PRD without saving"
                echo "  --output FILE      Save PRD to custom path (default: .loki/prd-issue-N.md)"
                echo ""
                echo "Options passed to 'start' (when --start is used):"
                echo "  --provider NAME    AI provider: claude (default), codex, cline, aider"
                echo "  --parallel         Enable parallel mode with git worktrees"
                echo "  --bg, --background Run in background mode"
                echo ""
                echo "Examples:"
                echo "  loki issue 123                           # Generate PRD from issue #123"
                echo "  loki issue 123 --dry-run                 # Preview without saving"
                echo "  loki issue 123 --start                   # Generate and start Loki Mode"
                echo "  loki issue 123 --start --parallel        # Generate, start in parallel mode"
                echo "  loki issue parse owner/repo#123          # Parse and output structured data"
                echo "  loki issue parse 123 --format json       # Parse and output JSON"
                echo "  loki issue view 123                      # View parsed issue details"
                echo "  loki issue --repo org/repo --number 456  # Specify repo and issue"
                echo "  loki issue https://github.com/org/repo/issues/789"
                exit 0
                ;;
            --repo)
                if [[ -n "${2:-}" ]]; then
                    repo="$2"
                    # Validate owner/repo format
                    if [[ ! "$repo" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
                        echo -e "${RED}Invalid --repo format: $repo${NC}"
                        echo "Expected: owner/repo (e.g., asklokesh/loki-mode)"
                        exit 1
                    fi
                    shift 2
                else
                    echo -e "${RED}--repo requires a value (owner/repo)${NC}"
                    exit 1
                fi
                ;;
            --repo=*)
                repo="${1#*=}"
                # Validate owner/repo format
                if [[ ! "$repo" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
                    echo -e "${RED}Invalid --repo format: $repo${NC}"
                    echo "Expected: owner/repo (e.g., asklokesh/loki-mode)"
                    exit 1
                fi
                shift
                ;;
            --number)
                if [[ -n "${2:-}" ]]; then
                    number="$2"
                    # Validate numeric
                    if [[ ! "$number" =~ ^[0-9]+$ ]]; then
                        echo -e "${RED}Invalid --number value: $number${NC}"
                        echo "Expected: numeric issue number (e.g., 123)"
                        exit 1
                    fi
                    shift 2
                else
                    echo -e "${RED}--number requires a value${NC}"
                    exit 1
                fi
                ;;
            --number=*)
                number="${1#*=}"
                # Validate numeric
                if [[ ! "$number" =~ ^[0-9]+$ ]]; then
                    echo -e "${RED}Invalid --number value: $number${NC}"
                    echo "Expected: numeric issue number (e.g., 123)"
                    exit 1
                fi
                shift
                ;;
            --start)
                start_loki=true
                shift
                ;;
            --dry-run)
                dry_run=true
                shift
                ;;
            --output)
                if [[ -n "${2:-}" ]]; then
                    output_file="$2"
                    if [[ "$output_file" == *".."* ]]; then
                        echo -e "${RED}Error: Output path must not contain '..'${NC}"
                        exit 1
                    fi
                    shift 2
                else
                    echo -e "${RED}--output requires a file path${NC}"
                    exit 1
                fi
                ;;
            --output=*)
                output_file="${1#*=}"
                if [[ "$output_file" == *".."* ]]; then
                    echo -e "${RED}Error: Output path must not contain '..'${NC}"
                    exit 1
                fi
                shift
                ;;
            # Pass through options to start command
            --provider|--parallel|--bg|--background|--simple|--complex|--github|--no-dashboard|--sandbox)
                start_args+=("$1")
                if [[ "$1" == "--provider" && -n "${2:-}" ]]; then
                    start_args+=("$2")
                    shift
                fi
                shift
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki issue --help' for usage."
                exit 1
                ;;
            *)
                issue_ref="$1"
                shift
                ;;
        esac
    done

    # Check gh CLI
    if ! command -v gh &> /dev/null; then
        echo -e "${RED}Error: gh CLI not found${NC}"
        echo "Install with: brew install gh"
        exit 1
    fi

    if ! gh auth status &> /dev/null 2>&1; then
        echo -e "${RED}Error: gh CLI not authenticated${NC}"
        echo "Run: gh auth login"
        exit 1
    fi

    # Parse issue reference
    if [[ -n "$issue_ref" ]]; then
        # Check if it's a URL
        if [[ "$issue_ref" =~ ^https?://github\.com/([^/]+/[^/]+)/issues/([0-9]+) ]]; then
            repo="${BASH_REMATCH[1]}"
            number="${BASH_REMATCH[2]}"
        elif [[ "$issue_ref" =~ ^([^/]+/[^/]+)#([0-9]+)$ ]]; then
            # owner/repo#number format
            repo="${BASH_REMATCH[1]}"
            number="${BASH_REMATCH[2]}"
        elif [[ "$issue_ref" =~ ^#?([0-9]+)$ ]]; then
            # Just a number (with optional #)
            number="${BASH_REMATCH[1]}"
        else
            echo -e "${RED}Invalid issue reference: $issue_ref${NC}"
            echo "Expected: issue number (123), owner/repo#123, or GitHub URL"
            exit 1
        fi
    fi

    # Validate we have a number
    if [[ -z "$number" ]]; then
        echo -e "${RED}Error: No issue number specified${NC}"
        echo "Usage: loki issue <number> or loki issue --number <number>"
        echo "Run 'loki issue --help' for full usage."
        exit 1
    fi

    # Auto-detect repo if not specified
    if [[ -z "$repo" ]]; then
        # Try to get from gh CLI (uses git remote)
        repo=$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null) || true
        if [[ -z "$repo" ]]; then
            echo -e "${RED}Error: Could not determine repository${NC}"
            echo "Specify with: --repo owner/repo"
            echo "Or run from a directory with a GitHub remote configured."
            exit 1
        fi
    fi

    echo -e "${CYAN}Fetching issue #$number from $repo...${NC}"

    # Fetch issue details
    local issue_json
    if ! issue_json=$(gh issue view "$number" --repo "$repo" --json number,title,body,labels,milestone,author,createdAt,url 2>&1); then
        echo -e "${RED}Error fetching issue: $issue_json${NC}"
        exit 1
    fi

    # Extract fields
    local title body labels milestone author created_at issue_url
    title=$(echo "$issue_json" | jq -r '.title')
    body=$(echo "$issue_json" | jq -r '.body // ""')
    labels=$(echo "$issue_json" | jq -r '[.labels[].name] | join(", ") // ""')
    milestone=$(echo "$issue_json" | jq -r '.milestone.title // ""')
    author=$(echo "$issue_json" | jq -r '.author.login // ""')
    created_at=$(echo "$issue_json" | jq -r '.createdAt // ""')
    issue_url=$(echo "$issue_json" | jq -r '.url')

    # Generate PRD content
    local prd_content
    prd_content=$(cat <<EOF
# PRD: $title

**Source:** GitHub Issue [#$number]($issue_url)
**Author:** @$author
**Created:** $created_at
$([ -n "$labels" ] && echo "**Labels:** $labels")
$([ -n "$milestone" ] && echo "**Milestone:** $milestone")

---

## Overview

$body

---

## Acceptance Criteria

Based on the issue description, implement the following:

1. Address all requirements specified in the issue body above
2. Ensure backward compatibility (unless explicitly breaking changes are requested)
3. Add appropriate tests for new functionality
4. Update documentation as needed

---

## Technical Notes

- Source: GitHub Issue #$number
- Repository: $repo
- Generated by: Loki Mode CLI

---

## References

- Original Issue: $issue_url
EOF
)

    # Handle dry-run
    if [[ "$dry_run" == "true" ]]; then
        echo ""
        echo -e "${BOLD}Generated PRD Preview:${NC}"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "$prd_content"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""
        echo -e "${DIM}(dry-run mode - PRD not saved)${NC}"
        exit 0
    fi

    # Determine output file
    if [[ -z "$output_file" ]]; then
        mkdir -p "$LOKI_DIR"
        output_file="$LOKI_DIR/prd-issue-$number.md"
    fi

    # Write PRD file
    echo "$prd_content" > "$output_file"
    echo -e "${GREEN}PRD generated: $output_file${NC}"

    # Fetch comments if any exist
    local comments_count
    comments_count=$(gh issue view "$number" --repo "$repo" --json comments -q '.comments | length' 2>/dev/null) || comments_count=0

    if [[ "$comments_count" -gt 0 ]]; then
        echo -e "${DIM}Issue has $comments_count comment(s) - view with: gh issue view $number --repo $repo${NC}"
    fi

    # Start Loki Mode if requested
    if [[ "$start_loki" == "true" ]]; then
        echo ""
        echo -e "${GREEN}Starting Loki Mode with generated PRD...${NC}"
        cmd_start "$output_file" ${start_args[@]+"${start_args[@]}"}
    else
        echo ""
        echo "Next steps:"
        echo -e "  ${CYAN}loki start $output_file${NC}           # Start Loki Mode with this PRD"
        echo -e "  ${CYAN}loki issue $number --start${NC}        # Or re-run with --start flag"
        echo -e "  ${DIM}cat $output_file${NC}  # View the generated PRD"
    fi
}

# Show configuration
#===============================================================================
# loki watch - Auto-rerun on PRD file changes (v6.33.0)
#===============================================================================

cmd_watch() {
    local prd_path=""
    local run_once=false
    local poll_interval=2
    local no_auto_start=false
    local debounce=3
    local _watch_child_pid=""

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki watch${NC} - Auto-rerun on PRD file changes (v6.33.0)"
                echo ""
                echo "Usage: loki watch [prd-path] [options]"
                echo ""
                echo "Monitors a PRD file for changes and automatically re-runs Loki Mode"
                echo "when the file is saved. Enables a tight edit-PRD-see-results loop."
                echo ""
                echo "Arguments:"
                echo "  prd-path            Path to PRD file (default: auto-detect prd.md, PRD.md, or first *.md)"
                echo ""
                echo "Options:"
                echo "  --once              Run once immediately then exit"
                echo "  --interval N        Poll interval in seconds for fallback watcher (default: 2)"
                echo "  --no-auto-start     Watch but do not auto-start, just print change timestamps"
                echo "  --debounce N        Wait N seconds after change before triggering (default: 3)"
                echo "  --help, -h          Show this help"
                echo ""
                echo "File watcher priority:"
                echo "  1. fswatch (macOS)    -- native filesystem events"
                echo "  2. inotifywait (Linux) -- inotify-based"
                echo "  3. stat polling        -- universal fallback"
                echo ""
                echo "Examples:"
                echo "  loki watch                       # Auto-detect PRD and watch"
                echo "  loki watch ./my-prd.md           # Watch specific file"
                echo "  loki watch --once                # Run once immediately then exit"
                echo "  loki watch --no-auto-start       # Just report changes, don't run"
                echo "  loki watch --debounce 5          # Wait 5s after change before triggering"
                echo "  loki watch --interval 1          # Poll every 1s (fallback mode)"
                return 0
                ;;
            --once) run_once=true; shift ;;
            --interval) poll_interval="${2:-2}"; shift 2 ;;
            --interval=*) poll_interval="${1#*=}"; shift ;;
            --no-auto-start) no_auto_start=true; shift ;;
            --debounce) debounce="${2:-3}"; shift 2 ;;
            --debounce=*) debounce="${1#*=}"; shift ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki watch --help' for usage."
                return 1
                ;;
            *)
                if [ -z "$prd_path" ]; then
                    prd_path="$1"
                else
                    echo -e "${RED}Unexpected argument: $1${NC}"
                    return 1
                fi
                shift
                ;;
        esac
    done

    # Validate numeric arguments (no leading zeros, no decimals, positive integers only)
    if ! [[ "$poll_interval" =~ ^[1-9][0-9]*$ ]]; then
        echo -e "${RED}Invalid --interval: $poll_interval (expected positive integer)${NC}"
        return 1
    fi
    if ! [[ "$debounce" =~ ^[1-9][0-9]*$ ]] && [ "$debounce" != "0" ]; then
        echo -e "${RED}Invalid --debounce: $debounce (expected non-negative integer)${NC}"
        return 1
    fi

    # Auto-detect PRD file if not specified
    if [ -z "$prd_path" ]; then
        if [ -f "prd.md" ]; then
            prd_path="prd.md"
        elif [ -f "PRD.md" ]; then
            prd_path="PRD.md"
        else
            # Find first .md file in current directory
            local first_md
            first_md=$(ls -1 *.md 2>/dev/null | head -1 || true)
            if [ -n "$first_md" ]; then
                prd_path="$first_md"
            else
                echo -e "${RED}No PRD file found.${NC}"
                echo "Specify a file: loki watch ./my-prd.md"
                echo "Or create one: prd.md, PRD.md, or any .md file in current directory."
                return 1
            fi
        fi
    fi

    if [ ! -f "$prd_path" ]; then
        echo -e "${RED}PRD file not found: $prd_path${NC}"
        return 1
    fi

    # Verify the PRD file is readable (#67)
    if [ ! -r "$prd_path" ]; then
        echo -e "${RED}PRD file is not readable: $prd_path${NC}"
        echo "Check file permissions: ls -la $prd_path"
        return 1
    fi

    # Resolve to absolute path
    prd_path="$(cd "$(dirname "$prd_path")" && pwd)/$(basename "$prd_path")"
    local prd_basename
    prd_basename="$(basename "$prd_path")"

    # --once mode: run immediately and exit
    if [ "$run_once" = true ]; then
        echo -e "${BOLD}loki watch${NC} --once: running loki start ${prd_basename}"
        "$0" start "$prd_path"
        return $?
    fi

    # Graceful shutdown handler
    _watch_cleanup() {
        echo ""
        echo "Watch stopped."
        if [ -n "$_watch_child_pid" ] && kill -0 "$_watch_child_pid" 2>/dev/null; then
            echo "Stopping running loki session (PID $_watch_child_pid)..."
            kill "$_watch_child_pid" 2>/dev/null
            wait "$_watch_child_pid" 2>/dev/null || true
        fi
        # Kill any background watcher process
        if [ -n "${_watch_bg_pid:-}" ] && kill -0 "$_watch_bg_pid" 2>/dev/null; then
            kill "$_watch_bg_pid" 2>/dev/null
            wait "$_watch_bg_pid" 2>/dev/null || true
        fi
        exit 0
    }
    trap '_watch_cleanup' INT TERM

    local last_run="never"

    echo -e "${BOLD}loki watch${NC} -- monitoring ${prd_basename} for changes"
    echo "Watching: $prd_path"
    echo "Debounce: ${debounce}s | Ctrl+C to exit"
    if [ "$no_auto_start" = true ]; then
        echo "Mode: observe only (--no-auto-start)"
    fi
    echo ""

    _watch_status_line() {
        echo -e "\rWatching ${prd_basename} -- last run: ${last_run} -- next: on change"
    }

    _watch_trigger() {
        local change_time
        change_time="$(date '+%H:%M:%S')"

        if [ "$no_auto_start" = true ]; then
            echo "PRD changed: ${change_time}"
            return 0
        fi

        # Kill any currently running loki session
        if [ -n "$_watch_child_pid" ] && kill -0 "$_watch_child_pid" 2>/dev/null; then
            echo "Stopping previous loki session (PID $_watch_child_pid)..."
            kill "$_watch_child_pid" 2>/dev/null
            wait "$_watch_child_pid" 2>/dev/null || true
            _watch_child_pid=""
        fi

        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "PRD changed -- starting loki... (${change_time})"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""

        "$0" start "$prd_path" &
        _watch_child_pid=$!
        last_run="$change_time"
    }

    # Determine watcher backend
    local watcher="poll"
    if command -v fswatch &>/dev/null; then
        watcher="fswatch"
    elif command -v inotifywait &>/dev/null; then
        watcher="inotifywait"
    fi

    echo "Watcher: $watcher"
    _watch_status_line

    case "$watcher" in
        fswatch)
            # Use fswatch with loop-based approach (avoids pipe subshell PID loss)
            while true; do
                fswatch -1 --latency "$debounce" "$prd_path" > /dev/null 2>&1 || true
                _watch_trigger
                _watch_status_line
            done
            ;;
        inotifywait)
            while true; do
                inotifywait -qq -e modify "$prd_path" 2>/dev/null
                if [ "$debounce" -gt 0 ]; then
                    sleep "$debounce"
                fi
                _watch_trigger
                _watch_status_line
            done
            ;;
        poll)
            # Universal fallback: stat-based polling
            local last_mtime
            last_mtime=$(stat -f '%m' "$prd_path" 2>/dev/null || stat -c '%Y' "$prd_path" 2>/dev/null || echo "0")
            local debounce_pending=false
            local debounce_deadline=0

            while true; do
                sleep "$poll_interval"
                local current_mtime
                current_mtime=$(stat -f '%m' "$prd_path" 2>/dev/null || stat -c '%Y' "$prd_path" 2>/dev/null || echo "0")

                if [ "$current_mtime" != "$last_mtime" ]; then
                    last_mtime="$current_mtime"
                    if [ "$debounce" -gt 0 ]; then
                        # Start debounce window
                        debounce_pending=true
                        debounce_deadline=$(( $(date +%s) + debounce ))
                    else
                        _watch_trigger
                        _watch_status_line
                    fi
                fi

                # Check debounce deadline
                if [ "$debounce_pending" = true ] && [ "$(date +%s)" -ge "$debounce_deadline" ]; then
                    debounce_pending=false
                    _watch_trigger
                    _watch_status_line
                fi
            done
            ;;
    esac
}

#===============================================================================
# loki export - Export session data (v6.0.0)
#===============================================================================

cmd_export() {
    local format="${1:---help}"
    local output_path="${2:-}"

    case "$format" in
        --help|-h)
            echo -e "${BOLD}loki export${NC} - Export session data (v6.0.0)"
            echo ""
            echo "Usage: loki export <format> [output-path]"
            echo ""
            echo "Formats:"
            echo "  json       Full session state as JSON"
            echo "  markdown   Human-readable session summary"
            echo "  csv        Task queue as CSV"
            echo "  timeline   Iteration timeline as JSON"
            echo ""
            echo "Examples:"
            echo "  loki export json                         # Print to stdout"
            echo "  loki export json session-export.json     # Save to file"
            echo "  loki export markdown                     # Readable summary"
            echo "  loki export csv tasks.csv                # Task queue CSV"
            echo "  loki export timeline                     # Iteration timeline"
            exit 0
            ;;
        json)
            require_jq || return 1
            _export_json "$output_path"
            ;;
        markdown|md)
            _export_markdown "$output_path"
            ;;
        csv)
            _export_csv "$output_path"
            ;;
        timeline)
            require_jq || return 1
            _export_timeline "$output_path"
            ;;
        *)
            echo -e "${RED}Unknown format: $format${NC}"
            echo "Supported: json, markdown, csv, timeline"
            exit 1
            ;;
    esac
}

# R8: Shareable team assets. Bundles a team's invested, reusable assets
# (cross-project + project memory/learnings, the agent registry, PRD
# templates, council config, optionally the R5 wiki) into a portable,
# REDACTED tarball, and re-imports them into another project or fresh clone.
#
# Scope vs cmd_export: cmd_export emits a per-run SESSION SNAPSHOT
# (json/md/csv/timeline) and is NOT portable or redacted. cmd_assets emits a
# portable, redacted TEAM-ASSET tarball -- different scope, different artifact.
# Redaction REUSES the single proof_redact chokepoint via the
# autonomy/lib/assets_bundle.py helper. No second redactor or parallel exporter.
cmd_assets() {
    local subcommand="${1:-help}"
    shift 2>/dev/null || true

    local helper="$_LOKI_SCRIPT_DIR/lib/assets_bundle.py"
    if [ ! -f "$helper" ]; then
        echo -e "${RED}Error: assets helper not found at $helper${NC}" >&2
        return 1
    fi
    if ! command -v python3 &>/dev/null; then
        echo -e "${RED}Error: python3 is required for 'loki assets'${NC}" >&2
        return 1
    fi

    # Project dir holds .loki/ (memory, council, wiki). Default to cwd.
    local project_dir
    project_dir="$(pwd)"

    # repo_root holds agents/types.json + templates/. The export and import
    # roots are DELIBERATELY ASYMMETRIC (not a bug):
    #   - EXPORT reads from the loki install ($SKILL_DIR), where a team's
    #     edited registry / templates actually live and are read at runtime
    #     (autonomy/loki:9781, 8840 read templates from $SKILL_DIR; cmd_agent
    #     reads agents/types.json from the install first).
    #   - IMPORT writes to the current working directory (the root of the
    #     target clone the user is setting up), NEVER back into the install.
    #     Writing into $SKILL_DIR would clobber the live install's shipped
    #     registry / templates with redacted copies.
    # Honest limitation: agents/templates restore relative to cwd, so run
    # `loki assets import` from the root of the target clone. A global-install
    # user must copy the restored agents/ + templates/ into their install for
    # loki to read them at runtime (memory/learnings/council/wiki are unaffected
    # -- those restore to $HOME/.loki and <project>/.loki and take effect
    # immediately).
    local export_repo_root="${SKILL_DIR:-$(cd "$_LOKI_SCRIPT_DIR/.." && pwd)}"
    local import_repo_root="$project_dir"

    case "$subcommand" in
        --help|-h|help|"")
            echo -e "${BOLD}loki assets${NC} - Export/import shareable team assets (R8)"
            echo ""
            echo "Bundles a team's reusable assets into a portable, redacted"
            echo "tarball so setup compounds into shared, importable value."
            echo ""
            echo "Usage:"
            echo "  loki assets export <bundle.tgz> [--wiki] [--categories a,b,c]"
            echo "  loki assets import <bundle.tgz> [--no-merge] [--into-install]"
            echo "  loki assets inspect <bundle.tgz>"
            echo ""
            echo "Assets bundled (default):"
            echo "  learnings   ~/.loki/learnings/*.jsonl (cross-project)"
            echo "  memory      <project>/.loki/memory/** (episodic/semantic/skills)"
            echo "  agents      agents/types.json (agent registry)"
            echo "  templates   templates/*.md (PRD templates)"
            echo "  council     <project>/.loki/council/*.json (council config/state)"
            echo "  wiki        <project>/.loki/wiki/** (opt-in via --wiki)"
            echo ""
            echo "All bundled content is redacted via the proof_redact chokepoint:"
            echo "secrets, keys, tokens, and absolute home/repo paths are stripped"
            echo "before the bundle is written. Originals on disk are never changed."
            echo ""
            echo "Restore locations on import:"
            echo "  learnings -> \$HOME/.loki/learnings   (effective immediately)"
            echo "  memory    -> <cwd>/.loki/memory       (effective immediately)"
            echo "  council   -> <cwd>/.loki/council      (effective immediately)"
            echo "  wiki      -> <cwd>/.loki/wiki         (effective immediately)"
            echo "  agents    -> <cwd>/agents/types.json"
            echo "  templates -> <cwd>/templates/*.md"
            echo "Run import from the ROOT of the target clone. Note: agents and"
            echo "templates are read by loki from its install dir at runtime. On a"
            echo "global install, pass --into-install so agents/templates restore"
            echo "into the install dir and are read at runtime (default is cwd,"
            echo "which suits a repo-clone where loki runs from the clone root)."
            echo ""
            echo "Examples:"
            echo "  loki assets export team-assets.tgz"
            echo "  loki assets export team-assets.tgz --wiki"
            echo "  loki assets import team-assets.tgz       # merge learnings, overwrite rest"
            echo "  loki assets import team-assets.tgz --no-merge"
            echo "  loki assets inspect team-assets.tgz      # show manifest only"
            return 0
            ;;
        export)
            local out_path="${1:-}"
            if [ -z "$out_path" ]; then
                echo -e "${RED}Usage: loki assets export <bundle.tgz> [--wiki] [--categories a,b,c]${NC}" >&2
                return 1
            fi
            shift 2>/dev/null || true
            if ! _export_check_overwrite "$out_path"; then
                return 1
            fi
            echo "Bundling team assets (redacting secrets and paths)..."
            if LOKI_ASSETS_HOME="$HOME" LOKI_ASSETS_REPO="$export_repo_root" \
               LOKI_ASSETS_PROJECT="$project_dir" \
               python3 "$helper" export "$out_path" "$@"; then
                echo -e "${GREEN}Bundle written: $out_path${NC}"
            else
                echo -e "${RED}Export failed${NC}" >&2
                return 1
            fi
            ;;
        import)
            local bundle_path="${1:-}"
            if [ -z "$bundle_path" ]; then
                echo -e "${RED}Usage: loki assets import <bundle.tgz> [--no-merge] [--into-install]${NC}" >&2
                return 1
            fi
            shift 2>/dev/null || true
            if [ ! -f "$bundle_path" ]; then
                echo -e "${RED}Error: bundle not found: $bundle_path${NC}" >&2
                return 1
            fi
            # --into-install: restore agents/templates into the loki install
            # ($SKILL_DIR) so a global-install user has them read at runtime,
            # instead of into cwd. Strip the flag before passing the rest to
            # the helper (the helper does not understand it). The install dir
            # is the same source export reads from, so this is the symmetric
            # round-trip for a global install. Off by default (cwd is safe).
            local _import_args=()
            local arg
            for arg in "$@"; do
                if [ "$arg" = "--into-install" ]; then
                    import_repo_root="$export_repo_root"
                else
                    _import_args+=("$arg")
                fi
            done
            if [ "$import_repo_root" = "$export_repo_root" ]; then
                echo "Importing team assets (agents/templates -> install dir: $import_repo_root)..."
            else
                echo "Importing team assets into this project/clone (cwd)..."
            fi
            # Bash 3.2 (macOS) errors on "${arr[@]}" when the array is empty
            # and set -u is active; guard with the count.
            if [ "${#_import_args[@]}" -gt 0 ]; then
                LOKI_ASSETS_HOME="$HOME" LOKI_ASSETS_REPO="$import_repo_root" \
                LOKI_ASSETS_PROJECT="$project_dir" \
                python3 "$helper" import "$bundle_path" "${_import_args[@]}"
            else
                LOKI_ASSETS_HOME="$HOME" LOKI_ASSETS_REPO="$import_repo_root" \
                LOKI_ASSETS_PROJECT="$project_dir" \
                python3 "$helper" import "$bundle_path"
            fi
            if [ $? -eq 0 ]; then
                echo -e "${GREEN}Import complete${NC}"
            else
                echo -e "${RED}Import failed${NC}" >&2
                return 1
            fi
            ;;
        inspect)
            local bundle_path="${1:-}"
            if [ -z "$bundle_path" ]; then
                echo -e "${RED}Usage: loki assets inspect <bundle.tgz>${NC}" >&2
                return 1
            fi
            if [ ! -f "$bundle_path" ]; then
                echo -e "${RED}Error: bundle not found: $bundle_path${NC}" >&2
                return 1
            fi
            python3 "$helper" inspect "$bundle_path"
            ;;
        *)
            echo -e "${RED}Unknown subcommand: $subcommand${NC}" >&2
            echo "Run 'loki assets --help' for usage."
            return 1
            ;;
    esac
}

# Guard: check if output file exists and warn before overwriting
_export_check_overwrite() {
    local output="$1"
    if [ -n "$output" ] && [ -f "$output" ]; then
        echo -e "${YELLOW}Warning: File already exists: $output${NC}"
        echo -n "Overwrite? [y/N] "
        local reply
        read -r reply
        case "$reply" in
            [yY]|[yY][eE][sS]) return 0 ;;
            *) echo "Export cancelled."; return 1 ;;
        esac
    fi
    return 0
}

_export_json() {
    local output="$1"

    if [ ! -d "$LOKI_DIR" ]; then
        echo -e "${RED}No active session found.${NC}"
        exit 1
    fi

    local json_output
    json_output=$(LOKI_DIR="$LOKI_DIR" LOKI_VERSION="$(get_version)" python3 << 'EXPORT_JSON'
import json, os, glob
from datetime import datetime

loki_dir = os.environ.get("LOKI_DIR", ".loki")
export = {
    "exported_at": datetime.utcnow().isoformat() + "Z",
    "version": os.environ.get("LOKI_VERSION", "0.0.0"),
    "session": {},
    "queue": {},
    "quality": {},
    "config": {}
}

# Session state
for state_file in glob.glob(os.path.join(loki_dir, "state", "*")):
    name = os.path.basename(state_file)
    try:
        with open(state_file) as f:
            content = f.read().strip()
        try:
            export["session"][name] = json.loads(content)
        except json.JSONDecodeError:
            export["session"][name] = content
    except:
        pass

# Queue
for queue in ["pending", "in-progress", "completed", "failed"]:
    qf = os.path.join(loki_dir, "queue", f"{queue}.json")
    if os.path.exists(qf):
        try:
            with open(qf) as f:
                export["queue"][queue] = json.load(f)
        except:
            pass

# Quality reviews (latest 5)
reviews_dir = os.path.join(loki_dir, "quality", "reviews")
if os.path.isdir(reviews_dir):
    reviews = sorted(os.listdir(reviews_dir), reverse=True)[:5]
    export["quality"]["recent_reviews"] = reviews

# Config
cfg = os.path.join(loki_dir, "config", "settings.json")
if os.path.exists(cfg):
    try:
        with open(cfg) as f:
            export["config"] = json.load(f)
    except:
        pass

print(json.dumps(export, indent=2, default=str))
EXPORT_JSON
    )

    if [ -n "$output" ]; then
        # Reject path traversal
        if [[ "$output" == *".."* ]]; then
            echo -e "${RED}Error: Output path must not contain '..'${NC}"
            return 1
        fi
        _export_check_overwrite "$output" || return 0
        echo "$json_output" > "$output"
        echo -e "${GREEN}Exported to $output${NC}"
    else
        echo "$json_output"
    fi
}

_export_markdown() {
    local output="$1"

    if [ ! -d "$LOKI_DIR" ]; then
        echo -e "${RED}No active session found.${NC}"
        exit 1
    fi

    local md_output
    md_output=$(cat << MDEOF
# Loki Mode Session Export

**Exported:** $(date -u +%Y-%m-%dT%H:%M:%SZ)
**Directory:** $(pwd)

## Status

$(cat "$LOKI_DIR/STATUS.txt" 2>/dev/null || echo "No status file")

## Recent Commits

$(git log --oneline -10 2>/dev/null || echo "No git history")

## Task Queue Summary

$(for q in pending in-progress completed failed; do
    f="$LOKI_DIR/queue/${q}.json"
    if [ -f "$f" ]; then
        count=$(_QUEUE_FILE="$f" python3 -c "import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "0")
        echo "- **$q:** $count tasks"
    fi
done)
MDEOF
    )

    if [ -n "$output" ]; then
        if [[ "$output" == *".."* ]]; then
            echo -e "${RED}Error: Output path must not contain '..'${NC}"
            return 1
        fi
        _export_check_overwrite "$output" || return 0
        echo "$md_output" > "$output"
        echo -e "${GREEN}Exported to $output${NC}"
    else
        echo "$md_output"
    fi
}

_export_csv() {
    local output="$1"

    if [ ! -d "$LOKI_DIR" ]; then
        echo -e "${RED}No active session found.${NC}"
        exit 1
    fi

    local csv_output
    csv_output=$(LOKI_DIR="$LOKI_DIR" python3 << 'EXPORT_CSV'
import json, os, csv, io

loki_dir = os.environ.get("LOKI_DIR", ".loki")
writer_buf = io.StringIO()
writer = csv.writer(writer_buf)
writer.writerow(["status", "id", "title", "created_at"])

for queue in ["pending", "in-progress", "completed", "failed"]:
    qf = os.path.join(loki_dir, "queue", f"{queue}.json")
    if os.path.exists(qf):
        try:
            with open(qf) as f:
                data = json.load(f)
            tasks = data.get("tasks", data) if isinstance(data, dict) else data
            for t in tasks:
                writer.writerow([
                    queue,
                    t.get("id", ""),
                    t.get("title", t.get("task", "")),
                    t.get("created_at", "")
                ])
        except:
            pass

print(writer_buf.getvalue())
EXPORT_CSV
    )

    if [ -n "$output" ]; then
        if [[ "$output" == *".."* ]]; then
            echo -e "${RED}Error: Output path must not contain '..'${NC}"
            return 1
        fi
        _export_check_overwrite "$output" || return 0
        echo "$csv_output" > "$output"
        echo -e "${GREEN}Exported to $output${NC}"
    else
        echo "$csv_output"
    fi
}

_export_timeline() {
    local output="$1"

    if [ ! -d "$LOKI_DIR" ]; then
        echo -e "${RED}No active session found.${NC}"
        exit 1
    fi

    local timeline_output
    timeline_output=$(python3 << 'EXPORT_TIMELINE'
import json, os, glob

loki_dir = os.environ.get("LOKI_DIR", ".loki")
timeline = []

# Collect iteration logs
log_dir = os.path.join(loki_dir, "logs")
if os.path.isdir(log_dir):
    for f in sorted(glob.glob(os.path.join(log_dir, "iteration-*.json"))):
        try:
            with open(f) as fh:
                timeline.append(json.load(fh))
        except:
            pass

# Collect council verdicts
council_dir = os.path.join(loki_dir, "council")
if os.path.isdir(council_dir):
    for f in sorted(glob.glob(os.path.join(council_dir, "votes", "iteration-*", "aggregate.json"))):
        try:
            with open(f) as fh:
                data = json.load(fh)
                data["_type"] = "council_vote"
                timeline.append(data)
        except:
            pass

print(json.dumps(timeline, indent=2, default=str))
EXPORT_TIMELINE
    )

    if [ -n "$output" ]; then
        if [[ "$output" == *".."* ]]; then
            echo -e "${RED}Error: Output path must not contain '..'${NC}"
            return 1
        fi
        _export_check_overwrite "$output" || return 0
        echo "$timeline_output" > "$output"
        echo -e "${GREEN}Exported to $output${NC}"
    else
        echo "$timeline_output"
    fi
}

cmd_config() {
    local subcommand="${1:-show}"
    shift 2>/dev/null || true

    case "$subcommand" in
        show)
            cmd_config_show
            ;;
        init)
            cmd_config_init
            ;;
        edit)
            cmd_config_edit
            ;;
        path)
            cmd_config_path
            ;;
        set)
            cmd_config_set "$@"
            ;;
        get)
            cmd_config_get "$@"
            ;;
        *)
            echo -e "${YELLOW}Usage: loki config [show|init|edit|path|set|get]${NC}"
            echo ""
            echo "  show           Show current configuration (default)"
            echo "  init           Create a config file from template"
            echo "  edit           Open config file in editor"
            echo "  path           Show config file paths"
            echo "  set KEY VALUE  Set a configuration value"
            echo "  get KEY        Get a configuration value"
            echo ""
            echo "Settable keys (v6.0.0):"
            echo "  maxTier              Cost ceiling: opus, sonnet, haiku (default: opus)"
            echo "  model.planning       Model for planning tier"
            echo "  model.development    Model for development tier"
            echo "  model.fast           Model for fast tier"
            echo "  provider             Default AI provider: claude, codex, cline, aider"
            echo "  issue.provider       Default issue provider: github, gitlab, jira, azure_devops"
            echo "  blind_validation     Blind validation mode: true, false (default: true)"
            echo "  adversarial_testing  Adversarial testing: true, false (default: true)"
            echo "  spawn_timeout        Provider spawn timeout in seconds (default: 120)"
            echo "  spawn_retries        Provider spawn retry count (default: 2)"
            echo "  notify.slack         Slack webhook URL"
            echo "  notify.discord       Discord webhook URL"
            echo "  budget               Cost budget limit in USD"
            ;;
    esac
}

# v6.0.0: Set a configuration value
cmd_config_set() {
    local use_global=false

    # Check for --global flag
    if [[ "${1:-}" == "--global" ]]; then
        use_global=true
        shift
    fi

    local key="${1:-}"
    local value="${2:-}"

    if [[ -z "$key" || -z "$value" ]]; then
        echo -e "${RED}Usage: loki config set [--global] <key> <value>${NC}"
        echo "Run 'loki config' for list of settable keys."
        return 1
    fi

    # Determine config directory: --global writes to ~/.config/loki-mode/
    local config_dir
    if $use_global; then
        config_dir="${HOME}/.config/loki-mode"
    else
        config_dir="$LOKI_DIR/config"
    fi

    # Ensure config directory exists
    mkdir -p "$config_dir"
    local config_store="$config_dir/settings.json"

    # Initialize if not exists
    if [ ! -f "$config_store" ]; then
        echo '{}' > "$config_store"
    fi

    # Validate known keys and values
    case "$key" in
        maxTier)
            case "$value" in
                opus|sonnet|haiku) ;;
                *) echo -e "${RED}Invalid maxTier: $value (expected: opus, sonnet, haiku)${NC}"; return 1 ;;
            esac
            ;;
        provider)
            case "$value" in
                gemini)
                    echo -e "${RED}Invalid provider: 'gemini' is deprecated as of v7.5.18. Active providers: claude, codex, cline, aider${NC}"
                    return 1
                    ;;
                claude|codex|cline|aider) ;;
                *) echo -e "${RED}Invalid provider: $value (expected: claude, codex, cline, aider)${NC}"; return 1 ;;
            esac
            ;;
        issue.provider)
            case "$value" in
                github|gitlab|jira|azure_devops) ;;
                *) echo -e "${RED}Invalid issue.provider: $value${NC}"; return 1 ;;
            esac
            ;;
        blind_validation|adversarial_testing)
            case "$value" in
                true|false) ;;
                *) echo -e "${RED}Invalid $key: $value (expected: true, false)${NC}"; return 1 ;;
            esac
            ;;
        spawn_timeout|spawn_retries)
            if ! echo "$value" | grep -qE '^[0-9]+$'; then
                echo -e "${RED}Invalid $key: $value (expected: integer)${NC}"; return 1
            fi
            ;;
        budget)
            if ! echo "$value" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
                echo -e "${RED}Invalid budget: $value (expected: positive numeric USD amount)${NC}"; return 1
            fi
            # Reject zero and negative values (#68)
            if echo "$value" | grep -qE '^0+(\.0+)?$'; then
                echo -e "${RED}Invalid budget: $value (must be greater than 0)${NC}"; return 1
            fi
            ;;
        model.planning|model.development|model.fast)
            # Validate model names: alphanumeric, dots, hyphens, underscores only
            if ! echo "$value" | grep -qE '^[a-zA-Z0-9._-]+$'; then
                echo -e "${RED}Invalid model name: $value (only alphanumeric, dots, hyphens, underscores)${NC}"; return 1
            fi
            ;;
        notify.slack|notify.discord)
            # Validate webhook URLs: must be https
            if ! echo "$value" | grep -qiE '^https://'; then
                echo -e "${RED}Invalid webhook URL: must start with https://${NC}"; return 1
            fi
            ;;
        *)
            echo -e "${RED}Unknown configuration key: '$key'${NC}"
            echo ""
            echo "Valid keys: maxTier, provider, issue.provider, blind_validation,"
            echo "  adversarial_testing, spawn_timeout, spawn_retries, budget,"
            echo "  model.planning, model.development, model.fast,"
            echo "  notify.slack, notify.discord"
            echo ""
            echo "Run 'loki config' for details on each key."
            return 1
            ;;
    esac

    # Update JSON config via python (use inline env vars to avoid leaking into environment)
    LOKI_CFG_FILE="$config_store" LOKI_CFG_KEY="$key" LOKI_CFG_VALUE="$value" \
    python3 << 'SET_CONFIG'
import json, os
cfg_file = os.environ["LOKI_CFG_FILE"]
key = os.environ["LOKI_CFG_KEY"]
value = os.environ["LOKI_CFG_VALUE"]

with open(cfg_file) as f:
    config = json.load(f)

# Handle dotted keys (model.planning -> {"model": {"planning": value}})
parts = key.split(".")
current = config
for part in parts[:-1]:
    if part not in current or not isinstance(current[part], dict):
        current[part] = {}
    current = current[part]
# Type coercion: try int, then float, then bool, else string
def coerce_type(v):
    if v.lower() in ('true', 'false'):
        return v.lower() == 'true'
    try:
        return int(v)
    except ValueError:
        pass
    try:
        return float(v)
    except ValueError:
        pass
    return v

current[parts[-1]] = coerce_type(value)

with open(cfg_file, "w") as f:
    json.dump(config, f, indent=2)
SET_CONFIG

    echo -e "${GREEN}Set $key = $value${NC}"

    # Also set env var for immediate effect in current session
    case "$key" in
        maxTier)          export LOKI_MAX_TIER="$value" ;;
        provider)         export LOKI_PROVIDER="$value" ;;
        blind_validation) export LOKI_BLIND_VALIDATION="$value" ;;
        adversarial_testing) export LOKI_ADVERSARIAL_TESTING="$value" ;;
        spawn_timeout)    export LOKI_SPAWN_TIMEOUT="$value" ;;
        spawn_retries)    export LOKI_SPAWN_RETRIES="$value" ;;
        budget)           export LOKI_BUDGET_LIMIT="$value" ;;
    esac
}

# v6.0.0: Get a configuration value
cmd_config_get() {
    local key="${1:-}"

    if [[ -z "$key" ]]; then
        echo -e "${RED}Usage: loki config get <key>${NC}"
        return 1
    fi

    local config_store="$LOKI_DIR/config/settings.json"
    if [ ! -f "$config_store" ]; then
        echo -e "${YELLOW}No config found (using defaults)${NC}"
        return 0
    fi

    local result
    result=$(LOKI_CFG_FILE="$config_store" LOKI_CFG_KEY="$key" \
    python3 << 'GET_CONFIG'
import json, os
cfg_file = os.environ["LOKI_CFG_FILE"]
key = os.environ["LOKI_CFG_KEY"]

with open(cfg_file) as f:
    config = json.load(f)

parts = key.split(".")
current = config
for part in parts:
    if isinstance(current, dict) and part in current:
        current = current[part]
    else:
        print("")
        exit(0)

print(current if not isinstance(current, dict) else json.dumps(current, indent=2))
GET_CONFIG
    ) 2>/dev/null || {
        echo -e "${RED}Error reading config key '$key'${NC}"
        return 1
    }
    echo "$result"
}

cmd_config_show() {
    echo -e "${BOLD}Loki Mode Configuration${NC}"
    echo ""
    echo -e "${CYAN}Installation:${NC} $SKILL_DIR"
    echo -e "${CYAN}Version:${NC} $(get_version)"
    echo ""

    # Check for config files
    local config_file=""
    if [ -f ".loki/config.yaml" ]; then
        config_file=".loki/config.yaml"
        echo -e "${GREEN}Config file:${NC} $config_file (project-local)"
    elif [ -f ".loki/config.yml" ]; then
        config_file=".loki/config.yml"
        echo -e "${GREEN}Config file:${NC} $config_file (project-local)"
    elif [ -f "${HOME}/.config/loki-mode/config.yaml" ]; then
        config_file="${HOME}/.config/loki-mode/config.yaml"
        echo -e "${GREEN}Config file:${NC} $config_file (user-global)"
    elif [ -f "${HOME}/.config/loki-mode/config.yml" ]; then
        config_file="${HOME}/.config/loki-mode/config.yml"
        echo -e "${GREEN}Config file:${NC} $config_file (user-global)"
    else
        echo -e "${YELLOW}Config file:${NC} Not found (using defaults)"
    fi
    echo ""

    echo -e "${CYAN}Current Settings:${NC}"
    echo ""
    echo "Core:"
    echo "  max_retries:      ${LOKI_MAX_RETRIES:-50}"
    echo "  base_wait:        ${LOKI_BASE_WAIT:-60}s"
    echo "  max_wait:         ${LOKI_MAX_WAIT:-3600}s"
    echo ""
    echo "Dashboard:"
    echo "  enabled:          ${LOKI_DASHBOARD:-true}"
    echo "  port:             ${LOKI_DASHBOARD_PORT:-57374}"
    echo ""
    echo "Notifications:"
    echo "  enabled:          ${LOKI_NOTIFICATIONS:-true}"
    echo "  sound:            ${LOKI_NOTIFICATION_SOUND:-true}"
    echo ""
    echo "GitHub Integration:"
    echo "  import:           ${LOKI_GITHUB_IMPORT:-false}"
    echo "  pr:               ${LOKI_GITHUB_PR:-false}"
    echo "  sync:             ${LOKI_GITHUB_SYNC:-false}"
    echo ""
    echo "Provider:"
    echo "  provider:         ${LOKI_PROVIDER:-claude}"
    echo ""
    echo "Execution:"
    echo "  complexity:       ${LOKI_COMPLEXITY:-auto}"
    echo "  parallel_mode:    ${LOKI_PARALLEL_MODE:-false}"
    echo "  max_iterations:   ${LOKI_MAX_ITERATIONS:-1000}"
    echo "  autonomy_mode:    ${LOKI_AUTONOMY_MODE:-perpetual}"
    echo ""
    echo "v6.0.0 Settings:"
    echo "  maxTier:              ${LOKI_MAX_TIER:-(unlimited)}"
    echo "  blind_validation:     ${LOKI_BLIND_VALIDATION:-true}"
    echo "  adversarial_testing:  ${LOKI_ADVERSARIAL_TESTING:-true}"
    echo "  spawn_timeout:        ${LOKI_SPAWN_TIMEOUT:-120}s"
    echo "  spawn_retries:        ${LOKI_SPAWN_RETRIES:-2}"
    echo "  issue_provider:       ${LOKI_ISSUE_PROVIDER:-(auto-detect)}"
    echo "  budget:               ${LOKI_BUDGET_LIMIT:-(no limit)}"
    echo ""
    echo -e "Run ${CYAN}loki config path${NC} to see all config file locations"
    echo -e "Run ${CYAN}loki config set <key> <value>${NC} to change a setting"
}

cmd_config_init() {
    local template="$SKILL_DIR/autonomy/config.example.yaml"
    local target=".loki/config.yaml"
    local global_target="${HOME}/.config/loki-mode/config.yaml"

    if [ ! -f "$template" ]; then
        echo -e "${RED}Error: Config template not found at $template${NC}"
        exit 1
    fi

    echo -e "${BOLD}Initialize Configuration${NC}"
    echo ""
    echo "Where do you want to create the config file?"
    echo ""
    echo "  1) $target (project-local, recommended)"
    echo "  2) $global_target (user-global)"
    echo ""
    read -p "Choice [1]: " choice
    choice="${choice:-1}"

    case "$choice" in
        1)
            mkdir -p ".loki"
            cp "$template" "$target"
            echo -e "${GREEN}Created: $target${NC}"
            ;;
        2)
            mkdir -p "${HOME}/.config/loki-mode"
            cp "$template" "$global_target"
            echo -e "${GREEN}Created: $global_target${NC}"
            ;;
        *)
            echo -e "${RED}Invalid choice${NC}"
            exit 1
            ;;
    esac

    echo ""
    echo "Edit with: loki config edit"
}

cmd_config_edit() {
    local config_file=""

    # Find existing config file
    if [ -f ".loki/config.yaml" ]; then
        config_file=".loki/config.yaml"
    elif [ -f ".loki/config.yml" ]; then
        config_file=".loki/config.yml"
    elif [ -f "${HOME}/.config/loki-mode/config.yaml" ]; then
        config_file="${HOME}/.config/loki-mode/config.yaml"
    elif [ -f "${HOME}/.config/loki-mode/config.yml" ]; then
        config_file="${HOME}/.config/loki-mode/config.yml"
    fi

    if [ -z "$config_file" ]; then
        echo -e "${YELLOW}No config file found.${NC}"
        read -p "Create one? [Y/n]: " create
        create="${create:-Y}"
        if [[ "$create" =~ ^[Yy] ]]; then
            cmd_config_init
            config_file=".loki/config.yaml"
        else
            exit 0
        fi
    fi

    # Open in editor
    local editor="${EDITOR:-${VISUAL:-vim}}"
    echo -e "${GREEN}Opening $config_file with $editor${NC}"
    "$editor" "$config_file"
}

cmd_config_path() {
    echo -e "${BOLD}Config File Search Paths${NC}"
    echo ""
    echo "Loki Mode searches for config files in this order:"
    echo ""

    local paths=(
        ".loki/config.yaml"
        ".loki/config.yml"
        "${HOME}/.config/loki-mode/config.yaml"
        "${HOME}/.config/loki-mode/config.yml"
    )

    for path in "${paths[@]}"; do
        if [ -f "$path" ]; then
            echo -e "  ${GREEN}[FOUND]${NC}  $path"
        else
            echo -e "  ${YELLOW}[-----]${NC}  $path"
        fi
    done

    echo ""
    echo "Template: $SKILL_DIR/autonomy/config.example.yaml"
    echo ""
    echo "Create a config file with: loki config init"
}

# Set up skill symlinks for all providers
cmd_setup_skill() {
    # v7.6.2 B-13 fix: --help must print help, not install skills.
    case "${1:-}" in
        --help|-h|help)
            echo -e "${BOLD}Loki Mode -- install Loki skill into provider CLIs${NC}"
            echo ""
            echo "Usage: loki setup-skill [options]"
            echo ""
            echo "Installs the Loki Mode skill into each detected provider CLI's"
            echo "skills directory: ~/.claude/skills/, ~/.codex/skills/, etc."
            echo ""
            echo "Options:"
            echo "  --help, -h   Show this help and exit"
            echo ""
            echo "Side effects: writes symlinks/files under ~/.<provider>/skills/loki-mode/."
            return 0
            ;;
    esac
    echo -e "${BOLD}Loki Mode Skill Setup${NC}"
    echo ""

    local skill_targets=(
        "$HOME/.claude/skills/loki-mode:Claude Code"
        "$HOME/.codex/skills/loki-mode:Codex CLI"
        "$HOME/.cline/skills/loki-mode:Cline CLI"
        "$HOME/.aider/skills/loki-mode:Aider CLI"
    )

    local created=0
    local skipped=0
    local failed=0

    for entry in "${skill_targets[@]}"; do
        local target_dir="${entry%%:*}"
        local target_name="${entry#*:}"
        local parent_dir
        parent_dir=$(dirname "$target_dir")
        local short_path="${target_dir/#$HOME/\~}"

        # Already exists and valid
        if [ -f "$target_dir/SKILL.md" ]; then
            echo -e "  ${GREEN}OK${NC}    $target_name  ${DIM}($short_path)${NC}"
            skipped=$((skipped + 1))
            continue
        fi

        # Remove broken symlink
        if [ -L "$target_dir" ]; then
            rm -f "$target_dir"
        fi

        # Create parent directory
        if [ ! -d "$parent_dir" ]; then
            mkdir -p "$parent_dir" 2>/dev/null || true
        fi

        # Create symlink to skill dir
        if ln -sf "$SKILL_DIR" "$target_dir" 2>/dev/null; then
            echo -e "  ${GREEN}NEW${NC}   $target_name  ${DIM}($short_path)${NC}"
            created=$((created + 1))
        else
            echo -e "  ${RED}FAIL${NC}  $target_name  ${DIM}($short_path)${NC}"
            failed=$((failed + 1))
        fi
    done

    echo ""
    if [ "$created" -gt 0 ]; then
        echo -e "${GREEN}Created $created new skill symlink(s).${NC}"
    fi
    if [ "$skipped" -gt 0 ]; then
        echo -e "${DIM}$skipped already installed.${NC}"
    fi
    if [ "$failed" -gt 0 ]; then
        echo -e "${RED}$failed failed (check permissions).${NC}"
        return 1
    fi
    return 0
}

# v7.4.14: cmd_self_update -- one upgrade command for everyone.
#
# Auto-detects which package manager installed `loki` (npm, bun, brew) by
# resolving the binary path, and runs the right upgrade command.
#
# `--to <mgr>` switches managers (installs via the new one, then uninstalls
# the old). Useful for npm users wanting to migrate to Bun (the v8.0.0
# direction).
#
# Usage:
#   loki self-update                Upgrade in-place via current manager
#   loki self-update --to bun       Switch from current to Bun
#   loki self-update --to npm       Switch from current to npm
#   loki self-update --to brew      Switch from current to Homebrew
#   loki self-update --check        Print detection result + exit 0
#
cmd_self_update() {
    local target_manager=""
    local check_only=false

    while [ $# -gt 0 ]; do
        case "$1" in
            --to)
                if [ -z "${2:-}" ]; then
                    echo -e "${RED}--to requires an argument: bun|npm|brew${NC}" >&2
                    return 1
                fi
                target_manager="$2"
                shift 2
                ;;
            --check)
                check_only=true
                shift
                ;;
            --help|-h)
                cat <<'EOF'
loki self-update -- detect manager and upgrade in place, or switch managers

Usage:
  loki self-update                    Upgrade via current manager
  loki self-update --to bun           Switch to Bun (recommended for v8)
  loki self-update --to npm           Switch to npm
  loki self-update --to brew          Switch to Homebrew
  loki self-update --check            Print detected manager and exit
  loki self-update --help             Show this help

Auto-detection looks at the resolved path of the `loki` binary:
  ~/.bun/bin/loki                  -> bun
  /opt/homebrew/Cellar/loki-mode   -> brew (also /usr/local/Cellar)
  npm prefix + /bin/loki           -> npm
  anywhere else                    -> unknown

Switching managers installs via the new one first (so a failed install
does not leave you with no loki), then uninstalls the old one.
EOF
                return 0
                ;;
            *)
                echo -e "${RED}Unknown argument: $1${NC}" >&2
                echo "Run 'loki self-update --help' for usage." >&2
                return 1
                ;;
        esac
    done

    # Detect current install path. v7.4.15 fix: use BASH_SOURCE (the
    # running script's own path) instead of `command -v loki`. The latter
    # depended on the caller's PATH, which fails when the user invokes
    # loki via absolute path or a symlink directory that isn't on PATH.
    # BASH_SOURCE always resolves to the actual installed location.
    local current_mgr=""
    local script_real pkg_dir
    if command -v realpath >/dev/null 2>&1; then
        script_real=$(realpath "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")
    else
        script_real="${BASH_SOURCE[0]}"
    fi
    # autonomy/loki sits at <pkg_dir>/autonomy/loki -- walk two up to get pkg_dir.
    pkg_dir=$(dirname "$(dirname "$script_real")")

    # Also resolve the public binary path (what the user types). We try
    # `command -v loki` but tolerate failure -- $pkg_dir is sufficient.
    local resolved=""
    resolved=$(command -v loki 2>/dev/null || echo "${BASH_SOURCE[0]}")

    case "$pkg_dir" in
        */.bun/install/global/*|*/.bun/*)
            current_mgr="bun"
            ;;
        */Cellar/loki-mode/*|*/homebrew/Cellar/loki-mode/*|*/linuxbrew/Cellar/loki-mode/*)
            current_mgr="brew"
            ;;
        */node_modules/loki-mode*|*/lib/node_modules/loki-mode*)
            current_mgr="npm"
            ;;
        *)
            # Last resort: ask npm if it knows about a global loki-mode
            if command -v npm >/dev/null 2>&1; then
                local npm_root
                npm_root=$(npm root -g 2>/dev/null || true)
                if [ -n "$npm_root" ] && [[ "$pkg_dir" == "$npm_root"/* ]]; then
                    current_mgr="npm"
                fi
            fi
            ;;
    esac

    [ -z "$current_mgr" ] && current_mgr="unknown"

    if [ "$check_only" = "true" ]; then
        echo "loki binary:    $resolved"
        echo "package dir:    $pkg_dir"
        echo "current mgr:    $current_mgr"
        return 0
    fi

    # Default target: same as current (in-place upgrade)
    [ -z "$target_manager" ] && target_manager="$current_mgr"

    echo -e "${BOLD}loki self-update${NC}"
    echo "  current: $current_mgr ($pkg_dir)"
    echo "  target:  $target_manager"
    echo

    # Same-manager upgrade
    if [ "$target_manager" = "$current_mgr" ]; then
        case "$current_mgr" in
            npm)
                echo "Running: npm install -g loki-mode@latest"
                npm install -g loki-mode@latest
                ;;
            bun)
                echo "Running: bun update -g loki-mode (falling back to install if not present)"
                bun update -g loki-mode 2>/dev/null || bun install -g loki-mode@latest
                ;;
            brew)
                echo "Running: brew upgrade loki-mode"
                brew upgrade loki-mode
                ;;
            unknown)
                echo -e "${YELLOW}Cannot auto-detect package manager from: $pkg_dir${NC}" >&2
                if [ -d "$pkg_dir/.git" ]; then
                    echo "This looks like a git clone. To upgrade:" >&2
                    echo "  cd $pkg_dir && git pull && (cd loki-ts && bun install && bun run build)" >&2
                else
                    echo "Specify the target manager explicitly:" >&2
                    echo "  loki self-update --to bun" >&2
                    echo "  loki self-update --to npm" >&2
                    echo "  loki self-update --to brew" >&2
                fi
                return 1
                ;;
        esac
        local rc=$?
        if [ "$rc" -eq 0 ]; then
            echo
            echo -e "${GREEN}Upgrade complete.${NC} Verify with: loki version"
        fi
        return "$rc"
    fi

    # Cross-manager switch: install new, then uninstall old
    case "$target_manager" in
        bun)
            if ! command -v bun >/dev/null 2>&1; then
                echo -e "${RED}bun is not installed.${NC} Install it first:" >&2
                echo "  brew install oven-sh/bun/bun  # macOS via Homebrew" >&2
                echo "  curl -fsSL https://bun.sh/install | bash  # any platform" >&2
                return 1
            fi
            echo "Installing via Bun: bun install -g loki-mode@latest"
            bun install -g loki-mode@latest || return 1
            ;;
        npm)
            command -v npm >/dev/null 2>&1 || { echo -e "${RED}npm not found${NC}" >&2; return 1; }
            echo "Installing via npm: npm install -g loki-mode@latest"
            npm install -g loki-mode@latest || return 1
            ;;
        brew)
            command -v brew >/dev/null 2>&1 || { echo -e "${RED}brew not found${NC}" >&2; return 1; }
            echo "Tapping + installing via Homebrew"
            brew tap asklokesh/tap 2>/dev/null || true
            brew install loki-mode || brew upgrade loki-mode || return 1
            ;;
        *)
            echo -e "${RED}Unknown target manager: $target_manager${NC} (use bun, npm, or brew)" >&2
            return 1
            ;;
    esac

    # New install succeeded -- uninstall old. Best-effort; failures are
    # non-fatal because the user already has the new binary working.
    if [ "$current_mgr" != "unknown" ]; then
        echo
        echo "Removing old install via $current_mgr..."
        case "$current_mgr" in
            npm) npm uninstall -g loki-mode 2>/dev/null || true ;;
            bun) bun remove -g loki-mode 2>/dev/null || true ;;
            brew) brew uninstall loki-mode 2>/dev/null || true ;;
        esac
    fi

    echo
    echo -e "${GREEN}Switched from $current_mgr to $target_manager.${NC}"
    echo "New binary: $(command -v loki 2>/dev/null || echo 'restart your shell to pick up new PATH')"
    return 0
}

# Check system prerequisites
cmd_doctor() {
    local json_output=false

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --json)
                json_output=true
                shift
                ;;
            --help|-h)
                echo -e "${BOLD}loki doctor${NC} - Check system prerequisites"
                echo ""
                echo "Usage: loki doctor [--json]"
                echo ""
                echo "Options:"
                echo "  --json    Output machine-readable JSON"
                echo ""
                echo "Checks: node, python3, jq, git, curl, bash version,"
                echo "        claude/codex CLIs, and disk space."
                return 0
                ;;
            *)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Usage: loki doctor [--json]"
                return 1
                ;;
        esac
    done

    if [ "$json_output" = true ]; then
        cmd_doctor_json
        return $?
    fi

    echo -e "${BOLD}Loki Mode Doctor${NC}"
    echo ""
    echo "Checking system prerequisites..."
    echo ""

    local pass_count=0
    local fail_count=0
    local warn_count=0

    # Helper: check command exists and optionally check version
    doctor_check() {
        local name="$1"
        local cmd="$2"
        local required="$3"  # required, recommended, optional
        local min_version="${4:-}"

        if ! command -v "$cmd" &> /dev/null; then
            if [ "$required" = "required" ]; then
                echo -e "  ${RED}FAIL${NC}  $name - not found"
                fail_count=$((fail_count + 1))
            elif [ "$required" = "recommended" ]; then
                echo -e "  ${YELLOW}WARN${NC}  $name - not found (recommended)"
                warn_count=$((warn_count + 1))
            else
                echo -e "  ${YELLOW}WARN${NC}  $name - not found (optional)"
                warn_count=$((warn_count + 1))
            fi
            return 1
        fi

        local version=""
        case "$cmd" in
            node)
                version=$(node --version 2>/dev/null | tr -d 'v')
                ;;
            python3)
                version=$(python3 --version 2>/dev/null | awk '{print $2}')
                ;;
            git)
                version=$(git --version 2>/dev/null | awk '{print $3}')
                ;;
            bash)
                version=$("$cmd" --version 2>/dev/null | head -1 | sed 's/.*version \([0-9.]*\).*/\1/')
                ;;
            bun)
                version=$(bun --version 2>/dev/null | head -1 | tr -d 'v ')
                ;;
            jq)
                version=$(jq --version 2>/dev/null | tr -d 'jq-')
                ;;
            curl)
                version=$(curl --version 2>/dev/null | head -1 | awk '{print $2}')
                ;;
            claude)
                version=$(claude --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' | head -1)
                ;;
            codex)
                version=$(codex --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' | head -1)
                ;;
            cline)
                version=$(cline --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' | head -1)
                ;;
            aider)
                version=$(aider --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' | head -1)
                ;;
        esac

        local version_display=""
        if [ -n "$version" ]; then
            version_display=" (v$version)"
        fi

        # Simple major version check if min_version is specified
        if [ -n "$min_version" ] && [ -n "$version" ]; then
            local cur_major cur_minor min_major min_minor
            cur_major=$(echo "$version" | cut -d. -f1)
            min_major=$(echo "$min_version" | cut -d. -f1)
            cur_minor=$(echo "$version" | cut -d. -f2)
            min_minor=$(echo "$min_version" | cut -d. -f2)

            if [ "$cur_major" -lt "$min_major" ] 2>/dev/null || \
               { [ "$cur_major" -eq "$min_major" ] 2>/dev/null && [ "$cur_minor" -lt "$min_minor" ] 2>/dev/null; }; then
                if [ "$required" = "required" ]; then
                    echo -e "  ${RED}FAIL${NC}  $name$version_display - requires >= $min_version"
                    fail_count=$((fail_count + 1))
                else
                    echo -e "  ${YELLOW}WARN${NC}  $name$version_display - recommended >= $min_version"
                    warn_count=$((warn_count + 1))
                fi
                return 1
            fi
        fi

        echo -e "  ${GREEN}PASS${NC}  $name$version_display"
        pass_count=$((pass_count + 1))
        return 0
    }

    echo -e "${CYAN}Required:${NC}"
    doctor_check "Node.js (>= 18)"    node     required    18.0  || true
    doctor_check "Python 3 (>= 3.8)"  python3  required    3.8   || true
    doctor_check "jq"                  jq       required          || true
    doctor_check "git"                 git      required          || true
    doctor_check "curl"                curl     required          || true
    echo ""

    echo -e "${CYAN}AI Providers:${NC}"
    doctor_check "Claude CLI"          claude   optional || true
    doctor_check "Codex CLI"           codex    optional || true
    doctor_check "Cline CLI"           cline    optional || true
    doctor_check "Aider CLI"           aider    optional || true

    # Check if at least one provider is installed
    local _any_provider=false
    for _dp in claude codex cline aider; do
        command -v "$_dp" &>/dev/null && _any_provider=true && break
    done
    if ! $_any_provider; then
        echo -e "  ${RED}FAIL${NC}  No AI provider CLI installed -- at least one is required"
        echo -e "         ${YELLOW}Install: npm install -g @anthropic-ai/claude-code${NC}"
        fail_count=$((fail_count + 1))
    fi
    echo ""

    echo -e "${CYAN}API Keys:${NC}"
    # Note: CLI tools use their own login sessions outside Docker/K8s.
    # This section helps first-time users verify they can authenticate.
    if [ -n "${ANTHROPIC_API_KEY:-}" ]; then
        echo -e "  ${GREEN}PASS${NC}  ANTHROPIC_API_KEY is set"
        pass_count=$((pass_count + 1))
    elif command -v claude &>/dev/null; then
        echo -e "  ${DIM}  --  ${NC}  ANTHROPIC_API_KEY not set (Claude CLI uses its own login)"
    fi
    if [ -n "${OPENAI_API_KEY:-}" ]; then
        echo -e "  ${GREEN}PASS${NC}  OPENAI_API_KEY is set"
        pass_count=$((pass_count + 1))
    elif command -v codex &>/dev/null; then
        echo -e "  ${DIM}  --  ${NC}  OPENAI_API_KEY not set (Codex CLI uses its own login)"
    fi
    # Phase I (v7.5.25): detect ANTHROPIC_BASE_URL alt-provider routing
    # (OpenRouter, Ollama, LiteLLM, self-hosted). Claude Code reads this env
    # var natively; Loki passes it through unchanged. Warn when the user sets
    # an alt endpoint without LOKI_MODEL_OVERRIDE -- the default opus/sonnet/
    # haiku aliases may not resolve on the alt-provider.
    if [ -n "${ANTHROPIC_BASE_URL:-}" ]; then
        echo -e "  ${GREEN}PASS${NC}  ANTHROPIC_BASE_URL: ${ANTHROPIC_BASE_URL}"
        pass_count=$((pass_count + 1))
        if [ -z "${LOKI_MODEL_OVERRIDE:-}" ]; then
            echo -e "  ${YELLOW}WARN${NC}  LOKI_MODEL_OVERRIDE not set -- opus/sonnet/haiku aliases may not resolve on alt-provider"
            warn_count=$((warn_count + 1))
        else
            echo -e "  ${GREEN}PASS${NC}  LOKI_MODEL_OVERRIDE: ${LOKI_MODEL_OVERRIDE}"
            pass_count=$((pass_count + 1))
        fi
    fi
    echo ""

    echo -e "${CYAN}Skills:${NC}"
    local skill_dirs=(
        "$HOME/.claude/skills/loki-mode:Claude Code"
        "$HOME/.codex/skills/loki-mode:Codex CLI"
        "$HOME/.cline/skills/loki-mode:Cline CLI"
        "$HOME/.aider/skills/loki-mode:Aider CLI"
    )
    for entry in "${skill_dirs[@]}"; do
        local sdir="${entry%%:*}"
        local sname="${entry#*:}"
        local short_path="${sdir/$HOME/~}"
        if [ -f "$sdir/SKILL.md" ]; then
            echo -e "  ${GREEN}PASS${NC}  $sname  ${DIM}$short_path${NC}"
            pass_count=$((pass_count + 1))
        elif [ -L "$sdir" ]; then
            local _target
            _target=$(readlink "$sdir" 2>/dev/null || echo "unknown")
            echo -e "  ${RED}FAIL${NC}  $sname  ${DIM}(broken symlink -> $_target)${NC}"
            echo -e "         ${YELLOW}Fix: loki setup-skill${NC}"
            fail_count=$((fail_count + 1))
        else
            echo -e "  ${YELLOW}WARN${NC}  $sname  ${DIM}(not found - run 'loki setup-skill')${NC}"
            warn_count=$((warn_count + 1))
        fi
    done
    echo ""

    echo -e "${CYAN}Integrations:${NC}"
    # MCP SDK check
    if python3 -c "import mcp" 2>/dev/null; then
        echo -e "  ${GREEN}PASS${NC}  MCP SDK (Python)"
        pass_count=$((pass_count + 1))
    else
        echo -e "  ${YELLOW}WARN${NC}  MCP SDK - not installed (pip3 install mcp)"
        warn_count=$((warn_count + 1))
    fi
    # Detect best Python for ML packages (3.12 preferred, 3.14+ has compat issues)
    local _py_ml="python3"
    if command -v /opt/homebrew/bin/python3.12 &>/dev/null; then
        _py_ml="/opt/homebrew/bin/python3.12"
    elif command -v python3.12 &>/dev/null; then
        _py_ml="python3.12"
    fi
    # Vector search dependencies
    if $_py_ml -c "import numpy" 2>/dev/null; then
        echo -e "  ${GREEN}PASS${NC}  numpy (vector search)"
        pass_count=$((pass_count + 1))
    else
        echo -e "  ${YELLOW}WARN${NC}  numpy - not installed (pip3 install numpy)"
        warn_count=$((warn_count + 1))
    fi
    if $_py_ml -c "import sentence_transformers" 2>/dev/null; then
        echo -e "  ${GREEN}PASS${NC}  sentence-transformers (embeddings)"
        pass_count=$((pass_count + 1))
    else
        echo -e "  ${YELLOW}WARN${NC}  sentence-transformers - not installed (loki memory vectors setup)"
        warn_count=$((warn_count + 1))
    fi
    # ChromaDB check
    if curl -sf http://localhost:8100/api/v2/heartbeat >/dev/null 2>&1; then
        echo -e "  ${GREEN}PASS${NC}  ChromaDB server (port 8100)"
        pass_count=$((pass_count + 1))
    else
        echo -e "  ${YELLOW}WARN${NC}  ChromaDB - not running (docker start loki-chroma)"
        warn_count=$((warn_count + 1))
    fi
    # LSP servers check (v7.7.0): auto-detected by mcp.lsp_proxy; reported
    # here so users know which languages get agent-side symbol grounding.
    local _lsp_found=0 _lsp_bin
    local _lsp_list=""
    for _lsp_bin in pyright-langserver pylsp typescript-language-server gopls rust-analyzer jdtls; do
        if command -v "$_lsp_bin" >/dev/null 2>&1; then
            _lsp_found=$((_lsp_found + 1))
            _lsp_list="${_lsp_list:+$_lsp_list, }${_lsp_bin}"
        fi
    done
    if [ "$_lsp_found" -gt 0 ]; then
        echo -e "  ${GREEN}PASS${NC}  LSP servers detected ($_lsp_found): $_lsp_list"
        pass_count=$((pass_count + 1))
    else
        echo -e "  ${YELLOW}WARN${NC}  LSP servers - none on PATH (install for symbol grounding: npm i -g pyright typescript-language-server; brew install gopls)"
        warn_count=$((warn_count + 1))
    fi
    # MiroFish check (optional service)
    local _mf_url="${LOKI_MIROFISH_URL:-http://localhost:5001}"
    if docker inspect loki-mirofish &>/dev/null 2>&1 || [[ -n "${LOKI_MIROFISH_URL:-}" ]]; then
        if curl -sf "$_mf_url/health" >/dev/null 2>&1; then
            echo -e "  ${GREEN}PASS${NC}  MiroFish server ($_mf_url)"
            pass_count=$((pass_count + 1))
        else
            echo -e "  ${YELLOW}WARN${NC}  MiroFish - not running (loki start --mirofish-docker <image>)"
            warn_count=$((warn_count + 1))
        fi
    fi
    # OTEL status
    if [ -n "${LOKI_OTEL_ENDPOINT:-}" ]; then
        echo -e "  ${GREEN}PASS${NC}  OTEL endpoint: $LOKI_OTEL_ENDPOINT"
        pass_count=$((pass_count + 1))
    else
        echo -e "  ${YELLOW}WARN${NC}  OTEL - not configured (set LOKI_OTEL_ENDPOINT)"
        warn_count=$((warn_count + 1))
    fi
    # sentrux check (v7.5.14, optional architectural-drift gate)
    if command -v sentrux &>/dev/null; then
        local _sentrux_ver
        _sentrux_ver=$(sentrux --version 2>/dev/null | head -1 | awk '{print $NF}')
        echo -e "  ${GREEN}PASS${NC}  sentrux ${_sentrux_ver:-unknown} (architectural drift gate: loki sentrux help)"
        pass_count=$((pass_count + 1))
    else
        echo -e "  ${YELLOW}WARN${NC}  sentrux - not installed (optional, brew install sentrux/tap/sentrux)"
        warn_count=$((warn_count + 1))
    fi
    echo ""

    echo -e "${CYAN}System:${NC}"
    doctor_check "bash (>= 4.0)"      bash     recommended  4.0  || true
    # v7.4.9: Bun powers ported commands; warn if missing so users notice the
    # speedup is unavailable (bin/loki falls through to bash silently).
    doctor_check "Bun (>= 1.3)"       bun      recommended  1.3  || true

    # Disk space check
    local disk_avail
    if command -v df &> /dev/null; then
        # Get available space in GB (works on macOS and Linux).
        # v7.4.12: || true on the pipes so set -euo pipefail doesn't exit
        # the whole doctor when df -g fails on Linux (which doesn't support
        # the -g flag). Pre-v7.4.12 bash route truncated after the System
        # section on Linux because of this exact early exit.
        disk_avail=$(df -g "$HOME" 2>/dev/null | tail -1 | awk '{print $4}' || true)
        if [ -z "$disk_avail" ]; then
            # Linux fallback (df -g may not work)
            disk_avail=$(df -BG "$HOME" 2>/dev/null | tail -1 | awk '{print $4}' | tr -d 'G' || true)
        fi
        if [ -n "$disk_avail" ] && [ "$disk_avail" -gt 0 ] 2>/dev/null; then
            if [ "$disk_avail" -lt 1 ]; then
                echo -e "  ${RED}FAIL${NC}  Disk space: ${disk_avail}GB available (need >= 1GB)"
                fail_count=$((fail_count + 1))
            elif [ "$disk_avail" -lt 5 ]; then
                echo -e "  ${YELLOW}WARN${NC}  Disk space: ${disk_avail}GB available (low)"
                warn_count=$((warn_count + 1))
            else
                echo -e "  ${GREEN}PASS${NC}  Disk space: ${disk_avail}GB available"
                pass_count=$((pass_count + 1))
            fi
        else
            echo -e "  ${YELLOW}WARN${NC}  Disk space: unable to determine"
            warn_count=$((warn_count + 1))
        fi
    fi
    echo ""

    # v7.5.1 fix B23: Runtime route -- mirror loki-ts/src/commands/doctor.ts.
    # Informational only; does NOT contribute to pass/fail/warn counts so
    # the bun-parity matrix can normalize it without reconciling summary
    # totals across routes.
    echo -e "${CYAN}Runtime route:${NC}"
    echo -e "  ${GREEN}PASS${NC}  Active runtime: Bash (autonomy/loki)"
    if [ "${LOKI_LEGACY_BASH:-}" = "1" ] || [ "${LOKI_LEGACY_BASH:-}" = "true" ]; then
        echo -e "  ${YELLOW}WARN${NC}  LOKI_LEGACY_BASH set: shim routes every command to autonomy/loki (bash)"
    fi
    if [ -n "${LOKI_TS_ENTRY:-}" ]; then
        echo -e "  ${GREEN}PASS${NC}  LOKI_TS_ENTRY override: ${LOKI_TS_ENTRY}"
    fi
    if [ "${BUN_FROM_SOURCE:-}" = "1" ] || [ "${BUN_FROM_SOURCE:-}" = "true" ]; then
        echo -e "  ${GREEN}PASS${NC}  BUN_FROM_SOURCE set: shim prefers loki-ts/src/ over dist/"
    fi
    # v7.5.2: explicit python3.12 probe -- chromadb + sentence-transformers
    # require it; the generic Python 3 check passes Python 3.13/3.14 and
    # silently fails at runtime. Informational only.
    py312_path=$(command -v python3.12 2>/dev/null || true)
    if [ -n "$py312_path" ]; then
        py312_ver=$("$py312_path" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")' 2>/dev/null)
        echo -e "  ${GREEN}PASS${NC}  Python 3.12 (chromadb / sentence-transformers): ${py312_ver} at ${py312_path}"
    else
        echo -e "  ${YELLOW}WARN${NC}  Python 3.12 NOT on PATH -- chromadb / sentence-transformers may fail. Install via brew install python@3.12 or apt install python3.12."
    fi
    echo ""

    # Summary
    echo -e "${BOLD}Summary:${NC} ${GREEN}$pass_count passed${NC}, ${RED}$fail_count failed${NC}, ${YELLOW}$warn_count warnings${NC}"
    echo ""

    if [ "$fail_count" -gt 0 ]; then
        echo -e "${RED}Some required prerequisites are missing.${NC}"
        echo "Install missing dependencies and run 'loki doctor' again."
        return 1
    elif [ "$warn_count" -gt 0 ]; then
        echo -e "${YELLOW}All required checks passed with some warnings.${NC}"
        return 0
    else
        echo -e "${GREEN}All checks passed. System is ready for Loki Mode.${NC}"
        return 0
    fi
}

# JSON output for loki doctor --json
cmd_doctor_json() {
    local _loki_version
    _loki_version=$(get_version)
    LOKI_VERSION="$_loki_version" python3 -c "
import json, os, subprocess, sys, shutil

def get_version(cmd, args=None):
    try:
        if args is None:
            args = ['--version']
        result = subprocess.run([cmd] + args, capture_output=True, text=True, timeout=5)
        output = result.stdout.strip() or result.stderr.strip()
        # Extract version number
        import re
        match = re.search(r'(\d+\.\d+[\.\d]*)', output)
        return match.group(1) if match else None
    except (FileNotFoundError, subprocess.TimeoutExpired, Exception):
        return None

def check_tool(name, cmd, required, min_version=None):
    found = shutil.which(cmd) is not None
    version = get_version(cmd) if found else None
    status = 'pass'

    if not found:
        status = 'fail' if required == 'required' else 'warn'
    elif min_version and version:
        cur_parts = [int(x) for x in version.split('.')[:2]]
        min_parts = [int(x) for x in min_version.split('.')[:2]]
        while len(cur_parts) < 2: cur_parts.append(0)
        while len(min_parts) < 2: min_parts.append(0)
        if cur_parts < min_parts:
            status = 'fail' if required == 'required' else 'warn'

    return {
        'name': name,
        'command': cmd,
        'found': found,
        'version': version,
        'required': required,
        'min_version': min_version,
        'status': status,
        'path': shutil.which(cmd)
    }

checks = []
checks.append(check_tool('Node.js', 'node', 'required', '18.0'))
checks.append(check_tool('Python 3', 'python3', 'required', '3.8'))
checks.append(check_tool('jq', 'jq', 'required'))
checks.append(check_tool('git', 'git', 'required'))
checks.append(check_tool('curl', 'curl', 'required'))
checks.append(check_tool('bash', 'bash', 'recommended', '4.0'))
checks.append(check_tool('Bun', 'bun', 'recommended', '1.3'))
checks.append(check_tool('Claude CLI', 'claude', 'optional'))
checks.append(check_tool('Codex CLI', 'codex', 'optional'))
checks.append(check_tool('Cline CLI', 'cline', 'optional'))
checks.append(check_tool('Aider CLI', 'aider', 'optional'))

# Disk space
disk_gb = None
try:
    stat = os.statvfs(os.path.expanduser('~'))
    disk_gb = round((stat.f_bavail * stat.f_frsize) / (1024**3), 1)
except Exception:
    pass

disk_status = 'pass'
if disk_gb is not None:
    if disk_gb < 1:
        disk_status = 'fail'
    elif disk_gb < 5:
        disk_status = 'warn'

# v7.5.15: expose the sentrux architectural-drift gate state in --json so
# dashboards/automation can surface it the same way the text-mode block does
# (added in v7.5.14). Sibling of checks/disk -- intentionally not counted in
# the summary tally to keep summary numbers backwards-compatible.
sentrux_found = shutil.which('sentrux') is not None
sentrux_version = get_version('sentrux') if sentrux_found else None
sentrux_status = 'pass' if sentrux_found else 'warn'
sentrux = {
    'found': sentrux_found,
    'version': sentrux_version,
    'status': sentrux_status,
    'required': 'optional'
}

# v7.7.17: memory subsystem health surface. Reports the latest entries
# from .loki/memory/.errors.log (rotated by memory/error_log.py) so
# developers see regressions in the previously-silent-fail call sites
# at run.sh:8724, 9087, 9136. Sibling of checks/disk/sentrux; not
# counted in the summary tally to keep backwards-compatible numbers.
# Inline file read (rather than importing memory.error_log) so this
# block works regardless of the doctor's invocation cwd / sys.path.
# v7.7.17 council fix (Opus 2): tail-only read (last 64 KB) so a
# pathological oversize log cannot OOM the doctor command. Bash + Bun
# emit the SAME relative path string for parity.
memory_base = os.path.join(os.environ.get('LOKI_DIR', '.loki'), 'memory')
_memory_log_path = os.path.join(memory_base, '.errors.log')
_TAIL_BYTES = 64 * 1024
memory_recent_errors = []
if os.path.isfile(_memory_log_path):
    try:
        with open(_memory_log_path, 'rb') as _f:
            _f.seek(0, 2)
            _size = _f.tell()
            _offset = max(0, _size - _TAIL_BYTES)
            _f.seek(_offset)
            _chunk = _f.read()
        _text = _chunk.decode('utf-8', errors='replace')
        _lines = _text.split('\n')
        if _offset > 0 and _lines:
            _lines = _lines[1:]  # drop possibly-partial first line
        _lines = [ln.strip() for ln in _lines if ln.strip()]
        memory_recent_errors = _lines[-5:]
    except OSError:
        memory_recent_errors = []
memory = {
    'errors_log_path': _memory_log_path if os.path.isfile(_memory_log_path) else None,
    'recent_errors': memory_recent_errors,
    'recent_error_count': len(memory_recent_errors),
    'status': 'pass' if not memory_recent_errors else 'warn',
}

pass_count = sum(1 for c in checks if c['status'] == 'pass')
fail_count = sum(1 for c in checks if c['status'] == 'fail')
warn_count = sum(1 for c in checks if c['status'] == 'warn')

if disk_status == 'pass': pass_count += 1
elif disk_status == 'fail': fail_count += 1
elif disk_status == 'warn': warn_count += 1

result = {
    'loki_mode_version': os.environ.get('LOKI_VERSION', 'unknown'),
    'checks': checks,
    'disk': {
        'available_gb': disk_gb,
        'status': disk_status
    },
    'sentrux': sentrux,
    'memory': memory,
    'summary': {
        'passed': pass_count,
        'failed': fail_count,
        'warnings': warn_count,
        'ok': fail_count == 0
    }
}

print(json.dumps(result, indent=2))
"
}

# Architectural-drift gate (v7.5.14, opt-in).
#
# Wraps the external sentrux Rust binary -- https://github.com/sentrux/sentrux --
# to give users a deterministic per-iteration architecture-drift signal that
# complements the existing 11 quality gates and 3-reviewer council. This is a
# manual subcommand only in v7.5.14; iteration-loop integration is deferred
# pending a real-PRD smoke test (tracked in CHANGELOG).
#
# Subcommands:
#   loki sentrux baseline [<path>]   -- save .sentrux/baseline.json
#   loki sentrux gate     [<path>]   -- compare current vs baseline
#   loki sentrux status   [<path>]   -- print current baseline + verdict
#
# Default path is the current working directory.
cmd_sentrux() {
    # shellcheck source=autonomy/lib/sentrux-gate.sh
    if ! source "$_LOKI_SCRIPT_DIR/lib/sentrux-gate.sh" 2>/dev/null \
       && ! source "$(dirname "$0")/lib/sentrux-gate.sh" 2>/dev/null; then
        echo -e "${RED}sentrux helper missing -- expected at autonomy/lib/sentrux-gate.sh${NC}" >&2
        return 1
    fi

    local sub="${1:-help}"
    if [ "$#" -gt 0 ]; then shift; fi

    # Parse --force flag (used by init-rules); collect remaining args into target.
    local force=0
    local positional=()
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --force|-f) force=1; shift ;;
            --) shift; while [ "$#" -gt 0 ]; do positional+=("$1"); shift; done ;;
            *) positional+=("$1"); shift ;;
        esac
    done
    local target="${positional[0]:-.}"

    case "$sub" in
        baseline)
            if ! sentrux_available; then
                echo -e "${YELLOW}sentrux not installed.${NC} Install with one of:" >&2
                echo "  brew install sentrux/tap/sentrux" >&2
                echo "  curl -fsSL https://raw.githubusercontent.com/sentrux/sentrux/main/install.sh | sh" >&2
                echo "  See also: https://github.com/sentrux/sentrux" >&2
                return 2
            fi
            if sentrux_baseline_save "$target"; then
                local q
                q=$(sentrux_baseline_quality "$target" || echo "?")
                echo -e "${GREEN}Baseline saved.${NC} Quality: $q (path: $target)"
                return 0
            fi
            echo -e "${RED}Failed to save baseline.${NC}" >&2
            return 1
            ;;
        gate)
            if ! sentrux_available; then
                echo -e "${YELLOW}sentrux not installed.${NC} Install with one of:" >&2
                echo "  brew install sentrux/tap/sentrux" >&2
                echo "  curl -fsSL https://raw.githubusercontent.com/sentrux/sentrux/main/install.sh | sh" >&2
                echo "  See also: https://github.com/sentrux/sentrux" >&2
                return 2
            fi
            local diff verdict before after
            diff=$(sentrux_gate_diff "$target")
            verdict=$(printf '%s' "$diff" | awk -F'|' '{print $3}')
            before=$(printf '%s' "$diff" | awk -F'|' '{print $1}')
            after=$(printf '%s' "$diff" | awk -F'|' '{print $2}')
            case "$verdict" in
                OK)
                    echo -e "${GREEN}OK${NC}  Quality: $before -> $after (no degradation)"
                    return 0
                    ;;
                DEGRADED)
                    echo -e "${RED}DEGRADED${NC}  Quality: $before -> $after"
                    echo "  Architecture regressed since the saved baseline."
                    echo "  Inspect with: sentrux scan $target"
                    return 1
                    ;;
                *)
                    echo -e "${YELLOW}UNKNOWN${NC}  Could not parse sentrux output."
                    echo "  Try: sentrux gate --save $target  (to refresh baseline)"
                    return 2
                    ;;
            esac
            ;;
        status)
            if ! sentrux_available; then
                echo "sentrux: not installed (optional)"
                echo "install with one of:"
                echo "  brew install sentrux/tap/sentrux"
                echo "  curl -fsSL https://raw.githubusercontent.com/sentrux/sentrux/main/install.sh | sh"
                echo "  See also: https://github.com/sentrux/sentrux"
                return 0
            fi
            local v
            v=$(sentrux_version || echo "unknown")
            echo "sentrux: v$v"
            local q
            if q=$(sentrux_baseline_quality "$target"); then
                echo "baseline ($target): quality=$q"
            else
                echo "baseline ($target): not saved (run: loki sentrux baseline $target)"
            fi
            return 0
            ;;
        init-rules)
            # Scaffold a conservative default .sentrux/rules.toml in <target>/.sentrux/.
            # Does not require sentrux binary -- the file is plain text.
            local rules_dir="$target/.sentrux"
            local rules_file="$rules_dir/rules.toml"
            local abs_path
            if [ -e "$rules_file" ] && [ "$force" -ne 1 ]; then
                echo -e "${YELLOW}Refusing to overwrite existing $rules_file${NC}" >&2
                echo "  Re-run with --force to replace it." >&2
                return 1
            fi
            if ! mkdir -p "$rules_dir" 2>/dev/null; then
                echo -e "${RED}Failed to create $rules_dir${NC}" >&2
                return 2
            fi
            if ! cat > "$rules_file" <<'SENTRUX_RULES_EOF'
# Sentrux architectural rules scaffolded by `loki sentrux init-rules` (Loki Mode v7.5.15).
# Conservative defaults -- tighten per-project as needed.
# See https://github.com/sentrux/sentrux for full spec.

[constraints]
# Block any iteration that introduces an import cycle.
max_cycles = 0
# Block files that grow into "god files" (very high churn + many dependents).
no_god_files = true
# Cap cyclomatic complexity per function (sentrux default is liberal).
max_cc = 30

# Layer enforcement is project-specific. Uncomment + edit when you know the
# layout you want enforced. Example for a typical app:
#
# [[layers]]
# name = "core"
# paths = ["src/core/*"]
# order = 0
#
# [[layers]]
# name = "app"
# paths = ["src/app/*"]
# order = 2
#
# [[boundaries]]
# from = "src/app/*"
# to = "src/core/internal/*"
# reason = "app must not depend on core internals"
SENTRUX_RULES_EOF
            then
                echo -e "${RED}Failed to write $rules_file${NC}" >&2
                return 2
            fi
            # Resolve absolute path for friendly output (portable across macOS/Linux).
            if command -v python3 >/dev/null 2>&1; then
                abs_path=$(python3 -c "import os,sys; print(os.path.abspath(sys.argv[1]))" "$rules_file" 2>/dev/null || echo "$rules_file")
            else
                abs_path=$(cd "$(dirname "$rules_file")" 2>/dev/null && pwd)/$(basename "$rules_file")
            fi
            echo -e "${GREEN}Wrote $abs_path${NC}"
            echo "Edit it to add layer/boundary rules, then run: loki sentrux baseline $target"
            return 0
            ;;
        help|--help|-h|"")
            echo -e "${BOLD}loki sentrux${NC} - Architectural drift gate (opt-in, requires sentrux binary)"
            echo ""
            echo "Usage:"
            echo "  loki sentrux baseline   [<path>]         Save current architecture as baseline"
            echo "  loki sentrux gate       [<path>]         Compare current vs baseline (exit 1 on DEGRADED)"
            echo "  loki sentrux status     [<path>]         Show binary version + saved baseline quality"
            echo "  loki sentrux init-rules [<path>] [--force]"
            echo "                                           Scaffold a default .sentrux/rules.toml"
            echo ""
            echo "Default path is the current directory."
            echo ""
            echo "About:"
            echo "  Wraps the sentrux CLI (https://github.com/sentrux/sentrux), a Rust tool that"
            echo "  scores codebase structure into a single 0-10000 quality signal. Useful as a"
            echo "  supplement to Loki's existing 11 quality gates when you want an objective"
            echo "  per-iteration architectural-drift number."
            echo ""
            echo "  Iteration-loop auto-gating is planned for a future release; for now this"
            echo "  subcommand is the manual integration surface."
            echo ""
            echo "Install sentrux:"
            echo "  brew install sentrux/tap/sentrux"
            echo "  curl -fsSL https://raw.githubusercontent.com/sentrux/sentrux/main/install.sh | sh"
            return 0
            ;;
        *)
            echo -e "${RED}Unknown subcommand: $sub${NC}" >&2
            echo "Run 'loki sentrux help' for usage." >&2
            return 1
            ;;
    esac
}

# Show version
cmd_version() {
    # v7.6.2 B-13 fix: --help prints help; bare invocation prints version.
    case "${1:-}" in
        --help|-h|help)
            echo -e "${BOLD}Loki Mode -- print version${NC}"
            echo ""
            echo "Usage: loki version"
            echo ""
            echo "Prints 'Loki Mode vX.Y.Z' from the VERSION file shipped with"
            echo "the package. Equivalent to 'loki --version' and 'loki -v'."
            return 0
            ;;
    esac
    echo "Loki Mode v$(get_version)"
}

# Secrets / credential management
cmd_secrets() {
    case "${1:-status}" in
        status)
            echo "API Key Status:"
            echo ""
            for key_var in ANTHROPIC_API_KEY OPENAI_API_KEY GOOGLE_API_KEY; do
                local value="${!key_var:-}"
                if [[ -n "$value" ]]; then
                    local masked="${value:0:8}...${value: -4}"
                    echo "  $key_var: SET ($masked, ${#value} chars)"
                else
                    # Check secret file mounts
                    local lower_name
                    lower_name=$(echo "$key_var" | tr '[:upper:]' '[:lower:]')
                    local found=false
                    for mount_path in /run/secrets /var/run/secrets; do
                        if [[ -f "$mount_path/$lower_name" ]]; then
                            echo "  $key_var: AVAILABLE (secret file: $mount_path/$lower_name)"
                            found=true
                            break
                        fi
                    done
                    if [[ "$found" != "true" ]]; then
                        echo "  $key_var: NOT SET"
                    fi
                fi
            done
            ;;
        validate)
            echo "Validating API keys..."
            python3 -c "
import sys
sys.path.insert(0, '$(dirname "$(dirname "$(resolve_script_path "$0")")")')
from dashboard.secrets import load_secrets
result = load_secrets()
for name, info in result.items():
    status = 'OK' if info['valid_format'] else 'INVALID'
    source = info['source']
    masked = info['masked']
    warning = info.get('warning', '')
    if info['set']:
        print(f'  {name}: {status} ({masked}, source: {source})')
        if warning:
            print(f'    WARNING: {warning}')
    else:
        print(f'  {name}: NOT SET')
" 2>/dev/null || echo "  (Python validation unavailable, showing basic status)"
            ;;
        help)
            echo "Usage: loki secrets [status|validate|help]"
            echo ""
            echo "Manage API keys and credentials."
            echo ""
            echo "Commands:"
            echo "  status    Show which API keys are configured (default)"
            echo "  validate  Validate API key formats"
            echo "  help      Show this help"
            echo ""
            echo "Secret Loading Priority:"
            echo "  1. Environment variable (ANTHROPIC_API_KEY, etc.)"
            echo "  2. Docker/K8s secret file (/run/secrets/anthropic_api_key)"
            echo "  3. Not set"
            ;;
        *)
            echo "Unknown secrets command: $1"
            cmd_secrets help
            return 1
            ;;
    esac
}

# Show recent logs
cmd_logs() {
    local lines="50"
    local follow=false

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --tail|-n) lines="$2"; shift 2 ;;
            --tail=*) lines="${1#--tail=}"; shift ;;
            -n=*) lines="${1#-n=}"; shift ;;
            --follow|-f) follow=true; shift ;;
            --all|-a) lines="+1"; shift ;;
            --help|-h)
                echo "Usage: loki logs [options]"
                echo ""
                echo "Options:"
                echo "  --tail, -n N   Show last N lines (default: 50)"
                echo "  --all, -a      Show all lines"
                echo "  --follow, -f   Follow log output (like tail -f)"
                echo "  --help, -h     Show this help"
                return 0
                ;;
            *)
                # Legacy: treat plain number as line count
                if [[ "$1" =~ ^[0-9]+$ ]]; then
                    lines="$1"
                fi
                shift
                ;;
        esac
    done

    local log_file="$LOKI_DIR/logs/session.log"

    if [ ! -f "$log_file" ]; then
        echo -e "${YELLOW}No log file found at $log_file${NC}"
        exit 0
    fi

    if [ "$follow" = true ]; then
        echo -e "${BOLD}Following logs (Ctrl+C to stop)${NC}"
        echo ""
        tail -f "$log_file"
    else
        echo -e "${BOLD}Recent Logs (last $lines lines)${NC}"
        echo -e "${DIM}Use --all to show all lines, --follow to stream${NC}"
        echo ""
        tail -n "$lines" "$log_file"
    fi
}

# API server management (delegates to unified FastAPI dashboard server).
# Shares the SAME machine-global PID/control dir as `loki dashboard`
# ($DASHBOARD_PID_DIR = ~/.loki/dashboard) so the two commands never fight
# over the port and so stop/status/open are consistent across both. Parses
# --host/--port, guards a busy port, computes the TLS scheme, and persists
# host/port/scheme side-files like cmd_dashboard_start does.
cmd_api() {
    local subcommand="${1:-help}"
    shift || true
    local port="${LOKI_DASHBOARD_PORT:-$DASHBOARD_DEFAULT_PORT}"
    local host="${LOKI_DASHBOARD_HOST:-$DASHBOARD_DEFAULT_HOST}"
    local pid_file="$DASHBOARD_PID_FILE"

    # Parse --host/--port (the old code ignored them, so `loki serve --port X`
    # silently bound 57374 and `--host 0.0.0.0` was dropped).
    while [ $# -gt 0 ]; do
        case "$1" in
            --port) port="${2:-$port}"; shift 2 ;;
            --port=*) port="${1#*=}"; shift ;;
            --host) host="${2:-$host}"; shift 2 ;;
            --host=*) host="${1#*=}"; shift ;;
            *) shift ;;
        esac
    done

    local scheme="http"
    if [ -n "${LOKI_TLS_CERT:-}" ] && [ -n "${LOKI_TLS_KEY:-}" ]; then
        scheme="https"
    fi
    # Loopback-reachable host for printed URLs when bound to 0.0.0.0.
    local url_host="$host"; [ "$url_host" = "0.0.0.0" ] && url_host="127.0.0.1"

    case "$subcommand" in
        start)
            # Check if Python is available
            if ! command -v python3 &> /dev/null; then
                echo -e "${RED}Error: Python 3 not found. Install with: brew install python3${NC}"
                exit 1
            fi

            # Check if already running
            if [ -f "$pid_file" ]; then
                local existing_pid=$(cat "$pid_file" 2>/dev/null)
                if [ -n "$existing_pid" ] && kill -0 "$existing_pid" 2>/dev/null; then
                    echo -e "${YELLOW}Dashboard server already running (PID: $existing_pid)${NC}"
                    echo "URL: ${scheme}://${url_host}:$port"
                    exit 0
                fi
                rm -f "$pid_file"
            fi

            # Refuse a busy port instead of writing a bogus PID.
            if command -v lsof &> /dev/null && lsof -i ":$port" &> /dev/null; then
                echo -e "${RED}Error: Port $port is already in use${NC}"
                echo "Stop the other server or set LOKI_DASHBOARD_PORT / --port to a free port."
                exit 1
            fi

            # Ensure dashboard Python dependencies
            if ! ensure_dashboard_venv; then
                echo -e "${RED}Cannot start dashboard without Python dependencies${NC}"
                exit 1
            fi
            local api_python="$DASHBOARD_PYTHON"

            # Start server. Persist host/port/scheme so status/open are accurate.
            mkdir -p "$LOKI_DIR/logs" "$DASHBOARD_PID_DIR"
            local uvicorn_args=("--host" "$host" "--port" "$port")
            if [ "$scheme" = "https" ]; then
                uvicorn_args+=("--ssl-certfile" "${LOKI_TLS_CERT}" "--ssl-keyfile" "${LOKI_TLS_KEY}")
            fi
            LOKI_DIR="$LOKI_DIR" LOKI_SKILL_DIR="$SKILL_DIR" PYTHONPATH="$SKILL_DIR" \
                LOKI_DASHBOARD_HOST="$host" LOKI_DASHBOARD_PORT="$port" \
                nohup "$api_python" -m uvicorn dashboard.server:app "${uvicorn_args[@]}" > "$LOKI_DIR/logs/api.log" 2>&1 &
            local new_pid=$!
            echo "$new_pid" > "$pid_file"
            echo "$port" > "${DASHBOARD_PID_DIR}/port"
            echo "$host" > "${DASHBOARD_PID_DIR}/host"
            echo "$scheme" > "${DASHBOARD_PID_DIR}/scheme"

            echo -e "${GREEN}Dashboard server started${NC}"
            echo "  PID:  $new_pid"
            echo "  URL:  ${scheme}://${url_host}:$port"
            echo "  Logs: $LOKI_DIR/logs/api.log"
            ;;

        stop)
            if [ -f "$pid_file" ]; then
                local pid=$(cat "$pid_file")
                if kill -0 "$pid" 2>/dev/null; then
                    kill "$pid"
                    rm -f "$pid_file"
                    echo -e "${GREEN}Dashboard server stopped${NC}"
                else
                    rm -f "$pid_file"
                    echo -e "${YELLOW}Dashboard server was not running${NC}"
                fi
            else
                echo -e "${YELLOW}No dashboard server PID file found${NC}"
            fi
            ;;

        status)
            if [ -f "$pid_file" ]; then
                local pid=$(cat "$pid_file")
                if kill -0 "$pid" 2>/dev/null; then
                    # Prefer persisted side-files for the real bind.
                    local s_scheme="$scheme" s_host="$url_host" s_port="$port"
                    [ -f "${DASHBOARD_PID_DIR}/scheme" ] && s_scheme="$(cat "${DASHBOARD_PID_DIR}/scheme" 2>/dev/null)"
                    [ -f "${DASHBOARD_PID_DIR}/host" ] && s_host="$(cat "${DASHBOARD_PID_DIR}/host" 2>/dev/null)"
                    [ -f "${DASHBOARD_PID_DIR}/port" ] && s_port="$(cat "${DASHBOARD_PID_DIR}/port" 2>/dev/null)"
                    [ "$s_host" = "0.0.0.0" ] && s_host="127.0.0.1"
                    echo -e "${GREEN}Dashboard server running${NC}"
                    echo "  PID:  $pid"
                    echo "  URL:  ${s_scheme}://${s_host}:${s_port}"

                    # Try to fetch status (best-effort; may 401 under auth)
                    if command -v curl &> /dev/null; then
                        echo ""
                        echo -e "${CYAN}Status:${NC}"
                        local _k=""; [ "$s_scheme" = "https" ] && _k="-k"
                        curl -s $_k "${s_scheme}://${s_host}:${s_port}/api/status" 2>/dev/null | jq . 2>/dev/null || true
                    fi
                else
                    echo -e "${YELLOW}Dashboard server not running (stale PID file)${NC}"
                    rm -f "$pid_file"
                fi
            else
                echo -e "${YELLOW}Dashboard server not running${NC}"
            fi
            ;;

        *)
            echo -e "${BOLD}Loki Mode Dashboard/API Server${NC}"
            echo ""
            echo "Usage: loki api <command>"
            echo ""
            echo "Commands:"
            echo "  start    Start the unified FastAPI dashboard server"
            echo "  stop     Stop the dashboard server"
            echo "  status   Check if dashboard server is running"
            echo ""
            echo "Environment:"
            echo "  LOKI_DASHBOARD_PORT  Port to listen on (default: 57374)"
            echo ""
            echo "Endpoints:"
            echo "  GET  /api/health  - Health check"
            echo "  GET  /api/status  - Session status"
            echo "  GET  /api/events  - SSE stream"
            echo "  GET  /api/logs    - Recent logs"
            echo "  POST /api/start   - Start session"
            echo "  POST /api/stop    - Stop session"
            echo "  POST /api/pause   - Pause session"
            echo "  POST /api/resume  - Resume session"
            ;;
    esac
}

# Multi-channel notifications (Slack, Discord, Webhook)
cmd_notify() {
    local subcommand="${1:-help}"
    shift || true

    case "$subcommand" in
        test)
            cmd_notify_test "$@"
            ;;
        slack)
            cmd_notify_send "slack" "$@"
            ;;
        discord)
            cmd_notify_send "discord" "$@"
            ;;
        webhook)
            cmd_notify_send "webhook" "$@"
            ;;
        status)
            cmd_notify_status
            ;;
        --help|-h|help)
            cmd_notify_help
            ;;
        *)
            # If first arg looks like a message, treat as test
            if [[ "$subcommand" != -* ]]; then
                cmd_notify_test "$subcommand" "$@"
            else
                echo -e "${RED}Unknown notify command: $subcommand${NC}"
                echo "Run 'loki notify help' for usage."
                exit 1
            fi
            ;;
    esac
}

cmd_notify_help() {
    echo -e "${BOLD}Loki Mode Notifications${NC}"
    echo ""
    echo "Usage: loki notify <command> [message]"
    echo ""
    echo "Commands:"
    echo "  test [message]     Send test notification to all configured channels"
    echo "  slack <message>    Send notification to Slack only"
    echo "  discord <message>  Send notification to Discord only"
    echo "  webhook <message>  Send notification to webhook only"
    echo "  status             Show which notification channels are configured"
    echo "  help               Show this help"
    echo ""
    echo "Environment Variables:"
    echo "  LOKI_SLACK_WEBHOOK    Slack incoming webhook URL"
    echo "  LOKI_DISCORD_WEBHOOK  Discord webhook URL"
    echo "  LOKI_WEBHOOK_URL      Custom webhook URL (POST JSON)"
    echo "  LOKI_NOTIFY_CHANNELS  Channels to use (comma-separated: slack,discord,webhook)"
    echo "                        Default: all configured channels"
    echo ""
    echo "Examples:"
    echo "  loki notify test                    # Test all configured channels"
    echo "  loki notify test 'Hello World'     # Test with custom message"
    echo "  loki notify slack 'Build complete' # Send to Slack only"
    echo "  loki notify status                 # Check configuration"
    echo ""
    echo "Configuration via .loki/config.yaml:"
    echo "  notifications:"
    echo "    slack_webhook: https://hooks.slack.com/services/..."
    echo "    discord_webhook: https://discord.com/api/webhooks/..."
    echo "    webhook_url: https://your-server.com/webhook"
    echo "    channels: slack,discord  # or 'all'"
}

cmd_notify_status() {
    echo -e "${BOLD}Notification Channel Status${NC}"
    echo ""

    local any_configured=false

    # Check Slack
    if [ -n "${LOKI_SLACK_WEBHOOK:-}" ]; then
        echo -e "  ${GREEN}[OK]${NC} Slack     - Configured"
        any_configured=true
    else
        echo -e "  ${DIM}[--]${NC} Slack     - Not configured (set LOKI_SLACK_WEBHOOK)"
    fi

    # Check Discord
    if [ -n "${LOKI_DISCORD_WEBHOOK:-}" ]; then
        echo -e "  ${GREEN}[OK]${NC} Discord   - Configured"
        any_configured=true
    else
        echo -e "  ${DIM}[--]${NC} Discord   - Not configured (set LOKI_DISCORD_WEBHOOK)"
    fi

    # Check custom webhook
    if [ -n "${LOKI_WEBHOOK_URL:-}" ]; then
        echo -e "  ${GREEN}[OK]${NC} Webhook   - Configured"
        any_configured=true
    else
        echo -e "  ${DIM}[--]${NC} Webhook   - Not configured (set LOKI_WEBHOOK_URL)"
    fi

    echo ""

    # Check channel filter
    if [ -n "${LOKI_NOTIFY_CHANNELS:-}" ]; then
        echo -e "${CYAN}Active channels:${NC} ${LOKI_NOTIFY_CHANNELS}"
    else
        echo -e "${CYAN}Active channels:${NC} all configured"
    fi

    echo ""

    if [ "$any_configured" = "false" ]; then
        echo -e "${YELLOW}No notification channels configured.${NC}"
        echo "Set environment variables or add to .loki/config.yaml"
    else
        echo -e "Test with: ${CYAN}loki notify test${NC}"
    fi
}

cmd_notify_test() {
    local message="${1:-Loki Mode test notification}"
    local channels_notified=0

    echo -e "${BOLD}Testing Notification Channels${NC}"
    echo ""

    # Determine which channels to test
    local channels="${LOKI_NOTIFY_CHANNELS:-all}"

    # Test Slack
    if [[ "$channels" == "all" || "$channels" == *"slack"* ]]; then
        if [ -n "${LOKI_SLACK_WEBHOOK:-}" ]; then
            echo -n "  Slack...    "
            if send_slack_notification "$message" "Test"; then
                echo -e "${GREEN}OK${NC}"
                channels_notified=$((channels_notified + 1))
            else
                echo -e "${RED}FAILED${NC}"
            fi
        fi
    fi

    # Test Discord
    if [[ "$channels" == "all" || "$channels" == *"discord"* ]]; then
        if [ -n "${LOKI_DISCORD_WEBHOOK:-}" ]; then
            echo -n "  Discord...  "
            if send_discord_notification "$message" "Test"; then
                echo -e "${GREEN}OK${NC}"
                channels_notified=$((channels_notified + 1))
            else
                echo -e "${RED}FAILED${NC}"
            fi
        fi
    fi

    # Test custom webhook
    if [[ "$channels" == "all" || "$channels" == *"webhook"* ]]; then
        if [ -n "${LOKI_WEBHOOK_URL:-}" ]; then
            echo -n "  Webhook...  "
            if send_webhook_notification "$message" "Test"; then
                echo -e "${GREEN}OK${NC}"
                channels_notified=$((channels_notified + 1))
            else
                echo -e "${RED}FAILED${NC}"
            fi
        fi
    fi

    echo ""

    if [ $channels_notified -eq 0 ]; then
        echo -e "${YELLOW}No channels configured or enabled.${NC}"
        echo "Run 'loki notify status' to check configuration."
        exit 1
    else
        echo -e "${GREEN}Sent to $channels_notified channel(s)${NC}"
    fi
}

cmd_notify_send() {
    local channel="$1"
    shift
    local message="$*"

    if [ -z "$message" ]; then
        echo -e "${RED}Error: Message required${NC}"
        echo "Usage: loki notify $channel <message>"
        exit 1
    fi

    case "$channel" in
        slack)
            if [ -z "${LOKI_SLACK_WEBHOOK:-}" ]; then
                echo -e "${RED}Error: LOKI_SLACK_WEBHOOK not set${NC}"
                exit 1
            fi
            if send_slack_notification "$message" "Manual"; then
                echo -e "${GREEN}Sent to Slack${NC}"
            else
                echo -e "${RED}Failed to send to Slack${NC}"
                exit 1
            fi
            ;;
        discord)
            if [ -z "${LOKI_DISCORD_WEBHOOK:-}" ]; then
                echo -e "${RED}Error: LOKI_DISCORD_WEBHOOK not set${NC}"
                exit 1
            fi
            if send_discord_notification "$message" "Manual"; then
                echo -e "${GREEN}Sent to Discord${NC}"
            else
                echo -e "${RED}Failed to send to Discord${NC}"
                exit 1
            fi
            ;;
        webhook)
            if [ -z "${LOKI_WEBHOOK_URL:-}" ]; then
                echo -e "${RED}Error: LOKI_WEBHOOK_URL not set${NC}"
                exit 1
            fi
            if send_webhook_notification "$message" "Manual"; then
                echo -e "${GREEN}Sent to webhook${NC}"
            else
                echo -e "${RED}Failed to send to webhook${NC}"
                exit 1
            fi
            ;;
    esac
}

# Send notification to Slack via incoming webhook
send_slack_notification() {
    local message="$1"
    local event_type="${2:-Notification}"
    local webhook_url="${LOKI_SLACK_WEBHOOK:-}"

    if [ -z "$webhook_url" ]; then
        return 1
    fi

    local project_name
    project_name=$(basename "$(pwd)")
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local message_escaped event_escaped project_escaped
    message_escaped=$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
    event_escaped=$(printf '%s' "$event_type" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
    project_escaped=$(printf '%s' "$project_name" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')

    # Build Slack payload with blocks for better formatting
    local payload
    payload=$(cat <<EOF
{
    "blocks": [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": "Loki Mode: $event_escaped"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "$message_escaped"
            }
        },
        {
            "type": "context",
            "elements": [
                {
                    "type": "mrkdwn",
                    "text": "*Project:* $project_escaped | *Time:* $timestamp"
                }
            ]
        }
    ]
}
EOF
)

    # Send to Slack
    curl -s -X POST -H 'Content-type: application/json' \
        --data "$payload" \
        "$webhook_url" > /dev/null 2>&1
}

# Send notification to Discord via webhook
send_discord_notification() {
    local message="$1"
    local event_type="${2:-Notification}"
    local webhook_url="${LOKI_DISCORD_WEBHOOK:-}"

    if [ -z "$webhook_url" ]; then
        return 1
    fi

    local project_name
    project_name=$(basename "$(pwd)")
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local message_escaped event_escaped project_escaped
    message_escaped=$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
    event_escaped=$(printf '%s' "$event_type" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
    project_escaped=$(printf '%s' "$project_name" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')

    # Build Discord embed payload
    local payload
    payload=$(cat <<EOF
{
    "embeds": [
        {
            "title": "Loki Mode: $event_escaped",
            "description": "$message_escaped",
            "color": 5814783,
            "footer": {
                "text": "Project: $project_escaped | $timestamp"
            }
        }
    ]
}
EOF
)

    # Send to Discord
    curl -s -X POST -H 'Content-type: application/json' \
        --data "$payload" \
        "$webhook_url" > /dev/null 2>&1
}

# Send notification to custom webhook
send_webhook_notification() {
    local message="$1"
    local event_type="${2:-Notification}"
    local webhook_url="${LOKI_WEBHOOK_URL:-}"

    if [ -z "$webhook_url" ]; then
        return 1
    fi

    local project_name
    project_name=$(basename "$(pwd)")
    local timestamp
    timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    local message_escaped event_escaped project_escaped cwd_escaped
    message_escaped=$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
    event_escaped=$(printf '%s' "$event_type" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
    project_escaped=$(printf '%s' "$project_name" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
    cwd_escaped=$(printf '%s' "$(pwd)" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')

    # Build generic JSON payload
    local payload
    payload=$(cat <<EOF
{
    "source": "loki-mode",
    "event": "$event_escaped",
    "message": "$message_escaped",
    "project": "$project_escaped",
    "timestamp": "$timestamp",
    "cwd": "$cwd_escaped"
}
EOF
)

    # Send to webhook
    curl -s -X POST -H 'Content-type: application/json' \
        --data "$payload" \
        "$webhook_url" > /dev/null 2>&1
}

# Broadcast notification to all configured channels
# Used by run.sh for automated notifications
broadcast_notification() {
    local message="$1"
    local event_type="${2:-Notification}"
    local channels="${LOKI_NOTIFY_CHANNELS:-all}"

    # Send to each enabled channel (silently, for background use)
    if [[ "$channels" == "all" || "$channels" == *"slack"* ]]; then
        send_slack_notification "$message" "$event_type" 2>/dev/null || true
    fi

    if [[ "$channels" == "all" || "$channels" == *"discord"* ]]; then
        send_discord_notification "$message" "$event_type" 2>/dev/null || true
    fi

    if [[ "$channels" == "all" || "$channels" == *"webhook"* ]]; then
        send_webhook_notification "$message" "$event_type" 2>/dev/null || true
    fi
}

# Sandbox management (delegates to sandbox.sh)
# sandbox.sh handles detection: Docker Desktop Sandbox > Docker Container > Worktree
cmd_sandbox() {
    local subcommand="${1:-help}"
    shift || true

    if [ ! -f "$SANDBOX_SH" ]; then
        echo -e "${RED}Error: sandbox.sh not found at $SANDBOX_SH${NC}"
        echo "Expected at: $SKILL_DIR/autonomy/sandbox.sh"
        exit 1
    fi

    # Delegate to sandbox.sh (it handles Docker/worktree detection internally)
    exec "$SANDBOX_SH" "$subcommand" "$@"
}

# Demo mode - build a real project from a bundled PRD template
cmd_demo() {
    # Handle --help
    if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
        echo -e "${BOLD}loki demo${NC} - Build a real project from a bundled template"
        echo ""
        echo "Creates a temporary directory, copies the Simple Todo App PRD, and"
        echo "runs 'loki start' to build it end-to-end. Shows a summary when done"
        echo "and offers to open the result in a browser."
        echo ""
        echo "Usage: loki demo [options]"
        echo ""
        echo "Options:"
        echo "  --dir PATH    Use a specific directory instead of a temp dir"
        echo "  --keep        Keep the temp directory on exit (default: kept)"
        echo "  --provider P  AI provider to use (default: claude)"
        echo "  --dry-run     Show what would happen without running"
        echo ""
        echo "Examples:"
        echo "  loki demo                        # Build todo app in temp dir"
        echo "  loki demo --dir ~/demo-project   # Build in specific directory"
        echo "  loki demo --provider codex       # Use Codex instead of Claude"
        return 0
    fi

    local version
    version=$(get_version)
    local demo_prd="$SKILL_DIR/templates/simple-todo-app.md"
    local demo_dir=""
    local provider=""
    local dry_run=false
    local start_args=()

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --dir)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --dir requires a path${NC}"
                    return 1
                fi
                demo_dir="$2"
                shift 2
                ;;
            --dir=*)
                demo_dir="${1#*=}"
                shift
                ;;
            --provider)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --provider requires a value${NC}"
                    return 1
                fi
                provider="$2"
                shift 2
                ;;
            --provider=*)
                provider="${1#*=}"
                shift
                ;;
            --keep)
                # kept by default now, accepted for compat
                shift
                ;;
            --dry-run)
                dry_run=true
                shift
                ;;
            *)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki demo --help' for usage."
                return 1
                ;;
        esac
    done

    # Fall back to examples/ if templates/ doesn't exist
    if [ ! -f "$demo_prd" ]; then
        demo_prd="$SKILL_DIR/examples/simple-todo-app.md"
    fi

    if [ ! -f "$demo_prd" ]; then
        echo -e "${RED}Error: Demo PRD not found${NC}"
        echo "Expected at: $SKILL_DIR/templates/simple-todo-app.md"
        return 1
    fi

    # Set up the demo directory
    if [ -z "$demo_dir" ]; then
        demo_dir=$(mktemp -d "${TMPDIR:-/tmp}/loki-demo-XXXXXX")
    else
        # Expand ~ if present
        demo_dir="${demo_dir/#\~/$HOME}"
        mkdir -p "$demo_dir"
    fi

    echo -e "${BOLD}Loki Mode v$version - Demo${NC}"
    echo ""
    echo -e "  PRD:       Simple Todo App"
    echo -e "  Directory: ${CYAN}$demo_dir${NC}"
    if [ -n "$provider" ]; then
        echo -e "  Provider:  $provider"
    else
        echo -e "  Provider:  claude (default)"
    fi
    echo ""

    # Copy PRD into demo directory
    cp "$demo_prd" "$demo_dir/prd.md"

    # Initialize git repo if not already one
    if [ ! -d "$demo_dir/.git" ]; then
        git -C "$demo_dir" init -q 2>/dev/null || true
    fi

    if [ "$dry_run" = true ]; then
        echo -e "${YELLOW}[dry-run] Would run:${NC}"
        echo "  cd $demo_dir"
        echo -n "  loki start prd.md --simple --yes"
        [ -n "$provider" ] && echo -n " --provider $provider"
        echo ""
        echo ""
        echo -e "${DIM}PRD copied to: $demo_dir/prd.md${NC}"
        return 0
    fi

    echo -e "${DIM}Starting autonomous build...${NC}"
    echo -e "${DIM}(Press Ctrl+C to stop at any time)${NC}"
    echo ""

    # Build start args
    start_args+=("prd.md" "--simple" "--yes")
    if [ -n "$provider" ]; then
        start_args+=("--provider" "$provider")
    fi

    # Run loki start from the demo directory
    local start_exit=0
    (cd "$demo_dir" && cmd_start "${start_args[@]}") || start_exit=$?

    echo ""
    echo -e "${DIM}---------------------------------------${NC}"
    echo -e "${BOLD}Demo Complete${NC}"
    echo -e "${DIM}---------------------------------------${NC}"
    echo ""

    # Show summary of what was generated
    echo -e "${BOLD}Project Directory:${NC} $demo_dir"
    echo ""

    if [ -d "$demo_dir" ]; then
        # Count generated files (exclude .git and .loki)
        local file_count
        file_count=$(find "$demo_dir" -type f \
            -not -path "$demo_dir/.git/*" \
            -not -path "$demo_dir/.loki/*" \
            -not -name "prd.md" \
            2>/dev/null | wc -l | tr -d ' ')

        local dir_count
        dir_count=$(find "$demo_dir" -type d \
            -not -path "$demo_dir/.git" \
            -not -path "$demo_dir/.git/*" \
            -not -path "$demo_dir/.loki" \
            -not -path "$demo_dir/.loki/*" \
            -not -path "$demo_dir" \
            2>/dev/null | wc -l | tr -d ' ')

        echo -e "${BOLD}Generated:${NC} $file_count files in $dir_count directories"
        echo ""

        # Show top-level structure
        if command -v tree &>/dev/null; then
            echo -e "${BOLD}Project Structure:${NC}"
            tree -L 2 --dirsfirst -I '.git|.loki' "$demo_dir" 2>/dev/null || \
                ls -1 "$demo_dir" 2>/dev/null
            echo ""
        else
            echo -e "${BOLD}Top-level contents:${NC}"
            ls -1 "$demo_dir" | grep -v '^\.\(git\|loki\)$' || true
            echo ""
        fi

        # Check for common web artifacts and offer to open
        local has_web=false
        local open_target=""

        # Check for common entry points
        for candidate in \
            "$demo_dir/index.html" \
            "$demo_dir/dist/index.html" \
            "$demo_dir/build/index.html" \
            "$demo_dir/public/index.html" \
            "$demo_dir/frontend/dist/index.html" \
            "$demo_dir/client/dist/index.html"; do
            if [ -f "$candidate" ]; then
                has_web=true
                open_target="$candidate"
                break
            fi
        done

        # Check for package.json with start script
        local has_start_script=false
        if [ -f "$demo_dir/package.json" ]; then
            if grep -q '"start"' "$demo_dir/package.json" 2>/dev/null; then
                has_start_script=true
            fi
            if grep -q '"dev"' "$demo_dir/package.json" 2>/dev/null; then
                has_start_script=true
            fi
        fi

        if [ "$has_web" = true ] && [ -n "$open_target" ]; then
            echo -e "${CYAN}A web frontend was generated.${NC}"
            echo ""
            echo -n "Open in browser? [Y/n] "
            read -r answer </dev/tty 2>/dev/null || answer="n"
            if [[ -z "$answer" || "$answer" =~ ^[Yy] ]]; then
                if command -v open &>/dev/null; then
                    open "$open_target"
                elif command -v xdg-open &>/dev/null; then
                    xdg-open "$open_target"
                else
                    echo -e "Open this file in your browser: ${BLUE}$open_target${NC}"
                fi
            fi
        elif [ "$has_start_script" = true ]; then
            echo -e "${CYAN}A start script was found in package.json.${NC}"
            echo -e "To run the app:"
            echo -e "  cd $demo_dir && npm install && npm start"
        fi
    fi

    echo ""
    echo -e "${BOLD}Next steps:${NC}"
    echo -e "  cd $demo_dir                   # Explore the generated project"
    echo -e "  loki start ./your-prd.md       # Build your own project"
    echo -e "  loki init                      # Create a PRD interactively"

    if [ $start_exit -ne 0 ]; then
        echo ""
        echo -e "${YELLOW}Note: loki start exited with code $start_exit.${NC}"
        echo -e "${YELLOW}The demo directory is preserved at: $demo_dir${NC}"
    fi

    return $start_exit
}

# R7 (zero-config first run): set the lightweight TTFV execution profile.
# Shared by `cmd_quick` and the `loki start "<brief>"` brief sub-path so the
# fast first pass means the same thing in both: capped iterations, completion
# council off, simple complexity tier, heavy phases off. This is HONEST fast --
# it genuinely shortens the path to first visible value; it does not fake
# progress. Depth is opt-in via a plain `loki start` re-run.
# Usage: set_ttfv_lightweight_profile [max_iter]
set_ttfv_lightweight_profile() {
    local max_iter="${1:-${LOKI_MAX_ITERATIONS:-3}}"
    export LOKI_MAX_ITERATIONS="$max_iter"
    export LOKI_COMPLEXITY=simple
    export LOKI_COUNCIL_ENABLED=false
    export LOKI_PHASE_CODE_REVIEW=false
    export LOKI_PHASE_PERFORMANCE=false
    export LOKI_PHASE_ACCESSIBILITY=false
    export LOKI_PHASE_REGRESSION=false
    export LOKI_PHASE_UAT=false
    export LOKI_PHASE_WEB_RESEARCH=false
}

# R7: synthesize a forward-looking PRD from a one-line brief. Writes to a
# unique path (caller-provided) and echoes nothing -- the file is the output.
# Kept DISTINCT from .loki/generated-prd.md (codebase-analysis artifact) so it
# never pollutes the v7.8.1 generated-PRD-reuse signature logic. The brief text
# is the project intent; the rest is a minimal scaffold the agent fills in.
# Usage: synthesize_brief_prd <output_file> <brief_text>
synthesize_brief_prd() {
    local out_file="$1"
    local brief_text="$2"
    local out_dir
    out_dir="$(dirname "$out_file")"
    mkdir -p "$out_dir" 2>/dev/null || true
    cat > "$out_file" << BRIEFEOF
# Project Brief

## Overview
$brief_text

## Requirements
- Build the smallest working version of the above that a user can see and run.
- Prefer a runnable artifact (a page, a CLI, an API endpoint) over scaffolding.
- Follow conventional structure for the chosen stack; keep dependencies minimal.
- Write a short README describing how to run it.

## Success Criteria
- A user can run the result and observe the core behavior described above.
- No errors on a clean start; the happy path works end to end.

## Constraints
- This is a fast first pass (zero-config first run). Keep scope tight.
- Do not over-engineer; depth and hardening come on a later full run.
- No emojis, no em dashes in code, comments, or docs.

---

**Mode:** Brief (zero-config first run, lightweight first pass)
BRIEFEOF
}

# Quick mode - lightweight single-task execution
cmd_quick() {
    # v7.6.3 B-11 fix: --help previously fell through to provider invocation
    # (task_desc="--help") which hung the CLI for 10+ seconds. Explicit guard.
    case "${1:-}" in
        --help|-h|help)
            echo -e "${BOLD}Loki Mode -- quick lightweight execution${NC}"
            echo ""
            echo "Usage: loki quick \"description of what to do\""
            echo ""
            echo "Runs Loki with a lightweight execution profile: 3 iterations max,"
            echo "no quality council, no completion gate. Best for tiny tasks."
            echo ""
            echo "Examples:"
            echo "  loki quick \"add dark mode to this React app\""
            echo "  loki quick \"fix the login bug in auth.js\""
            echo "  loki quick \"add input validation to the signup form\""
            echo "  loki quick \"write unit tests for the API endpoints\""
            echo ""
            echo "Options:"
            echo "  --help, -h    Show this help and exit"
            echo ""
            echo "Environment:"
            echo "  LOKI_MAX_ITERATIONS    Override the default 3-iteration cap"
            echo ""
            echo "See also: loki start (full RARV cycle), loki run (alias for start)"
            return 0
            ;;
    esac
    if [ $# -eq 0 ]; then
        echo -e "${RED}Error: No task description provided${NC}"
        echo ""
        echo "Usage: loki quick \"description of what to do\""
        echo ""
        echo "Examples:"
        echo "  loki quick \"add dark mode to this React app\""
        echo "  loki quick \"fix the login bug in auth.js\""
        echo "  loki quick \"add input validation to the signup form\""
        echo "  loki quick \"write unit tests for the API endpoints\""
        exit 1
    fi

    local task_desc="$*"
    local version=$(get_version)
    local max_iter="${LOKI_MAX_ITERATIONS:-3}"

    echo -e "${BOLD}Loki Mode v$version - Quick Mode${NC}"
    echo ""
    echo -e "${CYAN}Task:${NC} $task_desc"
    echo -e "${DIM}Running lightweight execution ($max_iter iterations max, no quality council)${NC}"
    echo ""

    # Create quick PRD from task description
    # BUG-PU-005: Use unique filename to prevent race conditions when
    # multiple simultaneous `loki quick` commands run in the same project
    mkdir -p "$LOKI_DIR"
    local quick_prd="$LOKI_DIR/quick-prd-$$.md"
    cat > "$quick_prd" << QPRDEOF
# Quick Task

## Overview
$task_desc

## Requirements
- Complete the task described above
- Follow existing code patterns and conventions
- Write tests if applicable
- Do not break existing functionality

## Success Criteria
- Task is completed as described
- No errors or regressions introduced
- Code follows project conventions

## Constraints
- This is a quick single-task execution
- Keep changes minimal and focused
- Do not refactor unrelated code

---

**Mode:** Quick (lightweight, single-task)
QPRDEOF

    echo -e "${GREEN}Quick PRD generated${NC} at $quick_prd"
    echo ""

    # Set lightweight execution environment (shared TTFV profile -- see
    # set_ttfv_lightweight_profile; same profile the R7 brief sub-path uses).
    set_ttfv_lightweight_profile "$max_iter"

    # Record start for efficiency tracking
    record_session_start

    # Emit event
    emit_event session cli quick_start "task=$(echo "$task_desc" | head -c 100)"

    # Pre-flight: check that the provider CLI is installed
    local _quick_provider="${LOKI_PROVIDER:-claude}"
    if ! command -v "$_quick_provider" &>/dev/null; then
        echo -e "${RED}Error: Provider CLI '$_quick_provider' is not installed.${NC}"
        echo ""
        echo "Install your AI provider CLI first:"
        case "$_quick_provider" in
            claude) echo "  npm install -g @anthropic-ai/claude-code" ;;
            codex)  echo "  npm install -g @openai/codex" ;;
            cline)  echo "  npm install -g @anthropic-ai/cline" ;;
            aider)  echo "  pip install aider-chat" ;;
            *)      echo "  Check the provider documentation for installation." ;;
        esac
        echo ""
        echo "Then verify: loki doctor"
        exit 1
    fi

    # Run the orchestrator with quick settings (session leader -- see
    # _loki_new_session_exec; enables atomic group-kill on stop).
    _loki_new_session_exec "$RUN_SH" "$quick_prd"
}

# Docker Compose monitoring with auto-fix (v6.67.0)
cmd_monitor() {
    # Verify Docker is available
    if ! command -v docker &>/dev/null; then
        echo -e "${RED}Docker is not installed. Install from https://docker.com${NC}"
        return 1
    fi
    if ! docker info &>/dev/null 2>&1; then
        echo -e "${RED}Docker daemon is not running. Start Docker Desktop or the Docker service.${NC}"
        return 1
    fi

    local project_dir="."
    local watch_only=false
    local poll_interval="${LOKI_MONITOR_INTERVAL:-10}"
    local max_fixes="${LOKI_MONITOR_MAX_FIXES:-10}"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki monitor${NC} - AI-powered Docker Compose monitoring with auto-fix"
                echo ""
                echo "Usage: loki monitor [path] [options]"
                echo ""
                echo "Arguments:"
                echo "  path                  Project directory (default: current directory)"
                echo ""
                echo "Options:"
                echo "  --watch-only          Monitor only, do not auto-fix failures"
                echo "  --no-fix              Same as --watch-only"
                echo "  --interval SECONDS    Poll interval (default: 10, env: LOKI_MONITOR_INTERVAL)"
                echo "  --max-fixes N         Max fix attempts per session (default: 10, env: LOKI_MONITOR_MAX_FIXES)"
                echo "  --help, -h            Show this help"
                echo ""
                echo "The monitor polls Docker Compose services every N seconds."
                echo "When a service fails, it captures logs, feeds them to the AI"
                echo "provider (${LOKI_PROVIDER:-claude}), and lets the AI fix the code."
                echo "After fixing, it rebuilds and verifies the service."
                echo ""
                echo "Environment Variables:"
                echo "  LOKI_MONITOR_INTERVAL     Poll interval in seconds (default: 10)"
                echo "  LOKI_MONITOR_MAX_FIXES    Max fix attempts (default: 10)"
                echo "  LOKI_PROVIDER             AI provider for diagnosis (default: claude)"
                return 0
                ;;
            --watch-only|--no-fix)
                watch_only=true
                shift
                ;;
            --interval)
                poll_interval="${2:-10}"
                shift 2
                ;;
            --max-fixes)
                max_fixes="${2:-10}"
                shift 2
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki monitor --help' for usage."
                return 1
                ;;
            *)
                project_dir="$1"
                shift
                ;;
        esac
    done

    # Resolve to absolute path
    if [[ ! "$project_dir" = /* ]]; then
        project_dir="$(cd "$project_dir" 2>/dev/null && pwd)" || {
            echo -e "${RED}Error: directory not found: $1${NC}"
            return 1
        }
    fi

    if [[ ! -f "$project_dir/docker-compose.yml" ]] && [[ ! -f "$project_dir/docker-compose.yaml" ]]; then
        echo -e "${RED}No docker-compose.yml found in $project_dir${NC}"
        return 1
    fi

    echo -e "${CYAN}AI-Powered Docker Monitor${NC}"
    echo -e "Project: ${BOLD}$project_dir${NC}"
    echo -e "Provider: ${GREEN}${LOKI_PROVIDER:-claude}${NC}"
    echo "Press Ctrl+C to stop."

    # Show what we're monitoring
    local initial_services
    initial_services=$(cd "$project_dir" && docker compose ps -a --format json 2>/dev/null | PARSE_MODE=names python3 -c "
import json, sys
raw = sys.stdin.read().strip()
if not raw: sys.exit(0)
try:
    parsed = json.loads(raw)
    services = parsed if isinstance(parsed, list) else [parsed]
except json.JSONDecodeError:
    services = []
    for line in raw.split(chr(10)):
        try: services.append(json.loads(line))
        except: pass
names = [s.get('Service', s.get('Name','?')) for s in services if isinstance(s, dict)]
print(', '.join(names))
" 2>/dev/null)

    if [[ -n "$initial_services" ]]; then
        echo -e "Services: ${GREEN}$initial_services${NC}"
    else
        echo -e "${YELLOW}No running services detected yet. Waiting for docker compose up...${NC}"
    fi

    if [[ "$watch_only" == "true" ]]; then
        echo -e "Mode: ${YELLOW}watch-only${NC} (auto-fix disabled)"
    else
        echo -e "Mode: ${GREEN}auto-fix enabled${NC} (max $max_fixes attempts)"
    fi
    echo ""

    local fix_count=0
    local consecutive_healthy=0

    trap 'echo ""; echo -e "${CYAN}Monitor stopped.${NC}"; return 0' INT TERM

    while true; do
        # 1. CAPTURE: Get service status (all services including stopped)
        local ps_output
        ps_output=$(cd "$project_dir" && docker compose ps -a --format json 2>/dev/null)

        if [[ -z "$ps_output" ]]; then
            echo -e "${YELLOW}No services found. Waiting...${NC}"
            sleep "$poll_interval"
            continue
        fi

        # Parse services and find failures
        local failed_services=""
        local all_healthy=true
        local service_summary=""

        while IFS= read -r line; do
            [[ -z "$line" ]] && continue
            local svc_name svc_state svc_exit
            svc_name=$(echo "$line" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('Service',d.get('Name','')))" 2>/dev/null || echo "unknown")
            svc_state=$(echo "$line" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('State','unknown'))" 2>/dev/null || echo "unknown")
            svc_exit=$(echo "$line" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('ExitCode',0))" 2>/dev/null || echo "0")

            service_summary="${service_summary}${svc_name}=${svc_state} "

            if [[ "$svc_state" == "exited" ]] || [[ "$svc_state" == "dead" ]]; then
                all_healthy=false
                failed_services="${failed_services} ${svc_name}"
            fi
        done <<< "$ps_output"

        if [[ "$all_healthy" == "true" ]]; then
            consecutive_healthy=$((consecutive_healthy + 1))
            printf "\r${GREEN}[healthy]${NC} %s (%s)   " "$service_summary" "$(date +%H:%M:%S)"
            sleep "$poll_interval"
            continue
        fi

        consecutive_healthy=0

        # 2. CAPTURE LOGS and CONTEXT for each failed service, then feed to AI
        for svc_name in $failed_services; do
            if [[ $fix_count -ge $max_fixes ]]; then
                echo -e "\n${RED}Max fix attempts ($max_fixes) reached for this session.${NC}"
                echo "Manual intervention needed. Check: cd $project_dir && docker compose logs $svc_name"
                sleep "$poll_interval"
                continue 2
            fi

            if [[ "$watch_only" == "true" ]]; then
                echo -e "${YELLOW}[WATCH] $svc_name is down. Fix disabled (--watch-only mode).${NC}"
                echo -e "  Logs: cd $project_dir && docker compose logs $svc_name"
            elif [[ $fix_count -lt $max_fixes ]]; then
                echo -e "\n${RED}[FAILURE] $svc_name is down${NC}"

                local logs
                logs=$(cd "$project_dir" && docker compose logs --tail 50 "$svc_name" 2>/dev/null | head -c 3000)

                # Gather docker-compose.yml for AI context (truncated to avoid ARG_MAX)
                local compose_content=""
                if [[ -f "$project_dir/docker-compose.yml" ]]; then
                    compose_content=$(head -c 5000 "$project_dir/docker-compose.yml")
                elif [[ -f "$project_dir/docker-compose.yaml" ]]; then
                    compose_content=$(head -c 5000 "$project_dir/docker-compose.yaml")
                fi

                # Gather Dockerfile for the failing service (truncated)
                local dockerfile_content=""
                for df_path in "$project_dir/$svc_name/Dockerfile" "$project_dir/Dockerfile" "$project_dir/Dockerfile.$svc_name"; do
                    if [[ -f "$df_path" ]]; then
                        dockerfile_content=$(head -c 3000 "$df_path")
                        break
                    fi
                done

                # 3. FEED TO AI: Construct a rich prompt with all context
                local ai_prompt="You are debugging a Docker Compose service that has failed.

SERVICE: $svc_name
STATUS: exited/dead

DOCKER COMPOSE LOGS (last 50 lines):
$logs

DOCKER-COMPOSE.YML:
$compose_content"

                if [[ -n "$dockerfile_content" ]]; then
                    ai_prompt="${ai_prompt}

DOCKERFILE ($svc_name/Dockerfile):
$dockerfile_content"
                fi

                ai_prompt="${ai_prompt}

INSTRUCTIONS:
1. Analyze the error in the logs above
2. Identify the root cause
3. Fix the issue by editing the necessary files (docker-compose.yml, Dockerfile, source code, package.json, requirements.txt, etc.)
4. Make sure the fix works on any platform (Docker Desktop, Linux, Docker-in-Docker, Kubernetes)
5. Do NOT just restart -- actually fix the underlying code/config problem
6. After fixing, the system will rebuild with 'docker compose up --build'
7. Common issues: named volumes for node_modules (use anonymous), missing dependencies, port conflicts, wrong commands"

                echo -e "${CYAN}[AI] Analyzing $svc_name failure with ${LOKI_PROVIDER:-claude}...${NC}"

                # 4. LET AI FIX: Run loki quick with the AI prompt
                # The AI provider (claude/codex/ollama) decides what to fix
                fix_count=$((fix_count + 1))
                echo -e "${CYAN}[FIX] Attempt $fix_count/$max_fixes${NC}"

                (
                    cd "$project_dir"
                    LOKI_MAX_ITERATIONS=5 LOKI_AUTO_FIX=true \
                    "$0" quick "$ai_prompt" 2>&1 | while IFS= read -r fline; do
                        echo "  $fline"
                    done
                )

                # 5. REBUILD: Docker compose up --build for the fixed service
                echo -e "${CYAN}[REBUILD] Rebuilding $svc_name...${NC}"
                (cd "$project_dir" && docker compose up -d --build --no-deps "$svc_name" 2>&1) | while IFS= read -r fline; do
                    echo "  $fline"
                done

                # 6. VERIFY: Wait and check if the fix worked
                echo -e "${CYAN}[VERIFY] Waiting 15s for $svc_name to stabilize...${NC}"
                sleep 15

                local verify_state
                verify_state=$(cd "$project_dir" && docker compose ps -a --format json 2>/dev/null | VERIFY_SVC="$svc_name" python3 -c "
import json, sys, os
svc = os.environ['VERIFY_SVC']
raw = sys.stdin.read().strip()
if not raw:
    print('unknown')
    sys.exit(0)
# Handle both JSON array (v2.21+) and NDJSON formats
try:
    parsed = json.loads(raw)
    services = parsed if isinstance(parsed, list) else [parsed]
except json.JSONDecodeError:
    services = []
    for line in raw.split(chr(10)):
        try: services.append(json.loads(line))
        except: pass
for s in services:
    if not isinstance(s, dict): continue
    if s.get('Service', s.get('Name','')) == svc:
        print(s.get('State','unknown'))
        sys.exit(0)
print('unknown')
" 2>/dev/null || echo "unknown")

                if [[ "$verify_state" == "running" ]]; then
                    echo -e "${GREEN}[SUCCESS] $svc_name is now running!${NC}"
                else
                    echo -e "${YELLOW}[RETRY] $svc_name still not healthy (state: $verify_state). Will retry on next poll.${NC}"
                fi
            fi
        done

        sleep "$poll_interval"
    done
}

# Project scaffolding (v6.28.0)
cmd_init() {
    # Guard: check if .loki/ already exists to avoid overwriting active session
    if [[ -d ".loki" ]]; then
        if [[ -f ".loki/loki.pid" ]]; then
            local pid
            pid=$(cat ".loki/loki.pid" 2>/dev/null)
            if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
                echo -e "${RED}Cannot initialize: active session running. Stop it first with 'loki stop'.${NC}"
                return 1
            fi
        fi
        echo -e "${YELLOW}Reinitializing existing .loki/ directory${NC}"
    fi

    local version=$(get_version)
    local project_name=""
    local template_name=""
    local no_git=false
    local stdout_mode=false
    local dry_run=false
    local list_mode=false
    local json_mode=false

    # 21 built-in template names (order: simple, standard, complex)
    local TEMPLATE_NAMES=(
        simple-todo-app
        static-landing-page
        api-only
        rest-api
        rest-api-auth
        cli-tool
        discord-bot
        chrome-extension
        blog-platform
        full-stack-demo
        web-scraper
        data-pipeline
        dashboard
        game
        slack-bot
        npm-library
        microservice
        mobile-app
        saas-starter
        e-commerce
        ai-chatbot
    )

    # Template label lookup function (bash 3.2 compatible - no associative arrays)
    _get_template_label() {
        case "$1" in
            simple-todo-app) echo "Simple Todo App" ;;
            static-landing-page) echo "Static Landing Page" ;;
            api-only) echo "REST API (No Frontend)" ;;
            rest-api) echo "REST API" ;;
            rest-api-auth) echo "REST API with Auth" ;;
            cli-tool) echo "CLI Tool" ;;
            discord-bot) echo "Discord Bot" ;;
            chrome-extension) echo "Chrome Extension" ;;
            blog-platform) echo "Blog Platform" ;;
            full-stack-demo) echo "Full-Stack Demo" ;;
            web-scraper) echo "Web Scraper" ;;
            data-pipeline) echo "Data Pipeline" ;;
            dashboard) echo "Analytics Dashboard" ;;
            game) echo "Browser Game" ;;
            slack-bot) echo "Slack Bot" ;;
            npm-library) echo "npm Library" ;;
            microservice) echo "Microservice" ;;
            mobile-app) echo "Mobile App" ;;
            saas-starter) echo "SaaS Starter Kit" ;;
            e-commerce) echo "E-Commerce Store" ;;
            ai-chatbot) echo "AI Chatbot (RAG)" ;;
            *) echo "$1" ;;
        esac
    }

    local template_count=${#TEMPLATE_NAMES[@]}

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --list)
                list_mode=true
                shift
                ;;
            --json)
                json_mode=true
                list_mode=true
                shift
                ;;
            --template|-t)
                if [[ -n "${2:-}" ]]; then
                    template_name="$2"
                    shift 2
                else
                    echo -e "${RED}--template requires a value${NC}"
                    exit 1
                fi
                ;;
            --no-git)
                no_git=true
                shift
                ;;
            --stdout)
                stdout_mode=true
                shift
                ;;
            --dry-run)
                dry_run=true
                shift
                ;;
            --help|-h)
                echo -e "${BOLD}loki init${NC} - Project scaffolding with PRD templates"
                echo ""
                echo "Usage: loki init [project-name] [--template TYPE]"
                echo ""
                echo "Creates a new project scaffold with a PRD, config directory, and README."
                echo "If no project-name is given, scaffolds the current directory."
                echo ""
                echo "What it creates:"
                echo "  prd.md                   PRD from selected template"
                echo "  .loki/loki.config.json   Project configuration"
                echo "  README.md                Project README stub"
                echo "  .gitignore               Git ignore rules (with git init)"
                echo ""
                echo "Options:"
                echo "  --template, -t TYPE     Template name (e.g., saas-starter, cli-tool)"
                echo "  --no-git                Skip git init"
                echo "  --stdout                Print PRD to stdout instead of writing files"
                echo "  --list                  List all available templates"
                echo "  --json                  Machine-readable template list (JSON)"
                echo "  --dry-run               Show what would be created without doing it"
                echo "  --help, -h              Show this help"
                echo ""
                echo "Examples:"
                echo "  loki init my-saas --template saas-starter    Create my-saas/ with SaaS PRD"
                echo "  loki init --template cli-tool            Scaffold current dir with CLI PRD"
                echo "  loki init my-app                         Create my-app/ (prompts for template)"
                echo "  loki init --list                         Show all $template_count templates"
                echo "  loki init --template rest-api --stdout   Print REST API PRD to stdout"
                echo "  loki init my-app --dry-run               Preview without writing anything"
                exit 0
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki init --help' for usage."
                exit 1
                ;;
            *)
                if [[ -z "$project_name" ]]; then
                    project_name="$1"
                else
                    echo -e "${RED}Too many arguments${NC}"
                    echo "Usage: loki init [project-name] [--template TYPE]"
                    exit 1
                fi
                shift
                ;;
        esac
    done

    # Handle --list / --json
    if $list_mode; then
        if $json_mode; then
            echo "["
            local first=true
            for tname in "${TEMPLATE_NAMES[@]}"; do
                if $first; then
                    first=false
                else
                    echo ","
                fi
                printf '  {"name": "%s", "label": "%s"}' "$tname" "$(_get_template_label "$tname")"
            done
            echo ""
            echo "]"
        else
            echo -e "${BOLD}Available PRD Templates ($template_count)${NC}"
            echo ""
            echo -e "  ${DIM}Simple:${NC}"
            local idx=1
            for tname in "${TEMPLATE_NAMES[@]}"; do
                if [[ "$tname" == "rest-api" ]]; then
                    echo -e "  ${DIM}Standard:${NC}"
                elif [[ "$tname" == "mobile-app" ]]; then
                    echo -e "  ${DIM}Complex:${NC}"
                fi
                printf "  %2d. ${CYAN}%-22s${NC} %s\n" "$idx" "$tname" "$(_get_template_label "$tname")"
                idx=$((idx + 1))
            done
            echo ""
            echo "Usage: loki init [project-name] --template <name>"
        fi
        exit 0
    fi

    # Interactive template picker if no template specified
    if [[ -z "$template_name" ]]; then
        echo -e "${BOLD}Loki Mode v$version - Project Scaffolding${NC}"
        echo ""
        echo "Select a PRD template:"
        echo ""
        echo -e "  ${DIM}Simple:${NC}"
        local idx=1
        for tname in "${TEMPLATE_NAMES[@]}"; do
            if [[ "$tname" == "rest-api" ]]; then
                echo -e "  ${DIM}Standard:${NC}"
            elif [[ "$tname" == "mobile-app" ]]; then
                echo -e "  ${DIM}Complex:${NC}"
            fi
            printf "  %2d. %-22s %s\n" "$idx" "$tname" "$(_get_template_label "$tname")"
            idx=$((idx + 1))
        done
        echo ""
        echo -e -n "${BOLD}Choose [1-$template_count]:${NC} "
        read -r choice

        if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "$template_count" ]; then
            echo -e "${RED}Invalid choice. Enter a number between 1 and $template_count.${NC}"
            exit 1
        fi

        template_name="${TEMPLATE_NAMES[$((choice - 1))]}"
        echo ""
        echo -e "Selected: ${CYAN}$template_name${NC} ($(_get_template_label "$template_name"))"
    fi

    # R10: a hub-installed template (.loki/templates/<name>.md) is also valid.
    # Resolve it via agents/hub_install.py (validates the name as a safe id).
    local _installed_tpl=""
    local _hub_py_init="$SKILL_DIR/agents/hub_install.py"
    [ -f "$_hub_py_init" ] || _hub_py_init="agents/hub_install.py"
    if [[ -f "$_hub_py_init" ]]; then
        _installed_tpl=$(TPL_NAME="$template_name" HUB_PY="$_hub_py_init" python3 -c "
import os
import importlib.util as ilu
sp = ilu.spec_from_file_location('loki_hub_install', os.environ.get('HUB_PY'))
m = ilu.module_from_spec(sp); sp.loader.exec_module(m)
p = m.installed_template_path(os.environ['TPL_NAME'])
print(p or '')
" 2>/dev/null)
    fi

    # Validate template_name against known list before filesystem lookup
    local _tpl_valid=false
    for _tpl_check in "${TEMPLATE_NAMES[@]}"; do
        if [[ "$_tpl_check" == "$template_name" ]]; then
            _tpl_valid=true
            break
        fi
    done
    if [[ -n "$_installed_tpl" ]]; then
        _tpl_valid=true
    fi
    if ! $_tpl_valid; then
        echo -e "${RED}Unknown template: $template_name${NC}"
        echo ""
        echo "Available templates:"
        for tname in "${TEMPLATE_NAMES[@]}"; do
            echo "  $tname"
        done
        echo ""
        echo "Run 'loki init --list' for built-in details, 'loki template list' for installed."
        exit 1
    fi

    # Resolve template file
    local template_file=""
    if [[ -n "$_installed_tpl" && -f "$_installed_tpl" ]]; then
        template_file="$_installed_tpl"
    fi
    if [[ -z "$template_file" ]]; then
        for dir in "$SKILL_DIR/templates" "$SKILL_DIR/examples"; do
            if [[ -f "$dir/${template_name}.md" ]]; then
                template_file="$dir/${template_name}.md"
                break
            fi
        done
    fi

    if [[ -z "$template_file" ]]; then
        echo -e "${RED}Unknown template: $template_name${NC}"
        echo ""
        echo "Available templates:"
        for tname in "${TEMPLATE_NAMES[@]}"; do
            echo "  $tname"
        done
        echo ""
        echo "Run 'loki init --list' for details."
        exit 1
    fi

    # Read template content
    local prd_content
    prd_content=$(cat "$template_file")

    # --stdout mode: just print PRD and exit
    if $stdout_mode; then
        echo "$prd_content"
        exit 0
    fi

    # Determine target directory
    local target_dir
    if [[ -n "$project_name" ]]; then
        target_dir="$(pwd)/$project_name"
    else
        target_dir="$(pwd)"
    fi

    # Guard: check if target directory has an active .loki/ session
    if [[ -n "$project_name" ]] && [[ -d "$target_dir/.loki" ]]; then
        if [[ -f "$target_dir/.loki/loki.pid" ]]; then
            local _guard_pid
            _guard_pid=$(cat "$target_dir/.loki/loki.pid" 2>/dev/null)
            if [[ -n "$_guard_pid" ]] && kill -0 "$_guard_pid" 2>/dev/null; then
                echo -e "${RED}Cannot initialize: active session running in $target_dir. Stop it first.${NC}"
                return 1
            fi
        fi
        echo -e "${YELLOW}Reinitializing existing .loki/ directory in $target_dir${NC}"
    fi

    # Build config JSON
    local config_json
    config_json=$(cat <<ENDJSON
{
  "version": "$version",
  "template": "$template_name",
  "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "provider": "claude",
  "complexity": "auto",
  "quality_gates": true,
  "parallel_mode": false,
  "dashboard": true
}
ENDJSON
)

    # Build README content
    local display_name="${project_name:-$(basename "$target_dir")}"
    local readme_content
    readme_content=$(cat <<ENDREADME
# $display_name

Built with [Loki Mode](https://www.autonomi.dev/) using the \`$template_name\` template.

## Getting Started

\`\`\`bash
# Review the PRD
cat prd.md

# Launch autonomous development
claude --dangerously-skip-permissions
# Then: "Loki Mode with PRD at prd.md"

# Or via CLI
loki start prd.md
\`\`\`

## Project Structure

This project was scaffolded by \`loki init\`. Key files:

- \`prd.md\` - Product Requirements Document
- \`.loki/loki.config.json\` - Loki Mode configuration
ENDREADME
)

    # Build gitignore content
    local gitignore_content
    gitignore_content=$(cat <<'ENDGITIGNORE'
# Dependencies
node_modules/
venv/
__pycache__/

# Build output
dist/
build/
*.egg-info/

# Loki Mode runtime state (keep .loki/loki.config.json)
.loki/state/
.loki/memory/episodic/
.loki/metrics/
.loki/queue/
.loki/sessions/
.loki/logs/

# Environment
.env
.env.local

# OS
.DS_Store
Thumbs.db
ENDGITIGNORE
)

    # --dry-run mode: show what would be created
    if $dry_run; then
        echo -e "${BOLD}Dry run: loki init${NC}"
        echo ""
        if [[ -n "$project_name" ]]; then
            echo -e "  ${GREEN}CREATE${NC} $target_dir/"
        fi
        echo -e "  ${GREEN}CREATE${NC} $target_dir/prd.md ($(_get_template_label "$template_name") template)"
        echo -e "  ${GREEN}CREATE${NC} $target_dir/.loki/"
        echo -e "  ${GREEN}CREATE${NC} $target_dir/.loki/loki.config.json"
        echo -e "  ${GREEN}CREATE${NC} $target_dir/README.md"
        if ! $no_git; then
            if [[ -n "$project_name" ]] || ! git rev-parse --git-dir &>/dev/null; then
                echo -e "  ${GREEN}CREATE${NC} $target_dir/.gitignore"
                echo -e "  ${GREEN}RUN${NC}    git init"
            fi
        fi
        echo ""
        echo "Run without --dry-run to create these files."
        exit 0
    fi

    # Create project directory if project name given
    if [[ -n "$project_name" ]]; then
        if [[ -d "$target_dir" ]]; then
            echo -e "${RED}Directory already exists: $target_dir${NC}"
            exit 1
        fi
        if ! mkdir -p "$target_dir"; then
            echo -e "${RED}Failed to create directory: $target_dir${NC}"
            exit 1
        fi
    fi

    # Create .loki config directory
    if ! mkdir -p "$target_dir/.loki"; then
        echo -e "${RED}Failed to create .loki directory in: $target_dir${NC}"
        exit 1
    fi

    # Write files
    echo "$prd_content" > "$target_dir/prd.md"
    echo "$config_json" > "$target_dir/.loki/loki.config.json"

    # Only write README if it does not already exist
    local did_write_readme=false
    if [[ ! -f "$target_dir/README.md" ]]; then
        echo "$readme_content" > "$target_dir/README.md"
        did_write_readme=true
    fi

    # Git init (unless --no-git or already in a git repo)
    local did_git_init=false
    if ! $no_git; then
        if [[ -n "$project_name" ]] || ! git rev-parse --git-dir &>/dev/null; then
            echo "$gitignore_content" > "$target_dir/.gitignore"
            (cd "$target_dir" && git init -q)
            did_git_init=true
        fi
    fi

    # Print summary
    echo ""
    echo -e "${GREEN}Project scaffolded:${NC} $target_dir"
    echo ""
    echo "  Files created:"
    echo -e "    prd.md                   ${DIM}$(_get_template_label "$template_name") template${NC}"
    echo -e "    .loki/loki.config.json   ${DIM}project configuration${NC}"
    if $did_write_readme; then
        echo -e "    README.md                ${DIM}project readme${NC}"
    fi
    if $did_git_init; then
        echo -e "    .gitignore               ${DIM}git ignore rules${NC}"
        echo -e "    ${DIM}git repo initialized${NC}"
    fi
    echo ""
    echo "Next steps:"
    if [[ -n "$project_name" ]]; then
        echo -e "  1. ${BOLD}cd $project_name${NC}"
        echo -e "  2. Review and edit: ${BOLD}prd.md${NC}"
        echo -e "  3. Run: ${BOLD}loki start prd.md${NC}"
    else
        echo -e "  1. Review and edit: ${BOLD}prd.md${NC}"
        echo -e "  2. Run: ${BOLD}loki start prd.md${NC}"
    fi

    # Check if an AI provider CLI is available
    local _has_provider=false
    for _pcli in claude codex cline aider; do
        if command -v "$_pcli" &>/dev/null; then
            _has_provider=true
            break
        fi
    done
    if ! $_has_provider; then
        echo ""
        echo -e "${YELLOW}Note: No AI provider CLI detected.${NC}"
        echo "  Install at least one before running 'loki start':"
        echo "    npm install -g @anthropic-ai/claude-code   (recommended)"
        echo "    npm install -g @openai/codex"
        echo ""
        echo "  Then verify your setup: ${BOLD}loki doctor${NC}"
    fi
}

# Dogfooding statistics
cmd_dogfood() {
    # Handle --help
    if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
        echo -e "${BOLD}loki dogfood${NC} - Show self-development statistics"
        echo ""
        echo "Shows what percentage of loki-mode code was written by loki-mode itself."
        echo ""
        echo "Usage: loki dogfood [--json]"
        echo ""
        echo "Options:"
        echo "  --json    Output in JSON format"
        return 0
    fi

    local stats_script="$SKILL_DIR/scripts/dogfood-stats.sh"
    if [ ! -f "$stats_script" ]; then
        echo -e "${RED}Error: dogfood-stats.sh not found${NC}"
        exit 1
    fi

    local json_flag=""
    if [[ "${1:-}" == "--json" ]]; then
        json_flag="--json"
    fi

    bash "$stats_script" $json_flag
}

# List available templates (legacy helper, kept for backward compat)
_list_templates() {
    cmd_init --list
}

# Watchdog: process health monitoring
cmd_watchdog() {
    local subcommand="${1:-status}"
    shift 2>/dev/null || true

    case "$subcommand" in
        status)
            # Show watchdog state
            if [[ "${LOKI_WATCHDOG:-false}" == "true" ]]; then
                echo -e "${GREEN}Watchdog: ENABLED${NC} (interval: ${LOKI_WATCHDOG_INTERVAL:-30}s)"
            else
                echo "Watchdog: DISABLED"
                echo "Enable with: LOKI_WATCHDOG=true loki start"
            fi

            echo ""
            echo -e "${BOLD}Process Health:${NC}"

            # Check dashboard
            local dpid_file="$LOKI_DIR/dashboard/dashboard.pid"
            if [[ -f "$dpid_file" ]]; then
                local dpid
                dpid=$(cat "$dpid_file" 2>/dev/null)
                if [[ -n "$dpid" ]] && kill -0 "$dpid" 2>/dev/null; then
                    echo -e "  Dashboard (PID $dpid): ${GREEN}alive${NC}"
                else
                    echo -e "  Dashboard (PID ${dpid:-?}): ${RED}DEAD${NC}"
                fi
            else
                echo -e "  Dashboard: ${DIM}not running${NC}"
            fi

            # Check session
            local spid_file="$LOKI_DIR/loki.pid"
            if [[ -f "$spid_file" ]]; then
                local spid
                spid=$(cat "$spid_file" 2>/dev/null)
                if [[ -n "$spid" ]] && kill -0 "$spid" 2>/dev/null; then
                    echo -e "  Session  (PID $spid): ${GREEN}alive${NC}"
                else
                    echo -e "  Session  (PID ${spid:-?}): ${RED}DEAD${NC}"
                fi
            else
                echo -e "  Session:  ${DIM}not running${NC}"
            fi

            # Check agents
            local agents_file="$LOKI_DIR/state/agents.json"
            if [[ -f "$agents_file" ]]; then
                local agent_info
                agent_info=$(python3 -c "
import json, os
try:
    agents = json.load(open('$agents_file'))
    alive = dead = other = 0
    for a in agents:
        pid = a.get('pid')
        status = a.get('status', '')
        if status in ('terminated', 'completed', 'failed', 'crashed'):
            other += 1
            continue
        if pid:
            try:
                os.kill(int(pid), 0)
                alive += 1
            except (OSError, ValueError):
                dead += 1
        else:
            other += 1
    print(f'{alive}:{dead}:{other}')
except Exception:
    print('0:0:0')
" 2>/dev/null || echo "0:0:0")
                local a_alive a_dead a_other
                IFS=: read -r a_alive a_dead a_other <<< "$agent_info"
                if [[ "$a_alive" -gt 0 ]] || [[ "$a_dead" -gt 0 ]]; then
                    echo -e "  Agents:   ${GREEN}${a_alive} alive${NC}, ${RED}${a_dead} dead${NC}, ${DIM}${a_other} finished${NC}"
                elif [[ "$a_other" -gt 0 ]]; then
                    echo -e "  Agents:   ${DIM}${a_other} finished${NC}"
                else
                    echo -e "  Agents:   ${DIM}none${NC}"
                fi
            else
                echo -e "  Agents:   ${DIM}none${NC}"
            fi
            ;;
        help)
            echo "Usage: loki watchdog [status|help]"
            echo ""
            echo "Monitor process health for Loki Mode sessions."
            echo ""
            echo "Subcommands:"
            echo "  status    Show health of dashboard, session, and agent processes (default)"
            echo "  help      Show this help"
            echo ""
            echo "Environment:"
            echo "  LOKI_WATCHDOG=true          Enable watchdog monitoring during sessions"
            echo "  LOKI_WATCHDOG_INTERVAL=30   Check interval in seconds (default: 30)"
            ;;
        *)
            echo -e "${RED}Unknown watchdog command: $subcommand${NC}"
            cmd_watchdog help
            return 1
            ;;
    esac
}

# Prompt optimization via dashboard API
cmd_optimize() {
    local sessions=10
    local dry_run=""

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --sessions) if [[ -z "${2:-}" ]]; then echo -e "${RED}Error: --sessions requires a value${NC}"; exit 1; fi; sessions="$2"; shift 2 ;;
            --sessions=*) sessions="${1#*=}"; shift ;;
            --dry-run) dry_run="true"; shift ;;
            --help|-h)
                echo -e "${BOLD}loki optimize${NC} - Optimize prompts based on session history"
                echo ""
                echo "Usage: loki optimize [options]"
                echo ""
                echo "Analyzes recent session failures and proposes prompt improvements."
                echo "Requires the dashboard API to be running (loki serve)."
                echo ""
                echo "Options:"
                echo "  --sessions N   Number of recent sessions to analyze (default: 10)"
                echo "  --dry-run      Show proposed changes without applying"
                echo ""
                echo "Examples:"
                echo "  loki optimize                  # Optimize using last 10 sessions"
                echo "  loki optimize --sessions 20    # Analyze last 20 sessions"
                echo "  loki optimize --dry-run        # Preview changes only"
                exit 0
                ;;
            *)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki optimize --help' for usage."
                exit 1
                ;;
        esac
    done

    local port="${LOKI_DASHBOARD_PORT:-57374}"
    local host="127.0.0.1"
    local dry_run_param="false"
    if [ -n "$dry_run" ]; then
        dry_run_param="true"
    fi
    local url="http://${host}:${port}/api/prompt-optimize?sessions=${sessions}&dry_run=${dry_run_param}"

    echo -e "${BOLD}Analyzing sessions for prompt optimization...${NC}"
    echo -e "${DIM}Sessions: $sessions | Dry run: ${dry_run_param}${NC}"
    echo ""

    local response http_code
    response=$(curl -s -w "\n%{http_code}" -X POST "$url" 2>/dev/null) || true
    http_code=$(echo "$response" | tail -1)
    response=$(echo "$response" | sed '$d')
    if [ -z "$http_code" ] || [ "$http_code" = "000" ]; then
        echo -e "${RED}Error: Could not connect to dashboard API at http://${host}:${port}${NC}"
        echo "Make sure the dashboard is running: loki serve"
        exit 1
    fi
    if [ "$http_code" -ge 400 ] 2>/dev/null; then
        echo -e "${RED}Error: Dashboard API returned HTTP $http_code${NC}"
        [ -n "$response" ] && echo "$response"
        exit 1
    fi

    if ! command -v python3 &>/dev/null; then
        echo "$response" | jq . 2>/dev/null || echo "$response"
    else
        echo "$response" | python3 -c "
import json, sys
data = json.loads(sys.stdin.read())

failures = data.get('failures_analyzed', 0)
changes = data.get('changes', [])
version = data.get('version', None)

print(f'Failures analyzed: {failures}')
print(f'Changes proposed:  {len(changes)}')
print('---')

for i, change in enumerate(changes, 1):
    prompt = change.get('prompt', '?')
    rationale = change.get('rationale', 'No rationale provided')
    old = change.get('old_value', '')
    new = change.get('new_value', '')
    print(f'')
    print(f'  {i}. {prompt}')
    print(f'     Rationale: {rationale}')
    if old:
        print(f'     Old: {old[:80]}...' if len(old) > 80 else f'     Old: {old}')
    if new:
        print(f'     New: {new[:80]}...' if len(new) > 80 else f'     New: {new}')

if version is not None:
    print(f'')
    print(f'Prompts optimized to version {version}')
elif not changes:
    print(f'')
    print('No optimization changes proposed.')
print()
" 2>/dev/null || echo "$response"
    fi
}

# ============================================================================
# Migration Command
# ============================================================================

cmd_migrate_help() {
    echo -e "${BOLD}loki migrate${NC} - Codebase migration with autonomous agents"
    echo ""
    echo "Usage: loki migrate <path-to-codebase> [options]"
    echo ""
    echo "Migrates a codebase to a new language, framework, or architecture using"
    echo "multi-agent orchestration with guardrails and incremental verification."
    echo ""
    echo "Options:"
    echo "  --target <lang|framework|arch>   Migration target (e.g. typescript, react, microservices)"
    echo "  --plan-only                      Generate migration plan without executing"
    echo "  --show-plan                      Display current migration plan"
    echo "  --phase <phase>                  Run specific phase:"
    echo "                                     understand  - Analyze codebase structure"
    echo "                                     guardrail   - Set up tests and safety nets"
    echo "                                     migrate     - Execute migration transforms"
    echo "                                     verify      - Run verification suite"
    echo "  --parallel <N>                   Max parallel agents (default: 4, max: 10)"
    echo "  --compliance <preset>            Compliance preset: healthcare, fintech, government"
    echo "  --dry-run                        Show what would change without executing"
    echo "  --resume                         Resume from last checkpoint"
    echo "  --multi-repo <glob>              Multiple repository paths (glob pattern)"
    echo "  --export-report                  Export migration report to file"
    echo "  --no-dashboard                   Disable web dashboard during migration"
    echo "  --no-docs                        Skip migration_docs/ generation"
    echo "  --list                           List all migrations"
    echo "  --status [migration-id]          Show migration status"
    echo ""
    echo "Examples:"
    echo "  loki migrate ./my-app --target typescript"
    echo "  loki migrate ./my-app --target react --plan-only"
    echo "  loki migrate ./my-app --target microservices --compliance fintech"
    echo "  loki migrate --list"
    echo "  loki migrate --status mig-20260223-001"
    echo "  loki migrate ./my-app --target typescript --resume"
    echo "  loki migrate ./my-app --target typescript --phase verify"
    echo "  loki migrate --multi-repo './services/*' --target typescript"
}

cmd_migrate_list() {
    PYTHONPATH="${SKILL_DIR:-.}" python3 -c "
import sys
sys.path.insert(0, '.')
from dashboard.migration_engine import list_migrations
migrations = list_migrations()
if not migrations:
    print('No migrations found.')
    print('Start a migration with: loki migrate <path> --target <target>')
else:
    print(f'\033[1mMigrations\033[0m ({len(migrations)} total)')
    print('---')
    for m in migrations:
        print(f\"  {m['id']}\")
        print(f\"    Target:   {m.get('target', '?')}\")
        print(f\"    Source:   {m.get('source_path', '?')}\")
        print(f\"    Status:   {m.get('status', '?')}\")
        print(f\"    Created:  {m.get('created_at', '?')[:10] if m.get('created_at') else '?'}\")
        print()
" || {
        echo -e "${RED}Error listing migrations${NC}"
        return 1
    }
}

cmd_migrate_status() {
    local migration_id="${1:-}"
    local migrations_dir="${HOME}/.loki/migrations"

    if [ -z "$migration_id" ]; then
        # Show status of most recent migration
        local latest
        latest=$(find "$migrations_dir" -name "manifest.json" -maxdepth 2 2>/dev/null | sort | tail -1)
        if [ -z "$latest" ]; then
            echo -e "${YELLOW}No migrations found.${NC}"
            return 0
        fi
        migration_id=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1]))['id'])" "$latest" 2>/dev/null || echo "")
    fi

    local manifest="${migrations_dir}/${migration_id}/manifest.json"
    if [ ! -f "$manifest" ]; then
        echo -e "${RED}Error: Migration not found: ${migration_id}${NC}"
        echo "Run 'loki migrate --list' to see available migrations."
        return 1
    fi

    PYTHONPATH="${SKILL_DIR:-.}" python3 -c "
import json, sys
sys.path.insert(0, '.')
from dashboard.migration_engine import MigrationPipeline
try:
    pipeline = MigrationPipeline.load(sys.argv[1])
    progress = pipeline.get_progress()
    print(json.dumps(progress, indent=2))
except Exception as e:
    print(f'\033[0;31mError reading migration: {e}\033[0m', file=sys.stderr)
    sys.exit(1)
" "$migration_id" || {
        echo -e "${RED}Error reading migration manifest${NC}"
        return 1
    }
}

cmd_migrate_show_plan() {
    local codebase_path="${1:-$(pwd)}"
    local migrations_dir="${HOME}/.loki/migrations"

    # Find plan for this codebase
    local plan_file=""
    if [ -f "${codebase_path}/.loki/migration-plan.json" ]; then
        plan_file="${codebase_path}/.loki/migration-plan.json"
    else
        # Search in global migrations dir
        local latest
        latest=$(find "$migrations_dir" -name "migration-plan.json" -maxdepth 2 2>/dev/null | sort | tail -1)
        if [ -n "$latest" ]; then
            plan_file="$latest"
        fi
    fi

    if [ -z "$plan_file" ] || [ ! -f "$plan_file" ]; then
        echo -e "${YELLOW}No migration plan found.${NC}"
        echo "Generate one with: loki migrate <path> --target <target> --plan-only"
        return 0
    fi

    echo -e "${BOLD}Migration Plan${NC}"
    echo -e "${DIM}Source: ${plan_file}${NC}"
    echo "---"

    # Try engine-based summary first, fall back to raw JSON display
    local migration_id_for_plan=""
    migration_id_for_plan=$(python3 -c "
import json, sys
with open(sys.argv[1]) as f:
    data = json.load(f)
# Try to extract migration ID from path or data
print(data.get('id', ''))
" "$plan_file" 2>/dev/null || echo "")

    if [ -n "$migration_id_for_plan" ]; then
        PYTHONPATH="${SKILL_DIR:-.}" python3 -c "
import sys
sys.path.insert(0, '.')
from dashboard.migration_engine import MigrationPipeline
pipeline = MigrationPipeline.load(sys.argv[1])
print(pipeline.generate_plan_summary())
" "$migration_id_for_plan" 2>/dev/null || {
            # Fall back to basic display if engine fails
            python3 -c "
import json, sys
with open(sys.argv[1]) as f:
    plan = json.load(f)
print(json.dumps(plan, indent=2))
" "$plan_file" || echo -e "${RED}Error reading migration plan${NC}"
        }
    else
        python3 -c "
import json, sys
with open(sys.argv[1]) as f:
    plan = json.load(f)
print(json.dumps(plan, indent=2))
" "$plan_file" || {
            echo -e "${RED}Error reading migration plan${NC}"
            return 1
        }
    fi
}

cmd_migrate_start() {
    local codebase_path="$1"
    local target="$2"
    local plan_only="$3"
    local phase="$4"
    local parallel="$5"
    local compliance="$6"
    local dry_run="$7"
    local resume="$8"
    local multi_repo="$9"
    local export_report="${10}"
    local no_dashboard="${11:-false}"
    local no_docs="${12:-false}"

    local migrations_dir="${HOME}/.loki/migrations"
    local migration_id=""

    # Check python3 availability
    if ! command -v python3 &>/dev/null; then
        echo -e "${RED}Error: python3 is required for migration${NC}"
        return 1
    fi

    # Check for path traversal BEFORE canonicalization
    case "$codebase_path" in
        ../*|*/../*|*/..|--)
            echo -e "${RED}Error: Path traversal not allowed in codebase path${NC}"
            return 1
            ;;
    esac

    # Validate codebase path
    if [ ! -d "$codebase_path" ]; then
        echo -e "${RED}Error: Codebase path does not exist: ${codebase_path}${NC}"
        return 1
    fi

    # Then canonicalize
    codebase_path=$(cd "$codebase_path" && pwd) || {
        echo -e "${RED}Error: Cannot access $codebase_path${NC}"
        return 1
    }

    # Validate target
    if [ -z "$target" ]; then
        echo -e "${RED}Error: --target is required${NC}"
        echo "Specify the migration target (e.g. --target typescript, --target react, --target microservices)"
        echo ""
        echo "Run 'loki migrate --help' for usage."
        return 1
    fi

    # Validate compliance preset
    if [ -n "$compliance" ]; then
        case "$compliance" in
            healthcare|fintech|government) ;;
            *)
                echo -e "${RED}Error: Invalid compliance preset: ${compliance}${NC}"
                echo "Valid presets: healthcare, fintech, government"
                return 1
                ;;
        esac
    fi

    # Validate parallel count
    if [ -n "$parallel" ]; then
        if ! [[ "$parallel" =~ ^[0-9]+$ ]]; then
            echo -e "${RED}Error: --parallel must be a number${NC}"
            return 1
        fi
        if [ "$parallel" -lt 1 ] || [ "$parallel" -gt 10 ]; then
            echo -e "${RED}Error: --parallel must be between 1 and 10${NC}"
            return 1
        fi
    else
        parallel=4
    fi

    # Handle resume
    if [ "$resume" = "true" ]; then
        local latest_manifest
        latest_manifest=$(find "$migrations_dir" -name "manifest.json" -maxdepth 2 -exec python3 -c "
import json, sys
codebase = sys.argv[1]
manifests = []
for path in sys.argv[2:]:
    try:
        with open(path) as f:
            m = json.load(f)
        if m.get('source_path') == codebase and m.get('status') in ('paused', 'running', 'failed'):
            manifests.append(path)
    except: pass
if manifests:
    print(manifests[-1])
" "$codebase_path" {} + 2>/dev/null || echo "")

        if [ -z "$latest_manifest" ]; then
            echo -e "${RED}Error: No resumable migration found for ${codebase_path}${NC}"
            echo "Start a new migration with: loki migrate ${codebase_path} --target ${target}"
            return 1
        fi

        migration_id=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1]))['id'])" "$latest_manifest" 2>/dev/null)
        echo -e "${GREEN}Resuming migration: ${migration_id}${NC}"
        echo -e "${DIM}Loading checkpoint...${NC}"
    fi

    # Handle multi-repo
    if [ -n "$multi_repo" ]; then
        local repo_count=0
        local repos=()
        local _repo
        # Use an array to safely handle glob expansion with spaces in paths
        local _expanded_repos=()
        # Save and restore globbing options
        local _old_nullglob=$(shopt -p nullglob 2>/dev/null || true)
        shopt -s nullglob
        local _expanded_repos=($multi_repo)
        eval "$_old_nullglob" 2>/dev/null || true
        for _repo in "${_expanded_repos[@]}"; do
            if [ -d "$_repo" ]; then
                repos+=("$_repo")
                repo_count=$((repo_count + 1))
            fi
        done
        if [ "$repo_count" -eq 0 ]; then
            echo -e "${RED}Error: No repositories found matching: ${multi_repo}${NC}"
            return 1
        fi
        echo -e "${BOLD}Multi-repo migration${NC} ($repo_count repositories)"
        echo "---"
        for repo in "${repos[@]}"; do
            echo "  - $repo"
        done
        echo ""
    fi

    local migration_dir
    if [ -n "$migration_id" ]; then
        # Resume: load existing migration directory
        migration_dir="${migrations_dir}/${migration_id}"
        if [ ! -d "$migration_dir" ]; then
            echo -e "${RED}Error: Migration directory not found: ${migration_dir}${NC}"
            return 1
        fi
    else
        # Create new migration via engine
        local create_result
        create_result=$(PYTHONPATH="${SKILL_DIR:-.}" python3 -c "
import json, sys
sys.path.insert(0, '.')
from dashboard.migration_engine import MigrationPipeline
pipeline = MigrationPipeline(sys.argv[1], sys.argv[2])
manifest = pipeline.create_manifest()
print(json.dumps({'id': manifest.id, 'dir': str(pipeline.migration_dir)}))
" "$codebase_path" "$target" 2>&1) || {
            echo -e "${RED}Error: Failed to create migration${NC}"
            echo "$create_result"
            return 1
        }
        migration_id=$(echo "$create_result" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
        migration_dir=$(echo "$create_result" | python3 -c "import json,sys; print(json.load(sys.stdin)['dir'])")
    fi

    # Security scan (first step of understand phase)
    echo -e "${BOLD}[Security] Scanning for secrets...${NC}"
    local secrets_found=0
    if command -v grep &>/dev/null; then
        local secret_patterns="(password|secret|api_key|apikey|token|private_key|AWS_SECRET|DB_PASS)\\s*[=:]\\s*['\"][^'\"]+['\"]"
        secrets_found=$(grep -rEli "$secret_patterns" "$codebase_path" --include='*.js' --include='*.ts' --include='*.py' --include='*.rb' --include='*.go' --include='*.java' --include='*.env' --include='*.yml' --include='*.yaml' --include='*.json' --include='*.toml' --include='*.cfg' --include='*.ini' 2>/dev/null || true)
        if [ -n "$secrets_found" ]; then
            secrets_found=$(echo "$secrets_found" | wc -l | tr -d ' ')
        else
            secrets_found=0
        fi
    fi

    if [ "$secrets_found" -gt 0 ]; then
        echo -e "${RED}WARNING: Found $secrets_found file(s) potentially containing secrets${NC}"
        echo -e "${YELLOW}Review these files before migration to avoid leaking credentials.${NC}"
        echo ""
    else
        echo -e "${GREEN}No secrets detected.${NC}"
        echo ""
    fi

    # Handle dry-run
    if [ "$dry_run" = "true" ]; then
        echo -e "${BOLD}[Dry Run] Migration preview${NC}"
        echo "---"
        echo -e "  Codebase:    ${codebase_path}"
        echo -e "  Target:      ${target}"
        echo -e "  Parallel:    ${parallel} agents"
        [ -n "$compliance" ] && echo -e "  Compliance:  ${compliance}"
        [ -n "$multi_repo" ] && echo -e "  Multi-repo:  ${multi_repo}"
        echo ""
        echo -e "${DIM}Phases that would execute:${NC}"
        echo "  1. understand  - Analyze codebase structure and dependencies"
        echo "  2. guardrail   - Set up test safety nets and rollback points"
        echo "  3. migrate     - Execute migration transforms"
        echo "  4. verify      - Run verification suite and regression tests"
        echo ""
        echo -e "${YELLOW}No changes were made (dry-run mode).${NC}"
        # Clean up migration dir created by engine
        rm -rf "$migration_dir" 2>/dev/null || true
        return 0
    fi

    emit_event migration cli start "id=$migration_id" "target=$target" "source=$codebase_path" 2>/dev/null || true

    echo -e "${BOLD}Migration: ${migration_id}${NC}"
    echo "---"
    echo -e "  Codebase:    ${codebase_path}"
    echo -e "  Target:      ${target}"
    echo -e "  Parallel:    ${parallel} agents"
    [ -n "$compliance" ] && echo -e "  Compliance:  ${compliance}"
    echo ""

    # Dashboard cleanup trap for error paths
    local migrate_dashboard_pid=""
    _migrate_cleanup_dashboard() {
        if [ -n "$migrate_dashboard_pid" ]; then
            kill "$migrate_dashboard_pid" 2>/dev/null || true
            wait "$migrate_dashboard_pid" 2>/dev/null || true
            rm -f "${codebase_path}/.loki/dashboard/dashboard.pid" 2>/dev/null || true
        fi
    }
    trap '_migrate_cleanup_dashboard' RETURN

    # Start dashboard for migration monitoring (unless --no-dashboard)
    if [ "$no_dashboard" != "true" ]; then
        local dashboard_port="${LOKI_DASHBOARD_PORT:-57374}"
        local skill_dir="${SKILL_DIR:-.}"
        local dashboard_venv="$HOME/.loki/dashboard-venv"
        local python_cmd="python3"
        local url_scheme="http"

        # Use venv python if available
        if [ -x "${dashboard_venv}/bin/python3" ]; then
            python_cmd="${dashboard_venv}/bin/python3"
        fi

        # Check if dashboard deps are available
        if "$python_cmd" -c "import fastapi" 2>/dev/null; then
            # Find available port (increment until a free port is found)
            local port_attempts=0
            while lsof -i :"$dashboard_port" &>/dev/null && [ $port_attempts -lt 10 ]; do
                dashboard_port=$((dashboard_port + 1))
                port_attempts=$((port_attempts + 1))
            done

            if [ $port_attempts -lt 10 ]; then
                # Determine TLS
                if [ -n "${LOKI_TLS_CERT:-}" ] && [ -n "${LOKI_TLS_KEY:-}" ]; then
                    url_scheme="https"
                fi

                if ! mkdir -p "${codebase_path}/.loki/dashboard/logs" 2>/dev/null; then
                    echo -e "  ${YELLOW}Dashboard skipped (cannot create log directory)${NC}"
                else
                    local log_file="${codebase_path}/.loki/dashboard/logs/dashboard.log"

                    LOKI_DASHBOARD_PORT="$dashboard_port" \
                    LOKI_DASHBOARD_HOST="127.0.0.1" \
                    LOKI_PROJECT_PATH="$codebase_path" \
                    LOKI_SKILL_DIR="$skill_dir" \
                    LOKI_TLS_CERT="${LOKI_TLS_CERT:-}" \
                    LOKI_TLS_KEY="${LOKI_TLS_KEY:-}" \
                    PYTHONPATH="$skill_dir" \
                        nohup "$python_cmd" -m dashboard.server > "$log_file" 2>&1 &
                    migrate_dashboard_pid=$!

                    echo "$migrate_dashboard_pid" > "${codebase_path}/.loki/dashboard/dashboard.pid"

                    sleep 2
                    if kill -0 "$migrate_dashboard_pid" 2>/dev/null; then
                        echo -e "  Dashboard:   ${CYAN}${url_scheme}://127.0.0.1:${dashboard_port}/${NC}"
                        if [[ "$OSTYPE" == "darwin"* ]]; then
                            open "${url_scheme}://127.0.0.1:${dashboard_port}/" 2>/dev/null || true
                        fi
                        echo ""
                    else
                        echo -e "  ${YELLOW}Dashboard failed to start (check logs)${NC}"
                        migrate_dashboard_pid=""
                        echo ""
                    fi
                fi
            fi
        fi
    fi

    # Determine which phases to run
    local phases_to_run=()
    if [ -n "$phase" ]; then
        case "$phase" in
            understand|guardrail|migrate|verify)
                phases_to_run=("$phase")
                ;;
            *)
                echo -e "${RED}Error: Invalid phase: ${phase}${NC}"
                echo "Valid phases: understand, guardrail, migrate, verify"
                return 1
                ;;
        esac
    elif [ "$plan_only" = "true" ]; then
        phases_to_run=("understand" "guardrail")
    else
        phases_to_run=("understand" "guardrail" "migrate" "verify")
    fi

    # Validate prerequisite artifacts exist before running phases (#81)
    for p in "${phases_to_run[@]}"; do
        case "$p" in
            guardrail)
                if [ ! -f "${migration_dir}/docs/analysis.md" ] || [ ! -f "${migration_dir}/seams.json" ]; then
                    echo -e "${RED}Error: Phase 'guardrail' requires artifacts from 'understand' phase${NC}"
                    echo -e "${DIM}  Missing: analysis.md or seams.json in ${migration_dir}${NC}"
                    echo "Run: loki migrate ${codebase_path} --target ${target} --phase understand"
                    return 1
                fi
                ;;
            migrate)
                if [ ! -f "${migration_dir}/features.json" ]; then
                    echo -e "${RED}Error: Phase 'migrate' requires artifacts from 'guardrail' phase${NC}"
                    echo -e "${DIM}  Missing: features.json in ${migration_dir}${NC}"
                    echo "Run: loki migrate ${codebase_path} --target ${target} --phase guardrail"
                    return 1
                fi
                ;;
            verify)
                if [ ! -f "${migration_dir}/migration-plan.json" ]; then
                    echo -e "${RED}Error: Phase 'verify' requires migration-plan.json from 'migrate' phase${NC}"
                    echo -e "${DIM}  Missing: migration-plan.json in ${migration_dir}${NC}"
                    echo "Run: loki migrate ${codebase_path} --target ${target} --phase migrate"
                    return 1
                fi
                ;;
        esac
    done

    # Execute phases
    for p in "${phases_to_run[@]}"; do
        echo -e "${CYAN}[Phase: ${p}]${NC} Starting..."

        # Start phase via engine
        PYTHONPATH="${SKILL_DIR:-.}" python3 -c "
import sys
sys.path.insert(0, '.')
from dashboard.migration_engine import MigrationPipeline
pipeline = MigrationPipeline.load(sys.argv[1])
pipeline.start_phase(sys.argv[2])
" "$migration_id" "$p" || {
            echo -e "${RED}Error: Failed to start phase $p${NC}"
            return 1
        }

        # Phase-specific logic -- invoke Claude to do real work
        local phase_prompt=""
        case "$p" in
            understand)
                echo -e "  ${DIM}Analyzing codebase structure...${NC}"
                phase_prompt="You are performing the UNDERSTAND phase of a codebase migration.
Target: ${target}
Codebase: ${codebase_path}

Tasks:
1. Analyze the full codebase structure (languages, frameworks, dependencies, architecture)
2. Create the docs directory: mkdir -p ${migration_dir}/docs
3. Write analysis documentation to ${migration_dir}/docs/analysis.md
4. Identify migration seams (logical boundaries for incremental migration) and write them to ${migration_dir}/seams.json as a JSON array of objects with fields: id (string, e.g. 'seam-01'), name (string), description (string), type (string: 'module'/'api'/'config'/'adapter'), files (array of file paths), dependencies (array of seam ids), priority (string: 'high'/'medium'/'low')

You MUST create both files. The migration cannot proceed without them.
Write the analysis doc first, then the seams.json."
                ;;
            guardrail)
                echo -e "  ${DIM}Setting up safety nets and characterization tests...${NC}"
                phase_prompt="You are performing the GUARDRAIL phase of a codebase migration.
Target: ${target}
Codebase: ${codebase_path}
Migration dir: ${migration_dir}

Read ${migration_dir}/docs/analysis.md and ${migration_dir}/seams.json for context.

Tasks:
1. Identify existing tests and create characterization tests that capture current behavior
2. Write ${migration_dir}/features.json as a JSON array of objects with fields: id (string, e.g. 'F01'), category (string, e.g. 'core'), description (string), characterization_test (string, shell command to verify), passes (boolean, set to true for existing passing behavior), risk (string: 'low'/'medium'/'high')
3. Create a git checkpoint: cd ${codebase_path} && git stash || true

All features in features.json must have passes: true for the gate to pass."
                ;;
            migrate)
                echo -e "  ${DIM}Executing migration transforms...${NC}"
                phase_prompt="You are performing the MIGRATE phase of a codebase migration.
Target: ${target}
Codebase: ${codebase_path}
Migration dir: ${migration_dir}

Read ${migration_dir}/docs/analysis.md, ${migration_dir}/seams.json, and ${migration_dir}/features.json for context.

Tasks:
1. Create a migration plan and write it to ${migration_dir}/migration-plan.json as a JSON object with fields: version (integer, default 1), strategy (string: 'incremental' or 'big_bang'), steps (array of objects with: id (string), description (string), type (string: 'refactor'/'rewrite'/'config'/'test'), status (string, set to 'completed' after you do the step))
2. Execute the actual code migration transforms in ${codebase_path} -- convert code from the current framework/language to ${target}
3. Update each step status to 'completed' as you finish it
4. Work incrementally seam by seam from ${migration_dir}/seams.json

All steps must have status: completed for the gate to pass."
                ;;
            verify)
                echo -e "  ${DIM}Running verification suite...${NC}"
                phase_prompt="You are performing the VERIFY phase of a codebase migration.
Target: ${target}
Codebase: ${codebase_path}
Migration dir: ${migration_dir}

Tasks:
1. Run syntax validation on all migrated files
2. Run any test commands from ${migration_dir}/features.json to verify behavior is preserved
3. Check for common migration issues (missing imports, broken references, etc.)
4. Write a verification report to ${migration_dir}/docs/verification-report.md
5. Summarize what was migrated, what works, and any remaining issues"
                ;;
        esac

        # Invoke Claude to execute the phase
        local phase_exit=0
        if [ -n "$phase_prompt" ]; then
            local provider_name="${LOKI_PROVIDER:-claude}"
            case "$provider_name" in
                claude)
                    { (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$phase_prompt" --output-format stream-json --verbose 2>&1) | \
                        while IFS= read -r line; do
                            # Extract text from stream-json
                            if echo "$line" | python3 -c "
import sys, json
try:
    d = json.loads(sys.stdin.read())
    if d.get('type') == 'assistant':
        for item in d.get('message', {}).get('content', []):
            if item.get('type') == 'text':
                print(item.get('text', ''), end='')
except Exception: pass
" 2>/dev/null; then
                                true
                            fi
                        done; } && phase_exit=0 || phase_exit=$?
                    ;;
                codex)
                    (cd "$codebase_path" && codex exec --full-auto "$phase_prompt" 2>&1) || phase_exit=$?
                    ;;
                cline)
                    (cd "$codebase_path" && cline -y "$phase_prompt" 2>&1) || phase_exit=$?
                    ;;
                aider)
                    local aider_model="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
                    local aider_flags="${LOKI_AIDER_FLAGS:-}"
                    # shellcheck disable=SC2086
                    (cd "$codebase_path" && aider --message "$phase_prompt" --yes-always --no-auto-commits --model "$aider_model" $aider_flags 2>&1) || phase_exit=$?
                    ;;
                *)
                    echo -e "${RED}Error: Unknown provider '${provider_name}'. Supported: claude, codex, cline, aider${NC}"
                    phase_exit=1
                    ;;
            esac
        fi

        # Check provider exit code before proceeding
        if [ "$phase_exit" -ne 0 ]; then
            echo -e "${RED}Error: Provider exited with code $phase_exit during phase $p${NC}"
            # Don't advance phase -- gate check will catch missing artifacts
        fi

        # Verify phase gate artifacts exist before advancing
        local gate_ok=true
        case "$p" in
            understand)
                if [ ! -f "${migration_dir}/docs/analysis.md" ] || [ ! -f "${migration_dir}/seams.json" ]; then
                    echo -e "${RED}Error: Phase 'understand' did not produce required artifacts${NC}"
                    echo -e "${DIM}  Expected: docs/analysis.md, seams.json${NC}"
                    gate_ok=false
                fi
                ;;
            guardrail)
                if [ ! -f "${migration_dir}/features.json" ]; then
                    echo -e "${RED}Error: Phase 'guardrail' did not produce required artifacts${NC}"
                    echo -e "${DIM}  Expected: features.json${NC}"
                    gate_ok=false
                fi
                ;;
            migrate)
                if [ ! -f "${migration_dir}/migration-plan.json" ]; then
                    echo -e "${RED}Error: Phase 'migrate' did not produce required artifacts${NC}"
                    echo -e "${DIM}  Expected: migration-plan.json${NC}"
                    gate_ok=false
                fi
                ;;
        esac

        if [ "$gate_ok" != "true" ]; then
            echo -e "${YELLOW}Phase '$p' artifacts missing. Migration halted.${NC}"
            echo -e "${DIM}Re-run with: loki migrate <path> --target ${target} --phase ${p} --resume${NC}"
            return 1
        fi

        # Complete phase via engine
        PYTHONPATH="${SKILL_DIR:-.}" python3 -c "
import sys
sys.path.insert(0, '.')
from dashboard.migration_engine import MigrationPipeline
pipeline = MigrationPipeline.load(sys.argv[1])
pipeline.advance_phase(sys.argv[2])
" "$migration_id" "$p" || {
            echo -e "${RED}Error: Failed to complete phase $p${NC}"
            return 1
        }

        echo -e "${GREEN}  [Phase: ${p}] Complete${NC}"
        echo ""
    done

    # Generate migration_docs/ in the codebase (unless --no-docs or --plan-only)
    if [ "$plan_only" != "true" ] && [ "$no_docs" != "true" ]; then
        echo -e "${CYAN}[Phase: document]${NC} Generating migration documentation..."

        local doc_prompt="You are generating comprehensive migration documentation for a completed codebase migration.

Target: ${target}
Codebase: ${codebase_path}
Migration dir: ${migration_dir}

Read the following files for context:
- ${migration_dir}/docs/analysis.md (original codebase analysis)
- ${migration_dir}/docs/verification-report.md (verification results)
- ${migration_dir}/features.json (feature tracking)
- ${migration_dir}/seams.json (migration boundaries)
- ${migration_dir}/migration-plan.json (migration steps)

Create a directory called migration_docs/ in the codebase root (${codebase_path}/migration_docs/) with the following files:

1. **README.md** - Executive summary of the migration:
   - What the codebase was before (source language/framework/architecture)
   - What it is now (target: ${target})
   - High-level summary of changes made
   - Link to other docs in migration_docs/

2. **USAGE.md** - How to use the migrated codebase:
   - Getting started / quick start
   - Key commands and workflows
   - Configuration options
   - Environment variables

3. **INSTALLATION.md** - Installation and setup:
   - Prerequisites and dependencies
   - Step-by-step installation
   - Build instructions
   - Development environment setup

4. **TESTING.md** - Testing the migrated codebase:
   - How to run tests
   - Test coverage status
   - Known test gaps
   - How to add new tests

5. **WHAT-CHANGED.md** - Detailed migration changelog:
   - Files added, modified, and removed
   - Architecture changes
   - API changes (if any)
   - Configuration changes
   - Dependency changes

6. **WHAT-WORKS.md** - Current status:
   - Features confirmed working (from verification)
   - Features that need manual testing
   - Known limitations

7. **WHAT-PENDING.md** - Remaining work:
   - Items flagged during verification
   - Manual steps required
   - Recommended follow-up tasks
   - Security items to address

8. **DEPLOYMENT.md** - Deployment guide (if applicable):
   - How to deploy the migrated application
   - CI/CD considerations
   - Infrastructure changes needed
   - Rollback instructions

IMPORTANT RULES:
- Only document what is TRUE and VERIFIED - never fabricate features or capabilities
- Reference actual files and test results from the migration artifacts
- If something was not tested or verified, say so explicitly
- Mark uncertain items with 'NEEDS VERIFICATION' tags
- Do NOT use emojis anywhere in the documentation"

        local doc_exit=0
        local provider_name="${LOKI_PROVIDER:-claude}"
        case "$provider_name" in
            claude)
                { (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$doc_prompt" --output-format stream-json --verbose 2>&1) | \
                    while IFS= read -r line; do
                        if echo "$line" | python3 -c "
import sys, json
try:
    d = json.loads(sys.stdin.read())
    if d.get('type') == 'assistant':
        for item in d.get('message', {}).get('content', []):
            if item.get('type') == 'text':
                print(item.get('text', ''), end='')
except Exception: pass
" 2>/dev/null; then
                            true
                        fi
                    done; } && doc_exit=0 || doc_exit=$?
                ;;
            codex)
                (cd "$codebase_path" && codex exec --full-auto "$doc_prompt" 2>&1) || doc_exit=$?
                ;;
            cline)
                (cd "$codebase_path" && cline -y "$doc_prompt" 2>&1) || doc_exit=$?
                ;;
            aider)
                local aider_model="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
                local aider_flags="${LOKI_AIDER_FLAGS:-}"
                # shellcheck disable=SC2086
                (cd "$codebase_path" && aider --message "$doc_prompt" --yes-always --no-auto-commits --model "$aider_model" $aider_flags 2>&1) || doc_exit=$?
                ;;
        esac

        if [ "$doc_exit" -eq 0 ] && [ -d "${codebase_path}/migration_docs" ]; then
            local doc_count
            doc_count=$(find "${codebase_path}/migration_docs" -name "*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
            echo -e "${GREEN}  [Phase: document] Complete (${doc_count} docs generated)${NC}"
        else
            echo -e "${YELLOW}  [Phase: document] Warning: Documentation may be incomplete${NC}"
        fi
        echo ""
    fi

    # Plan-only: generate plan file and stop
    if [ "$plan_only" = "true" ]; then
        echo -e "${BOLD}Migration plan generated.${NC}"
        echo -e "View with: loki migrate --show-plan"

        python3 -c "
import json, sys
from datetime import datetime
plan = {
    'target': sys.argv[1],
    'source_path': sys.argv[2],
    'estimated_hours': '?',
    'risk_level': 'medium',
    'steps': [],
    'dependencies': [],
    'breaking_changes': [],
    'generated_at': datetime.now().isoformat()
}
with open(sys.argv[3], 'w') as f:
    json.dump(plan, f, indent=2)
" "$target" "$codebase_path" "${migration_dir}/migration-plan.json" || {
            echo -e "${RED}Error: Failed to create migration plan${NC}"
            return 1
        }

        # Update status to paused
        python3 -c "
import json, sys
from datetime import datetime
manifest_path = sys.argv[1]
with open(manifest_path) as f:
    m = json.load(f)
m['status'] = 'paused'
m['updated_at'] = datetime.now().isoformat()
with open(manifest_path, 'w') as f:
    json.dump(m, f, indent=2)
" "${migration_dir}/manifest.json" || {
            echo -e "${RED}Error: Failed to update migration state${NC}"
            return 1
        }
        return 0
    fi

    # Mark migration complete
    python3 -c "
import json, sys
from datetime import datetime
manifest_path = sys.argv[1]
with open(manifest_path) as f:
    m = json.load(f)
m['status'] = 'completed'
m['progress_pct'] = 100
m['updated_at'] = datetime.now().isoformat()
with open(manifest_path, 'w') as f:
    json.dump(m, f, indent=2)
" "${migration_dir}/manifest.json" || {
        echo -e "${RED}Error: Failed to update migration state${NC}"
        return 1
    }

    emit_event migration cli complete "id=$migration_id" "target=$target" 2>/dev/null || true

    # Export report if requested
    if [ "$export_report" = "true" ]; then
        local report_file="${codebase_path}/migration-report-${migration_id}.json"
        cp "${migration_dir}/manifest.json" "$report_file" 2>/dev/null
        echo -e "${GREEN}Report exported: ${report_file}${NC}"
    fi

    echo -e "${GREEN}${BOLD}Migration complete: ${migration_id}${NC}"
    echo -e "View details: loki migrate --status ${migration_id}"
}

#===============================================================================
# loki heal - Legacy System Healing (v6.67.0)
# Inspired by Amazon AGI Lab's "How Agentic AI Helps Heal Systems We Can't Replace"
# Modernize legacy codebases incrementally without breaking existing behavior.
#===============================================================================

cmd_heal_help() {
    echo -e "${BOLD}loki heal${NC} - Legacy system healing (v6.67.0)"
    echo ""
    echo "Heal legacy codebases by understanding their real behaviors -- the quirks,"
    echo "delays, error states, and invisible dependencies -- then modernizing"
    echo "incrementally while preserving institutional logic."
    echo ""
    echo "Usage: loki heal <path-to-codebase> [options]"
    echo ""
    echo "Phases:"
    echo "  archaeology    Extract knowledge, map dependencies, catalog friction"
    echo "  stabilize      Add observability and tests without changing behavior"
    echo "  isolate        Create adapter boundaries between components"
    echo "  modernize      Replace components one at a time behind adapters"
    echo "  validate       Verify behavioral equivalence with baseline"
    echo ""
    echo "Options:"
    echo "  --phase PHASE        Start from specific phase (default: archaeology)"
    echo "  --resume             Resume healing from last checkpoint"
    echo "  --status             Show healing progress"
    echo "  --report             Generate healing report"
    echo "  --strict             Block ALL behavioral changes without approval"
    echo "  --archaeology-only   Extract knowledge only, don't modify code"
    echo "  --friction-map       Show friction map for target codebase"
    echo "  --compliance PRESET  Compliance mode (healthcare|fintech|government)"
    echo "  --provider NAME      AI provider (default: claude)"
    echo "  --parallel N         Parallel healing agents (default: 1)"
    echo "  --no-dashboard       Disable web dashboard"
    echo "  --dry-run            Show healing plan without executing"
    echo ""
    echo "Environment Variables:"
    echo "  LOKI_HEAL_MODE              Enable healing mode (auto-set by 'loki heal')"
    echo "  LOKI_HEAL_PHASE             Override starting phase"
    echo "  LOKI_HEAL_PRESERVE_FRICTION Warn before removing friction points (default: true)"
    echo "  LOKI_HEAL_STRICT            Block all behavioral changes (default: false)"
    echo ""
    echo "Examples:"
    echo "  loki heal ./legacy-app                        # Full healing pipeline"
    echo "  loki heal ./legacy-app --phase archaeology     # Knowledge extraction only"
    echo "  loki heal ./legacy-app --archaeology-only      # Extract without modifying"
    echo "  loki heal ./legacy-app --resume                # Resume from checkpoint"
    echo "  loki heal ./legacy-app --strict                # Strict behavioral preservation"
    echo "  loki heal --status                             # Show healing progress"
    echo "  loki heal --friction-map ./legacy-app          # View friction map"
}

cmd_heal() {
    local codebase_path=""
    local phase="${LOKI_HEAL_PHASE:-archaeology}"
    local do_resume="false"
    local do_status="false"
    local do_report="false"
    local do_friction_map="false"
    local archaeology_only="false"
    local strict="${LOKI_HEAL_STRICT:-false}"
    local compliance=""
    local provider="${LOKI_PROVIDER:-claude}"
    local parallel="1"
    local no_dashboard="false"
    local dry_run="false"

    if [ $# -eq 0 ]; then
        cmd_heal_help
        return 0
    fi

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                cmd_heal_help
                return 0
                ;;
            --status)
                do_status="true"
                shift
                ;;
            --report)
                do_report="true"
                shift
                ;;
            --friction-map)
                do_friction_map="true"
                shift
                ;;
            --phase)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --phase requires a value (archaeology|stabilize|isolate|modernize|validate)${NC}"
                    return 1
                fi
                phase="$2"
                shift 2
                ;;
            --phase=*)
                phase="${1#*=}"
                shift
                ;;
            --resume)
                do_resume="true"
                shift
                ;;
            --strict)
                strict="true"
                shift
                ;;
            --archaeology-only)
                archaeology_only="true"
                phase="archaeology"
                shift
                ;;
            --compliance)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --compliance requires a value${NC}"
                    return 1
                fi
                compliance="$2"
                shift 2
                ;;
            --provider)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --provider requires a value${NC}"
                    return 1
                fi
                provider="$2"
                shift 2
                ;;
            --parallel)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --parallel requires a value${NC}"
                    return 1
                fi
                parallel="$2"
                shift 2
                ;;
            --no-dashboard)
                no_dashboard="true"
                shift
                ;;
            --dry-run)
                dry_run="true"
                shift
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki heal --help' for usage."
                return 1
                ;;
            *)
                if [ -z "$codebase_path" ]; then
                    codebase_path="$1"
                else
                    echo -e "${RED}Error: Unexpected argument: $1${NC}"
                    return 1
                fi
                shift
                ;;
        esac
    done

    # Route to subcommands
    if [ "$do_status" = "true" ]; then
        local heal_dir="${codebase_path:-.}/.loki/healing"
        if [[ ! -d "$heal_dir" ]]; then
            echo -e "${YELLOW}No healing session found.${NC}"
            echo "Start one with: loki heal <path-to-codebase>"
            return 0
        fi
        local progress_file="$heal_dir/healing-progress.json"
        if [[ -f "$progress_file" ]]; then
            echo -e "${BOLD}Healing Progress${NC}"
            echo ""
            python3 -c "
import json, sys
with open(sys.argv[1]) as f:
    data = json.load(f)
print(f\"  Codebase:       {data.get('codebase', '?')}\")
print(f\"  Started:        {data.get('started', '?')}\")
print(f\"  Overall Health:  {data.get('overall_health', 0):.0%}\")
print()
for c in data.get('components', []):
    pct = c.get('characterization_passing', 0) / max(c.get('characterization_tests', 1), 1)
    print(f\"  [{c.get('phase', '?'):12s}] {c.get('name', '?')}\")
    print(f\"               Friction: {c.get('friction_resolved', 0)}/{c.get('friction_points', 0)} resolved\")
    print(f\"               Tests:    {c.get('characterization_passing', 0)}/{c.get('characterization_tests', 0)} passing\")
    print(f\"               Health:   {c.get('health_score', 0):.0%}\")
    print()
" "$progress_file" 2>/dev/null || echo "  Error reading progress file."
        else
            echo "  No progress data yet. Healing may still be in archaeology phase."
        fi
        return 0
    fi

    if [ "$do_friction_map" = "true" ]; then
        local friction_file="${codebase_path:-.}/.loki/healing/friction-map.json"
        if [[ ! -f "$friction_file" ]]; then
            echo -e "${YELLOW}No friction map found.${NC}"
            echo "Run archaeology first: loki heal ${codebase_path:-.} --phase archaeology"
            return 0
        fi
        echo -e "${BOLD}Friction Map${NC}"
        echo ""
        python3 -c "
import json, sys
with open(sys.argv[1]) as f:
    data = json.load(f)
for f in data.get('frictions', []):
    safe = 'YES' if f.get('safe_to_remove') else 'NO'
    cls = f.get('classification', 'unknown')
    print(f\"  [{f.get('id', '?')}] {f.get('location', '?')}\")
    print(f\"    Behavior:       {f.get('behavior', '?')}\")
    print(f\"    Classification: {cls}\")
    print(f\"    Safe to remove: {safe}\")
    if f.get('evidence'):
        print(f\"    Evidence:       {f.get('evidence')}\")
    print()
" "$friction_file" 2>/dev/null || echo "  Error reading friction map."
        return 0
    fi

    if [ "$do_report" = "true" ]; then
        local heal_dir="${codebase_path:-.}/.loki/healing"
        if [[ ! -d "$heal_dir" ]]; then
            echo -e "${YELLOW}No healing session found.${NC}"
            return 0
        fi
        echo -e "${BOLD}Healing Report${NC}"
        echo ""
        echo "  Friction map:               $(_HEAL_FILE="$heal_dir/friction-map.json" python3 -c "import json, os; print(len(json.load(open(os.environ['_HEAL_FILE'])).get('frictions', [])))" 2>/dev/null || echo '0') points"
        echo "  Failure modes:              $(_HEAL_FILE="$heal_dir/failure-modes.json" python3 -c "import json, os; print(len(json.load(open(os.environ['_HEAL_FILE'])).get('modes', [])))" 2>/dev/null || echo '0') cataloged"
        echo "  Institutional knowledge:    $(wc -l < "$heal_dir/institutional-knowledge.md" 2>/dev/null || echo '0') lines"
        echo "  Characterization tests:     $(find "$heal_dir/characterization-tests/" -name "*.json" 2>/dev/null | wc -l | tr -d ' ') tests"
        echo ""
        return 0
    fi

    # Validate codebase path
    if [ -z "$codebase_path" ]; then
        echo -e "${RED}Error: Codebase path is required${NC}"
        echo "Usage: loki heal <path-to-codebase> [options]"
        return 1
    fi

    if [[ ! -d "$codebase_path" ]]; then
        echo -e "${RED}Error: Directory not found: $codebase_path${NC}"
        return 1
    fi

    # Validate phase
    case "$phase" in
        archaeology|stabilize|isolate|modernize|validate) ;;
        *)
            echo -e "${RED}Error: Invalid phase: $phase${NC}"
            echo "Valid phases: archaeology, stabilize, isolate, modernize, validate"
            return 1
            ;;
    esac

    # Initialize healing directory
    local heal_dir="$codebase_path/.loki/healing"
    mkdir -p "$heal_dir"/{behavioral-baseline,characterization-tests}

    # BUG-HEAL-004: Source migration hooks for healing enforcement
    local hooks_file
    hooks_file="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hooks/migration-hooks.sh"
    if [[ -f "$hooks_file" ]]; then
        # shellcheck source=hooks/migration-hooks.sh
        source "$hooks_file"
        load_migration_hook_config "$codebase_path"
    fi

    # Export healing environment variables for hooks
    export LOKI_HEAL_MODE="true"
    export LOKI_HEAL_PHASE="$phase"
    export LOKI_HEAL_STRICT="$strict"
    export LOKI_CODEBASE_PATH="$codebase_path"

    # Initialize healing state files if they don't exist
    [[ ! -f "$heal_dir/friction-map.json" ]] && echo '{"frictions":[]}' > "$heal_dir/friction-map.json"
    [[ ! -f "$heal_dir/failure-modes.json" ]] && echo '{"modes":[]}' > "$heal_dir/failure-modes.json"
    [[ ! -f "$heal_dir/institutional-knowledge.md" ]] && echo "# Institutional Knowledge Registry" > "$heal_dir/institutional-knowledge.md"

    # Initialize or update healing progress
    if [[ ! -f "$heal_dir/healing-progress.json" ]] || [ "$do_resume" != "true" ]; then
        python3 -c "
import json
from datetime import datetime
progress = {
    'codebase': '$codebase_path',
    'started': datetime.now().isoformat(),
    'current_phase': '$phase',
    'strict_mode': $( [ "$strict" = "true" ] && echo "True" || echo "False" ),
    'components': [],
    'overall_health': 0.0
}
with open('$heal_dir/healing-progress.json', 'w') as f:
    json.dump(progress, f, indent=2)
" || true
    fi

    # BUG-HEAL-004: Validate phase gate when resuming from a previous phase
    if [ "$do_resume" = "true" ] && [[ -f "$heal_dir/healing-progress.json" ]] && type hook_healing_phase_gate &>/dev/null; then
        local prev_phase
        prev_phase=$(python3 -c "import json; print(json.load(open('$heal_dir/healing-progress.json')).get('current_phase', 'archaeology'))" 2>/dev/null || echo "archaeology")
        if [[ "$prev_phase" != "$phase" ]]; then
            local gate_result
            if ! gate_result=$(hook_healing_phase_gate "$prev_phase" "$phase" 2>&1); then
                echo -e "${RED}Phase gate check failed:${NC}"
                echo "  $gate_result"
                return 1
            fi
        fi
    fi

    emit_event healing cli start "phase=$phase" "codebase=$codebase_path" "strict=$strict" 2>/dev/null || true

    echo -e "${BOLD}Legacy System Healing${NC}"
    echo ""
    echo -e "  Codebase:   $codebase_path"
    echo -e "  Phase:      $phase"
    echo -e "  Strict:     $strict"
    echo -e "  Provider:   $provider"
    if [ "$archaeology_only" = "true" ]; then
        echo -e "  Mode:       archaeology-only (no modifications)"
    fi
    echo ""

    if [ "$dry_run" = "true" ]; then
        echo -e "${CYAN}Dry run: Showing healing plan without executing${NC}"
        echo ""
        echo "  Phase 1: Archaeology"
        echo "    - Map dependency graph"
        echo "    - Scan for friction points (sleeps, retries, magic values)"
        echo "    - Extract institutional knowledge from comments"
        echo "    - Write characterization tests for critical paths"
        echo ""
        echo "  Phase 2: Stabilize"
        echo "    - Add logging/observability without behavior changes"
        echo "    - Extract hardcoded config values"
        echo "    - Add type annotations where possible"
        echo ""
        echo "  Phase 3: Isolate"
        echo "    - Define component boundaries"
        echo "    - Create adapter interfaces"
        echo "    - Add integration tests at boundaries"
        echo ""
        echo "  Phase 4: Modernize"
        echo "    - Replace components one at a time behind adapters"
        echo "    - Verify characterization tests after each replacement"
        echo ""
        echo "  Phase 5: Validate"
        echo "    - Compare outputs with pre-healing baseline"
        echo "    - Generate healing report"
        return 0
    fi

    # Build healing prompt for the AI provider
    local heal_prompt="You are performing legacy system healing on the codebase at '${codebase_path}'.

IMPORTANT: Read skills/healing.md for the full healing protocol.

Current phase: ${phase}
Strict mode: ${strict}
Archaeology only: ${archaeology_only}

HEALING RULES:
1. FRICTION IS SEMANTICS: Before removing any 'quirky' code (sleeps, retries, magic values), verify it is not an undocumented business rule. Document all friction in .loki/healing/friction-map.json.
2. LEARN THROUGH FAILURE: Deliberately probe error paths and edge cases. Catalog every failure mode in .loki/healing/failure-modes.json. Each failure teaches you about the system.
3. CHARACTERIZE BEFORE MODIFYING: Write tests that capture CURRENT behavior (not intended behavior) before changing anything.
4. PRESERVE INSTITUTIONAL LOGIC: Extract business rules from comments, git history, error messages, and test fixtures into .loki/healing/institutional-knowledge.md.
5. INCREMENTAL ONLY: Change ONE component at a time. Verify ALL characterization tests pass after each change.

Phase-specific instructions:
- archaeology: Map dependencies, catalog friction, extract knowledge, write characterization tests. Do NOT modify source code.
- stabilize: Add logging and observability. Extract config. Add type hints. Do NOT change behavior.
- isolate: Create adapter interfaces at component boundaries. Add integration tests.
- modernize: Replace components behind adapters. Verify characterization tests pass.
- validate: Compare outputs with behavioral baseline. Generate healing report.

$([ "$strict" = "true" ] && echo "STRICT MODE: You MUST NOT change any observable behavior without creating a .loki/signals/HUMAN_REVIEW_NEEDED signal and waiting for approval.")
$([ "$archaeology_only" = "true" ] && echo "ARCHAEOLOGY ONLY: Do NOT modify any source files. Only create files in .loki/healing/.")

Begin healing now. Follow the RARV cycle for each action."

    # Execute with the appropriate provider
    local heal_exit=0
    case "$provider" in
        claude)
            local run_args=(--dangerously-skip-permissions -p "$heal_prompt" --output-format stream-json --verbose)
            (cd "$codebase_path" && claude "${run_args[@]}" 2>&1) | \
                while IFS= read -r line; do
                    if echo "$line" | python3 -c "
import sys, json
try:
    d = json.loads(sys.stdin.read())
    if d.get('type') == 'assistant':
        for item in d.get('message', {}).get('content', []):
            if item.get('type') == 'text':
                print(item.get('text', ''), end='')
except Exception: pass
" 2>/dev/null; then
                        true
                    fi
                done && heal_exit=0 || heal_exit=$?
            ;;
        codex)
            (cd "$codebase_path" && codex exec --full-auto "$heal_prompt" 2>&1) || heal_exit=$?
            ;;
        cline)
            (cd "$codebase_path" && cline -y "$heal_prompt" 2>&1) || heal_exit=$?
            ;;
        aider)
            local aider_model="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
            local aider_flags="${LOKI_AIDER_FLAGS:-}"
            # shellcheck disable=SC2086
            (cd "$codebase_path" && aider --message "$heal_prompt" --yes-always --no-auto-commits --model "$aider_model" $aider_flags 2>&1) || heal_exit=$?
            ;;
        # BUG-HEAL-003: Unknown provider should error, not silently succeed
        *)
            echo -e "${RED}Error: Unknown provider: $provider${NC}"
            echo "Supported providers: claude, codex, cline, aider"
            return 1
            ;;
    esac

    emit_event healing cli complete "phase=$phase" "exit=$heal_exit" 2>/dev/null || true

    if [ "$heal_exit" -eq 0 ]; then
        echo ""
        echo -e "${GREEN}Healing phase '${phase}' complete.${NC}"
        echo -e "View progress: loki heal --status ${codebase_path}"
        echo -e "View friction:  loki heal --friction-map ${codebase_path}"
    else
        echo ""
        echo -e "${YELLOW}Healing phase '${phase}' finished with warnings (exit: $heal_exit).${NC}"
        echo -e "Check: loki heal --status ${codebase_path}"
    fi
}

cmd_migrate() {
    local codebase_path=""
    local target=""
    local plan_only="false"
    local show_plan="false"
    local phase=""
    local parallel=""
    local compliance=""
    local dry_run="false"
    local do_resume="false"
    local multi_repo=""
    local export_report="false"
    local do_list="false"
    local do_status="false"
    local status_id=""
    local no_dashboard="false"
    local no_docs="false"

    if [ $# -eq 0 ]; then
        cmd_migrate_help
        return 0
    fi

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                cmd_migrate_help
                return 0
                ;;
            --list)
                do_list="true"
                shift
                ;;
            --status)
                do_status="true"
                if [[ -n "${2:-}" ]] && [[ ! "$2" =~ ^-- ]]; then
                    status_id="$2"
                    shift
                fi
                shift
                ;;
            --show-plan)
                show_plan="true"
                shift
                ;;
            --target)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --target requires a value${NC}"
                    return 1
                fi
                target="$2"
                shift 2
                ;;
            --target=*)
                target="${1#*=}"
                shift
                ;;
            --plan-only)
                plan_only="true"
                shift
                ;;
            --phase)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --phase requires a value${NC}"
                    return 1
                fi
                phase="$2"
                shift 2
                ;;
            --phase=*)
                phase="${1#*=}"
                shift
                ;;
            --parallel)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --parallel requires a value${NC}"
                    return 1
                fi
                parallel="$2"
                shift 2
                ;;
            --parallel=*)
                parallel="${1#*=}"
                shift
                ;;
            --compliance)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --compliance requires a value${NC}"
                    return 1
                fi
                compliance="$2"
                shift 2
                ;;
            --compliance=*)
                compliance="${1#*=}"
                shift
                ;;
            --dry-run)
                dry_run="true"
                shift
                ;;
            --resume)
                do_resume="true"
                shift
                ;;
            --multi-repo)
                if [[ -z "${2:-}" ]]; then
                    echo -e "${RED}Error: --multi-repo requires a value${NC}"
                    return 1
                fi
                multi_repo="$2"
                shift 2
                ;;
            --multi-repo=*)
                multi_repo="${1#*=}"
                shift
                ;;
            --export-report)
                export_report="true"
                shift
                ;;
            --no-dashboard)
                no_dashboard="true"
                shift
                ;;
            --no-docs)
                no_docs="true"
                shift
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki migrate --help' for usage."
                return 1
                ;;
            *)
                if [ -z "$codebase_path" ]; then
                    codebase_path="$1"
                else
                    echo -e "${RED}Error: Unexpected argument: $1${NC}"
                    echo "Run 'loki migrate --help' for usage."
                    return 1
                fi
                shift
                ;;
        esac
    done

    # Route to subcommands
    if [ "$do_list" = "true" ]; then
        cmd_migrate_list
        return 0
    fi

    if [ "$do_status" = "true" ]; then
        cmd_migrate_status "$status_id"
        return 0
    fi

    if [ "$show_plan" = "true" ]; then
        cmd_migrate_show_plan "${codebase_path:-$(pwd)}"
        return 0
    fi

    # Default: start or continue migration
    if [ -z "$codebase_path" ]; then
        echo -e "${RED}Error: Codebase path is required${NC}"
        echo "Usage: loki migrate <path-to-codebase> --target <target>"
        echo ""
        echo "Run 'loki migrate --help' for usage."
        return 1
    fi

    cmd_migrate_start "$codebase_path" "$target" "$plan_only" "$phase" "$parallel" "$compliance" "$dry_run" "$do_resume" "$multi_repo" "$export_report" "$no_dashboard" "$no_docs"
}

#===============================================================================
# loki cluster - Custom workflow templates (v6.2.0)
#===============================================================================

cmd_cluster() {
    local subcmd="${1:-list}"
    shift 2>/dev/null || true

    case "$subcmd" in
        --help|-h|help)
            echo -e "${BOLD}loki cluster${NC} - Custom workflow templates (v6.3.0)"
            echo ""
            echo "Usage: loki cluster <command> [options]"
            echo ""
            echo "Commands:"
            echo "  list               List available workflow templates"
            echo "  validate <name>    Validate a cluster template"
            echo "  run <name> [args]  Execute a cluster workflow"
            echo "  info <name>        Show template details"
            echo ""
            echo "Options for 'run':"
            echo "  --cluster-id <id>  Named cluster ID for crash recovery"
            echo "  --resume           Resume a previously crashed cluster run"
            echo ""
            echo "Examples:"
            echo "  loki cluster list"
            echo "  loki cluster validate security-review"
            echo "  loki cluster info code-review"
            echo "  loki cluster run security-review --cluster-id my-review"
            echo "  loki cluster run security-review --cluster-id my-review --resume"
            ;;
        list)
            echo -e "${BOLD}Available Cluster Templates${NC}"
            echo ""
            local template_dir="$SKILL_DIR/templates/clusters"
            if [[ ! -d "$template_dir" ]]; then
                echo "No templates found at $template_dir"
                return 1
            fi
            for f in "$template_dir"/*.json; do
                [[ ! -f "$f" ]] && continue
                local name
                name=$(basename "$f" .json)
                local desc
                desc=$(python3 -c "
import json, sys
try:
    with open(sys.argv[1]) as f:
        d = json.load(f)
    print(d.get('description', 'No description'))
except: print('Error reading template')
" "$f" 2>/dev/null || echo "")
                local agent_count
                agent_count=$(python3 -c "
import json, sys
try:
    with open(sys.argv[1]) as f:
        d = json.load(f)
    print(len(d.get('agents', [])))
except: print('?')
" "$f" 2>/dev/null || echo "?")
                printf "  %-20s %s agents  %s\n" "$name" "$agent_count" "$desc"
            done
            ;;
        validate)
            local template_name="${1:-}"
            if [[ -z "$template_name" ]]; then
                echo -e "${RED}Usage: loki cluster validate <template-name>${NC}"
                return 1
            fi
            local template_file="$SKILL_DIR/templates/clusters/${template_name}.json"
            if [[ ! -f "$template_file" ]]; then
                echo -e "${RED}Template not found: $template_name${NC}"
                echo "Run 'loki cluster list' to see available templates."
                return 1
            fi
            echo -e "${CYAN}Validating: $template_name${NC}"
            local errors
            errors=$(python3 -c "
import json, sys
sys.path.insert(0, '$SKILL_DIR')
from swarm.patterns import TopologyValidator
errors = TopologyValidator.validate_file(sys.argv[1])
if errors:
    for e in errors:
        print(f'ERROR: {e}')
else:
    print('VALID')
" "$template_file" 2>&1)
            if echo "$errors" | grep -q "^VALID$"; then
                echo -e "${GREEN}Template is valid${NC}"
                return 0
            else
                echo -e "${RED}Validation errors:${NC}"
                echo "$errors"
                return 1
            fi
            ;;
        info)
            local template_name="${1:-}"
            if [[ -z "$template_name" ]]; then
                echo -e "${RED}Usage: loki cluster info <template-name>${NC}"
                return 1
            fi
            local template_file="$SKILL_DIR/templates/clusters/${template_name}.json"
            if [[ ! -f "$template_file" ]]; then
                echo -e "${RED}Template not found: $template_name${NC}"
                return 1
            fi
            python3 -c "
import json, sys
with open(sys.argv[1]) as f:
    d = json.load(f)
print(f\"Name:        {d.get('name', 'unknown')}\")
print(f\"Description: {d.get('description', 'none')}\")
print(f\"Version:     {d.get('version', 'unknown')}\")
print(f\"Topology:    {d.get('topology', 'unknown')}\")
print(f\"Agents:      {len(d.get('agents', []))}\")
print()
for a in d.get('agents', []):
    subs = ', '.join(a.get('subscribes', []))
    pubs = ', '.join(a.get('publishes', []))
    print(f\"  [{a['id']}] ({a.get('type', '?')})\")
    print(f\"    Role: {a.get('role', '?')}\")
    print(f\"    Subscribes: {subs}\")
    print(f\"    Publishes:  {pubs}\")
    print()
" "$template_file"
            ;;
        run)
            local template_name="${1:-}"
            shift 2>/dev/null || true
            if [[ -z "$template_name" ]]; then
                echo -e "${RED}Usage: loki cluster run <template-name> [--cluster-id ID] [--resume]${NC}"
                return 1
            fi
            local template_file="$SKILL_DIR/templates/clusters/${template_name}.json"
            if [[ ! -f "$template_file" ]]; then
                echo -e "${RED}Template not found: $template_name${NC}"
                return 1
            fi

            # Parse run options
            local cluster_id="" do_resume="false"
            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --cluster-id) cluster_id="$2"; shift 2 ;;
                    --cluster-id=*) cluster_id="${1#*=}"; shift ;;
                    --resume) do_resume="true"; shift ;;
                    *) shift ;;
                esac
            done

            # Auto-generate cluster ID if not provided
            if [[ -z "$cluster_id" ]]; then
                cluster_id="${template_name}_$(date +%Y%m%d_%H%M%S)"
            fi

            # Validate first
            local result
            result=$(python3 -c "
import json, sys
sys.path.insert(0, '$SKILL_DIR')
from swarm.patterns import TopologyValidator
errors = TopologyValidator.validate_file(sys.argv[1])
if errors:
    for e in errors: print(f'ERROR: {e}')
else:
    print('VALID')
" "$template_file" 2>&1)
            if ! echo "$result" | grep -q "^VALID$"; then
                echo -e "${RED}Template validation failed:${NC}"
                echo "$result"
                return 1
            fi

            # Fire lifecycle hooks and record state
            PYTHONPATH="${SKILL_DIR:-.}" python3 -c "
import json, sys
sys.path.insert(0, '${SKILL_DIR:-.}')
from swarm.patterns import ClusterLifecycleHooks
from state.sqlite_backend import SqliteStateBackend

# Load template hooks config
with open('${template_file}') as f:
    tpl = json.load(f)

hooks = ClusterLifecycleHooks(tpl.get('hooks', {}))
db = SqliteStateBackend()

cluster_id = '${cluster_id}'
resume = '${do_resume}' == 'true'

if resume:
    events = db.query_events(event_type='cluster_state', migration_id=cluster_id, limit=1)
    if events:
        print(f'Resuming cluster {cluster_id} from last checkpoint')
    else:
        print(f'No previous state found for {cluster_id}. Starting fresh.')

# Fire pre_run hooks
results = hooks.fire('pre_run', {'cluster_id': cluster_id, 'template': '${template_name}'})
for r in results:
    if not r['success']:
        print(f'Pre-run hook failed: {r[\"output\"]}')

# Record cluster start
db.record_event('cluster_start', {
    'cluster_id': cluster_id,
    'template': '${template_name}',
    'agents': len(tpl.get('agents', [])),
    'resume': resume,
}, migration_id=cluster_id)

print(f'Cluster {cluster_id} initialized with {len(tpl.get(\"agents\", []))} agents')
print('Template validated. Lifecycle hooks active.')
" 2>&1

            echo -e "${GREEN}Cluster: $cluster_id${NC}"
            echo -e "Template: $template_name"
            if [[ "$do_resume" == "true" ]]; then
                echo -e "Mode: resume"
            fi
            echo -e "Use 'loki state query events --migration $cluster_id' to inspect state."
            ;;
        *)
            echo -e "${RED}Unknown cluster command: $subcmd${NC}"
            echo "Run 'loki cluster --help' for usage."
            return 1
            ;;
    esac
}

# Event-driven trigger management (v6.17.0)
cmd_trigger() {
    local subcmd="${1:-help}"
    shift || true

    local trigger_server_script
    trigger_server_script="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/trigger-server.py"
    local trigger_schedule_script
    trigger_schedule_script="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/trigger-schedule.py"
    local pid_file=".loki/triggers/server.pid"

    case "$subcmd" in
        start)
            local port=""
            local secret=""
            local dry_run_flag=""
            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --port) port="$2"; shift 2 ;;
                    --port=*) port="${1#--port=}"; shift ;;
                    --secret) secret="$2"; shift 2 ;;
                    --secret=*) secret="${1#--secret=}"; shift ;;
                    --dry-run) dry_run_flag="--dry-run"; shift ;;
                    *) shift ;;
                esac
            done
            if [[ -f "$pid_file" ]]; then
                local existing_pid
                existing_pid=$(cat "$pid_file" 2>/dev/null)
                if kill -0 "$existing_pid" 2>/dev/null; then
                    echo "Trigger server already running (pid=$existing_pid)"
                    return 0
                fi
            fi
            local args=()
            [[ -n "$port" ]] && args+=("--port" "$port")
            [[ -n "$secret" ]] && args+=("--secret" "$secret")
            [[ -n "$dry_run_flag" ]] && args+=("--dry-run")
            mkdir -p .loki/triggers
            # v7.7.13 fix: safe empty-array expansion (bash 3.2 + set -u).
            nohup python3 "$trigger_server_script" ${args[@]+"${args[@]}"} \
                > .loki/triggers/server.log 2>&1 &
            local server_pid=$!
            echo "$server_pid" > "$pid_file"
            sleep 0.5
            if kill -0 "$server_pid" 2>/dev/null; then
                echo "Trigger server started (pid=$server_pid)"
                local p="${port:-7373}"
                echo "Webhook endpoint: POST http://localhost:$p/webhook"
            else
                echo -e "${RED}Trigger server failed to start. Check .loki/triggers/server.log${NC}"
                return 1
            fi
            ;;
        stop)
            if [[ ! -f "$pid_file" ]]; then
                echo "Trigger server not running (no pid file)"
                return 0
            fi
            local pid
            pid=$(cat "$pid_file" 2>/dev/null)
            if [[ -z "$pid" ]]; then
                echo "Trigger server not running"
                rm -f "$pid_file"
                return 0
            fi
            if kill "$pid" 2>/dev/null; then
                echo "Trigger server stopped (pid=$pid)"
                rm -f "$pid_file"
            else
                echo "Trigger server was not running (pid=$pid)"
                rm -f "$pid_file"
            fi
            ;;
        status)
            if [[ -f "$pid_file" ]]; then
                local pid
                pid=$(cat "$pid_file" 2>/dev/null)
                if kill -0 "$pid" 2>/dev/null; then
                    echo "Trigger server: running (pid=$pid)"
                    if command -v curl >/dev/null 2>&1; then
                        local config_port
                        config_port=$(python3 -c "
import json, pathlib
p = pathlib.Path('.loki/triggers/config.json')
print(json.loads(p.read_text()).get('port', 7373) if p.exists() else 7373)
" 2>/dev/null || echo "7373")
                        curl -sf "http://localhost:$config_port/status" 2>/dev/null | \
                            python3 -m json.tool 2>/dev/null || true
                    fi
                else
                    echo "Trigger server: stopped (stale pid=$pid)"
                    rm -f "$pid_file"
                fi
            else
                echo "Trigger server: not running"
            fi
            local sched_file=".loki/triggers/schedules.json"
            if [[ -f "$sched_file" ]]; then
                local count
                count=$(_SCHED_FILE="$sched_file" python3 -c "import json, os; d=json.load(open(os.environ['_SCHED_FILE'])); print(len(d) if isinstance(d,list) else len(d.get('schedules',[])))" 2>/dev/null || echo "?")
                echo "Schedules configured: $count"
            else
                echo "Schedules configured: 0"
            fi
            ;;
        list)
            python3 "$trigger_schedule_script" list
            ;;
        add)
            local add_type="${1:-}"
            shift || true
            if [[ "$add_type" == "schedule" ]]; then
                local name="${1:-}"
                local cron_expr="${2:-}"
                local action="${3:-}"
                shift 3 || true
                if [[ -z "$name" || -z "$cron_expr" || -z "$action" ]]; then
                    echo "Usage: loki trigger add schedule <name> <cron_expr> <action> [args...]"
                    echo ""
                    echo "Example:"
                    echo "  loki trigger add schedule daily-review '0 9 * * 1-5' quality-review"
                    echo "  loki trigger add schedule auto-issue '*/30 * * * *' run 42"
                    return 1
                fi
                python3 "$trigger_schedule_script" add "$name" "$cron_expr" "$action" "$@"
            else
                echo "Usage: loki trigger add schedule <name> <cron_expr> <action> [args...]"
                return 1
            fi
            ;;
        remove)
            local name="${1:-}"
            if [[ -z "$name" ]]; then
                echo "Usage: loki trigger remove <name>"
                return 1
            fi
            python3 "$trigger_schedule_script" remove "$name"
            ;;
        daemon)
            local dry_run_flag=""
            [[ "${1:-}" == "--dry-run" ]] && dry_run_flag="--dry-run"
            python3 "$trigger_schedule_script" daemon $dry_run_flag
            ;;
        test)
            local event_type="${1:-issues}"
            shift || true
            local repo=""
            local issue_num=""
            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --repo) repo="$2"; shift 2 ;;
                    --repo=*) repo="${1#--repo=}"; shift ;;
                    --issue) issue_num="$2"; shift 2 ;;
                    --issue=*) issue_num="${1#--issue=}"; shift ;;
                    *) shift ;;
                esac
            done
            local config_port
            config_port=$(python3 -c "
import json, pathlib
p = pathlib.Path('.loki/triggers/config.json')
print(json.loads(p.read_text()).get('port', 7373) if p.exists() else 7373)
" 2>/dev/null || echo "7373")

            local payload
            case "$event_type" in
                issues)
                    local num="${issue_num:-1}"
                    local r="${repo:-owner/repo}"
                    payload="{\"action\":\"opened\",\"issue\":{\"number\":$num,\"title\":\"Test issue\"},\"repository\":{\"full_name\":\"$r\"}}"
                    ;;
                pull_request)
                    local num="${issue_num:-1}"
                    local r="${repo:-owner/repo}"
                    payload="{\"action\":\"synchronize\",\"pull_request\":{\"number\":$num,\"title\":\"Test PR\"},\"repository\":{\"full_name\":\"$r\"}}"
                    ;;
                workflow_run)
                    local r="${repo:-owner/repo}"
                    payload="{\"action\":\"completed\",\"workflow_run\":{\"name\":\"CI\",\"conclusion\":\"failure\"},\"repository\":{\"full_name\":\"$r\"}}"
                    ;;
                *)
                    echo "Supported event types: issues, pull_request, workflow_run"
                    return 1
                    ;;
            esac

            if command -v curl >/dev/null 2>&1; then
                echo "Sending test $event_type event to http://localhost:$config_port/webhook"
                curl -sf -X POST \
                    -H "Content-Type: application/json" \
                    -H "X-GitHub-Event: $event_type" \
                    -d "$payload" \
                    "http://localhost:$config_port/webhook" | python3 -m json.tool 2>/dev/null || \
                    echo "Server not running or request failed. Start with: loki trigger start"
            else
                echo "curl not available. Cannot send test event."
                return 1
            fi
            ;;
        logs)
            local tail_n="50"
            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --tail) tail_n="$2"; shift 2 ;;
                    --tail=*) tail_n="${1#--tail=}"; shift ;;
                    *) shift ;;
                esac
            done
            local log_file=".loki/triggers/events.log"
            if [[ -f "$log_file" ]]; then
                tail -n "$tail_n" "$log_file" | python3 -c "
import sys, json
for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    try:
        e = json.loads(line)
        print('[%s] %-15s %-12s %-8s %s' % (
            e.get('timestamp','?'),
            e.get('event','?'),
            e.get('action','?'),
            e.get('status','?'),
            e.get('summary',''),
        ))
    except Exception:
        print(line)
" 2>/dev/null || cat "$log_file" | tail -n "$tail_n"
            else
                echo "No events logged yet. (.loki/triggers/events.log does not exist)"
            fi
            local server_log=".loki/triggers/server.log"
            if [[ -f "$server_log" ]]; then
                echo ""
                echo "--- Server log (last $tail_n lines) ---"
                tail -n "$tail_n" "$server_log"
            fi
            ;;
        help|--help|-h)
            echo -e "${BOLD}loki trigger${NC} - Event-driven autonomous execution"
            echo ""
            echo "Usage: loki trigger <subcommand> [options]"
            echo ""
            echo "Subcommands:"
            echo "  start [--port PORT] [--secret SECRET] [--dry-run]"
            echo "                        Start the webhook receiver server (default port: 7373)"
            echo "  stop                  Stop the webhook receiver server"
            echo "  status                Show server and schedule status"
            echo "  list                  List configured schedules"
            echo "  add schedule <name> <cron_expr> <action> [args...]"
            echo "                        Add a cron-based schedule trigger"
            echo "  remove <name>         Remove a schedule trigger"
            echo "  daemon [--dry-run]    Run one pass of schedule checks (call from cron)"
            echo "  test <event-type> [--repo owner/repo] [--issue NUM]"
            echo "                        Send a test webhook event (event-types: issues, pull_request, workflow_run)"
            echo "  logs [--tail N]       Show recent trigger event log (default: last 50)"
            echo "  help                  Show this help"
            echo ""
            echo "Webhook events handled:"
            echo "  issues (opened)        -> loki run <issue_number> --pr --detach"
            echo "  pull_request (sync)    -> loki run <pr_number> --detach"
            echo "  workflow_run (failure) -> loki run --detach"
            echo ""
            echo "Schedule cron format: minute hour day-of-month month day-of-week"
            echo "  Examples:"
            echo "    '0 9 * * 1-5'   - 9am Mon-Fri"
            echo "    '*/30 * * * *'  - Every 30 minutes"
            echo "    '0 0 * * 0'     - Midnight Sunday"
            echo ""
            echo "Examples:"
            echo "  loki trigger start --port 7373 --secret mywebhooksecret"
            echo "  loki trigger add schedule daily-review '0 9 * * 1-5' quality-review"
            echo "  loki trigger test issues --repo owner/repo --issue 42"
            echo "  loki trigger logs --tail 20"
            echo "  loki trigger stop"
            ;;
        *)
            echo -e "${RED}Unknown trigger command: $subcmd${NC}"
            echo "Run 'loki trigger help' for usage."
            return 1
            ;;
    esac
}

# Cross-provider auto-failover management (v6.19.0)
cmd_failover() {
    local loki_dir="${TARGET_DIR:-.}/.loki"
    local failover_file="$loki_dir/state/failover.json"
    local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

    if [ $# -eq 0 ]; then
        # No args: show current failover config and health
        if [ ! -f "$failover_file" ]; then
            echo -e "${BOLD}Cross-Provider Auto-Failover${NC}"
            echo ""
            echo "Status: DISABLED"
            echo ""
            echo "Enable with: loki failover --enable"
            echo "Or set:      LOKI_FAILOVER=true loki start"
            return 0
        fi

        echo -e "${BOLD}Cross-Provider Auto-Failover${NC}"
        echo ""
        python3 << 'PYEOF'
import json, os
fpath = os.path.join(os.environ.get('TARGET_DIR', '.'), '.loki/state/failover.json')
try:
    with open(fpath) as f:
        d = json.load(f)
    status = "ENABLED" if d.get("enabled") else "DISABLED"
    print(f"Status:           {status}")
    print(f"Chain:            {' -> '.join(d.get('chain', []))}")
    print(f"Current provider: {d.get('currentProvider', 'unknown')}")
    print(f"Primary provider: {d.get('primaryProvider', 'unknown')}")
    print(f"Failover count:   {d.get('failoverCount', 0)}")
    last = d.get('lastFailover')
    print(f"Last failover:    {last if last else 'never'}")
    print()
    print("Health Status:")
    health = d.get('healthCheck', {})
    for provider in d.get('chain', []):
        h = health.get(provider, 'unknown')
        indicator = '[OK]' if h == 'healthy' else '[--]' if h == 'unknown' else '[!!]'
        print(f"  {indicator} {provider}: {h}")
except FileNotFoundError:
    print("Failover not initialized. Run: loki failover --enable")
except Exception as e:
    print(f"Error reading failover state: {e}")
PYEOF
        return 0
    fi

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --enable)
                mkdir -p "$loki_dir/state"
                local current_provider
                current_provider=$(cat "$loki_dir/state/provider" 2>/dev/null || echo "claude")
                local chain="${LOKI_FAILOVER_CHAIN:-claude,codex,cline}"

                cat > "$failover_file" << FEOF
{
  "enabled": true,
  "chain": $(printf '%s' "$chain" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip().split(",")))' 2>/dev/null || echo '["claude","codex","cline"]'),
  "currentProvider": "$current_provider",
  "primaryProvider": "$current_provider",
  "lastFailover": null,
  "failoverCount": 0,
  "healthCheck": {}
}
FEOF
                echo -e "${GREEN}Failover enabled${NC}"
                echo "Chain: $chain"
                echo "Primary: $current_provider"
                shift
                ;;
            --disable)
                if [ -f "$failover_file" ]; then
                    python3 -c "
import json
with open('$failover_file') as f: d = json.load(f)
d['enabled'] = False
with open('$failover_file', 'w') as f: json.dump(d, f, indent=2)
" 2>/dev/null
                    echo -e "${YELLOW}Failover disabled${NC}"
                else
                    echo "Failover not initialized."
                fi
                shift
                ;;
            --chain)
                shift
                local new_chain="$1"
                if [ -z "$new_chain" ]; then
                    echo -e "${RED}Error: --chain requires a comma-separated list of providers${NC}"
                    return 1
                fi
                # Validate each provider in chain
                local IFS=','
                for p in $new_chain; do
                    case "$p" in
                        claude|codex|cline|aider) ;;
                        *) echo -e "${RED}Error: invalid provider '$p' in chain${NC}"; return 1 ;;
                    esac
                done
                unset IFS

                if [ ! -f "$failover_file" ]; then
                    echo -e "${RED}Error: failover not initialized. Run: loki failover --enable${NC}"
                    return 1
                fi

                python3 -c "
import json
with open('$failover_file') as f: d = json.load(f)
d['chain'] = '$new_chain'.split(',')
with open('$failover_file', 'w') as f: json.dump(d, f, indent=2)
" 2>/dev/null
                echo "Failover chain updated: $new_chain"
                shift
                ;;
            --test)
                echo -e "${BOLD}Testing all providers in failover chain...${NC}"
                echo ""
                # Source provider loader for health checks
                if [ -f "$script_dir/../providers/loader.sh" ]; then
                    source "$script_dir/../providers/loader.sh"
                fi

                local chain_providers="claude,codex,cline"
                if [ -f "$failover_file" ]; then
                    chain_providers=$(_FAILOVER_FILE="$failover_file" python3 -c "import json, os; print(','.join(json.load(open(os.environ['_FAILOVER_FILE'])).get('chain', ['claude','codex','cline'])))" 2>/dev/null || echo "claude,codex,cline")
                fi

                local IFS=','
                local all_pass=true
                for p in $chain_providers; do
                    printf "  %-10s " "$p:"

                    # Check CLI installed
                    local cli_name="$p"
                    if command -v "$cli_name" &>/dev/null; then
                        local cli_version
                        cli_version=$("$cli_name" --version 2>/dev/null | head -1 || echo "unknown")
                        printf "CLI %-20s " "[$cli_version]"
                    else
                        printf "CLI %-20s " "[NOT INSTALLED]"
                        echo -e "${RED}FAIL${NC}"
                        all_pass=false
                        continue
                    fi

                    # Check API key
                    local has_key=false
                    case "$p" in
                        claude) [ -n "${ANTHROPIC_API_KEY:-}" ] && has_key=true ;;
                        codex) [ -n "${OPENAI_API_KEY:-}" ] && has_key=true ;;
                        cline|aider) has_key=true ;;  # Key check varies
                    esac

                    if [ "$has_key" = "true" ]; then
                        printf "Key [OK]  "
                        echo -e "${GREEN}PASS${NC}"
                    else
                        printf "Key [MISSING]  "
                        echo -e "${RED}FAIL${NC}"
                        all_pass=false
                    fi
                done
                unset IFS

                echo ""
                if [ "$all_pass" = "true" ]; then
                    echo -e "${GREEN}All providers healthy${NC}"
                else
                    echo -e "${YELLOW}Some providers unavailable - failover chain may be limited${NC}"
                fi
                shift
                ;;
            --reset)
                if [ -f "$failover_file" ]; then
                    rm -f "$failover_file"
                    echo "Failover state reset to defaults."
                else
                    echo "No failover state to reset."
                fi
                shift
                ;;
            --help|-h)
                echo -e "${BOLD}loki failover${NC} - Cross-provider auto-failover management (v6.19.0)"
                echo ""
                echo "Usage: loki failover [options]"
                echo ""
                echo "Options:"
                echo "  (no args)        Show failover status and health"
                echo "  --enable         Enable auto-failover"
                echo "  --disable        Disable auto-failover"
                echo "  --chain X,Y,Z    Set failover chain (e.g., claude,codex,cline)"
                echo "  --test           Test all providers in chain"
                echo "  --reset          Reset failover state to defaults"
                echo "  --help, -h       Show this help"
                echo ""
                echo "Environment:"
                echo "  LOKI_FAILOVER=true         Enable failover at startup"
                echo "  LOKI_FAILOVER_CHAIN=X,Y,Z  Set default chain"
                echo ""
                echo "When a rate limit (429/529) is detected, Loki automatically switches"
                echo "to the next healthy provider in the chain. After each successful"
                echo "iteration on a fallback provider, the primary is health-checked and"
                echo "execution switches back when it recovers."
                return 0
                ;;
            *)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki failover --help' for usage."
                return 1
                ;;
        esac
    done
}

# Reusable PRD plan analysis (v6.81.1)
# Shared by `loki plan`, `loki start` (auto-plan), and `loki run` (auto-plan).
# Arguments:
#   $1 - absolute PRD path
#   $2 - show_json (true|false)
#   $3 - show_verbose (true|false)
# Respects:
#   LOKI_SESSION_MODEL          - Pins ALL iterations to one tier (default: sonnet)
#   LOKI_LEGACY_TIER_SWITCHING  - When 'true', restores per-iteration rotation
show_prd_plan() {
    local prd_path="$1"
    local show_json="${2:-false}"
    local show_verbose="${3:-false}"

    if [ -z "$prd_path" ] || [ ! -f "$prd_path" ]; then
        echo -e "${RED}show_prd_plan: PRD file not found: $prd_path${NC}" >&2
        return 1
    fi

    python3 -c "
import json, sys, os, re, math

prd_path = sys.argv[1]
show_json = sys.argv[2] == 'true'
show_verbose = sys.argv[3] == 'true'

# v6.81.1: Respect LOKI_SESSION_MODEL (pinned by v6.81.0) so the estimate
# matches what the main loop actually invokes. LOKI_LEGACY_TIER_SWITCHING=true
# restores the legacy per-iteration Opus/Sonnet/Haiku rotation for users who
# explicitly opt out.
session_model_env = (os.environ.get('LOKI_SESSION_MODEL', 'sonnet') or 'sonnet').strip().lower()
legacy_tier_switching = (os.environ.get('LOKI_LEGACY_TIER_SWITCHING', 'false') or 'false').strip().lower() == 'true'

# Map session model name to tier key used in tokens_per_tier below.
# Unknown models fall through to 'development' (Sonnet) as a safe default.
_session_tier_map = {
    'opus': 'planning',
    'sonnet': 'development',
    'haiku': 'fast',
}
session_tier = _session_tier_map.get(session_model_env, 'development')

# Colors (disabled for JSON mode)
if show_json:
    RED = GREEN = YELLOW = BLUE = CYAN = BOLD = DIM = NC = ''
else:
    RED = '\033[0;31m'
    GREEN = '\033[0;32m'
    YELLOW = '\033[1;33m'
    BLUE = '\033[0;34m'
    CYAN = '\033[0;36m'
    BOLD = '\033[1m'
    DIM = '\033[2m'
    NC = '\033[0m'

# --- Read and analyze PRD ---
with open(prd_path, 'r') as f:
    content = f.read()

prd_words = len(content.split())
prd_lines = content.count('\n') + 1

# Detect PRD format
is_json_prd = prd_path.endswith('.json')
sections = []
features = []
endpoints = []
integrations = []
databases = []
ui_components = []

if is_json_prd:
    try:
        prd_data = json.loads(content)
        features = prd_data.get('features', prd_data.get('requirements', []))
        if isinstance(features, list):
            features = [f.get('title', f.get('name', str(f))) if isinstance(f, dict) else str(f) for f in features]
        else:
            features = []
    except Exception:
        features = []
else:
    # Markdown analysis
    for line in content.split('\n'):
        stripped = line.strip()
        if stripped.startswith('## '):
            sections.append(stripped[3:].strip())
        elif stripped.startswith('### '):
            sections.append(stripped[4:].strip())

    # Count features: headers + checkboxes
    feature_patterns = re.findall(r'^(?:##?\s+|[-*]\s+\[.\]\s+)(.+)', content, re.MULTILINE)
    features = feature_patterns

    # Count API endpoints
    endpoint_patterns = re.findall(
        r'(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+[/\w{}:.-]+',
        content, re.IGNORECASE
    )
    endpoints = endpoint_patterns

    # Count external integrations
    integration_keywords = [
        'oauth', 'saml', 'oidc', 'stripe', 'twilio', 'sendgrid',
        'aws', 'gcp', 'azure', 'firebase', 's3', 'redis', 'kafka',
        'elasticsearch', 'rabbitmq', 'webhook', 'socket.io', 'websocket',
        'graphql', 'grpc', 'docker', 'kubernetes', 'k8s'
    ]
    content_lower = content.lower()
    for kw in integration_keywords:
        if kw in content_lower:
            integrations.append(kw)

    # Count database mentions
    db_keywords = ['postgresql', 'postgres', 'mysql', 'mongodb', 'sqlite',
                   'dynamodb', 'cassandra', 'redis', 'prisma', 'typeorm',
                   'sequelize', 'drizzle', 'knex', 'database', 'migration']
    for kw in db_keywords:
        if kw in content_lower:
            databases.append(kw)

    # Count UI components
    ui_keywords = ['dashboard', 'form', 'table', 'modal', 'navbar',
                   'sidebar', 'chart', 'graph', 'calendar', 'kanban',
                   'drag.and.drop', 'responsive', 'mobile', 'animation']
    for kw in ui_keywords:
        if re.search(kw, content_lower):
            ui_components.append(kw)

feature_count = len(features)
endpoint_count = len(endpoints)
integration_count = len(integrations)
db_count = len(databases)
ui_count = len(ui_components)
section_count = len(sections)

# --- Determine complexity ---
complexity_score = 0
complexity_reasons = []

if prd_words < 200 and feature_count < 5:
    complexity = 'simple'
    complexity_reasons.append('Short PRD (<200 words, <5 features)')
elif prd_words > 3000 or feature_count > 30:
    complexity_score += 3
    complexity_reasons.append(f'Large PRD ({prd_words} words, {feature_count} features)')
elif prd_words > 1000 or feature_count > 15:
    complexity_score += 2
    complexity_reasons.append(f'Medium PRD ({prd_words} words, {feature_count} features)')
else:
    complexity_score += 1
    complexity_reasons.append(f'Standard PRD ({prd_words} words, {feature_count} features)')

if endpoint_count > 20:
    complexity_score += 2
    complexity_reasons.append(f'{endpoint_count} API endpoints detected')
elif endpoint_count > 5:
    complexity_score += 1
    complexity_reasons.append(f'{endpoint_count} API endpoints detected')

if integration_count > 3:
    complexity_score += 2
    complexity_reasons.append(str(integration_count) + ' external integrations: ' + ', '.join(integrations[:5]))
elif integration_count > 0:
    complexity_score += 1
    complexity_reasons.append(str(integration_count) + ' external integrations: ' + ', '.join(integrations))

if db_count > 1:
    complexity_score += 1
    complexity_reasons.append('Multiple data stores: ' + ', '.join(databases[:4]))

if ui_count > 5:
    complexity_score += 1
    complexity_reasons.append(f'{ui_count} UI components detected')

# Map score to tier
if complexity_score <= 1:
    complexity = 'simple'
elif complexity_score <= 3:
    complexity = 'moderate'
elif complexity_score <= 5:
    complexity = 'complex'
else:
    complexity = 'enterprise'

# --- Estimate iterations ---
iteration_map = {
    'simple': (3, 5),
    'moderate': (6, 12),
    'complex': (12, 24),
    'enterprise': (24, 48),
}
min_iter, max_iter = iteration_map[complexity]

# Adjust based on specific counts
if endpoint_count > 10:
    max_iter = max(max_iter, max_iter + endpoint_count // 5)
if integration_count > 2:
    max_iter += integration_count

estimated_iterations = (min_iter + max_iter) // 2

# --- Token estimation per iteration by RARV tier ---
# RARV cycle: 4 iterations per cycle (plan, develop, develop, test)
# Tokens estimated per iteration step
tokens_per_tier = {
    'planning': {'input': 50000, 'output': 8000, 'model': 'Opus'},
    'development': {'input': 80000, 'output': 15000, 'model': 'Sonnet'},
    'fast': {'input': 30000, 'output': 5000, 'model': 'Haiku'},
}

# Pricing per 1M tokens
pricing = {
    'Opus':   {'input': 15.00, 'output': 75.00},
    'Sonnet': {'input': 3.00, 'output': 15.00},
    'Haiku':  {'input': 0.25, 'output': 1.25},
}

# Build per-iteration plan
iteration_plan = []
total_input_tokens = 0
total_output_tokens = 0
total_cost = 0.0
tier_totals = {'Opus': 0.0, 'Sonnet': 0.0, 'Haiku': 0.0}
tier_iterations = {'Opus': 0, 'Sonnet': 0, 'Haiku': 0}

for i in range(estimated_iterations):
    rarv_step = i % 4
    if rarv_step == 0:
        phase_label = 'Reason (Planning)'
    elif rarv_step == 1:
        phase_label = 'Act (Implementation)'
    elif rarv_step == 2:
        phase_label = 'Reflect (Review)'
    else:
        phase_label = 'Verify (Testing)'

    if legacy_tier_switching:
        # Legacy pre-v6.81.0 behavior: rotate tier per RARV step.
        if rarv_step == 0:
            tier = 'planning'
        elif rarv_step in (1, 2):
            tier = 'development'
        else:
            tier = 'fast'
    else:
        # v6.81.0+: main loop is pinned to LOKI_SESSION_MODEL. Estimate
        # MUST use the same tier for all iterations to avoid quoting a
        # cost higher than the user will actually pay.
        tier = session_tier

    info = tokens_per_tier[tier]
    model = info['model']
    inp = info['input']
    out = info['output']

    cost = (inp / 1_000_000) * pricing[model]['input'] + \
           (out / 1_000_000) * pricing[model]['output']

    total_input_tokens += inp
    total_output_tokens += out
    total_cost += cost
    tier_totals[model] += cost
    tier_iterations[model] += 1

    iteration_plan.append({
        'iteration': i + 1,
        'phase': phase_label,
        'model': model,
        'input_tokens': inp,
        'output_tokens': out,
        'cost_usd': round(cost, 4),
    })

# --- Execution plan (what each cycle tackles) ---
execution_phases = []
cycle_count = math.ceil(estimated_iterations / 4)
phase_names = []

# Generate phase names based on PRD content
if sections:
    phase_names = sections[:cycle_count]
else:
    # Generic phases based on complexity
    generic = [
        'Project setup and scaffolding',
        'Core data models and database schema',
        'API endpoints and business logic',
        'Frontend components and pages',
        'Authentication and authorization',
        'External integrations',
        'Testing and quality assurance',
        'Performance optimization',
        'Documentation and deployment',
        'Edge cases and error handling',
        'Security hardening',
        'Final polish and review',
    ]
    phase_names = generic[:cycle_count]

for idx, name in enumerate(phase_names):
    execution_phases.append({
        'cycle': idx + 1,
        'iterations': f'{idx * 4 + 1}-{min((idx + 1) * 4, estimated_iterations)}',
        'focus': name,
    })

# --- Time estimation ---
# Average ~3.5 minutes per iteration (API calls + processing)
minutes_per_iteration = 3.5
total_minutes = estimated_iterations * minutes_per_iteration
if total_minutes < 60:
    time_estimate = f'{int(total_minutes)} minutes'
elif total_minutes < 120:
    time_estimate = f'{int(total_minutes // 60)} hour {int(total_minutes % 60)} minutes'
else:
    time_estimate = f'{total_minutes / 60:.1f} hours'

min_time = min_iter * minutes_per_iteration
max_time = max_iter * minutes_per_iteration

# --- Quality gates ---
quality_gates = [
    {'gate': 'Static Analysis', 'trigger': 'Every iteration', 'applies': True},
    {'gate': 'Unit Tests', 'trigger': 'Verify phase (every 4th iteration)', 'applies': True},
    {'gate': 'Code Review (3-reviewer)', 'trigger': 'After implementation cycles', 'applies': complexity in ('complex', 'enterprise')},
    {'gate': 'Anti-sycophancy Check', 'trigger': 'On unanimous review approval', 'applies': complexity in ('complex', 'enterprise')},
    {'gate': 'Integration Tests', 'trigger': 'Mid-project and final', 'applies': integration_count > 0 or endpoint_count > 3},
    {'gate': 'Coverage Gate (>80%)', 'trigger': 'Final verification', 'applies': True},
    {'gate': 'Security Scan', 'trigger': 'Before completion', 'applies': integration_count > 0},
    {'gate': 'Performance Benchmark', 'trigger': 'Before completion', 'applies': complexity in ('complex', 'enterprise')},
    {'gate': 'Completion Council', 'trigger': 'After all phases', 'applies': True},
]
active_gates = [g for g in quality_gates if g['applies']]

# --- Provider recommendation ---
if integration_count > 3 or complexity in ('complex', 'enterprise'):
    recommended_provider = 'Claude'
    provider_reason = 'Full feature support needed (subagents, parallel, MCP)'
elif complexity == 'simple' and endpoint_count == 0:
    recommended_provider = 'Any (Claude/Codex/Cline)'
    provider_reason = 'Simple project works with all providers'
else:
    recommended_provider = 'Claude'
    provider_reason = 'Best iteration quality and tool support'

# --- Output ---
if show_json:
    result = {
        'prd_file': prd_path,
        'prd_stats': {
            'words': prd_words,
            'lines': prd_lines,
            'sections': section_count,
            'features': feature_count,
            'endpoints': endpoint_count,
            'integrations': integrations,
            'databases': databases,
            'ui_components': ui_count,
        },
        'complexity': {
            'tier': complexity,
            'score': complexity_score,
            'reasons': complexity_reasons,
        },
        'iterations': {
            'estimated': estimated_iterations,
            'range': [min_iter, max_iter],
            'rarv_cycles': cycle_count,
        },
        'tokens': {
            'total_input': total_input_tokens,
            'total_output': total_output_tokens,
            'total': total_input_tokens + total_output_tokens,
        },
        'cost': {
            'total_usd': round(total_cost, 2),
            'by_model': {k: round(v, 2) for k, v in tier_totals.items()},
            'iterations_by_model': tier_iterations,
        },
        'time': {
            'estimated': time_estimate,
            'minutes': round(total_minutes, 1),
            'range_minutes': [round(min_time, 1), round(max_time, 1)],
        },
        'execution_plan': execution_phases,
        'quality_gates': [g['gate'] for g in active_gates],
        'provider': {
            'recommended': recommended_provider,
            'reason': provider_reason,
        },
    }
    if show_verbose:
        result['iteration_details'] = iteration_plan
    print(json.dumps(result, indent=2))
    sys.exit(0)

# --- Formatted output ---
print()
print(f'{BOLD}PRD Analysis: {os.path.basename(prd_path)}{NC}')
print(f'{DIM}' + '=' * 60 + f'{NC}')

# PRD Stats
print(f'\n{CYAN}PRD Statistics{NC}')
print(f'  Words: {prd_words}  |  Lines: {prd_lines}  |  Sections: {section_count}')
print(f'  Features: {feature_count}  |  Endpoints: {endpoint_count}  |  Integrations: {integration_count}')
if databases:
    print('  Data stores: ' + ', '.join(databases[:4]))
if ui_count > 0:
    print(f'  UI components: {ui_count}')

# Complexity
color = {'simple': GREEN, 'moderate': YELLOW, 'complex': RED, 'enterprise': RED}
cx_color = color.get(complexity, NC)
print(f'\n{CYAN}Complexity{NC}')
print(f'  Tier: {cx_color}{BOLD}{complexity.upper()}{NC} (score: {complexity_score})')
for reason in complexity_reasons:
    print(f'  {DIM}- {reason}{NC}')

# Iterations
print(f'\n{CYAN}Estimated Iterations{NC}')
print(f'  Count: {BOLD}{estimated_iterations}{NC} (range: {min_iter}-{max_iter})')
print(f'  RARV cycles: {cycle_count} (4 iterations per cycle)')
opus_n = tier_iterations.get('Opus', 0)
sonnet_n = tier_iterations.get('Sonnet', 0)
haiku_n = tier_iterations.get('Haiku', 0)
if legacy_tier_switching:
    print(f'  Model distribution: Opus x{opus_n} | Sonnet x{sonnet_n} | Haiku x{haiku_n}')
    print(f'  {DIM}(legacy rotation: LOKI_LEGACY_TIER_SWITCHING=true){NC}')
else:
    # Session-pinned: exactly one tier has all iterations.
    pinned_parts = []
    if opus_n > 0:
        pinned_parts.append(f'Opus x{opus_n}')
    if sonnet_n > 0:
        pinned_parts.append(f'Sonnet x{sonnet_n}')
    if haiku_n > 0:
        pinned_parts.append(f'Haiku x{haiku_n}')
    print('  Model distribution: ' + (' | '.join(pinned_parts) if pinned_parts else '(none)'))
    print(f'  {DIM}(pinned via LOKI_SESSION_MODEL={session_model_env}; set LOKI_LEGACY_TIER_SWITCHING=true to rotate){NC}')

# Tokens
total_tok = total_input_tokens + total_output_tokens
print(f'\n{CYAN}Token Usage Estimate{NC}')
print(f'  Input:  {total_input_tokens:>12,} tokens')
print(f'  Output: {total_output_tokens:>12,} tokens')
print(f'  Total:  {total_tok:>12,} tokens')

# Cost
ds = chr(36)  # dollar sign
print(f'\n{CYAN}Cost Estimate{NC}')
print(f'  {BOLD}Total: {ds}{total_cost:.2f}{NC}')
for model in ['Opus', 'Sonnet', 'Haiku']:
    # v6.81.1: suppress zero-cost tiers (e.g. session-pinned runs). Keeps
    # the breakdown focused on models actually used. Legacy mode still
    # shows all three because each tier has non-zero iterations.
    if tier_iterations.get(model, 0) == 0 and tier_totals.get(model, 0) == 0:
        continue
    pct = (tier_totals[model] / total_cost * 100) if total_cost > 0 else 0
    bar_len = int(pct / 5)
    bar = '#' * bar_len + '.' * (20 - bar_len)
    mc = tier_totals[model]
    print('  {:>6}: {}{:>7.2f} ({:4.1f}%) [{}]'.format(model, ds, mc, pct, bar))

# Time
print(f'\n{CYAN}Time Estimate{NC}')
print(f'  Estimated: {BOLD}{time_estimate}{NC}')
min_t_str = f'{int(min_time)} min' if min_time < 60 else f'{min_time/60:.1f} hr'
max_t_str = f'{int(max_time)} min' if max_time < 60 else f'{max_time/60:.1f} hr'
print(f'  Range: {min_t_str} - {max_t_str}')

# Execution plan
print(f'\n{CYAN}Execution Plan{NC}')
for phase in execution_phases:
    c_num = phase.get('cycle', '')
    c_iters = phase.get('iterations', '')
    c_focus = phase.get('focus', '')
    print(f'  {BOLD}Cycle {c_num}{NC} (iterations {c_iters}): {c_focus}')

# Quality gates
print(f'\n{CYAN}Quality Gates ({len(active_gates)} active){NC}')
for g in active_gates:
    g_name = g.get('gate', '')
    g_trig = g.get('trigger', '')
    print(f'  {GREEN}[*]{NC} {g_name} -- {DIM}{g_trig}{NC}')

# Provider recommendation
print(f'\n{CYAN}Recommended Provider{NC}')
print(f'  {BOLD}{recommended_provider}{NC}')
print(f'  {DIM}{provider_reason}{NC}')

# Verbose: per-iteration breakdown
if show_verbose:
    print(f'\n{CYAN}Per-Iteration Breakdown{NC}')
    hdr = '  ' + DIM + '{:>4}  {:<24} {:<7} {:>8} {:>8} {:>8}'.format('Iter', 'Phase', 'Model', 'Input', 'Output', 'Cost') + NC
    print(hdr)
    print('  ' + DIM + '-' * 65 + NC)
    for it in iteration_plan:
        i_num = it.get('iteration', 0)
        i_phase = it.get('phase', '')
        i_model = it.get('model', '')
        i_inp = it.get('input_tokens', 0)
        i_out = it.get('output_tokens', 0)
        i_cost = it.get('cost_usd', 0)
        print('  {:>4}  {:<24} {:<7} {:>7,} {:>7,} {}{:>6.2f}'.format(i_num, i_phase, i_model, i_inp, i_out, ds, i_cost))

print(f'\n{DIM}This is an estimate. Actual usage depends on PRD complexity,')
print(f'code review cycles, and test failures.{NC}')
print()
" "$prd_path" "$show_json" "$show_verbose"
}

# Dry-run PRD analysis and cost estimation (v6.18.0; refactored v6.81.1)
# Thin wrapper around show_prd_plan() so the same analysis is reusable from
# `loki start`, `loki run`, and other commands without duplicated code.
cmd_plan() {
    local prd_file=""
    local show_json=false
    local show_verbose=false

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki plan${NC} - Dry-run PRD analysis and cost estimation"
                echo ""
                echo "Usage: loki plan <PRD> [options]"
                echo ""
                echo "Analyzes a PRD file without executing anything. Outputs complexity,"
                echo "estimated iterations, token usage, cost, and execution plan."
                echo ""
                echo "Options:"
                echo "  --json        Machine-readable JSON output"
                echo "  --verbose     Show detailed per-iteration breakdown"
                echo "  --help, -h    Show this help"
                echo ""
                echo "Environment Variables:"
                echo "  LOKI_SESSION_MODEL            Pin cost estimate to a single tier"
                echo "                                (opus|sonnet|haiku, default: sonnet)"
                echo "  LOKI_LEGACY_TIER_SWITCHING    Set to 'true' to restore the legacy"
                echo "                                Opus/Sonnet/Haiku per-iteration rotation"
                echo ""
                echo "Examples:"
                echo "  loki plan ./prd.md"
                echo "  loki plan ./prd.md --json"
                echo "  loki plan ./prd.md --verbose"
                return 0
                ;;
            --json) show_json=true; shift ;;
            --verbose) show_verbose=true; shift ;;
            *)
                if [ -z "$prd_file" ]; then
                    prd_file="$1"
                fi
                shift
                ;;
        esac
    done

    if [ -z "$prd_file" ]; then
        echo -e "${RED}Usage: loki plan <PRD file>${NC}"
        echo "Run 'loki plan --help' for usage."
        return 1
    fi

    if [ ! -f "$prd_file" ]; then
        echo -e "${RED}PRD file not found: $prd_file${NC}"
        return 1
    fi

    local prd_path
    prd_path="$(cd "$(dirname "$prd_file")" && pwd)/$(basename "$prd_file")"

    show_prd_plan "$prd_path" "$show_json" "$show_verbose"
}

# Helper: decide whether to auto-show the plan from a major command.
# v6.81.1: default ON, opt-out via --no-plan. Also auto-skipped when stdout is
# not a TTY and --no-plan was not explicitly set (CI/automation contexts),
# logging a one-line "plan skipped: non-interactive" to stderr for visibility.
maybe_show_auto_plan() {
    local prd_path="$1"
    local no_plan_flag="${2:-false}"  # user passed --no-plan explicitly

    # Explicit opt-out wins.
    if [[ "$no_plan_flag" == "true" ]]; then
        return 0
    fi

    # PRD missing: silently skip (other commands will emit their own errors).
    if [ -z "$prd_path" ] || [ ! -f "$prd_path" ]; then
        return 0
    fi

    # Non-TTY heuristic: keep CI / pipe output clean. Log to stderr so it is
    # still surfaced in logs without corrupting stdout parsers.
    if [ ! -t 1 ]; then
        echo "plan skipped: non-interactive (pass --no-plan to silence, or run 'loki plan <PRD>' explicitly)" >&2
        return 0
    fi

    local abs_prd
    abs_prd="$(cd "$(dirname "$prd_path")" && pwd)/$(basename "$prd_path")"
    show_prd_plan "$abs_prd" "false" "false"
}

# Main command dispatcher
main() {
    # v7.5.18: early guard -- LOKI_PROVIDER=gemini is no longer supported.
    if [ "${LOKI_PROVIDER:-}" = "gemini" ]; then
        echo -e "${RED}Error: Provider 'gemini' is deprecated as of v7.5.18 and has been removed.${NC}" >&2
        echo "Active providers: claude, codex, cline, aider" >&2
        echo "Unset LOKI_PROVIDER or use: LOKI_PROVIDER=claude" >&2
        exit 1
    fi

    if [ $# -eq 0 ]; then
        show_help
        exit 0
    fi

    local command="$1"
    shift

    # v7.4.13: first-run telemetry moved to bin/loki shim so it fires for
    # both Bun-routed and bash-routed commands (autonomy/loki main() never
    # runs for the 8 ported commands). Marker file: ~/.loki-first-run.
    loki_telemetry "cli_command" "command=$command" 2>/dev/null || true

    case "$command" in
        run)
            cmd_run "$@"
            ;;
        start)
            cmd_start "$@"
            ;;
        quick)
            cmd_quick "$@"
            ;;
        monitor)
            cmd_monitor "$@"
            ;;
        demo)
            cmd_demo "$@"
            ;;
        welcome)
            cmd_welcome "$@"
            ;;
        init)
            cmd_init "$@"
            ;;
        stop)
            cmd_stop "$@"
            ;;
        cleanup)
            cmd_cleanup "$@"
            ;;
        pause)
            cmd_pause "$@"
            ;;
        resume)
            cmd_resume "$@"
            ;;
        status)
            cmd_status "$@"
            ;;
        stats)
            cmd_stats "$@"
            ;;
        dashboard)
            cmd_dashboard "$@"
            ;;
        web)
            cmd_web "$@"
            ;;
        logs)
            cmd_logs "$@"
            ;;
        serve)
            # v7.6.2 B-12 fix: 'serve --help' previously routed to 'cmd_api start --help'
            # which started the dashboard as a side effect instead of printing help.
            case "${1:-}" in
                --help|-h|help)
                    echo -e "${BOLD}Loki Mode -- start dashboard API server${NC}"
                    echo ""
                    echo "Usage: loki serve [options]"
                    echo ""
                    echo "Alias for 'loki api start'. Starts the dashboard HTTP API"
                    echo "server (FastAPI, port 57374 by default)."
                    echo ""
                    echo "Options:"
                    echo "  --host HOST    Bind host (default: 127.0.0.1)"
                    echo "  --port PORT    Bind port (default: 57374)"
                    echo "  --help, -h     Show this help and exit"
                    echo ""
                    echo "Side effects: starts a long-running HTTP server bound to a TCP port."
                    echo "Stop with: loki dashboard stop"
                    return 0
                    ;;
            esac
            cmd_api start "$@"
            ;;
        api)
            cmd_api "$@"
            ;;
        sandbox)
            cmd_sandbox "$@"
            ;;
        notify)
            cmd_notify "$@"
            ;;
        import)
            cmd_import "$@"
            ;;
        github)
            cmd_github "$@"
            ;;
        issue)
            cmd_issue "$@"
            ;;
        watch)
            cmd_watch "$@"
            ;;
        export)
            cmd_export "$@"
            ;;
        assets)
            cmd_assets "$@"
            ;;
        config)
            cmd_config "$@"
            ;;
        provider)
            cmd_provider "$@"
            ;;
        reset)
            cmd_reset "$@"
            ;;
        memory)
            cmd_memory "$@"
            ;;
        compound)
            cmd_compound "$@"
            ;;
        checkpoint|cp)
            cmd_checkpoint "$@"
            ;;
        rollback)
            cmd_rollback "$@"
            ;;
        council)
            cmd_council "$@"
            ;;
        dogfood)
            cmd_dogfood "$@"
            ;;
        projects)
            cmd_projects "$@"
            ;;
        enterprise)
            cmd_enterprise "$@"
            ;;
        voice)
            echo "Voice mode is planned for a future release. Track progress at github.com/asklokesh/loki-mode/issues/85"
            exit 0
            ;;
        secrets)
            cmd_secrets "$@"
            ;;
        doctor)
            cmd_doctor "$@"
            ;;
        sentrux)
            cmd_sentrux "$@"
            ;;
        setup-skill)
            cmd_setup_skill "$@"
            ;;
        self-update|self_update|update)
            cmd_self_update "$@"
            ;;
        watchdog)
            cmd_watchdog "$@"
            ;;
        audit)
            cmd_audit "$@"
            ;;
        review)
            cmd_review "$@"
            ;;
        optimize)
            cmd_optimize "$@"
            ;;
        heal)
            cmd_heal "$@"
            ;;
        migrate)
            cmd_migrate "$@"
            ;;
        cluster)
            cmd_cluster "$@"
            ;;
        worktree|wt)
            cmd_worktree "$@"
            ;;
        agent)
            cmd_agent "$@"
            ;;
        template)
            cmd_template "$@"
            ;;
        state)
            cmd_state "$@"
            ;;
        metrics)
            cmd_metrics "$@"
            ;;
        cost)
            cmd_cost "$@"
            ;;
        trust)
            cmd_trust "$@"
            ;;
        syslog)
            cmd_syslog "$@"
            ;;
        telemetry|otel)
            cmd_telemetry "$@"
            ;;
        remote|rc)
            cmd_remote "$@"
            ;;
        trigger)
            cmd_trigger "$@"
            ;;
        plan)
            cmd_plan "$@"
            ;;
        failover)
            cmd_failover "$@"
            ;;
        onboard)
            cmd_onboard "$@"
            ;;
        explain)
            cmd_explain "$@"
            ;;
        docs)
            cmd_docs "$@"
            ;;
        wiki)
            cmd_wiki "$@"
            ;;
        magic)
            cmd_magic "$@"
            ;;
        ci)
            cmd_ci "$@"
            ;;
        test)
            cmd_test "$@"
            ;;
        report)
            cmd_report "$@"
            ;;
        crash)
            cmd_crash "$@"
            ;;
        share)
            cmd_share "$@"
            ;;
        proof)
            cmd_proof "$@"
            ;;
        bench)
            cmd_bench "$@"
            ;;
        context|ctx)
            cmd_context "$@"
            ;;
        code)
            cmd_code "$@"
            ;;
        version|--version|-v)
            cmd_version "$@"
            ;;
	completions)
            cmd_completions "$@"
            ;;
        help|--help|-h)
            show_help
            ;;
        *)
            echo -e "${RED}Unknown command: $command${NC}"
            echo "Run 'loki help' for usage."
            exit 1
            ;;
    esac
}
# Standalone code review - diff-based quality gates (v6.20.0)
cmd_review() {
    local review_staged=false
    local review_pr=""
    local review_since=""
    local review_target=""
    local review_format="text"
    local review_severity="all"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki review${NC} - Standalone code review with quality gates"
                echo ""
                echo "Usage: loki review [options] [file-or-dir]"
                echo ""
                echo "Reviews code changes using static analysis, security scanning,"
                echo "style checks, and anti-pattern detection. No AI needed."
                echo ""
                echo "Sources (default: uncommitted changes via git diff):"
                echo "  --staged             Review staged changes only"
                echo "  --pr <number>        Review a GitHub PR"
                echo "  --since <commit>     Review changes since a commit"
                echo "  <file-or-dir>        Review specific files or directory"
                echo ""
                echo "Options:"
                echo "  --format json        Output as JSON (for CI integration)"
                echo "  --severity <level>   Filter: critical, high, medium, low, info (shows level+above)"
                echo "  --help, -h           Show this help"
                echo ""
                echo "Exit codes:"
                echo "  0  No HIGH or CRITICAL findings"
                echo "  1  HIGH severity findings present"
                echo "  2  CRITICAL severity findings present"
                echo ""
                echo "Examples:"
                echo "  loki review                     # Review uncommitted changes"
                echo "  loki review --staged             # Review staged changes"
                echo "  loki review --pr 42              # Review GitHub PR #42"
                echo "  loki review src/                 # Review all files in src/"
                echo "  loki review --since HEAD~5       # Changes in last 5 commits"
                echo "  loki review --format json        # JSON output for CI"
                echo "  loki review --severity high      # Only HIGH+ findings"
                return 0
                ;;
            --staged) review_staged=true; shift ;;
            --pr)
                shift
                review_pr="${1:-}"
                if [ -z "$review_pr" ]; then
                    echo -e "${RED}Error: --pr requires a PR number${NC}"
                    return 1
                fi
                shift
                ;;
            --since)
                shift
                review_since="${1:-}"
                if [ -z "$review_since" ]; then
                    echo -e "${RED}Error: --since requires a commit reference${NC}"
                    return 1
                fi
                shift
                ;;
            --format)
                shift
                review_format="${1:-text}"
                shift
                ;;
            --severity)
                shift
                review_severity="${1:-all}"
                review_severity="$(echo "$review_severity" | tr '[:upper:]' '[:lower:]')"
                shift
                ;;
            -*) echo -e "${RED}Unknown option: $1${NC}"; return 1 ;;
            *) review_target="$1"; shift ;;
        esac
    done

    # Severity level ordering (higher number = more severe)
    _review_sev_level() {
        case "$1" in
            INFO) echo 1 ;;
            LOW) echo 2 ;;
            MEDIUM) echo 3 ;;
            HIGH) echo 4 ;;
            CRITICAL) echo 5 ;;
            *) echo 0 ;;
        esac
    }

    local min_sev=0
    case "$review_severity" in
        info) min_sev=1 ;;
        low) min_sev=2 ;;
        medium) min_sev=3 ;;
        high) min_sev=4 ;;
        critical) min_sev=5 ;;
        all) min_sev=0 ;;
    esac

    # Gather code to review
    local diff_content=""
    local review_files=""
    local source_desc=""

    if [ -n "$review_pr" ]; then
        # PR review via gh CLI
        if ! command -v gh &>/dev/null; then
            echo -e "${RED}Error: gh CLI required for PR review. Install: https://cli.github.com${NC}"
            return 1
        fi
        diff_content=$(gh pr diff "$review_pr" 2>&1) || {
            echo -e "${RED}Error: Could not fetch PR #${review_pr}: $diff_content${NC}"
            return 1
        }
        source_desc="PR #${review_pr}"
    elif [ -n "$review_since" ]; then
        diff_content=$(git diff "$review_since" 2>&1) || {
            echo -e "${RED}Error: Invalid commit reference: $review_since${NC}"
            return 1
        }
        source_desc="changes since $review_since"
    elif [ -n "$review_target" ]; then
        # File or directory target - get full contents
        if [ -d "$review_target" ]; then
            review_files=$(find "$review_target" -type f \
                \( -name "*.sh" -o -name "*.bash" -o -name "*.py" -o -name "*.js" -o -name "*.ts" \
                -o -name "*.jsx" -o -name "*.tsx" -o -name "*.go" -o -name "*.rs" -o -name "*.rb" \
                -o -name "*.java" -o -name "*.c" -o -name "*.cpp" -o -name "*.h" \) \
                -not -path "*/node_modules/*" -not -path "*/.git/*" \
                -not -path "*/vendor/*" -not -path "*/__pycache__/*" \
                -not -path "*/.venv/*" -not -path "*/target/*" 2>/dev/null)
            source_desc="directory $review_target"
        elif [ -f "$review_target" ]; then
            review_files="$review_target"
            source_desc="file $review_target"
        else
            echo -e "${RED}Error: Not found: $review_target${NC}"
            return 1
        fi
    elif [ "$review_staged" = true ]; then
        diff_content=$(git diff --cached 2>/dev/null)
        source_desc="staged changes"
    else
        diff_content=$(git diff 2>/dev/null)
        source_desc="uncommitted changes"
    fi

    # Check if there is anything to review
    if [ -z "$diff_content" ] && [ -z "$review_files" ]; then
        if [ "$review_format" = "json" ]; then
            echo '{"source":"'"$source_desc"'","findings":[],"summary":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"total":0},"exit_code":0}'
        else
            echo -e "${GREEN}No changes to review ($source_desc).${NC}"
        fi
        return 0
    fi

    # Build file content for non-diff reviews
    local all_content=""
    if [ -n "$review_files" ]; then
        while IFS= read -r f; do
            [ -z "$f" ] && continue
            all_content+="=== FILE: $f ===
$(cat "$f" 2>/dev/null)
"
        done <<< "$review_files"
    else
        all_content="$diff_content"
    fi

    # Findings array: "file|line|severity|category|finding|suggestion"
    local findings=()
    local has_critical=false
    local has_high=false

    # Helper to add a finding
    _add_finding() {
        local file="$1" line="$2" sev="$3" cat="$4" finding="$5" suggestion="$6"
        local sev_num
        sev_num=$(_review_sev_level "$sev")
        if [ "$sev_num" -ge "$min_sev" ]; then
            findings+=("${file}|${line}|${sev}|${cat}|${finding}|${suggestion}")
            [ "$sev" = "CRITICAL" ] && has_critical=true || true
            [ "$sev" = "HIGH" ] && has_high=true || true
        fi
    }

    # --- Gate 1: Static Analysis ---
    # Run shellcheck on shell files if available
    if command -v shellcheck &>/dev/null; then
        local shell_files=""
        if [ -n "$review_files" ]; then
            shell_files=$(echo "$review_files" | grep -E '\.(sh|bash)$' || true)
        elif [ -n "$diff_content" ]; then
            shell_files=$(echo "$diff_content" | grep -E '^\+\+\+ b/' | sed 's|^+++ b/||' | grep -E '\.(sh|bash)$' || true)
        fi
        if [ -n "$shell_files" ]; then
            while IFS= read -r sf; do
                [ -z "$sf" ] && continue
                [ ! -f "$sf" ] && continue
                local sc_out
                sc_out=$(shellcheck -f gcc "$sf" 2>/dev/null || true)
                while IFS= read -r sc_line; do
                    [ -z "$sc_line" ] && continue
                    local sc_file sc_lineno sc_sev sc_msg
                    sc_file=$(echo "$sc_line" | cut -d: -f1)
                    sc_lineno=$(echo "$sc_line" | cut -d: -f2)
                    sc_sev=$(echo "$sc_line" | sed -n 's/.*: \(warning\|error\|note\|info\):.*/\1/p')
                    sc_msg=$(echo "$sc_line" | sed 's/.*: \(warning\|error\|note\|info\): //')
                    local mapped_sev="LOW"
                    case "$sc_sev" in
                        error) mapped_sev="HIGH" ;;
                        warning) mapped_sev="MEDIUM" ;;
                        *) mapped_sev="LOW" ;;
                    esac
                    _add_finding "$sc_file" "$sc_lineno" "$mapped_sev" "static-analysis" "$sc_msg" "Fix shellcheck finding"
                done <<< "$sc_out"
            done <<< "$shell_files"
        fi
    fi

    # Run eslint if available on JS/TS files
    if command -v npx &>/dev/null; then
        local js_files=""
        if [ -n "$review_files" ]; then
            js_files=$(echo "$review_files" | grep -E '\.(js|ts|jsx|tsx)$' || true)
        elif [ -n "$diff_content" ]; then
            js_files=$(echo "$diff_content" | grep -E '^\+\+\+ b/' | sed 's|^+++ b/||' | grep -E '\.(js|ts|jsx|tsx)$' || true)
        fi
        if [ -n "$js_files" ]; then
            while IFS= read -r jsf; do
                [ -z "$jsf" ] && continue
                [ ! -f "$jsf" ] && continue
                if [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ] || [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ]; then
                    local eslint_out
                    eslint_out=$(npx eslint --format compact "$jsf" 2>/dev/null || true)
                    while IFS= read -r el; do
                        [ -z "$el" ] && continue
                        [[ "$el" == *"problem"* ]] && continue
                        local el_file el_line el_msg
                        el_file=$(echo "$el" | cut -d: -f1)
                        el_line=$(echo "$el" | cut -d: -f2)
                        el_msg=$(echo "$el" | sed 's/^[^)]*) //')
                        local el_sev="LOW"
                        [[ "$el" == *"Error"* ]] && el_sev="MEDIUM"
                        _add_finding "$el_file" "$el_line" "$el_sev" "static-analysis" "$el_msg" "Fix eslint finding"
                    done <<< "$eslint_out"
                fi
            done <<< "$js_files"
        fi
    fi

    # --- Gate 2: Security Scan ---
    # Helper: scan for a pattern across files or diff content
    _review_scan() {
        local pattern="$1" sev="$2" cat="$3" finding="$4" suggestion="$5"
        if [ -n "$review_files" ]; then
            # Scan actual files with grep -Hn (file:line:content)
            local scan_files_arr=()
            while IFS= read -r sf; do
                [ -z "$sf" ] && continue
                [ -f "$sf" ] && scan_files_arr+=("$sf")
            done <<< "$review_files"
            [ ${#scan_files_arr[@]} -eq 0 ] && return
            while IFS= read -r match; do
                [ -z "$match" ] && continue
                local mf ml
                mf=$(echo "$match" | cut -d: -f1)
                ml=$(echo "$match" | cut -d: -f2)
                _add_finding "$mf" "$ml" "$sev" "$cat" "$finding" "$suggestion"
            done < <(grep -HnE "$pattern" "${scan_files_arr[@]}" 2>/dev/null || true)
        else
            # Scan diff content (line numbers are from the text blob)
            while IFS= read -r match; do
                [ -z "$match" ] && continue
                local ml
                ml=$(echo "$match" | cut -d: -f1)
                _add_finding "diff" "$ml" "$sev" "$cat" "$finding" "$suggestion"
            done < <(echo "$all_content" | grep -nE "$pattern" 2>/dev/null || true)
        fi
    }

    # Hardcoded secrets
    local secret_patterns=(
        'API_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
        'SECRET_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
        'PASSWORD\s*[=:]\s*["\x27][^\x27"]{4,}'
        'PRIVATE_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
        'AWS_ACCESS_KEY_ID\s*[=:]\s*["\x27]AK[A-Z0-9]{18}'
        'ghp_[A-Za-z0-9]{36}'
        'sk-[A-Za-z0-9]{32,}'
        'Bearer\s+[A-Za-z0-9._-]{20,}'
    )
    for pattern in "${secret_patterns[@]}"; do
        _review_scan "$pattern" "CRITICAL" "security" \
            "Potential hardcoded secret detected" \
            "Use environment variables or a secrets manager instead"
    done

    # SQL injection patterns
    _review_scan '(SELECT|INSERT|UPDATE|DELETE|DROP)\s.*\+\s*(req\.|request\.|params\.|user)' \
        "HIGH" "security" \
        "Potential SQL injection: string concatenation in query" \
        "Use parameterized queries or prepared statements"

    # eval/exec usage
    _review_scan '(^|\s)(eval|exec)\s*\(' \
        "HIGH" "security" \
        "Dangerous eval/exec usage detected" \
        "Avoid eval/exec with dynamic input; use safer alternatives"

    # Unsafe deserialization
    _review_scan '(pickle\.loads?|yaml\.load\s*\()' \
        "HIGH" "security" \
        "Unsafe deserialization detected (pickle/yaml.load)" \
        "Use yaml.safe_load or avoid pickle with untrusted data"

    # --- Gate 3: Code Style ---
    # Long functions (>100 lines heuristic from diff)
    if [ -n "$review_files" ]; then
        while IFS= read -r f; do
            [ -z "$f" ] && continue
            [ ! -f "$f" ] && continue
            local line_count
            line_count=$(wc -l < "$f" | tr -d ' ')
            if [ "$line_count" -gt 500 ]; then
                _add_finding "$f" "1" "MEDIUM" "style" \
                    "File has $line_count lines (>500)" \
                    "Consider breaking into smaller modules"
            fi
            # Check for very long lines
            local long_lines
            long_lines=$(awk 'length > 200 {count++} END {print count+0}' "$f" 2>/dev/null)
            if [ "$long_lines" -gt 5 ]; then
                _add_finding "$f" "1" "LOW" "style" \
                    "$long_lines lines exceed 200 characters" \
                    "Break long lines for readability"
            fi
        done <<< "$review_files"
    fi

    # TODO/FIXME/HACK/XXX detection
    _review_scan '(TODO|FIXME|HACK|XXX):' "INFO" "style" \
        "TODO/FIXME/HACK/XXX comment marker found" \
        "Track in issue tracker instead of code comments"

    # --- Gate 4: Anti-pattern Detection ---
    # console.log left in code (JS/TS)
    _review_scan 'console\.(log|debug|warn)\(' "LOW" "anti-pattern" \
        "console.log statement (debug artifact?)" \
        "Remove debug logging or use a proper logger"

    # Catch bare except in Python
    _review_scan 'except\s*:' "MEDIUM" "anti-pattern" \
        "Bare except clause catches all exceptions" \
        "Catch specific exceptions (e.g., except ValueError)"

    # Disabled SSL verification
    _review_scan '(verify\s*=\s*False|VERIFY_SSL\s*=\s*False|NODE_TLS_REJECT_UNAUTHORIZED.*0|rejectUnauthorized.*false)' \
        "HIGH" "anti-pattern" \
        "SSL verification disabled" \
        "Enable SSL verification in production"

    # Hardcoded IP addresses (skip localhost/0.0.0.0/version patterns)
    _review_scan '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' \
        "LOW" "anti-pattern" \
        "Hardcoded IP address detected" \
        "Use configuration or DNS names instead"

    # --- Tally results ---
    local count_critical=0 count_high=0 count_medium=0 count_low=0 count_info=0
    for f in "${findings[@]}"; do
        local sev
        sev=$(echo "$f" | cut -d'|' -f3)
        case "$sev" in
            CRITICAL) count_critical=$((count_critical + 1)) ;;
            HIGH) count_high=$((count_high + 1)) ;;
            MEDIUM) count_medium=$((count_medium + 1)) ;;
            LOW) count_low=$((count_low + 1)) ;;
            INFO) count_info=$((count_info + 1)) ;;
        esac
    done
    local count_total=${#findings[@]}

    # Determine exit code
    local exit_code=0
    [ "$has_high" = true ] && exit_code=1 || true
    [ "$has_critical" = true ] && exit_code=2 || true

    # --- Output ---
    if [ "$review_format" = "json" ]; then
        local json_findings="["
        local first=true
        for f in "${findings[@]}"; do
            local f_file f_line f_sev f_cat f_finding f_suggestion
            f_file=$(echo "$f" | cut -d'|' -f1)
            f_line=$(echo "$f" | cut -d'|' -f2)
            f_sev=$(echo "$f" | cut -d'|' -f3)
            f_cat=$(echo "$f" | cut -d'|' -f4)
            f_finding=$(echo "$f" | cut -d'|' -f5)
            f_suggestion=$(echo "$f" | cut -d'|' -f6)
            # Escape quotes for JSON
            f_finding=$(echo "$f_finding" | sed 's/"/\\"/g')
            f_suggestion=$(echo "$f_suggestion" | sed 's/"/\\"/g')
            f_file=$(echo "$f_file" | sed 's/"/\\"/g')
            [ "$first" = true ] && first=false || json_findings+=","
            json_findings+="{\"file\":\"$f_file\",\"line\":$f_line,\"severity\":\"$f_sev\",\"category\":\"$f_cat\",\"finding\":\"$f_finding\",\"suggestion\":\"$f_suggestion\"}"
        done
        json_findings+="]"
        printf '{"source":"%s","findings":%s,"summary":{"critical":%d,"high":%d,"medium":%d,"low":%d,"info":%d,"total":%d},"exit_code":%d}\n' \
            "$source_desc" "$json_findings" "$count_critical" "$count_high" "$count_medium" "$count_low" "$count_info" "$count_total" "$exit_code"
    else
        echo -e "${BOLD}Loki Code Review${NC}"
        echo -e "Source: ${CYAN}$source_desc${NC}"
        echo "---"

        if [ "$count_total" -eq 0 ]; then
            echo -e "${GREEN}No findings. Code looks clean.${NC}"
        else
            # Group by severity
            for sev_name in CRITICAL HIGH MEDIUM LOW INFO; do
                local printed_header=false
                for f in "${findings[@]}"; do
                    local f_sev
                    f_sev=$(echo "$f" | cut -d'|' -f3)
                    [ "$f_sev" != "$sev_name" ] && continue
                    if [ "$printed_header" = false ]; then
                        local sev_color="$NC"
                        case "$sev_name" in
                            CRITICAL) sev_color="$RED" ;;
                            HIGH) sev_color="$RED" ;;
                            MEDIUM) sev_color="$YELLOW" ;;
                            LOW) sev_color="$CYAN" ;;
                            INFO) sev_color="$DIM" ;;
                        esac
                        echo ""
                        echo -e "${sev_color}${BOLD}[$sev_name]${NC}"
                        printed_header=true
                    fi
                    local f_file f_line f_cat f_finding f_suggestion
                    f_file=$(echo "$f" | cut -d'|' -f1)
                    f_line=$(echo "$f" | cut -d'|' -f2)
                    f_cat=$(echo "$f" | cut -d'|' -f4)
                    f_finding=$(echo "$f" | cut -d'|' -f5)
                    f_suggestion=$(echo "$f" | cut -d'|' -f6)
                    echo -e "  ${DIM}$f_file:$f_line${NC} [$f_cat] $f_finding"
                    echo -e "    -> $f_suggestion"
                done
            done
        fi

        echo ""
        echo "---"
        echo -e "Summary: ${RED}$count_critical critical${NC}, ${RED}$count_high high${NC}, ${YELLOW}$count_medium medium${NC}, ${CYAN}$count_low low${NC}, ${DIM}$count_info info${NC} ($count_total total)"

        if [ "$exit_code" -eq 2 ]; then
            echo -e "${RED}CRITICAL findings detected. Review required.${NC}"
        elif [ "$exit_code" -eq 1 ]; then
            echo -e "${RED}HIGH severity findings detected.${NC}"
        else
            echo -e "${GREEN}No HIGH or CRITICAL findings.${NC}"
        fi
    fi

    return "$exit_code"
}


# Worktree management (v6.7.0)
cmd_worktree() {
    local subcommand="${1:-list}"
    shift 2>/dev/null || true

    case "$subcommand" in
        list)
            echo -e "${BOLD}Worktree Status${NC}"
            echo ""

            # List git worktrees
            local worktrees
            worktrees=$(git worktree list --porcelain 2>/dev/null)
            if [ -z "$worktrees" ]; then
                echo "  No active worktrees"
                return 0
            fi

            local count=0
            local current_wt=""
            while IFS= read -r line; do
                case "$line" in
                    "worktree "*)
                        current_wt="${line#worktree }"
                        ;;
                    "branch "*)
                        local branch="${line#branch refs/heads/}"
                        local status="running"
                        local stream_name=""

                        # Check for merge signal (branches named parallel-<stream> by run.sh)
                        if [[ "$branch" == parallel-* ]] || [[ "$branch" == loki-parallel-* ]]; then
                            stream_name="${branch#parallel-}"
                            stream_name="${stream_name#loki-}"
                            if [ -f ".loki/signals/MERGE_REQUESTED_${stream_name}" ]; then
                                status="merge-ready"
                            elif [ -f ".loki/signals/WORKTREE_FAILED_${stream_name}" ]; then
                                status="failed"
                            fi
                        fi

                        # Skip main worktree
                        if [ "$current_wt" != "$(git rev-parse --show-toplevel 2>/dev/null)" ]; then
                            case "$status" in
                                merge-ready) echo -e "  ${GREEN}[merge-ready]${NC} $branch  ${DIM}$current_wt${NC}" ;;
                                failed)      echo -e "  ${RED}[failed]${NC}      $branch  ${DIM}$current_wt${NC}" ;;
                                *)           echo -e "  ${CYAN}[active]${NC}      $branch  ${DIM}$current_wt${NC}" ;;
                            esac
                            count=$((count + 1))
                        fi
                        current_wt=""
                        ;;
                esac
            done <<< "$worktrees"

            if [ "$count" -eq 0 ]; then
                echo "  No parallel worktrees (only main)"
            fi
            echo ""
            echo "  Total: $count worktree(s)"
            ;;

        merge)
            local name="${1:-}"
            if [ -z "$name" ]; then
                echo -e "${RED}Usage: loki worktree merge <name>${NC}"
                return 1
            fi

            echo -e "${BOLD}Merging worktree: $name${NC}"
            local signal_file=".loki/signals/MERGE_REQUESTED_${name}"
            if [ ! -f "$signal_file" ]; then
                echo -e "${RED}No merge signal found for: $name${NC}"
                echo "Use 'loki worktree list' to see available worktrees."
                return 1
            fi

            # Validate JSON is parseable and contains required fields
            if ! LOKI_SIGNAL_FILE="$signal_file" python3 << 'MERGE_VALIDATE_PY'
import json, sys, os
try:
    data = json.load(open(os.environ['LOKI_SIGNAL_FILE']))
    assert data.get('branch'), 'missing branch'
    assert data.get('worktree'), 'missing worktree'
except Exception as e:
    print(str(e), file=sys.stderr)
    sys.exit(1)
MERGE_VALIDATE_PY
            then
                echo -e "${RED}Invalid or corrupted merge signal file${NC}"
                return 1
            fi

            local branch
            branch=$(LOKI_SIGNAL_FILE="$signal_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_SIGNAL_FILE']))['branch'])" 2>/dev/null)
            local worktree_path
            worktree_path=$(LOKI_SIGNAL_FILE="$signal_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_SIGNAL_FILE']))['worktree'])" 2>/dev/null)

            # Validate branch was extracted successfully
            if [ -z "$branch" ]; then
                echo -e "${RED}Could not extract branch name from merge signal${NC}"
                return 1
            fi

            # Verify branch exists before attempting merge
            if ! git rev-parse --verify "$branch" &>/dev/null; then
                echo -e "${RED}Branch '$branch' does not exist${NC}"
                echo "The worktree may have been cleaned up already."
                rm -f "$signal_file"
                return 1
            fi

            if git merge --no-ff "$branch" -m "merge($name): auto-merge from parallel worktree"; then
                echo -e "${GREEN}Merge successful${NC}"
                rm -f "$signal_file"
                if [ -n "$worktree_path" ] && [ -d "$worktree_path" ]; then
                    git worktree remove "$worktree_path" --force 2>/dev/null || true
                    git branch -d "$branch" 2>/dev/null || true
                fi
            else
                echo -e "${RED}Merge conflict - resolve manually${NC}"
                git merge --abort 2>/dev/null || true
                return 1
            fi
            ;;

        clean)
            echo -e "${BOLD}Cleaning all worktrees...${NC}"
            local removed=0
            local main_wt
            main_wt=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
            # BUG-PU-001: Track branches to clean up after worktree removal
            local branches_to_delete=()
            local current_wt_clean=""
            local current_branch_clean=""
            while IFS= read -r line; do
                case "$line" in
                    "worktree "*)
                        current_wt_clean="${line#worktree }"
                        ;;
                    "branch "*)
                        current_branch_clean="${line#branch refs/heads/}"
                        if [ -n "$current_wt_clean" ] && [ "$current_wt_clean" != "$main_wt" ]; then
                            # Kill any running session in this worktree before removing
                            local wt_pid_file="$current_wt_clean/.loki/loki.pid"
                            if [ -f "$wt_pid_file" ]; then
                                local wt_pid
                                wt_pid=$(cat "$wt_pid_file" 2>/dev/null)
                                if [ -n "$wt_pid" ] && kill -0 "$wt_pid" 2>/dev/null; then
                                    echo "  Stopping session in $current_wt_clean (PID: $wt_pid)..."
                                    kill "$wt_pid" 2>/dev/null || true
                                    sleep 1
                                    kill -0 "$wt_pid" 2>/dev/null && kill -9 "$wt_pid" 2>/dev/null || true
                                fi
                            fi
                            echo "  Removing: $current_wt_clean"
                            git worktree remove "$current_wt_clean" --force 2>/dev/null || true
                            removed=$((removed + 1))
                            # Queue branch for deletion (can only delete after worktree is gone)
                            if [[ "$current_branch_clean" == loki-parallel-* ]] || \
                               [[ "$current_branch_clean" == parallel-* ]]; then
                                branches_to_delete+=("$current_branch_clean")
                            fi
                        fi
                        current_wt_clean=""
                        current_branch_clean=""
                        ;;
                esac
            done <<< "$(git worktree list --porcelain 2>/dev/null)"
            # Prune worktree metadata for any already-removed directories
            git worktree prune 2>/dev/null || true
            # Clean up orphaned parallel branches
            for branch in "${branches_to_delete[@]}"; do
                git branch -D "$branch" 2>/dev/null || true
            done
            rm -f .loki/signals/MERGE_REQUESTED_* .loki/signals/WORKTREE_FAILED_* 2>/dev/null || true
            echo -e "${GREEN}Cleanup complete ($removed worktrees removed)${NC}"
            ;;

        status)
            echo -e "${BOLD}Parallel Orchestrator Status${NC}"
            echo ""

            local merge_ready=0
            local failed=0
            local active=0

            for f in .loki/signals/MERGE_REQUESTED_*; do
                [ -f "$f" ] && merge_ready=$((merge_ready + 1))
            done
            for f in .loki/signals/WORKTREE_FAILED_*; do
                [ -f "$f" ] && failed=$((failed + 1))
            done

            local total_wt
            total_wt=$(git worktree list 2>/dev/null | wc -l | tr -d ' ')
            total_wt=$((total_wt - 1))  # Exclude main
            [ "$total_wt" -lt 0 ] && total_wt=0
            active=$((total_wt - merge_ready - failed))
            [ "$active" -lt 0 ] && active=0

            echo "  Active worktrees:  $active"
            echo "  Merge-ready:       $merge_ready"
            echo "  Failed:            $failed"
            echo "  Total:             $total_wt"
            ;;

        --help|-h|help)
            echo -e "${BOLD}loki worktree${NC} - Manage parallel worktrees (v6.7.0)"
            echo ""
            echo "Usage: loki worktree <command>"
            echo ""
            echo "Commands:"
            echo "  list       List active worktrees with status"
            echo "  merge NAME Merge a completed worktree back"
            echo "  clean      Remove all worktrees"
            echo "  status     Show parallel orchestrator state"
            echo ""
            ;;
        *)
            echo -e "${RED}Unknown worktree command: $subcommand${NC}"
            echo "Run 'loki worktree help' for usage."
            return 1
            ;;
    esac
}

# SQLite queryable state inspection
cmd_state() {
    local subcmd="${1:-help}"
    shift 2>/dev/null || true

    case "$subcmd" in
        --help|-h|help)
            echo -e "${BOLD}loki state${NC} - Query the SQLite state layer (v6.3.0)"
            echo ""
            echo "Usage: loki state <command> [options]"
            echo ""
            echo "Commands:"
            echo "  db                 Print path to SQLite database file"
            echo "  query events       Query events [--agent ID] [--type TYPE] [--limit N]"
            echo "  query messages     Query messages [--topic TOPIC] [--cluster ID] [--limit N]"
            echo "  query checkpoints  Query checkpoints [--migration ID] [--limit N]"
            echo ""
            echo "Examples:"
            echo "  loki state db"
            echo "  loki state query events --agent arch_001 --limit 20"
            echo "  loki state query messages --topic 'task.*'"
            echo "  loki state query checkpoints --migration mig_123"
            ;;
        db)
            local db_path="${LOKI_DATA_DIR:-${HOME}/.loki}/state.db"
            echo "$db_path"
            if [[ -f "$db_path" ]]; then
                local size
                size=$(ls -lh "$db_path" 2>/dev/null | awk '{print $5}')
                echo -e "${DIM}Size: ${size}${NC}"
            else
                echo -e "${YELLOW}Database not yet created${NC}"
            fi
            ;;
        query)
            local query_type="${1:-}"
            shift 2>/dev/null || true

            if [[ -z "$query_type" ]]; then
                echo -e "${RED}Usage: loki state query <events|messages|checkpoints> [options]${NC}"
                return 1
            fi

            local agent_id="" event_type="" topic="" cluster_id="" migration_id="" limit="20"
            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --agent) agent_id="$2"; shift 2 ;;
                    --type) event_type="$2"; shift 2 ;;
                    --topic) topic="$2"; shift 2 ;;
                    --cluster) cluster_id="$2"; shift 2 ;;
                    --migration) migration_id="$2"; shift 2 ;;
                    --limit) limit="$2"; shift 2 ;;
                    *) shift ;;
                esac
            done

            PYTHONPATH="${SKILL_DIR:-.}" \
            LOKI_STATE_QUERY_TYPE="$query_type" \
            LOKI_STATE_AGENT_ID="$agent_id" \
            LOKI_STATE_EVENT_TYPE="$event_type" \
            LOKI_STATE_TOPIC="$topic" \
            LOKI_STATE_CLUSTER_ID="$cluster_id" \
            LOKI_STATE_MIGRATION_ID="$migration_id" \
            LOKI_STATE_LIMIT="$limit" \
            LOKI_STATE_SKILL_DIR="${SKILL_DIR:-.}" \
            python3 << 'STATE_QUERY_PY'
import json, sys, os
sys.path.insert(0, os.environ.get('LOKI_STATE_SKILL_DIR', '.'))
from state.sqlite_backend import SqliteStateBackend
db = SqliteStateBackend()

query_type = os.environ.get('LOKI_STATE_QUERY_TYPE', '')
agent_id = os.environ.get('LOKI_STATE_AGENT_ID', '') or None
event_type = os.environ.get('LOKI_STATE_EVENT_TYPE', '') or None
topic = os.environ.get('LOKI_STATE_TOPIC', '') or None
cluster_id = os.environ.get('LOKI_STATE_CLUSTER_ID', '') or None
migration_id = os.environ.get('LOKI_STATE_MIGRATION_ID', '') or None
limit = int(os.environ.get('LOKI_STATE_LIMIT', '20'))

if query_type == 'events':
    results = db.query_events(
        event_type=event_type,
        agent_id=agent_id,
        migration_id=migration_id,
        limit=limit
    )
elif query_type == 'messages':
    results = db.query_messages(
        topic=topic,
        cluster_id=cluster_id,
        limit=limit
    )
elif query_type == 'checkpoints':
    results = db.query_checkpoints(
        migration_id=migration_id,
        limit=limit
    )
else:
    print(f'Unknown query type: {query_type}')
    sys.exit(1)

if not results:
    print('No results found.')
else:
    for r in results:
        print(json.dumps(r, indent=2))
        print('---')
    print(f'{len(results)} result(s)')
STATE_QUERY_PY
            2>&1 || {
                echo -e "${RED}Error querying state database${NC}"
                return 1
            }
            ;;
        *)
            echo -e "${RED}Unknown state command: $subcmd${NC}"
            echo "Run 'loki state --help' for usage."
            return 1
            ;;
    esac
}

# Agent action audit log and quality scanning
cmd_audit() {
    local subcommand="${1:-help}"
    local audit_file="$LOKI_DIR/logs/agent-audit.jsonl"

    case "$subcommand" in
        log)
            if [ ! -f "$audit_file" ]; then
                echo -e "${YELLOW}No audit log found at $audit_file${NC}"
                echo "Agent action auditing records entries during loki sessions."
                return 0
            fi
            local lines="${2:-50}"
            echo -e "${BOLD}Agent Audit Log${NC} (last $lines entries)"
            echo "---"
            tail -n "$lines" "$audit_file" | while IFS= read -r line; do
                if command -v python3 &>/dev/null; then
                    python3 -c "
import json, sys
try:
    e = json.loads(sys.argv[1])
    ts = e.get('timestamp', '?')
    act = e.get('action', '?')
    desc = e.get('description', '')
    it = e.get('iteration', 0)
    print(f'  [{ts}] [{act}] iter={it} {desc}')
except: print(f'  {sys.argv[1]}')
" "$line" 2>/dev/null || echo "  $line"
                else
                    echo "  $line"
                fi
            done
            ;;
        count)
            if [ ! -f "$audit_file" ]; then
                echo -e "${YELLOW}No audit log found${NC}"
                return 0
            fi
            echo -e "${BOLD}Agent Action Counts${NC}"
            echo "---"
            if command -v python3 &>/dev/null; then
                python3 -c "
import json, sys
from collections import Counter
counts = Counter()
for line in open(sys.argv[1]):
    try:
        e = json.loads(line)
        counts[e.get('action', 'unknown')] += 1
    except: pass
for action, count in sorted(counts.items(), key=lambda x: -x[1]):
    print(f'  {action:25s} {count}')
print(f'  {\"---\":25s} ---')
print(f'  {\"TOTAL\":25s} {sum(counts.values())}')
" "$audit_file" 2>/dev/null
            else
                echo "  python3 required for count summary"
            fi
            ;;
        scan)
            shift
            local preset="default"
            local do_export=""

            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --preset) if [[ -z "${2:-}" ]]; then echo -e "${RED}Error: --preset requires a value${NC}"; exit 1; fi; preset="$2"; shift 2 ;;
                    --preset=*) preset="${1#*=}"; shift ;;
                    --export) do_export="true"; shift ;;
                    --help|-h)
                        echo -e "${BOLD}loki audit scan${NC} - Run quality scan"
                        echo ""
                        echo "Usage: loki audit scan [options]"
                        echo ""
                        echo "Options:"
                        echo "  --preset NAME   Compliance preset (default|healthcare|fintech|government)"
                        echo "  --export        Save report to .loki/quality/report-{date}.json"
                        echo ""
                        return 0
                        ;;
                    *) echo -e "${RED}Unknown option: $1${NC}"; return 1 ;;
                esac
            done

            case "$preset" in
                default|healthcare|fintech|government) ;;
                *) echo -e "${RED}Error: Invalid preset '$preset'. Must be one of: default, healthcare, fintech, government${NC}"; return 1 ;;
            esac

            local port="${LOKI_DASHBOARD_PORT:-57374}"
            local host="127.0.0.1"
            local url="http://${host}:${port}/api/quality-scan?preset=${preset}"

            echo -e "${BOLD}Running quality scan...${NC} (preset: $preset)"
            echo ""

            local response http_code
            response=$(curl -s -w "\n%{http_code}" -X POST "$url" 2>/dev/null) || true
            http_code=$(echo "$response" | tail -1)
            response=$(echo "$response" | sed '$d')
            if [ -z "$http_code" ] || [ "$http_code" = "000" ]; then
                echo -e "${RED}Error: Could not connect to dashboard API at http://${host}:${port}${NC}"
                echo "Make sure the dashboard is running: loki serve"
                return 1
            fi
            if [ "$http_code" -ge 400 ] 2>/dev/null; then
                echo -e "${RED}Error: Dashboard API returned HTTP $http_code${NC}"
                [ -n "$response" ] && echo "$response"
                return 1
            fi

            if ! command -v python3 &>/dev/null; then
                echo "$response" | jq . 2>/dev/null || echo "$response"
            else
                echo "$response" | python3 -c "
import json, sys
data = json.loads(sys.stdin.read())

score = data.get('score', 0)
grade = data.get('grade', '?')
print(f'Quality Score: {score}/100  Grade: {grade}')
print('---')

# Category scores
categories = data.get('categories', {})
if categories:
    print()
    print('Category Scores:')
    for cat, cat_score in sorted(categories.items()):
        print(f'  {cat:20s} {cat_score}/100')

# Findings by severity
findings = data.get('findings', [])
if findings:
    print()
    print('Findings:')
    for f in findings:
        sev = f.get('severity', 'info')
        msg = f.get('message', '')
        cat = f.get('category', '')
        if sev == 'critical':
            print(f'  \033[0;31m[CRITICAL]\033[0m {cat}: {msg}')
        elif sev == 'major':
            print(f'  \033[1;33m[MAJOR]\033[0m    {cat}: {msg}')
        else:
            print(f'  [MINOR]    {cat}: {msg}')
print()
" 2>/dev/null || echo "$response"
            fi

            # Export report if requested
            if [ -n "$do_export" ]; then
                local report_dir="$LOKI_DIR/quality"
                mkdir -p "$report_dir"
                local date_str
                date_str=$(date +%Y-%m-%d)
                local report_file="$report_dir/report-${date_str}.json"

                local report_url="http://${host}:${port}/api/quality-report?format=json"
                local report_data
                report_data=$(curl -sf "$report_url" 2>/dev/null) || true
                if [ -n "$report_data" ]; then
                    echo "$report_data" > "$report_file"
                    echo -e "${GREEN}Report exported to $report_file${NC}"
                else
                    # Fall back to scan response
                    echo "$response" > "$report_file"
                    echo -e "${GREEN}Report exported to $report_file${NC}"
                fi
            fi
            ;;
        --preset|--export)
            # Handle flags passed directly to 'loki audit' (shortcut for 'loki audit scan')
            cmd_audit scan "$@"
            return
            ;;
        lint)
            echo -e "${BOLD}Running static analysis...${NC}"
            echo ""
            local findings=0
            local target="${2:-.}"

            # JavaScript/TypeScript
            if [ -f "$target/package.json" ]; then
                echo -e "${CYAN}JavaScript/TypeScript:${NC}"
                local js_files
                js_files=$(find "$target" -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" | grep -v node_modules | grep -v dist | head -50)
                if [ -n "$js_files" ]; then
                    if [ -f "$target/.eslintrc.js" ] || [ -f "$target/.eslintrc.json" ] || [ -f "$target/.eslintrc.yml" ] || [ -f "$target/eslint.config.js" ] || [ -f "$target/eslint.config.mjs" ]; then
                        npx eslint $js_files 2>&1 | head -50
                        [ ${PIPESTATUS[0]} -ne 0 ] && findings=$((findings + 1))
                    else
                        echo "  No ESLint config found - running syntax check"
                        for f in $js_files; do
                            node --check "$f" 2>&1 || findings=$((findings + 1))
                        done
                    fi
                fi
            fi

            # Python
            if [ -f "$target/setup.py" ] || [ -f "$target/pyproject.toml" ] || [ -f "$target/requirements.txt" ]; then
                echo -e "${CYAN}Python:${NC}"
                local py_files
                py_files=$(find "$target" -name "*.py" | grep -v __pycache__ | grep -v .venv | grep -v venv | head -50)
                if [ -n "$py_files" ]; then
                    if command -v ruff &>/dev/null; then
                        ruff check $py_files 2>&1 | head -50
                        [ ${PIPESTATUS[0]} -ne 0 ] && findings=$((findings + 1))
                    else
                        echo "  ruff not available - running py_compile"
                        for f in $py_files; do
                            python3 -m py_compile "$f" 2>&1 || findings=$((findings + 1))
                        done
                    fi
                fi
            fi

            # Shell scripts
            local sh_files
            sh_files=$(find "$target" -name "*.sh" | grep -v node_modules | head -20)
            if [ -n "$sh_files" ]; then
                echo -e "${CYAN}Shell:${NC}"
                if command -v shellcheck &>/dev/null; then
                    shellcheck $sh_files 2>&1 | head -50
                    [ ${PIPESTATUS[0]} -ne 0 ] && findings=$((findings + 1))
                else
                    echo "  shellcheck not available - running bash -n"
                    for f in $sh_files; do
                        bash -n "$f" 2>&1 || findings=$((findings + 1))
                    done
                fi
            fi

            echo ""
            if [ "$findings" -gt 0 ]; then
                echo -e "${RED}Static analysis found issues ($findings checks failed)${NC}"
                return 1
            else
                echo -e "${GREEN}Static analysis passed${NC}"
                return 0
            fi
            ;;

        test)
            echo -e "${BOLD}Running test coverage check...${NC}"
            echo ""
            local target="${2:-.}"
            local min_coverage="${LOKI_MIN_COVERAGE:-80}"
            local test_failed=0

            # JavaScript/TypeScript (jest/vitest)
            if [ -f "$target/package.json" ]; then
                local test_cmd=""
                if grep -q '"vitest"' "$target/package.json" 2>/dev/null; then
                    test_cmd="npx vitest run --coverage"
                elif grep -q '"jest"' "$target/package.json" 2>/dev/null; then
                    test_cmd="npx jest --coverage --passWithNoTests"
                fi
                if [ -n "$test_cmd" ]; then
                    echo -e "${CYAN}Running: $test_cmd${NC}"
                    (cd "$target" && eval "$test_cmd") 2>&1
                    [ $? -ne 0 ] && test_failed=1
                fi
            fi

            # Python (pytest)
            if [ -f "$target/setup.py" ] || [ -f "$target/pyproject.toml" ]; then
                if command -v pytest &>/dev/null; then
                    echo -e "${CYAN}Running: pytest --cov${NC}"
                    (cd "$target" && pytest --cov 2>&1) || test_failed=1
                fi
            fi

            # Go
            if [ -f "$target/go.mod" ]; then
                echo -e "${CYAN}Running: go test -cover${NC}"
                (cd "$target" && go test -cover ./... 2>&1) || test_failed=1
            fi

            # Rust
            if [ -f "$target/Cargo.toml" ]; then
                echo -e "${CYAN}Running: cargo test${NC}"
                (cd "$target" && cargo test 2>&1) || test_failed=1
            fi

            echo ""
            if [ "$test_failed" -ne 0 ]; then
                echo -e "${RED}Test coverage gate FAILED${NC}"
                return 1
            else
                echo -e "${GREEN}Tests passed (min coverage: ${min_coverage}%)${NC}"
                return 0
            fi
            ;;

        --help|-h|help)
            echo -e "${BOLD}loki audit${NC} - Agent audit log and quality scanning"
            echo ""
            echo "Usage: loki audit <subcommand> [options]"
            echo ""
            echo "Subcommands:"
            echo "  log [N]     Show last N audit log entries (default: 50)"
            echo "  count       Count actions by type"
            echo "  scan        Run quality scan against dashboard API"
            echo "  lint        Run static analysis on project files"
            echo "  test        Run test coverage check"
            echo "  help        Show this help"
            echo ""
            echo "Quality Scan Options (loki audit scan):"
            echo "  --preset NAME   Compliance preset (default|healthcare|fintech|government)"
            echo "  --export        Save report to .loki/quality/report-{date}.json"
            echo ""
            echo "The agent audit log records actions taken during Loki sessions,"
            echo "including CLI invocations, git commits, and session lifecycle events."
            echo "Log file: $audit_file"
            ;;
        *)
            echo -e "${RED}Unknown audit subcommand: $subcommand${NC}"
            echo "Run 'loki audit help' for usage."
            exit 1
            ;;
    esac
}

# Reset session state
cmd_reset() {
    require_jq || return 1

    local subcommand="${1:-all}"

    case "$subcommand" in
        retries|retry)
            if [ -f "$LOKI_DIR/autonomy-state.json" ]; then
                # Reset only retry count
                if command -v python3 &> /dev/null; then
                    python3 -c "
import json, sys, os
p = os.path.join(sys.argv[1], 'autonomy-state.json')
with open(p, 'r') as f:
    state = json.load(f)
state['retryCount'] = 0
state['status'] = 'reset'
with open(p, 'w') as f:
    json.dump(state, f, indent=4)
" "$LOKI_DIR" 2>/dev/null
                    echo -e "${GREEN}Retry count reset to 0${NC}"
                else
                    rm -f "$LOKI_DIR/autonomy-state.json"
                    echo -e "${GREEN}Autonomy state cleared${NC}"
                fi
            else
                echo -e "${YELLOW}No autonomy state found${NC}"
            fi
            ;;
        failed|failures)
            if [ -f "$LOKI_DIR/queue/failed.json" ]; then
                local count
                count=$(jq 'length' "$LOKI_DIR/queue/failed.json" 2>/dev/null || echo "?")
                rm -f "$LOKI_DIR/queue/failed.json"
                echo "[]" > "$LOKI_DIR/queue/failed.json"
                echo -e "${GREEN}Cleared $count failed tasks${NC}"
            else
                echo -e "${YELLOW}No failed tasks found${NC}"
            fi
            ;;
        all)
            # Reset everything
            rm -f "$LOKI_DIR/autonomy-state.json"
            echo "[]" > "$LOKI_DIR/queue/failed.json" 2>/dev/null
            echo -e "${GREEN}Session state reset${NC}"
            echo "  - Autonomy state cleared"
            echo "  - Failed task queue cleared"
            ;;
        --help|-h|help)
            echo -e "${BOLD}loki reset${NC} - Reset session state"
            echo ""
            echo "Usage: loki reset [target]"
            echo ""
            echo "Targets:"
            echo "  all       Reset everything (default)"
            echo "  retries   Reset only the retry counter"
            echo "  failed    Clear failed task queue"
            echo ""
            echo "Examples:"
            echo "  loki reset              # Reset all state"
            echo "  loki reset retries      # Just reset retry count"
            echo "  loki reset failed       # Clear failed tasks"
            ;;
        *)
            echo -e "${RED}Unknown reset target: $subcommand${NC}"
            echo "Valid targets: all, retries, failed"
            exit 1
            ;;
    esac
}

# Cross-project memory/learnings management
cmd_memory() {
    local subcommand="${1:-list}"
    local learnings_dir="${HOME}/.loki/learnings"

    # Ensure directory exists
    mkdir -p "$learnings_dir"

    case "$subcommand" in
        list|ls)
            echo -e "${BOLD}Cross-Project Learnings${NC}"
            echo ""

            local patterns=0 mistakes=0 successes=0
            [ -f "$learnings_dir/patterns.jsonl" ] && patterns=$(grep -c '"description"' "$learnings_dir/patterns.jsonl" 2>/dev/null) || patterns=0
            [ -f "$learnings_dir/mistakes.jsonl" ] && mistakes=$(grep -c '"description"' "$learnings_dir/mistakes.jsonl" 2>/dev/null) || mistakes=0
            [ -f "$learnings_dir/successes.jsonl" ] && successes=$(grep -c '"description"' "$learnings_dir/successes.jsonl" 2>/dev/null) || successes=0

            echo -e "  Patterns:  ${GREEN}$patterns${NC}"
            echo -e "  Mistakes:  ${YELLOW}$mistakes${NC}"
            echo -e "  Successes: ${CYAN}$successes${NC}"
            echo ""
            echo "Location: $learnings_dir"
            echo ""
            echo "Use 'loki memory show <type>' to view entries"
            ;;

        show)
            # Parse arguments: show <type> [--limit N] [--project P]
            local type="${2:-all}"
            local limit=20
            local project=""
            shift 2 2>/dev/null || true

            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --limit|-l)
                        limit="${2:-20}"
                        if [ $# -ge 2 ]; then shift 2; else shift; fi
                        ;;
                    --project|-p)
                        project="${2:-}"
                        if [ $# -ge 2 ]; then shift 2; else shift; fi
                        ;;
                    *)
                        shift
                        ;;
                esac
            done

            case "$type" in
                patterns|pattern)
                    echo -e "${BOLD}Patterns${NC}"
                    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                    if [ -f "$learnings_dir/patterns.jsonl" ]; then
                        python3 -c "
import json, sys, os
limit = int(sys.argv[1])
project_filter = sys.argv[2]
filepath = os.path.join(sys.argv[3], 'patterns.jsonl')
count = 0
with open(filepath, 'r') as f:
    for line in f:
        if count >= limit:
            break
        try:
            e = json.loads(line)
            if 'description' in e:
                if project_filter and e.get('project', '') != project_filter:
                    continue
                print(f\"[{e.get('project', 'unknown')}] {e['description'][:100]}\")
                count += 1
        except: pass
if count == 0:
    print('(empty - no patterns recorded)')
" "$limit" "$project" "$learnings_dir" 2>/dev/null
                    else
                        echo "(empty - no patterns recorded)"
                    fi
                    ;;

                mistakes|mistake)
                    echo -e "${BOLD}Mistakes${NC}"
                    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                    if [ -f "$learnings_dir/mistakes.jsonl" ]; then
                        python3 -c "
import json, sys, os
limit = int(sys.argv[1])
project_filter = sys.argv[2]
filepath = os.path.join(sys.argv[3], 'mistakes.jsonl')
count = 0
with open(filepath, 'r') as f:
    for line in f:
        if count >= limit:
            break
        try:
            e = json.loads(line)
            if 'description' in e:
                if project_filter and e.get('project', '') != project_filter:
                    continue
                print(f\"[{e.get('project', 'unknown')}] {e['description'][:100]}\")
                count += 1
        except: pass
if count == 0:
    print('(empty - no mistakes recorded)')
" "$limit" "$project" "$learnings_dir" 2>/dev/null
                    else
                        echo "(empty - no mistakes recorded)"
                    fi
                    ;;

                successes|success)
                    echo -e "${BOLD}Successes${NC}"
                    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                    if [ -f "$learnings_dir/successes.jsonl" ]; then
                        python3 -c "
import json, sys, os
limit = int(sys.argv[1])
project_filter = sys.argv[2]
filepath = os.path.join(sys.argv[3], 'successes.jsonl')
count = 0
with open(filepath, 'r') as f:
    for line in f:
        if count >= limit:
            break
        try:
            e = json.loads(line)
            if 'description' in e:
                if project_filter and e.get('project', '') != project_filter:
                    continue
                print(f\"[{e.get('project', 'unknown')}] {e['description'][:100]}\")
                count += 1
        except: pass
if count == 0:
    print('(empty - no successes recorded)')
" "$limit" "$project" "$learnings_dir" 2>/dev/null
                    else
                        echo "(empty - no successes recorded)"
                    fi
                    ;;

                all)
                    cmd_memory show patterns --limit "$limit" --project "$project"
                    echo ""
                    cmd_memory show mistakes --limit "$limit" --project "$project"
                    echo ""
                    cmd_memory show successes --limit "$limit" --project "$project"
                    ;;

                *)
                    echo -e "${RED}Unknown type: $type${NC}"
                    echo "Valid types: patterns, mistakes, successes, all"
                    return 1
                    ;;
            esac
            ;;

        search)
            local query="${2:-}"
            if [ -z "$query" ]; then
                echo -e "${RED}Usage: loki memory search <query>${NC}"
                return 1
            fi

            echo -e "${BOLD}Memory Search: $query${NC}"
            echo ""

            # Use python3.12 for ML packages (3.14 lacks sentence-transformers)
            local _search_py="python3"
            if command -v /opt/homebrew/bin/python3.12 &>/dev/null; then
                _search_py="/opt/homebrew/bin/python3.12"
            elif command -v python3.12 &>/dev/null; then
                _search_py="python3.12"
            fi

            # BUG-PU-010: Pass query via environment variable instead of embedding
            # directly in heredoc to prevent shell/Python injection via triple-quotes
            export LOKI_MEM_QUERY="$query"
            $_search_py << 'PYEOF'
import os, json, sys, glob

query = os.environ.get('LOKI_MEM_QUERY', '')
results = []

# Keyword search across all memory files
memory_dirs = [
    '.loki/memory/episodic',
    '.loki/memory/semantic',
    '.loki/memory/skills',
    '.loki/memory/handoffs',
]

for mdir in memory_dirs:
    if not os.path.isdir(mdir):
        continue
    for fpath in glob.glob(os.path.join(mdir, '*.json')):
        try:
            with open(fpath) as f:
                data = json.load(f)
            text = json.dumps(data).lower()
            if query.lower() in text:
                # Score by number of matches
                score = text.count(query.lower())
                title = data.get('title', data.get('task_id', data.get('name', os.path.basename(fpath))))
                category = os.path.basename(mdir)
                results.append((score, category, title, fpath))
        except Exception:
            continue

# Also search markdown files
for mdir in ['.loki/memory', '.loki']:
    if not os.path.isdir(mdir):
        continue
    for fpath in glob.glob(os.path.join(mdir, '*.md')):
        try:
            with open(fpath) as f:
                text = f.read().lower()
            if query.lower() in text:
                score = text.count(query.lower())
                title = os.path.basename(fpath)
                results.append((score, 'docs', title, fpath))
        except Exception:
            continue

# Try vector search if available
vector_results = []
try:
    sys.path.insert(0, '.')
    from memory.retrieval import MemoryRetrieval
    from memory.storage import MemoryStorage
    storage = MemoryStorage('.loki/memory')
    retriever = MemoryRetrieval(storage)
    retriever.build_indices()
    for coll in ['episodic', 'semantic', 'skills']:
        try:
            vr = retriever.retrieve_by_similarity(query, coll, top_k=3)
            for item in vr:
                if isinstance(item, dict):
                    item['_collection'] = coll
                vector_results.append(item)
        except Exception:
            continue
    if vector_results:
        print("  [Vector Search Results]")
        for item in vector_results[:5]:
            if isinstance(item, dict):
                coll = item.get('_collection', '?')
                title = item.get('title', item.get('name', item.get('id', '?')))
                summary = item.get('summary', item.get('pattern', ''))[:80]
                print(f"    [{coll}] {title}")
                if summary:
                    print(f"           {summary}")
            else:
                print(f"    {str(item)[:80]}")
        print()
except ImportError:
    pass  # Vector search not available
except Exception:
    pass

# Show keyword results
if results:
    results.sort(key=lambda x: -x[0])
    print("  [Keyword Search Results]")
    for score, category, title, fpath in results[:10]:
        print(f"    [{category}] {title}  ({score} matches)")
        print(f"           {fpath}")
    print()
    print(f"  Found: {len(results)} result(s)")
else:
    if not vector_results:
        print("  No results found for: " + query)
        print("  Tip: Try broader search terms")
PYEOF
            ;;

        clear)
            local type="${2:-}"
            local confirm=""

            if [ -z "$type" ]; then
                # LOKI_AUTO_CONFIRM takes precedence when explicitly set;
                # fall back to CI env var only when LOKI_AUTO_CONFIRM is unset
                local _auto_confirm="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
                if [[ "$_auto_confirm" == "true" ]]; then
                    confirm="yes"
                else
                    echo -e "${YELLOW}This will delete ALL cross-project learnings.${NC}"
                    echo -n "Are you sure? (yes/no): "
                    read -r confirm
                fi
                if [ "$confirm" = "yes" ]; then
                    rm -rf "$learnings_dir"
                    mkdir -p "$learnings_dir"
                    echo '{"version":"1.0","created":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$learnings_dir/patterns.jsonl"
                    echo '{"version":"1.0","created":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$learnings_dir/mistakes.jsonl"
                    echo '{"version":"1.0","created":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$learnings_dir/successes.jsonl"
                    echo -e "${GREEN}All learnings cleared${NC}"
                else
                    echo "Cancelled"
                fi
            else
                case "$type" in
                    patterns|mistakes|successes)
                        echo '{"version":"1.0","created":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$learnings_dir/${type}.jsonl"
                        echo -e "${GREEN}Cleared $type${NC}"
                        ;;
                    *)
                        echo -e "${RED}Unknown type: $type${NC}"
                        echo "Valid types: patterns, mistakes, successes"
                        return 1
                        ;;
                esac
            fi
            ;;

        export)
            local output="${2:-learnings-export.json}"

            LOKI_LEARNINGS_DIR="$learnings_dir" python3 -c "
import json
import os

learnings_dir = os.environ['LOKI_LEARNINGS_DIR']
result = {'patterns': [], 'mistakes': [], 'successes': []}

for category in ['patterns', 'mistakes', 'successes']:
    filepath = os.path.join(learnings_dir, f'{category}.jsonl')
    if os.path.exists(filepath):
        with open(filepath, 'r') as f:
            for line in f:
                try:
                    e = json.loads(line)
                    if 'description' in e:
                        result[category].append(e)
                except: pass

with open('$output', 'w') as f:
    json.dump(result, f, indent=2)
print(f'Exported to $output')
" 2>/dev/null
            ;;

        stats)
            echo -e "${BOLD}Learning Statistics${NC}"
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

            LOKI_LEARNINGS_DIR="$learnings_dir" python3 -c "
import json
import os
from collections import Counter

learnings_dir = os.environ['LOKI_LEARNINGS_DIR']
projects = Counter()
categories = Counter()

for filename in ['patterns.jsonl', 'mistakes.jsonl', 'successes.jsonl']:
    filepath = os.path.join(learnings_dir, filename)
    if not os.path.exists(filepath):
        continue

    category = filename.replace('.jsonl', '')
    with open(filepath, 'r') as f:
        for line in f:
            try:
                e = json.loads(line)
                if 'description' in e:
                    projects[e.get('project', 'unknown')] += 1
                    categories[category] += 1
            except: pass

print('By Category:')
for cat, count in categories.most_common():
    print(f'  {cat}: {count}')

print()
print('By Project:')
for proj, count in projects.most_common(10):
    print(f'  {proj}: {count}')
" 2>/dev/null
            ;;

        dedupe|dedup)
            echo -e "${BOLD}Deduplicating Learnings${NC}"
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

            LOKI_LEARNINGS_DIR="$learnings_dir" python3 -c "
import json
import os
import hashlib
from datetime import datetime, timezone

learnings_dir = os.environ['LOKI_LEARNINGS_DIR']

def dedupe_file(filepath):
    '''Deduplicate a JSONL file, keeping first occurrence of each unique description'''
    if not os.path.exists(filepath):
        return 0, 0

    seen_hashes = set()
    unique_entries = []
    duplicates = 0

    # Read all entries
    with open(filepath, 'r') as f:
        for line in f:
            try:
                entry = json.loads(line)
                if 'description' not in entry:
                    # Keep header/metadata lines
                    if 'version' in entry:
                        unique_entries.append(entry)
                    continue

                # Normalize description for hashing (case-insensitive, trimmed)
                desc = entry['description'].strip().lower()
                h = hashlib.md5(desc.encode()).hexdigest()

                if h not in seen_hashes:
                    seen_hashes.add(h)
                    unique_entries.append(entry)
                else:
                    duplicates += 1
            except json.JSONDecodeError:
                continue

    # Write back unique entries
    with open(filepath, 'w') as f:
        for entry in unique_entries:
            f.write(json.dumps(entry) + '\n')

    return len(unique_entries) - (1 if unique_entries and 'version' in unique_entries[0] else 0), duplicates

total_kept = 0
total_removed = 0

for category in ['patterns', 'mistakes', 'successes']:
    filepath = os.path.join(learnings_dir, f'{category}.jsonl')
    kept, removed = dedupe_file(filepath)
    total_kept += kept
    total_removed += removed
    if removed > 0:
        print(f'{category}: kept {kept}, removed {removed} duplicates')
    else:
        print(f'{category}: {kept} entries (no duplicates)')

print()
print(f'Total: kept {total_kept} unique entries, removed {total_removed} duplicates')
if total_removed > 0:
    reduction = total_removed / (total_kept + total_removed) * 100
    print(f'Storage reduced by {reduction:.1f}%')
" 2>/dev/null
            ;;

        index)
            # Show or rebuild the memory index layer
            if [ "${2:-}" = "rebuild" ]; then
                PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
try:
    from memory.layers import IndexLayer
    layer = IndexLayer('.loki/memory')
    layer.update([])  # Will scan and rebuild
    print('Index rebuilt')
except ImportError:
    print('Error: memory.layers module not found')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
            else
                cat .loki/memory/index.json 2>/dev/null | python3 -m json.tool 2>/dev/null || echo "No index found"
            fi
            ;;

        timeline)
            # Show timeline layer
            cat .loki/memory/timeline.json 2>/dev/null | python3 -m json.tool 2>/dev/null || echo "No timeline found"
            ;;

        consolidate)
            # Run consolidation pipeline
            local hours="${2:-24}"
            LOKI_HOURS="$hours" PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
import os
try:
    from memory.consolidation import ConsolidationPipeline
    from memory.storage import MemoryStorage
    storage = MemoryStorage('.loki/memory')
    pipeline = ConsolidationPipeline(storage)
    result = pipeline.consolidate(since_hours=int(os.environ.get('LOKI_HOURS', '24')))
    print('Consolidation complete:')
    print(f'  Patterns created: {result.patterns_created}')
    print(f'  Patterns merged: {result.patterns_merged}')
    print(f'  Anti-patterns: {result.anti_patterns_created}')
    print(f'  Links created: {result.links_created}')
    print(f'  Episodes processed: {result.episodes_processed}')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
            ;;

        economics)
            # v7.6.6 B-3d fix: previously only read .loki/memory/token_economics.json
            # which is rarely written by current sessions, so users saw
            # "No token economics data" while `loki kpis` correctly read
            # .loki/metrics/efficiency/iter-*.json. Unify: prefer the
            # canonical kpis source; fall back to the legacy file for
            # backward compat with pre-v7.6.6 sessions.
            # v7.7.7 fix: also check iteration-*.json (the canonical filename)
            # in addition to iter-*.json. Use a portable glob check (no compgen)
            # so the function works in any POSIX shell, not just bash.
            local _eff_dir=".loki/metrics/efficiency"
            local _legacy_file=".loki/memory/token_economics.json"
            local _have_iters=0
            if [ -d "$_eff_dir" ]; then
                if ls "$_eff_dir"/iteration-*.json >/dev/null 2>&1 || ls "$_eff_dir"/iter-*.json >/dev/null 2>&1; then
                    _have_iters=1
                fi
            fi
            if [ "$_have_iters" -eq 1 ]; then
                PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 - <<'PYEOF' 2>/dev/null
import json, glob, os, sys
# v7.7.7 fix: canonical filename is `iteration-N.json`, NOT `iter-N.json`.
# We glob both for compat with any older runs.
files = sorted(set(glob.glob('.loki/metrics/efficiency/iteration-*.json') + glob.glob('.loki/metrics/efficiency/iter-*.json')))
totals = {
    'source': '.loki/metrics/efficiency/iteration-*.json',
    'iterations': len(files),
    'total_input_tokens': 0,
    'total_output_tokens': 0,
    'total_tokens': 0,
    'total_cost_usd': 0.0,
    'total_duration_ms': 0,
    'by_model': {},
    'by_phase': {},
}
def _num(x, cast=float, default=0):
    try:
        return cast(x) if x is not None else default
    except (TypeError, ValueError):
        return default
for f in files:
    try:
        d = json.load(open(f))
    except Exception:
        continue
    ti = int(_num(d.get('input_tokens'), int))
    to = int(_num(d.get('output_tokens'), int))
    co = float(_num(d.get('cost_usd'), float))
    du = int(_num(d.get('duration_ms'), int))
    totals['total_input_tokens'] += ti
    totals['total_output_tokens'] += to
    totals['total_tokens'] += ti + to
    totals['total_cost_usd'] += co
    totals['total_duration_ms'] += du
    m = d.get('model') or 'unknown'
    totals['by_model'][m] = totals['by_model'].get(m, 0) + 1
    p = d.get('phase') or 'unknown'
    totals['by_phase'][p] = totals['by_phase'].get(p, 0) + 1
print(json.dumps(totals, indent=2))
PYEOF
            elif [ -f "$_legacy_file" ]; then
                # Backward compat: pre-v7.6.6 sessions wrote here.
                cat "$_legacy_file" | python3 -m json.tool 2>/dev/null || echo "Error parsing token economics JSON"
            else
                echo "No token economics data. Run a session first."
            fi
            ;;

        retrieve)
            # Test memory retrieval
            local query="${2:-}"
            if [ -z "$query" ]; then
                echo "Usage: loki memory retrieve <query>"
                exit 1
            fi
            LOKI_QUERY="$query" PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
import os
try:
    from memory.retrieval import MemoryRetrieval
    from memory.storage import MemoryStorage
    query = os.environ.get('LOKI_QUERY', '')
    storage = MemoryStorage('.loki/memory')
    retriever = MemoryRetrieval(storage)
    results = retriever.retrieve_task_aware({'goal': query}, top_k=5)
    if not results:
        print('No relevant memories found')
    else:
        for i, r in enumerate(results, 1):
            source = r.get('source', 'unknown')
            summary = r.get('summary', r.get('pattern', 'No summary'))[:80]
            score = r.get('score', 0)
            print(f'{i}. [{source}] {summary}... (score: {score:.2f})')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
            ;;

        episode)
            # Show full episode by ID
            local id="${2:-}"
            if [ -z "$id" ]; then
                echo "Usage: loki memory episode <id>"
                exit 1
            fi
            LOKI_ID="$id" PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
import os
try:
    from memory.engine import MemoryEngine
    import json
    episode_id = os.environ.get('LOKI_ID', '')
    engine = MemoryEngine(base_path='.loki/memory')
    episode = engine.get_episode(episode_id)
    if episode:
        print(json.dumps(episode.to_dict(), indent=2))
    else:
        print(f'Episode not found: {episode_id}')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
            ;;

        pattern)
            # Show semantic pattern by ID or list all patterns
            local id="${2:-}"
            if [ -z "$id" ]; then
                # List all patterns
                PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
try:
    from memory.engine import MemoryEngine
    engine = MemoryEngine(base_path='.loki/memory')
    patterns = engine.find_patterns()
    if not patterns:
        print('No patterns found')
    else:
        for p in patterns[:20]:
            pattern_text = p.pattern[:60] if hasattr(p, 'pattern') else str(p)[:60]
            confidence = p.confidence if hasattr(p, 'confidence') else 0.0
            print(f'{p.id}: {pattern_text}... (conf: {confidence:.2f})')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
            else
                LOKI_ID="$id" PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
import os
try:
    from memory.engine import MemoryEngine
    import json
    pattern_id = os.environ.get('LOKI_ID', '')
    engine = MemoryEngine(base_path='.loki/memory')
    pattern = engine.get_pattern(pattern_id)
    if pattern:
        print(json.dumps(pattern.to_dict(), indent=2))
    else:
        print(f'Pattern not found: {pattern_id}')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
            fi
            ;;

        skill)
            # Show procedural skill by name or list all skills
            local name="${2:-}"
            if [ -z "$name" ]; then
                # List all skills
                if ls -1 .loki/memory/skills/*.json 2>/dev/null | head -1 >/dev/null 2>&1; then
                    ls -1 .loki/memory/skills/*.json 2>/dev/null | xargs -I{} basename {} .json
                else
                    echo "No skills found"
                fi
            else
                if [ -f ".loki/memory/skills/${name}.json" ]; then
                    cat ".loki/memory/skills/${name}.json" | python3 -m json.tool 2>/dev/null || echo "Error parsing skill JSON"
                else
                    echo "Skill not found: $name"
                fi
            fi
            ;;

        vectors)
            local vec_cmd="${2:-stats}"
            case "$vec_cmd" in
                setup)
                    echo -e "${BOLD}Vector Search Setup${NC}"
                    echo ""

                    # Detect best Python for ML packages (3.12 preferred, 3.14+ has compat issues)
                    local py_ml="python3"
                    local pip_ml="pip3"
                    if command -v /opt/homebrew/bin/python3.12 &>/dev/null; then
                        py_ml="/opt/homebrew/bin/python3.12"
                        pip_ml="/opt/homebrew/bin/pip3.12"
                        echo -e "  Using Python 3.12 for ML packages (${DIM}system python3 may be 3.14+${NC})"
                    elif command -v python3.12 &>/dev/null; then
                        py_ml="python3.12"
                        pip_ml="pip3.12"
                        echo -e "  Using Python 3.12 for ML packages"
                    else
                        local py_ver
                        py_ver=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "0.0")
                        local py_minor
                        py_minor=$(echo "$py_ver" | cut -d. -f2)
                        if [ "${py_minor:-0}" -ge 13 ] 2>/dev/null; then
                            echo -e "  ${YELLOW}WARNING: Python $py_ver detected. sentence-transformers may not install.${NC}"
                            echo -e "  ${YELLOW}Install Python 3.12 for best compatibility: brew install python@3.12${NC}"
                        fi
                    fi

                    # Check/install numpy
                    echo -n "  Checking numpy... "
                    if $py_ml -c "import numpy" 2>/dev/null; then
                        echo -e "${GREEN}installed${NC}"
                    else
                        echo -n "installing... "
                        if $pip_ml install numpy 2>/dev/null || $pip_ml install --break-system-packages numpy 2>/dev/null; then
                            echo -e "${GREEN}done${NC}"
                        else
                            echo -e "${RED}failed${NC}"
                            echo "    Try: $pip_ml install numpy"
                        fi
                    fi

                    # Check/install sentence-transformers
                    echo -n "  Checking sentence-transformers... "
                    if $py_ml -c "import sentence_transformers" 2>/dev/null; then
                        echo -e "${GREEN}installed${NC}"
                    else
                        echo -n "installing (this may take a while)... "
                        if $pip_ml install sentence-transformers 2>/dev/null || $pip_ml install --break-system-packages sentence-transformers 2>/dev/null; then
                            echo -e "${GREEN}done${NC}"
                        else
                            echo -e "${RED}failed${NC}"
                            echo "    Try: $pip_ml install sentence-transformers"
                        fi
                    fi

                    # Create vector directory
                    mkdir -p .loki/memory/vectors
                    echo -e "  Vector directory: ${GREEN}.loki/memory/vectors/${NC}"

                    # Build initial index if memory files exist
                    echo ""
                    echo -n "  Building vector indices... "
                    $py_ml -c "
try:
    import sys
    sys.path.insert(0, '.')
    from memory.retrieval import MemoryRetrieval
    from memory.storage import MemoryStorage
    storage = MemoryStorage('.loki/memory')
    retriever = MemoryRetrieval(storage)
    retriever.build_indices()
    retriever.save_indices()
    print('done')
except ImportError as e:
    print(f'skipped (missing: {e})')
except Exception as e:
    print(f'error: {e}')
" 2>/dev/null || echo "skipped"

                    echo ""
                    echo -e "${GREEN}Vector search setup complete${NC}"
                    echo "Run 'loki memory search <query>' to search."
                    ;;

                rebuild)
                    local py_rb="python3"
                    if command -v /opt/homebrew/bin/python3.12 &>/dev/null; then
                        py_rb="/opt/homebrew/bin/python3.12"
                    elif command -v python3.12 &>/dev/null; then
                        py_rb="python3.12"
                    fi
                    $py_rb -c "
try:
    import sys
    sys.path.insert(0, '.')
    from memory.retrieval import MemoryRetrieval
    from memory.storage import MemoryStorage
    storage = MemoryStorage('.loki/memory')
    retriever = MemoryRetrieval(storage)
    retriever.build_indices()
    retriever.save_indices()
    print('Vector indices rebuilt')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    ;;

                stats|*)
                    echo "Vector index stats:"
                    if ls -1 .loki/memory/vectors/*.npz 2>/dev/null | head -1 >/dev/null 2>&1; then
                        for f in .loki/memory/vectors/*.npz; do
                            if [ -f "$f" ]; then
                                count=$(_NPZ_FILE="$f" python3 -c "import numpy as np, os; d=np.load(os.environ['_NPZ_FILE']); print(len(d['ids']))" 2>/dev/null || echo "error")
                                echo "  $(basename "$f"): $count vectors"
                            fi
                        done
                    else
                        echo "  No vector indices found"
                        echo "  Run 'loki memory vectors setup' to initialize"
                    fi
                    ;;
            esac
            ;;

        namespace|ns)
            # Namespace management (COMP-005)
            local ns_cmd="${2:-detect}"
            shift 2 2>/dev/null || true

            case "$ns_cmd" in
                detect)
                    # Auto-detect namespace from current directory
                    python3 -c "
try:
    from memory.namespace import detect_namespace
    ns = detect_namespace()
    print(f'Detected namespace: {ns}')
except ImportError as e:
    print(f'Error: memory.namespace module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    ;;

                list|ls)
                    # List all namespaces
                    python3 -c "
try:
    from memory.storage import MemoryStorage
    storage = MemoryStorage('.loki/memory')
    namespaces = storage.list_namespaces()
    if not namespaces:
        print('No namespaces found')
    else:
        print('Available namespaces:')
        for ns in namespaces:
            stats = storage.get_namespace_stats(ns)
            print(f'  {ns}: {stats[\"episode_count\"]} episodes, {stats[\"pattern_count\"]} patterns, {stats[\"skill_count\"]} skills')
except ImportError as e:
    print(f'Error: memory.storage module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    ;;

                create)
                    # Create a new namespace
                    local name="${1:-}"
                    local parent="${2:-}"

                    if [ -z "$name" ]; then
                        echo "Usage: loki memory namespace create <name> [parent]"
                        exit 1
                    fi

                    LOKI_NS_NAME="$name" LOKI_NS_PARENT="$parent" python3 -c "
import os
try:
    from memory.namespace import NamespaceManager
    name = os.environ.get('LOKI_NS_NAME', '')
    parent = os.environ.get('LOKI_NS_PARENT', '') or None

    manager = NamespaceManager('.loki/memory')
    info = manager.create_namespace(name=name, parent=parent)
    print(f'Created namespace: {info.name}')
    print(f'  Path: {info.path}')
    if info.parent:
        print(f'  Parent: {info.parent}')
except ValueError as e:
    print(f'Error: {e}')
except ImportError as e:
    print(f'Error: memory.namespace module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    ;;

                delete|rm)
                    # Delete a namespace
                    local name="${1:-}"
                    local delete_data="${2:-}"

                    if [ -z "$name" ]; then
                        echo "Usage: loki memory namespace delete <name> [--data]"
                        exit 1
                    fi

                    local with_data="false"
                    if [ "$delete_data" = "--data" ]; then
                        with_data="true"
                    fi

                    LOKI_NS_NAME="$name" LOKI_DELETE_DATA="$with_data" python3 -c "
import os
try:
    from memory.namespace import NamespaceManager
    name = os.environ.get('LOKI_NS_NAME', '')
    delete_data = os.environ.get('LOKI_DELETE_DATA', 'false') == 'true'

    manager = NamespaceManager('.loki/memory')
    result = manager.delete_namespace(name, delete_data=delete_data)
    if result:
        msg = f'Deleted namespace: {name}'
        if delete_data:
            msg += ' (including data)'
        print(msg)
    else:
        print(f'Namespace not found: {name}')
except ValueError as e:
    print(f'Error: {e}')
except ImportError as e:
    print(f'Error: memory.namespace module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    ;;

                stats)
                    # Show stats for a namespace
                    local name="${1:-}"

                    if [ -z "$name" ]; then
                        # Show current namespace stats
                        python3 -c "
try:
    from memory.namespace import detect_namespace
    from memory.storage import MemoryStorage
    ns = detect_namespace()
    storage = MemoryStorage('.loki/memory', namespace=ns)
    stats = storage.get_namespace_stats()
    print(f'Namespace: {stats[\"namespace\"]}')
    print(f'  Path: {stats[\"path\"]}')
    print(f'  Episodes: {stats[\"episode_count\"]}')
    print(f'  Patterns: {stats[\"pattern_count\"]}')
    print(f'  Skills: {stats[\"skill_count\"]}')
    print(f'  Total: {stats[\"total_count\"]}')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    else
                        LOKI_NS_NAME="$name" python3 -c "
import os
try:
    from memory.storage import MemoryStorage
    name = os.environ.get('LOKI_NS_NAME', '')
    storage = MemoryStorage('.loki/memory', namespace=name)
    stats = storage.get_namespace_stats()
    print(f'Namespace: {stats[\"namespace\"]}')
    print(f'  Path: {stats[\"path\"]}')
    print(f'  Episodes: {stats[\"episode_count\"]}')
    print(f'  Patterns: {stats[\"pattern_count\"]}')
    print(f'  Skills: {stats[\"skill_count\"]}')
    print(f'  Total: {stats[\"total_count\"]}')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    fi
                    ;;

                copy)
                    # Copy from current namespace to target
                    local target="${1:-}"

                    if [ -z "$target" ]; then
                        echo "Usage: loki memory namespace copy <target-namespace>"
                        exit 1
                    fi

                    LOKI_NS_TARGET="$target" python3 -c "
import os
try:
    from memory.namespace import detect_namespace
    from memory.storage import MemoryStorage
    target = os.environ.get('LOKI_NS_TARGET', '')

    source_ns = detect_namespace()
    storage = MemoryStorage('.loki/memory', namespace=source_ns)
    result = storage.copy_to_namespace(target)

    print(f'Copied from {source_ns} to {target}:')
    print(f'  Episodes: {result[\"episodes\"]}')
    print(f'  Patterns: {result[\"patterns\"]}')
    print(f'  Skills: {result[\"skills\"]}')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    ;;

                merge)
                    # Merge from source namespace into current
                    local source="${1:-}"

                    if [ -z "$source" ]; then
                        echo "Usage: loki memory namespace merge <source-namespace>"
                        exit 1
                    fi

                    LOKI_NS_SOURCE="$source" python3 -c "
import os
try:
    from memory.namespace import detect_namespace
    from memory.storage import MemoryStorage
    source = os.environ.get('LOKI_NS_SOURCE', '')

    target_ns = detect_namespace()
    storage = MemoryStorage('.loki/memory', namespace=target_ns)
    result = storage.merge_from_namespace(source, deduplicate=True)

    print(f'Merged from {source} into {target_ns}:')
    print(f'  Episodes: {result[\"episodes\"]}')
    print(f'  Patterns: {result[\"patterns\"]}')
    print(f'  Skills: {result[\"skills\"]}')
except ImportError as e:
    print(f'Error: Required module not found - {e}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
                    ;;

                help|--help|-h)
                    echo "Namespace management commands:"
                    echo ""
                    echo "  detect            Auto-detect namespace from current directory"
                    echo "  list              List all available namespaces"
                    echo "  create <name>     Create a new namespace"
                    echo "  delete <name>     Delete a namespace (--data to also delete data)"
                    echo "  stats [name]      Show stats for a namespace"
                    echo "  copy <target>     Copy current namespace to target"
                    echo "  merge <source>    Merge source namespace into current"
                    echo ""
                    echo "Examples:"
                    echo "  loki memory namespace detect"
                    echo "  loki memory namespace list"
                    echo "  loki memory namespace create my-project"
                    echo "  loki memory namespace create child-project parent-project"
                    echo "  loki memory namespace stats my-project"
                    echo "  loki memory namespace copy backup-ns"
                    echo "  loki memory namespace merge shared-patterns"
                    ;;

                *)
                    echo -e "${RED}Unknown namespace command: $ns_cmd${NC}"
                    echo "Run 'loki memory namespace help' for usage."
                    exit 1
                    ;;
            esac
            ;;

        --namespace|-n)
            # Global option to specify namespace for memory commands
            echo "Note: --namespace is a global option, use it before the subcommand:"
            echo "  loki memory --namespace=myproject retrieve 'query'"
            echo ""
            echo "Or use the namespace subcommand:"
            echo "  loki memory namespace stats myproject"
            ;;

        --help|-h|help)
            echo -e "${BOLD}loki memory${NC} - Manage cross-project learnings and memory system"
            echo ""
            echo "Usage: loki memory <command> [args]"
            echo ""
            echo "Learnings Commands:"
            echo "  list              Show summary of all learnings"
            echo "  show [type]       Show entries (patterns|mistakes|successes|all)"
            echo "                    Options: --limit N, --project P"
            echo "  search <query>    Search across all learnings"
            echo "  stats             Show statistics by project and category"
            echo "  export [file]     Export to JSON file"
            echo "  dedupe            Remove duplicate entries"
            echo "  clear [type]      Clear learnings (all or specific type)"
            echo ""
            echo "Memory System Commands:"
            echo "  index [rebuild]   Show or rebuild the memory index layer"
            echo "  timeline          Show timeline layer with temporal context"
            echo "  consolidate [h]   Run consolidation pipeline (default: 24 hours)"
            echo "  economics         Show token economics data"
            echo "  retrieve <query>  Test memory retrieval for a query"
            echo "  episode <id>      Show full episode by ID"
            echo "  pattern [id]      Show pattern by ID or list all patterns"
            echo "  skill [name]      Show skill by name or list all skills"
            echo "  vectors [rebuild] Show vector index stats or rebuild indices"
            echo ""
            echo "Namespace Commands (v5.19.0):"
            echo "  namespace detect       Auto-detect namespace from current directory"
            echo "  namespace list         List all available namespaces"
            echo "  namespace create NAME  Create a new namespace"
            echo "  namespace delete NAME  Delete a namespace"
            echo "  namespace stats [NAME] Show stats for a namespace"
            echo "  namespace copy TARGET  Copy current namespace to target"
            echo "  namespace merge SOURCE Merge source namespace into current"
            echo ""
            echo "Examples:"
            echo "  loki memory list"
            echo "  loki memory show mistakes --limit 10"
            echo "  loki memory show patterns --project myapp"
            echo "  loki memory search 'rate limit'"
            echo "  loki memory dedupe"
            echo "  loki memory export backup.json"
            echo ""
            echo "  loki memory index"
            echo "  loki memory index rebuild"
            echo "  loki memory consolidate 48"
            echo "  loki memory retrieve 'API rate limiting'"
            echo "  loki memory episode ep-20260202-abc123"
            echo "  loki memory pattern"
            echo "  loki memory pattern pat-api-error-handling"
            echo "  loki memory skill"
            echo "  loki memory vectors"
            echo "  loki memory vectors rebuild"
            echo ""
            echo "  loki memory ingest --from-claude-transcript <path>  # v7.7.18"
            echo "  loki memory ingest --from-stdin                     # v7.7.18"
            echo "  loki memory replay <episode-id> [--json]            # v7.7.22"
            echo "  loki memory crossproject --for 'build api'          # v7.7.20"
            echo "  loki memory graph --export graph.json               # v7.7.20"
            echo "  loki memory graph rebuild                           # v7.7.20"
            echo "  loki memory enable-hook                             # v7.7.20"
            echo "  loki memory disable-hook                            # v7.7.20"
            echo ""
            echo "  loki memory namespace detect"
            echo "  loki memory namespace list"
            echo "  loki memory namespace create my-project"
            echo "  loki memory namespace stats"
            ;;

        ingest)
            # v7.7.18 capture wedge: ingest a Claude Code session transcript
            # into the project's .loki/memory/ store. Solves the diagnosis
            # root cause where memory only captured during `loki start`.
            #
            # Usage:
            #   loki memory ingest --from-claude-transcript <path>
            #   loki memory ingest --from-stdin   (reads a JSON summary doc)
            shift  # drop "ingest"
            local _ingest_mode=""
            local _ingest_path=""
            while [ $# -gt 0 ]; do
                case "$1" in
                    --from-claude-transcript)
                        _ingest_mode="transcript"
                        _ingest_path="${2:-}"
                        if [ $# -ge 2 ]; then shift 2; else shift; fi
                        ;;
                    --from-stdin)
                        _ingest_mode="stdin"
                        shift
                        ;;
                    -h|--help)
                        echo "Usage: loki memory ingest --from-claude-transcript <path>"
                        echo "       loki memory ingest --from-stdin   (JSON doc on stdin)"
                        echo ""
                        echo "Honors LOKI_MEMORY_CAPTURE_DISABLED=true (escape hatch)."
                        return 0
                        ;;
                    *) shift ;;
                esac
            done
            if [ -z "$_ingest_mode" ]; then
                echo -e "${RED}Usage: loki memory ingest --from-claude-transcript <path> | --from-stdin${NC}"
                exit 1
            fi
            local _project_root_for_ingest="${SKILL_DIR:-$(pwd)}"
            local _target_memory_dir
            _target_memory_dir="$(pwd)/.loki/memory"
            mkdir -p "$_target_memory_dir" 2>/dev/null || true
            if [ "$_ingest_mode" = "transcript" ]; then
                if [ -z "$_ingest_path" ] || [ ! -f "$_ingest_path" ]; then
                    echo -e "${RED}Transcript not found: $_ingest_path${NC}"
                    exit 1
                fi
                PYTHONPATH="$_project_root_for_ingest" \
                    python3 -c "
import sys, json
from memory.ingest import ingest_from_claude_transcript
path = ingest_from_claude_transcript(sys.argv[1], sys.argv[2])
print(json.dumps({'episode_path': path}))
" "$_ingest_path" "$_target_memory_dir"
            else
                PYTHONPATH="$_project_root_for_ingest" \
                    python3 -c "
import sys, json
from memory.ingest import ingest_from_summary
doc = json.loads(sys.stdin.read())
path = ingest_from_summary(
    sys.argv[1],
    goal=doc.get('goal',''),
    outcome=doc.get('outcome','success'),
    files_modified=doc.get('files_modified',[]),
    files_read=doc.get('files_read',[]),
    tool_calls_summary=doc.get('tool_calls_summary',''),
    duration_seconds=int(doc.get('duration_seconds',0) or 0),
)
print(json.dumps({'episode_path': path}))
" "$_target_memory_dir"
            fi
            ;;

        replay)
            # v7.7.22 wow feature: READ-ONLY deterministic session replay.
            # Renders a past episode's action timeline + current state of
            # touched files. Does NOT re-execute (LLM tool_uses are
            # non-deterministic + re-running edits could clobber work;
            # --apply deferred to a future release).
            # Usage: loki memory replay <episode-id> [--json]
            shift  # drop "replay"
            local _replay_id=""
            local _replay_json="false"
            while [ $# -gt 0 ]; do
                case "$1" in
                    --json) _replay_json="true"; shift ;;
                    -h|--help) echo "Usage: loki memory replay <episode-id> [--json]"; return 0 ;;
                    *) [ -z "$_replay_id" ] && _replay_id="$1"; shift ;;
                esac
            done
            if [ -z "$_replay_id" ]; then
                echo -e "${RED}Usage: loki memory replay <episode-id> [--json]${NC}"
                exit 1
            fi
            local _replay_mem
            _replay_mem="$(pwd)/.loki/memory"
            PYTHONPATH="${SKILL_DIR:-$(pwd)}" _LOKI_REPLAY_JSON="$_replay_json" python3 -c "
import sys, os, json
from memory.replay import replay_episode, render_markdown
report = replay_episode(sys.argv[1], sys.argv[2])
if os.environ.get('_LOKI_REPLAY_JSON') == 'true':
    print(json.dumps(report, indent=2, default=str))
else:
    print(render_markdown(report))
sys.exit(0 if report.get('found') else 1)
" "$_replay_id" "$_replay_mem"
            ;;

        crossproject)
            # v7.7.20: surface cross-project knowledge-graph patterns
            # (wakes the previously-dead memory/knowledge_graph.py).
            # Usage: loki memory crossproject [--for <goal text>]
            shift  # drop "crossproject"
            local _cp_query="general"
            while [ $# -gt 0 ]; do
                case "$1" in
                    --for) _cp_query="${2:-general}"; if [ $# -ge 2 ]; then shift 2; else shift; fi ;;
                    -h|--help) echo "Usage: loki memory crossproject [--for <goal>]"; return 0 ;;
                    *) shift ;;
                esac
            done
            PYTHONPATH="${SKILL_DIR:-$(pwd)}" python3 -c "
import sys, json
try:
    from memory.knowledge_graph import OrganizationKnowledgeGraph
    kg = OrganizationKnowledgeGraph()
    patterns = kg.query_patterns(sys.argv[1], max_results=10)
    if not patterns:
        print('No cross-project patterns found. Run sessions in multiple projects to build the graph.')
    else:
        for i, p in enumerate(patterns, 1):
            desc = p.get('description', p.get('pattern', p)) if isinstance(p, dict) else p
            print(f'{i}. {str(desc)[:160]}')
except ImportError:
    print('knowledge_graph module not available')
except Exception as e:
    print(f'Error: {e}')
" "$_cp_query"
            ;;

        graph)
            # v7.7.20: export OR rebuild the cross-project knowledge graph.
            # Usage: loki memory graph [--export <path>]
            #        loki memory graph rebuild   # populate from semantic patterns
            shift  # drop "graph"
            # v7.7.20 council fix (Opus 1): the read side (query_patterns)
            # was woken but the WRITE side had no caller, so the graph
            # stayed empty forever. `graph rebuild` mines .loki/memory/
            # semantic/*.json across discovered projects and persists them
            # to the org knowledge graph -- the population path that makes
            # crossproject + load_memory_context augmentation non-inert.
            if [ "${1:-}" = "rebuild" ]; then
                shift
                PYTHONPATH="${SKILL_DIR:-$(pwd)}" python3 -c "
import sys, os
try:
    from memory.knowledge_graph import OrganizationKnowledgeGraph
    from memory.cross_project import CrossProjectIndex
    cpi = CrossProjectIndex()
    cpi.discover_projects()
    project_dirs = cpi.get_project_dirs()
    kg = OrganizationKnowledgeGraph()
    # v7.7.20 council fix (Opus 1): save_patterns appends, so a naive
    # rebuild would accumulate duplicates across runs. Dedup the UNION of
    # existing + freshly-extracted, then truncate-rewrite so rebuild is
    # idempotent.
    existing = kg.load_patterns(limit=100000)
    fresh = kg.extract_patterns(project_dirs)
    merged = kg.deduplicate_patterns(list(existing) + list(fresh))
    # Truncate then write the deduped union (save_patterns appends).
    try:
        if os.path.exists(kg.patterns_file):
            open(kg.patterns_file, 'w').close()
    except OSError:
        pass
    if merged:
        kg.save_patterns(merged)
        print(f'Rebuilt knowledge graph: {len(merged)} unique patterns from {len(project_dirs)} project(s)')
    else:
        print(f'No semantic patterns found across {len(project_dirs)} project(s). Run sessions + consolidation first.')
except ImportError as e:
    print(f'knowledge_graph/cross_project module not available: {e}')
except Exception as e:
    print(f'Error: {e}')
"
                return 0
            fi
            local _graph_export=""
            while [ $# -gt 0 ]; do
                case "$1" in
                    --export) _graph_export="${2:-}"; if [ $# -ge 2 ]; then shift 2; else shift; fi ;;
                    -h|--help) echo "Usage: loki memory graph [--export <path>] | loki memory graph rebuild"; return 0 ;;
                    *) shift ;;
                esac
            done
            PYTHONPATH="${SKILL_DIR:-$(pwd)}" _LOKI_GRAPH_EXPORT="$_graph_export" python3 -c "
import sys, os, json
export_path = os.environ.get('_LOKI_GRAPH_EXPORT', '')
try:
    from memory.knowledge_graph import OrganizationKnowledgeGraph
    kg = OrganizationKnowledgeGraph()
    patterns = kg.load_patterns(limit=100)
    summary = {'pattern_count': len(patterns), 'patterns': patterns[:100]}
    if export_path:
        with open(export_path, 'w') as f:
            json.dump(summary, f, indent=2, default=str)
        print(f'Exported {len(patterns)} patterns to {export_path}')
    else:
        print(json.dumps(summary, indent=2, default=str)[:4000])
except ImportError:
    print('knowledge_graph module not available')
except Exception as e:
    print(f'Error: {e}')
"
            ;;

        enable-hook)
            # v7.7.20: idempotently install a Claude Code SessionEnd hook
            # using the VERIFIED schema {matcher, hooks:[{type,command}]}.
            # Per WebSearch (v7.7.18 council): SessionEnd fires on /clear,
            # payload is JSON on stdin with transcript_path. The shipped
            # script claude/hooks/loki-session-end.sh handles both stdin
            # JSON and env-var fallback, so we point the hook at it.
            # No-op under LOKI_MEMORY_HOOK_DISABLED=true.
            shift
            if [ "${LOKI_MEMORY_HOOK_DISABLED:-}" = "true" ]; then
                echo -e "${YELLOW}Hook install skipped (LOKI_MEMORY_HOOK_DISABLED=true)${NC}"
                return 0
            fi
            local _settings="$HOME/.claude/settings.json"
            local _hook_script="${SKILL_DIR:-$(pwd)}/claude/hooks/loki-session-end.sh"
            mkdir -p "$(dirname "$_settings")" 2>/dev/null || true
            [ ! -f "$_settings" ] && echo '{}' > "$_settings"
            _LOKI_HOOK_SCRIPT="$_hook_script" _LOKI_SETTINGS="$_settings" python3 -c "
import json, os, sys, tempfile
settings_path = os.environ['_LOKI_SETTINGS']
hook_script = os.environ['_LOKI_HOOK_SCRIPT']
hook_cmd = f'bash {hook_script}'
try:
    with open(settings_path) as f:
        data = json.load(f)
except Exception:
    data = {}
hooks = data.setdefault('hooks', {})
session_end = hooks.setdefault('SessionEnd', [])
# Verified Claude Code schema: list of {matcher, hooks:[{type,command}]}.
# Idempotency: detect an existing entry whose nested command references
# our hook script.
for entry in session_end:
    if isinstance(entry, dict):
        for h in entry.get('hooks', []):
            if isinstance(h, dict) and 'loki-session-end.sh' in str(h.get('command', '')):
                print('already-installed')
                sys.exit(0)
session_end.append({
    'matcher': 'clear',
    'hooks': [{'type': 'command', 'command': hook_cmd}],
})
tmp_fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(settings_path), prefix='.loki-settings-')
with os.fdopen(tmp_fd, 'w') as f:
    json.dump(data, f, indent=2)
os.replace(tmp_path, settings_path)
print('installed')
"
            local _r=$?
            if [ $_r -eq 0 ]; then
                echo -e "${GREEN}SessionEnd hook ready.${NC} Fires on /clear; pipes transcript to loki memory ingest."
                echo -e "${CYAN}Note: SessionEnd only fires on /clear, not normal exits (Claude Code limitation).${NC}"
                echo -e "${CYAN}Remove with: loki memory disable-hook${NC}"
            fi
            ;;

        disable-hook)
            # v7.7.20: reverse counterpart. Removes any SessionEnd entry
            # whose nested command references loki-session-end.sh.
            shift
            local _settings="$HOME/.claude/settings.json"
            if [ ! -f "$_settings" ]; then
                echo -e "${YELLOW}No ~/.claude/settings.json to clean${NC}"
                return 0
            fi
            _LOKI_SETTINGS="$_settings" python3 -c "
import json, os, sys, tempfile
settings_path = os.environ['_LOKI_SETTINGS']
try:
    with open(settings_path) as f:
        data = json.load(f)
except Exception:
    print('settings-not-readable')
    sys.exit(0)
hooks = data.get('hooks', {})
session_end = hooks.get('SessionEnd', [])
def refs_loki(entry):
    if not isinstance(entry, dict):
        return False
    for h in entry.get('hooks', []):
        if isinstance(h, dict) and 'loki-session-end.sh' in str(h.get('command', '')):
            return True
    return False
filtered = [e for e in session_end if not refs_loki(e)]
if len(filtered) == len(session_end):
    print('not-installed')
    sys.exit(0)
hooks['SessionEnd'] = filtered
data['hooks'] = hooks
tmp_fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(settings_path), prefix='.loki-settings-')
with os.fdopen(tmp_fd, 'w') as f:
    json.dump(data, f, indent=2)
os.replace(tmp_path, settings_path)
print('removed')
"
            ;;

        *)
            echo -e "${RED}Unknown memory command: $subcommand${NC}"
            echo "Run 'loki memory help' for usage."
            exit 1
            ;;
    esac
}

# Knowledge Compounding - Structured Solutions (v5.30.0)
# Inspired by Compound Engineering Plugin's docs/solutions/ with YAML frontmatter
cmd_compound() {
    local subcommand="${1:-help}"
    shift 2>/dev/null || true

    local solutions_dir="${HOME}/.loki/solutions"

    case "$subcommand" in
        list|ls)
            echo -e "${BOLD}Compound Solutions${NC}"
            echo ""

            if [ ! -d "$solutions_dir" ]; then
                echo "  No solutions yet. Solutions are created automatically during"
                echo "  Loki sessions or manually with 'loki compound run'."
                echo ""
                echo "  Location: $solutions_dir"
                return
            fi

            local total=0
            for category in security performance architecture testing debugging deployment general; do
                local cat_dir="$solutions_dir/$category"
                if [ -d "$cat_dir" ]; then
                    local count=$(find "$cat_dir" -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
                    if [ "$count" -gt 0 ]; then
                        printf "  %-16s ${GREEN}%d${NC} solutions\n" "$category" "$count"
                        total=$((total + count))
                    fi
                fi
            done

            if [ "$total" -eq 0 ]; then
                echo "  No solutions yet."
            else
                echo ""
                echo "  Total: ${total} solutions"
            fi
            echo ""
            echo "  Location: $solutions_dir"
            echo ""
            echo "  Use 'loki compound show <category>' to view solutions"
            ;;

        show)
            local category="${1:-}"
            if [ -z "$category" ]; then
                echo -e "${RED}Error: Specify a category${NC}"
                echo "Categories: security, performance, architecture, testing, debugging, deployment, general"
                return 1
            fi

            local cat_dir="$solutions_dir/$category"
            if [ ! -d "$cat_dir" ]; then
                echo "No solutions in category: $category"
                return
            fi

            echo -e "${BOLD}Solutions: $category${NC}"
            echo ""

            for file in "$cat_dir"/*.md; do
                [ -f "$file" ] || continue
                # Extract title from YAML frontmatter
                local title=$(grep '^title:' "$file" 2>/dev/null | head -1 | sed 's/title: *"//;s/"$//')
                local confidence=$(grep '^confidence:' "$file" 2>/dev/null | head -1 | sed 's/confidence: *//')
                local project=$(grep '^source_project:' "$file" 2>/dev/null | head -1 | sed 's/source_project: *"//;s/"$//')
                echo -e "  ${GREEN}*${NC} ${title:-$(basename "$file" .md)}"
                [ -n "$confidence" ] && echo -e "    confidence: ${confidence}  project: ${project:-unknown}"
            done
            echo ""
            ;;

        search)
            local query="${1:-}"
            if [ -z "$query" ]; then
                echo -e "${RED}Error: Specify a search query${NC}"
                echo "Usage: loki compound search <query>"
                return 1
            fi

            echo -e "${BOLD}Searching solutions for: ${query}${NC}"
            echo ""

            if [ ! -d "$solutions_dir" ]; then
                echo "No solutions directory found."
                return
            fi

            local found=0
            while IFS= read -r file; do
                local title=$(grep '^title:' "$file" 2>/dev/null | head -1 | sed 's/title: *"//;s/"$//')
                local category=$(grep '^category:' "$file" 2>/dev/null | head -1 | sed 's/category: *//')
                echo -e "  [${CYAN}${category}${NC}] ${title:-$(basename "$file" .md)}"
                echo -e "    ${DIM}${file}${NC}"
                found=$((found + 1))
            done < <(grep -rl "$query" "$solutions_dir" 2>/dev/null || true)

            if [ "$found" -eq 0 ]; then
                echo "  No solutions matching: $query"
            else
                echo ""
                echo "  Found: ${found} solutions"
            fi
            ;;

        run)
            echo -e "${BOLD}Compounding learnings into solutions...${NC}"
            echo ""

            local learnings_dir="${HOME}/.loki/learnings"
            if [ ! -d "$learnings_dir" ]; then
                echo "  No learnings directory found at: $learnings_dir"
                echo "  Run a Loki session first to generate learnings."
                return
            fi

            python3 << 'COMPOUND_RUN_SCRIPT'
import json, os, re
from datetime import datetime, timezone
from collections import defaultdict
learnings_dir = os.path.expanduser("~/.loki/learnings")
solutions_dir = os.path.expanduser("~/.loki/solutions")
CATEGORIES = ["security", "performance", "architecture", "testing", "debugging", "deployment", "general"]
CATEGORY_KEYWORDS = {
    "security": ["auth", "login", "password", "token", "injection", "xss", "csrf", "cors", "secret", "encrypt", "permission"],
    "performance": ["cache", "query", "n+1", "memory", "leak", "slow", "timeout", "pool", "index", "optimize", "bundle"],
    "architecture": ["pattern", "solid", "coupling", "abstraction", "module", "interface", "design", "refactor", "structure"],
    "testing": ["test", "mock", "fixture", "coverage", "assert", "spec", "e2e", "playwright", "jest", "flaky"],
    "debugging": ["debug", "error", "trace", "log", "stack", "crash", "exception", "breakpoint", "inspect"],
    "deployment": ["deploy", "docker", "ci", "cd", "pipeline", "kubernetes", "nginx", "ssl", "domain", "env", "config"],
}
def load_jsonl(filepath):
    entries = []
    if not os.path.exists(filepath): return entries
    with open(filepath, 'r') as f:
        for line in f:
            try:
                entry = json.loads(line)
                if 'description' in entry: entries.append(entry)
            except: continue
    return entries
def classify_category(description):
    desc_lower = description.lower()
    scores = {cat: sum(1 for kw in kws if kw in desc_lower) for cat, kws in CATEGORY_KEYWORDS.items()}
    best = max(scores, key=scores.get)
    return best if scores[best] > 0 else "general"
def slugify(text):
    return re.sub(r'[^a-z0-9]+', '-', text.lower().strip()).strip('-')[:80]
def solution_exists(solutions_dir, title_slug):
    for cat in CATEGORIES:
        cat_dir = os.path.join(solutions_dir, cat)
        if os.path.exists(cat_dir) and os.path.exists(os.path.join(cat_dir, f"{title_slug}.md")):
            return True
    return False
patterns = load_jsonl(os.path.join(learnings_dir, "patterns.jsonl"))
mistakes = load_jsonl(os.path.join(learnings_dir, "mistakes.jsonl"))
successes = load_jsonl(os.path.join(learnings_dir, "successes.jsonl"))
print(f"  Loaded: {len(patterns)} patterns, {len(mistakes)} mistakes, {len(successes)} successes")
grouped = defaultdict(list)
for entry in patterns + mistakes + successes:
    grouped[classify_category(entry.get('description', ''))].append(entry)
created = 0
now = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
for category, entries in grouped.items():
    if len(entries) < 2: continue
    cat_dir = os.path.join(solutions_dir, category)
    os.makedirs(cat_dir, exist_ok=True)
    best_entry = max(entries, key=lambda e: len(e.get('description', '')))
    title = best_entry['description'][:120]
    slug = slugify(title)
    if solution_exists(solutions_dir, slug): continue
    all_words = ' '.join(e.get('description', '') for e in entries).lower()
    tags = []
    for kw_list in CATEGORY_KEYWORDS.values():
        for kw in kw_list:
            if kw in all_words and kw not in tags: tags.append(kw)
    tags = tags[:8]
    symptoms = [e.get('description', '')[:200] for e in entries
                if any(w in e.get('description', '').lower() for w in ['error', 'fail', 'bug', 'crash', 'issue'])][:4]
    if not symptoms: symptoms = [entries[0].get('description', '')[:200]]
    solution_lines = [f"- {e.get('description', '')}" for e in entries
                      if not any(w in e.get('description', '').lower() for w in ['error', 'fail', 'bug', 'crash'])]
    if not solution_lines: solution_lines = [f"- {entries[0].get('description', '')}"]
    project = best_entry.get('project', os.path.basename(os.getcwd()))
    filepath = os.path.join(cat_dir, f"{slug}.md")
    with open(filepath, 'w') as f:
        f.write(f"---\ntitle: \"{title}\"\ncategory: {category}\ntags: [{', '.join(tags)}]\nsymptoms:\n")
        for s in symptoms: f.write(f'  - "{s}"\n')
        f.write(f'root_cause: "Identified from {len(entries)} related learnings"\n')
        f.write(f'prevention: "See solution details below"\nconfidence: {min(0.5 + 0.1 * len(entries), 0.95):.2f}\n')
        f.write(f'source_project: "{project}"\ncreated: "{now}"\napplied_count: 0\n---\n\n')
        f.write("## Solution\n\n" + '\n'.join(solution_lines) + '\n\n')
        f.write(f"## Context\n\nCompounded from {len(entries)} learnings from project: {project}\n")
    created += 1
    print(f"  Created: {category}/{slug}.md")
if created > 0: print(f"\n  Compounded {created} new solution files to {solutions_dir}")
else: print("  No new solutions to compound (need 2+ related learnings per category)")
COMPOUND_RUN_SCRIPT
            ;;

        stats)
            echo -e "${BOLD}Compound Solution Statistics${NC}"
            echo ""

            if [ ! -d "$solutions_dir" ]; then
                echo "  No solutions directory found."
                return
            fi

            local total=0
            local newest=""
            local oldest=""

            for category in security performance architecture testing debugging deployment general; do
                local cat_dir="$solutions_dir/$category"
                if [ -d "$cat_dir" ]; then
                    local count=$(find "$cat_dir" -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
                    total=$((total + count))
                fi
            done

            echo "  Total solutions: ${total}"

            if [ "$total" -gt 0 ]; then
                # Find newest and oldest
                newest=$(find "$solutions_dir" -name "*.md" -exec stat -f '%m %N' {} \; 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
                oldest=$(find "$solutions_dir" -name "*.md" -exec stat -f '%m %N' {} \; 2>/dev/null | sort -n | head -1 | cut -d' ' -f2-)

                if [ -n "$newest" ]; then
                    local newest_title=$(grep '^title:' "$newest" 2>/dev/null | head -1 | sed 's/title: *"//;s/"$//')
                    echo "  Newest: ${newest_title:-$(basename "$newest" .md)}"
                fi
                if [ -n "$oldest" ]; then
                    local oldest_title=$(grep '^title:' "$oldest" 2>/dev/null | head -1 | sed 's/title: *"//;s/"$//')
                    echo "  Oldest: ${oldest_title:-$(basename "$oldest" .md)}"
                fi
            fi

            echo ""
            echo "  Location: $solutions_dir"
            ;;

        help|--help|-h)
            echo -e "${BOLD}loki compound${NC} - Knowledge compounding system"
            echo ""
            echo "Extracts structured solutions from cross-project learnings."
            echo "Solutions are stored as markdown files with YAML frontmatter"
            echo "at ~/.loki/solutions/{category}/ and fed back into future planning."
            echo ""
            echo "Usage: loki compound <command>"
            echo ""
            echo "Commands:"
            echo "  list              List all solutions by category"
            echo "  show <category>   Show solutions in a category"
            echo "  search <query>    Search across all solutions"
            echo "  run               Manually compound current learnings into solutions"
            echo "  stats             Solution statistics"
            echo "  help              Show this help"
            echo ""
            echo "Categories: security, performance, architecture, testing,"
            echo "            debugging, deployment, general"
            echo ""
            echo "Solutions are created automatically at session end and"
            echo "loaded during the REASON phase for relevant tasks."
            ;;

        *)
            echo -e "${RED}Unknown compound command: $subcommand${NC}"
            echo "Run 'loki compound help' for usage."
            return 1
            ;;
    esac
}

# Checkpoint management - save and restore session state (v5.34.0)
cmd_checkpoint() {
    local subcommand="${1:-list}"
    shift 2>/dev/null || true

    local checkpoints_dir=".loki/state/checkpoints"
    local index_file="$checkpoints_dir/index.jsonl"

    case "$subcommand" in
        list|ls)
            echo -e "${BOLD}Session Checkpoints${NC}"
            echo ""

            if [ ! -f "$index_file" ]; then
                echo "  No checkpoints yet."
                echo ""
                echo "  Create one with: loki checkpoint create [message]"
                return 0
            fi

            local count=0
            local lines=()
            while IFS= read -r line; do
                lines+=("$line")
            done < "$index_file"

            # Show last 10 entries (most recent last)
            local total=${#lines[@]}
            local start=0
            if [ "$total" -gt 10 ]; then
                start=$((total - 10))
            fi

            printf "  ${DIM}%-14s %-20s %-10s %s${NC}\n" "ID" "TIMESTAMP" "GIT SHA" "MESSAGE"

            local i
            for (( i=start; i<total; i++ )); do
                local entry="${lines[$i]}"
                local cp_id=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null)
                local cp_ts=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('timestamp','') or d.get('ts',''))" 2>/dev/null)
                local cp_sha=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print((d.get('git_sha','') or d.get('sha',''))[:8])" 2>/dev/null)
                local cp_msg=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print((d.get('message','') or d.get('task',''))[:50])" 2>/dev/null)

                if [ -n "$cp_id" ]; then
                    printf "  ${GREEN}%-14s${NC} %-20s ${CYAN}%-10s${NC} %s\n" "$cp_id" "$cp_ts" "$cp_sha" "$cp_msg"
                    count=$((count + 1))
                fi
            done

            if [ "$count" -eq 0 ]; then
                echo "  No valid checkpoints found."
            else
                echo ""
                echo "  Showing ${count} of ${total} checkpoints"
            fi
            ;;

        create)
            local raw_message="${*:-manual checkpoint}"
            # Escape backslashes and double quotes for JSON safety
            local message
            message=$(printf '%s' "$raw_message" | sed 's/\\/\\\\/g; s/"/\\"/g' | head -c 200)

            echo -e "${BOLD}Creating checkpoint...${NC}"
            echo ""

            # Ensure .loki exists
            if [ ! -d ".loki" ]; then
                echo -e "${RED}Error: No .loki directory found. Are you in a Loki project?${NC}"
                return 1
            fi

            # Capture git info
            local git_sha=""
            local git_branch=""
            if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
                git_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
                git_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
            else
                git_sha="not-a-git-repo"
                git_branch="none"
            fi

            # Generate checkpoint ID with timestamp
            local ts=$(date -u '+%Y%m%d-%H%M%S')
            local cp_id="cp-${ts}"
            local cp_dir="$checkpoints_dir/$cp_id"

            # Create checkpoint directory
            mkdir -p "$cp_dir"

            # Copy state files
            local copied=0
            for item in .loki/session.json .loki/dashboard-state.json .loki/queue .loki/memory .loki/metrics .loki/council; do
                if [ -e "$item" ]; then
                    cp -r "$item" "$cp_dir/" 2>/dev/null && copied=$((copied + 1))
                fi
            done

            # Write metadata (use python3 json.dumps for safe serialization)
            local iso_ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
            # Truncate message to 500 chars to prevent abuse
            local safe_message="${message:0:500}"
            _CP_ID="$cp_id" _CP_TS="$iso_ts" _CP_SHA="$git_sha" _CP_BRANCH="$git_branch" \
            _CP_MSG="$safe_message" _CP_FILES="$copied" _CP_DIR="$cp_dir" _CP_INDEX="$index_file" \
            _CP_CHKDIR="$checkpoints_dir" python3 << 'WRITE_META_EOF'
import json, os
metadata = {
    "id": os.environ["_CP_ID"],
    "timestamp": os.environ["_CP_TS"],
    "git_sha": os.environ["_CP_SHA"],
    "git_branch": os.environ["_CP_BRANCH"],
    "message": os.environ["_CP_MSG"],
    "files_copied": int(os.environ["_CP_FILES"]),
    "created_by": "loki checkpoint create"
}
cp_dir = os.environ["_CP_DIR"]
os.makedirs(cp_dir, exist_ok=True)
with open(os.path.join(cp_dir, "metadata.json"), "w") as f:
    json.dump(metadata, f, indent=4)
index_file = os.environ["_CP_INDEX"]
os.makedirs(os.environ["_CP_CHKDIR"], exist_ok=True)
with open(index_file, "a") as f:
    index_entry = {
        "id": metadata["id"],
        "timestamp": metadata["timestamp"],
        "git_sha": metadata["git_sha"],
        "git_branch": metadata["git_branch"],
        "message": metadata["message"],
    }
    f.write(json.dumps(index_entry) + "\n")
WRITE_META_EOF

            echo -e "  Checkpoint: ${GREEN}$cp_id${NC}"
            echo -e "  Git SHA:    ${CYAN}${git_sha:0:8}${NC} ($git_branch)"
            echo -e "  Message:    $message"
            echo -e "  Files:      $copied state items copied"
            echo -e "  Location:   $cp_dir"
            echo ""
            echo "  Restore with: loki checkpoint rollback $cp_id"
            ;;

        show)
            local cp_id="${1:-}"
            if [ -z "$cp_id" ]; then
                echo -e "${RED}Error: Specify a checkpoint ID${NC}"
                echo "Usage: loki checkpoint show <id>"
                echo "Run 'loki checkpoint list' to see available checkpoints."
                return 1
            fi

            # Validate checkpoint ID (prevent path traversal)
            if [[ ! "$cp_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
                echo -e "${RED}Error: Invalid checkpoint ID (must be alphanumeric, hyphens, underscores only)${NC}"
                return 1
            fi

            local cp_dir="$checkpoints_dir/$cp_id"
            local metadata="$cp_dir/metadata.json"

            if [ ! -f "$metadata" ]; then
                echo -e "${RED}Error: Checkpoint not found: $cp_id${NC}"
                echo "Run 'loki checkpoint list' to see available checkpoints."
                return 1
            fi

            echo -e "${BOLD}Checkpoint: $cp_id${NC}"
            echo ""

            _CP_METADATA="$metadata" python3 << 'SHOW_EOF'
import json, os
with open(os.environ["_CP_METADATA"], "r") as f:
    d = json.load(f)
print(f"  ID:         {d.get('id', 'unknown')}")
print(f"  Timestamp:  {d.get('timestamp', 'unknown')}")
print(f"  Git SHA:    {d.get('git_sha', 'unknown')}")
print(f"  Git Branch: {d.get('git_branch', 'unknown')}")
print(f"  Message:    {d.get('message', 'none')}")
print(f"  Files:      {d.get('files_copied', 0)} state items")
print(f"  Created By: {d.get('created_by', 'unknown')}")
SHOW_EOF

            echo ""
            echo "  Contents:"
            for item in "$cp_dir"/*; do
                [ -e "$item" ] || continue
                local name=$(basename "$item")
                [ "$name" = "metadata.json" ] && continue
                if [ -d "$item" ]; then
                    local fcount=$(find "$item" -type f 2>/dev/null | wc -l | tr -d ' ')
                    echo -e "    ${DIM}[dir]${NC}  $name/ ($fcount files)"
                else
                    local fsize=$(wc -c < "$item" 2>/dev/null | tr -d ' ')
                    echo -e "    ${DIM}[file]${NC} $name (${fsize} bytes)"
                fi
            done
            ;;

        rollback)
            local cp_id="${1:-}"
            if [ -z "$cp_id" ]; then
                echo -e "${RED}Error: Specify a checkpoint ID${NC}"
                echo "Usage: loki checkpoint rollback <id>"
                echo "Run 'loki checkpoint list' to see available checkpoints."
                return 1
            fi

            # Validate checkpoint ID (prevent path traversal)
            if [[ ! "$cp_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
                echo -e "${RED}Error: Invalid checkpoint ID (must be alphanumeric, hyphens, underscores only)${NC}"
                return 1
            fi

            local cp_dir="$checkpoints_dir/$cp_id"
            local metadata="$cp_dir/metadata.json"

            if [ ! -f "$metadata" ]; then
                echo -e "${RED}Error: Checkpoint not found: $cp_id${NC}"
                echo "Run 'loki checkpoint list' to see available checkpoints."
                return 1
            fi

            echo -e "${BOLD}Rolling back to checkpoint: $cp_id${NC}"
            echo ""

            # Restore state files
            local restored=0
            for item in "$cp_dir"/*; do
                [ -e "$item" ] || continue
                local name
                name=$(basename "$item")
                [ "$name" = "metadata.json" ] && continue
                # R6: do not restore the worktree-snapshot sidecar as state.
                [ "$name" = "worktree-snapshot.txt" ] && continue

                if [ -d "$item" ]; then
                    # R6 data-loss fix: NEVER `rm -rf ".loki/$name"` -- the
                    # checkpoint store lives under .loki/state/checkpoints/, so
                    # blowing away .loki/state/ would destroy every checkpoint
                    # (including this one). Merge-copy directory contents instead.
                    mkdir -p ".loki/$name"
                    cp -r "$item"/. ".loki/$name"/ 2>/dev/null && restored=$((restored + 1))
                else
                    cp "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
                fi
            done

            echo -e "  Restored: ${GREEN}$restored${NC} state items from $cp_id"

            # Show how to also restore code. R6: prefer the anchored working-tree
            # snapshot over `git reset --hard <git_sha>`. git_sha is HEAD (the last
            # commit), and Loki does not commit per iteration, so a hard reset
            # discards the iteration's work instead of reconstructing it -- the old
            # hint was misleading.
            if git rev-parse --verify "refs/loki/cp/${cp_id}" >/dev/null 2>&1; then
                echo ""
                echo -e "  ${YELLOW}Note:${NC} Session state restored. Code is unchanged."
                echo "  To also restore the working tree to this checkpoint's snapshot:"
                echo ""
                echo -e "    ${DIM}git stash apply refs/loki/cp/${cp_id}${NC}"
                echo ""
                echo -e "  ${DIM}(restores tracked files; newly-added files are not removed)${NC}"
            else
                local cp_sha
                cp_sha=$(_CP_METADATA="$metadata" python3 -c "import json, os; d=json.load(open(os.environ['_CP_METADATA'])); print(d.get('git_sha','unknown'))" 2>/dev/null)
                if [ -n "$cp_sha" ] && [ "$cp_sha" != "unknown" ] && [ "$cp_sha" != "not-a-git-repo" ]; then
                    echo ""
                    echo -e "  ${YELLOW}Note:${NC} Session state restored, but no working-tree snapshot"
                    echo "  was captured for this checkpoint, so code changes since the last"
                    echo -e "  commit (${cp_sha:0:8}) are not restorable from here."
                fi
            fi
            ;;

        help|--help|-h)
            echo -e "${BOLD}loki checkpoint${NC} - Session state checkpoints"
            echo ""
            echo "Save and restore session state snapshots during autonomous runs."
            echo "Checkpoints capture .loki/ state files and record the git SHA"
            echo "at the time of creation."
            echo ""
            echo "Usage: loki checkpoint <command> [args]"
            echo "       loki cp <command> [args]"
            echo ""
            echo "Commands:"
            echo "  list              List recent checkpoints (default)"
            echo "  create [message]  Create a new checkpoint"
            echo "  show <id>         Show checkpoint details"
            echo "  rollback <id>     Restore state from a checkpoint"
            echo "  help              Show this help"
            echo ""
            echo "Examples:"
            echo "  loki checkpoint create 'before refactor'"
            echo "  loki cp list"
            echo "  loki cp show cp-20260212-143022"
            echo "  loki cp rollback cp-20260212-143022"
            ;;

        *)
            echo -e "${RED}Unknown checkpoint command: $subcommand${NC}"
            echo "Run 'loki checkpoint help' for usage."
            return 1
            ;;
    esac
}

# R6: one-command rollback. Top-level, obvious entry point that mirrors the Bun
# `loki rollback` subcommands (list/show/to/latest) so both routes are at parity.
# Restore is destructive on .loki/ state, so it ALWAYS captures a forced
# pre-rollback snapshot first (re-undoability invariant) and then glob-restores
# whatever the checkpoint dir contains (works across all three checkpoint writers).
cmd_rollback() {
    local subcommand="${1:-help}"
    shift 2>/dev/null || true

    local checkpoints_dir=".loki/state/checkpoints"
    local index_file="$checkpoints_dir/index.jsonl"

    # Resolve the most recent checkpoint id from on-disk cp-*/chk-* dirs, sorted
    # by mtime (newest last). Mirrors the Bun "latest = last entry" semantics but
    # is robust to the three differing id formats.
    _rollback_latest_id() {
        [ -d "$checkpoints_dir" ] || return 1
        ls -1dt "$checkpoints_dir"/*/ 2>/dev/null | head -1 | sed 's#/$##' | xargs -I{} basename {} 2>/dev/null
    }

    # Force a pre-rollback snapshot of current state, then glob-restore the target.
    # Delegates the actual restore to `cmd_checkpoint rollback`, which already
    # glob-restores and is shared with the manual path (no duplication).
    _rollback_restore() {
        local target_id="$1"
        local want_code="$2"

        # Validate id (defense in depth; cmd_checkpoint validates again).
        if [[ ! "$target_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
            echo -e "${RED}Error: Invalid checkpoint ID${NC}"
            return 1
        fi
        if [ ! -d "$checkpoints_dir/$target_id" ]; then
            echo -e "${RED}Error: Checkpoint not found: $target_id${NC}"
            echo "Run 'loki rollback list' to see available checkpoints."
            return 1
        fi

        # Re-undoability: snapshot current state before overwriting it.
        local pre_id
        pre_id="rb-pre-$(date -u '+%Y%m%d-%H%M%S')"
        local pre_dir="$checkpoints_dir/$pre_id"
        mkdir -p "$pre_dir"
        local saved=0
        for item in .loki/session.json .loki/dashboard-state.json .loki/CONTINUITY.md .loki/autonomy-state.json .loki/state .loki/queue; do
            if [ -e "$item" ]; then
                cp -r "$item" "$pre_dir/" 2>/dev/null && saved=$((saved + 1))
            fi
        done
        _RB_PID="$pre_id" _RB_TS="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" _RB_DIR="$pre_dir" \
        _RB_INDEX="$index_file" _RB_CHKDIR="$checkpoints_dir" python3 << 'RB_PRE_EOF' 2>/dev/null || true
import json, os
meta = {"id": os.environ["_RB_PID"], "timestamp": os.environ["_RB_TS"],
        "message": "pre-rollback snapshot", "created_by": "loki rollback"}
d = os.environ["_RB_DIR"]
os.makedirs(d, exist_ok=True)
with open(os.path.join(d, "metadata.json"), "w") as f:
    json.dump(meta, f, indent=2)
os.makedirs(os.environ["_RB_CHKDIR"], exist_ok=True)
with open(os.environ["_RB_INDEX"], "a") as f:
    f.write(json.dumps({"id": meta["id"], "timestamp": meta["timestamp"], "message": meta["message"]}) + "\n")
RB_PRE_EOF
        echo -e "  ${DIM}Saved prior state as ${pre_id} (undo this rollback with: loki rollback to ${pre_id})${NC}"

        # Perform the actual state restore via the shared glob-restore path.
        cmd_checkpoint rollback "$target_id"

        # Optional code restore from the anchored working-tree snapshot.
        if [ "$want_code" = "1" ]; then
            if git rev-parse --verify "refs/loki/cp/${target_id}" >/dev/null 2>&1; then
                echo ""
                echo -e "  ${YELLOW}Restoring working tree from snapshot refs/loki/cp/${target_id}...${NC}"
                if git stash apply "refs/loki/cp/${target_id}" 2>/dev/null; then
                    echo -e "  ${GREEN}Working tree restored.${NC} (tracked files only; newly-added files were not removed)"
                else
                    echo -e "  ${RED}Could not apply snapshot cleanly.${NC} Resolve conflicts, or run: git stash apply refs/loki/cp/${target_id}"
                fi
            else
                echo ""
                echo -e "  ${YELLOW}No working-tree snapshot anchored for ${target_id}; code not restored.${NC}"
            fi
        fi
    }

    case "$subcommand" in
        list|ls)
            cmd_checkpoint list
            ;;
        show)
            cmd_checkpoint show "$@"
            ;;
        to)
            local target="${1:-}"
            local want_code=0
            shift 2>/dev/null || true
            for a in "$@"; do [ "$a" = "--code" ] && want_code=1; done
            if [ -z "$target" ]; then
                echo -e "${RED}Error: Specify a checkpoint ID${NC}"
                echo "Usage: loki rollback to <id> [--code]"
                echo "Run 'loki rollback list' to see available checkpoints."
                return 1
            fi
            echo -e "${BOLD}Rolling back to checkpoint: $target${NC}"
            echo ""
            _rollback_restore "$target" "$want_code"
            ;;
        latest)
            local want_code=0
            for a in "$@"; do [ "$a" = "--code" ] && want_code=1; done
            local latest_id
            latest_id=$(_rollback_latest_id)
            if [ -z "$latest_id" ]; then
                echo -e "${RED}No checkpoints found to roll back to.${NC}"
                return 1
            fi
            echo -e "${BOLD}Rolling back to latest checkpoint: $latest_id${NC}"
            echo ""
            _rollback_restore "$latest_id" "$want_code"
            ;;
        help|--help|-h)
            echo -e "${BOLD}loki rollback${NC} - One-command rollback to a checkpoint"
            echo ""
            echo "Restore .loki/ state and iteration/conversation context to a"
            echo "previous checkpoint. Every rollback first saves your current state"
            echo "as a pre-rollback snapshot, so you can always undo the undo."
            echo ""
            echo "Usage: loki rollback <command> [args]"
            echo ""
            echo "Commands:"
            echo "  list              List recent checkpoints"
            echo "  show <id>         Show checkpoint details"
            echo "  to <id> [--code]  Restore to checkpoint <id>"
            echo "  latest [--code]   Restore to the most recent checkpoint"
            echo ""
            echo "  --code            Also restore the working tree from the anchored"
            echo "                    git snapshot (tracked files only; overwrites them)."
            echo ""
            echo "Examples:"
            echo "  loki rollback list"
            echo "  loki rollback latest"
            echo "  loki rollback to cp-3-1717000000"
            echo "  loki rollback to cp-3-1717000000 --code"
            ;;
        *)
            echo -e "${RED}Unknown rollback command: $subcommand${NC}"
            echo "Run 'loki rollback help' for usage."
            return 1
            ;;
    esac
}

# Completion Council management
cmd_council() {
    local subcommand="${1:-status}"
    shift 2>/dev/null || true

    local council_dir=".loki/council"

    case "$subcommand" in
        status)
            echo -e "${CYAN}=== Completion Council Status ===${NC}"

            if [ ! -d "$council_dir" ]; then
                echo -e "${YELLOW}Council not initialized (no active session)${NC}"
                return 0
            fi

            if [ -f "$council_dir/state.json" ]; then
                LOKI_COUNCIL_STATE="$council_dir/state.json" python3 -c "
import json, os, sys
try:
    with open(os.environ['LOKI_COUNCIL_STATE']) as f:
        state = json.load(f)
    print(f\"Enabled: {state.get('initialized', False)}\")
    print(f\"Total votes: {state.get('total_votes', 0)}\")
    print(f\"Approve votes: {state.get('approve_votes', 0)}\")
    print(f\"Reject votes: {state.get('reject_votes', 0)}\")
    print(f\"Stagnation streak: {state.get('consecutive_no_change', 0)}\")
    print(f\"Done signals: {state.get('done_signals', 0)}\")
    print(f\"Last check: iteration {state.get('last_check_iteration', 'none')}\")
    verdicts = state.get('verdicts', [])
    if verdicts:
        print(f\"\nRecent verdicts:\")
        for v in verdicts[-5:]:
            print(f\"  Iteration {v['iteration']}: {v['result']} ({v['approve']} approve / {v['reject']} reject)\")
    else:
        print(f\"\nNo verdicts yet\")
except (json.JSONDecodeError, KeyError, TypeError) as e:
    print(f'Error: Council state file is corrupted or invalid: {e}', file=sys.stderr)
    print('Delete .loki/council/state.json and restart the session to reset.')
    sys.exit(1)
except Exception as e:
    print(f'Error reading council state: {e}', file=sys.stderr)
    sys.exit(1)
" 2>&1
            else
                echo "Council state file not found"
            fi
            ;;

        verdicts|log)
            echo -e "${CYAN}=== Council Decision Log ===${NC}"

            if [ -f "$council_dir/state.json" ]; then
                LOKI_COUNCIL_STATE="$council_dir/state.json" python3 -c "
import json, os, sys
try:
    with open(os.environ['LOKI_COUNCIL_STATE']) as f:
        state = json.load(f)
except (json.JSONDecodeError, KeyError, TypeError) as e:
    print(f'Error: Council state file is corrupted: {e}', file=sys.stderr)
    print('Delete .loki/council/state.json and restart the session to reset.')
    sys.exit(1)
verdicts = state.get('verdicts', [])
if not verdicts:
    print('No decisions recorded yet')
else:
    for v in verdicts:
        result = v.get('result', 'UNKNOWN')
        color = '\033[32m' if result == 'APPROVED' else '\033[31m'
        print(f\"{color}{result}\033[0m - Iteration {v['iteration']} ({v.get('timestamp', 'unknown')}) - {v['approve']} approve / {v['reject']} reject\")
" 2>/dev/null
            else
                echo "No council data available"
            fi
            ;;

        convergence)
            echo -e "${CYAN}=== Convergence Tracking ===${NC}"

            if [ -f "$council_dir/convergence.log" ]; then
                echo "Timestamp | Iteration | Files Changed | No-Change Streak | Done Signals"
                echo "----------|-----------|---------------|-----------------|-------------"
                tail -20 "$council_dir/convergence.log" | while IFS='|' read -r ts iter files streak signals; do
                    printf "%-10s| %-9s | %-13s | %-15s | %s\n" "$ts" "$iter" "$files" "$streak" "$signals"
                done
            else
                echo "No convergence data available"
            fi
            ;;

        force-review)
            echo "Requesting immediate council review..."
            mkdir -p ".loki/signals"
            echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" > ".loki/signals/COUNCIL_REVIEW_REQUESTED"
            echo -e "${GREEN}Review requested. Council will convene at next iteration.${NC}"
            ;;

        report)
            if [ -f "$council_dir/report.md" ]; then
                cat "$council_dir/report.md"
            else
                echo "No completion report available yet"
            fi
            ;;

        config)
            echo -e "${CYAN}=== Council Configuration ===${NC}"
            echo "LOKI_COUNCIL_ENABLED=${LOKI_COUNCIL_ENABLED:-true}"
            echo "LOKI_COUNCIL_SIZE=${LOKI_COUNCIL_SIZE:-3}"
            echo "LOKI_COUNCIL_THRESHOLD=${LOKI_COUNCIL_THRESHOLD:-2}"
            echo "LOKI_COUNCIL_CHECK_INTERVAL=${LOKI_COUNCIL_CHECK_INTERVAL:-5}"
            echo "LOKI_COUNCIL_MIN_ITERATIONS=${LOKI_COUNCIL_MIN_ITERATIONS:-3}"
            echo "LOKI_COUNCIL_STAGNATION_LIMIT=${LOKI_COUNCIL_STAGNATION_LIMIT:-5}"
            ;;

        help|--help|-h)
            echo "Usage: loki council <command>"
            echo ""
            echo "Commands:"
            echo "  status         Show council status (default)"
            echo "  verdicts       Show decision log (alias: log)"
            echo "  convergence    Show convergence tracking data"
            echo "  force-review   Request immediate council review"
            echo "  report         Show completion report"
            echo "  config         Show council configuration"
            echo "  help           Show this help"
            ;;

        *)
            echo -e "${RED}Unknown council command: $subcommand${NC}"
            echo "Run 'loki council help' for usage."
            exit 1
            ;;
    esac
}

# Cross-project registry management
cmd_projects() {
    local subcommand="${1:-list}"
    local registry_dir="${HOME}/.loki/dashboard"
    local registry_file="${registry_dir}/projects.json"

    # Ensure directory exists
    mkdir -p "$registry_dir"

    # Initialize empty registry if needed
    if [ ! -f "$registry_file" ]; then
        echo '{"version":"1.0","projects":{}}' > "$registry_file"
    fi

    case "$subcommand" in
        list|ls)
            echo -e "${BOLD}Registered Projects${NC}"
            echo ""

            python3 -c "
import json
import os
from datetime import datetime

registry_file = '$registry_file'
if os.path.exists(registry_file):
    with open(registry_file, 'r') as f:
        data = json.load(f)
        projects = data.get('projects', {})
        if not projects:
            print('  (no projects registered)')
            print('')
            print('Use \"loki projects add <path>\" to register a project')
        else:
            # Sort by last accessed
            sorted_projects = sorted(
                projects.values(),
                key=lambda p: p.get('last_accessed') or p.get('registered_at', ''),
                reverse=True
            )
            for p in sorted_projects:
                status_icon = '[OK]' if os.path.isdir(p['path']) else '[MISSING]'
                alias = f\" ({p['alias']})\" if p.get('alias') else ''
                print(f\"  {status_icon} {p['name']}{alias}\")
                print(f\"      Path: {p['path']}\")
                if p.get('last_accessed'):
                    print(f\"      Last accessed: {p['last_accessed'][:10]}\")
                print()
else:
    print('  (no projects registered)')
" 2>/dev/null
            ;;

        add)
            local path="${2:-$(pwd)}"
            local name=""
            local alias=""
            shift 2 2>/dev/null || shift 1 2>/dev/null || true

            # Parse optional args
            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --name|-n)
                        name="${2:-}"
                        shift 2
                        ;;
                    --alias|-a)
                        alias="${2:-}"
                        shift 2
                        ;;
                    *)
                        shift
                        ;;
                esac
            done

            # Resolve path
            path=$(cd "$path" 2>/dev/null && pwd || echo "$path")

            if [ ! -d "$path" ]; then
                echo -e "${RED}Error: Path does not exist: $path${NC}"
                exit 1
            fi

            python3 -c "
import json
import os
import hashlib
from datetime import datetime, timezone

registry_file = '$registry_file'
path = '$path'
name = '$name' or os.path.basename(path)
alias = '$alias' or None

# Generate project ID
project_id = hashlib.md5(path.encode()).hexdigest()[:12]

# Load registry
with open(registry_file, 'r') as f:
    data = json.load(f)

projects = data.get('projects', {})
now = datetime.now(timezone.utc).isoformat()

if project_id in projects:
    # Update existing
    projects[project_id]['name'] = name
    if alias:
        projects[project_id]['alias'] = alias
    projects[project_id]['updated_at'] = now
    print(f'Updated: {name}')
else:
    # Add new
    projects[project_id] = {
        'id': project_id,
        'path': path,
        'name': name,
        'alias': alias,
        'registered_at': now,
        'updated_at': now,
        'last_accessed': None,
        'has_loki_dir': os.path.isdir(os.path.join(path, '.loki')),
        'status': 'active',
    }
    print(f'Registered: {name}')

data['projects'] = projects
with open(registry_file, 'w') as f:
    json.dump(data, f, indent=2)

print(f'  Path: {path}')
if alias:
    print(f'  Alias: {alias}')
" 2>/dev/null
            ;;

        remove|rm)
            local identifier="${2:-}"

            if [ -z "$identifier" ]; then
                echo -e "${RED}Error: Specify project ID, path, or alias${NC}"
                exit 1
            fi

            python3 -c "
import json
import os

registry_file = '$registry_file'
identifier = '$identifier'

with open(registry_file, 'r') as f:
    data = json.load(f)

projects = data.get('projects', {})
found_id = None

for pid, project in projects.items():
    if pid == identifier or project['path'] == identifier or project.get('alias') == identifier:
        found_id = pid
        break

if found_id:
    name = projects[found_id]['name']
    del projects[found_id]
    data['projects'] = projects
    with open(registry_file, 'w') as f:
        json.dump(data, f, indent=2)
    print(f'Removed: {name}')
else:
    print(f'Not found: {identifier}')
    exit(1)
" 2>/dev/null
            ;;

        discover)
            echo -e "${BOLD}Discovering Projects${NC}"
            echo ""

            python3 -c "
import os
from pathlib import Path

home = Path.home()
search_paths = [
    home / 'git',
    home / 'projects',
    home / 'code',
    home / 'dev',
    home / 'workspace',
    home / 'src',
]

discovered = []

def search_dir(path, depth=0, max_depth=3):
    if depth > max_depth:
        return
    try:
        if not path.is_dir():
            return
        loki_dir = path / '.loki'
        if loki_dir.is_dir():
            discovered.append({
                'path': str(path),
                'name': path.name,
                'has_state': (loki_dir / 'state' / 'session.json').exists(),
            })
            return
        for child in path.iterdir():
            if child.is_dir() and not child.name.startswith('.'):
                search_dir(child, depth + 1, max_depth)
    except (PermissionError, OSError):
        pass

for search_path in search_paths:
    if search_path.exists():
        search_dir(search_path)

if not discovered:
    print('  No projects with .loki directories found')
else:
    print(f'  Found {len(discovered)} project(s):')
    print('')
    for p in discovered:
        status = '[active]' if p['has_state'] else '[idle]'
        print(f\"  {status} {p['name']}\")
        print(f\"      {p['path']}\")
        print()
    print('Use \"loki projects sync\" to register all discovered projects')
" 2>/dev/null
            ;;

        sync)
            echo -e "${BOLD}Syncing Project Registry${NC}"
            echo ""

            python3 -c "
import json
import os
import hashlib
from pathlib import Path
from datetime import datetime, timezone

registry_file = '$registry_file'
home = Path.home()

# Load registry
with open(registry_file, 'r') as f:
    data = json.load(f)
projects = data.get('projects', {})

# Discover projects
search_paths = [
    home / 'git',
    home / 'projects',
    home / 'code',
    home / 'dev',
    home / 'workspace',
    home / 'src',
]

discovered = []

def search_dir(path, depth=0, max_depth=3):
    if depth > max_depth:
        return
    try:
        if not path.is_dir():
            return
        loki_dir = path / '.loki'
        if loki_dir.is_dir():
            discovered.append(str(path))
            return
        for child in path.iterdir():
            if child.is_dir() and not child.name.startswith('.'):
                search_dir(child, depth + 1, max_depth)
    except (PermissionError, OSError):
        pass

for search_path in search_paths:
    if search_path.exists():
        search_dir(search_path)

# Add new projects
added = 0
now = datetime.now(timezone.utc).isoformat()

for path in discovered:
    project_id = hashlib.md5(path.encode()).hexdigest()[:12]
    if project_id not in projects:
        projects[project_id] = {
            'id': project_id,
            'path': path,
            'name': os.path.basename(path),
            'alias': None,
            'registered_at': now,
            'updated_at': now,
            'last_accessed': None,
            'has_loki_dir': True,
            'status': 'active',
        }
        added += 1
        print(f'  Added: {os.path.basename(path)}')

# Check for missing
missing = 0
for pid, project in list(projects.items()):
    if not os.path.isdir(project['path']):
        project['status'] = 'missing'
        missing += 1

data['projects'] = projects
with open(registry_file, 'w') as f:
    json.dump(data, f, indent=2)

print('')
print(f'Added: {added}, Missing: {missing}, Total: {len(projects)}')
" 2>/dev/null
            ;;

        health)
            local identifier="${2:-$(pwd)}"

            python3 -c "
import json
import os

registry_file = '$registry_file'
identifier = '$identifier'

# If it's a path, resolve it
if os.path.isdir(identifier):
    identifier = os.path.abspath(identifier)

with open(registry_file, 'r') as f:
    data = json.load(f)

projects = data.get('projects', {})
project = None

for p in projects.values():
    if p['id'] == identifier or p['path'] == identifier or p.get('alias') == identifier:
        project = p
        break

if not project:
    print(f'Project not found: {identifier}')
    exit(1)

path = project['path']
checks = {
    'Path exists': os.path.isdir(path),
    '.loki dir exists': os.path.isdir(os.path.join(path, '.loki')),
    'Session state': os.path.isfile(os.path.join(path, '.loki', 'state', 'session.json')),
    'PRD exists': any(
        os.path.isfile(os.path.join(path, f))
        for f in ['PRD.md', 'prd.md', 'docs/PRD.md', 'docs/prd.md']
    ),
}

print(f\"Project: {project['name']}\")
print(f\"Path: {path}\")
print('')
print('Health Checks:')
for check, passed in checks.items():
    icon = '[OK]' if passed else '[FAIL]'
    print(f'  {icon} {check}')
" 2>/dev/null
            ;;

        --help|-h|help)
            echo -e "${BOLD}loki projects${NC} - Manage cross-project registry"
            echo ""
            echo "Usage: loki projects <command> [args]"
            echo ""
            echo "Commands:"
            echo "  list              List all registered projects"
            echo "  add <path>        Register a project"
            echo "  remove <id>       Remove a project from registry"
            echo "  discover          Find projects with .loki directories"
            echo "  sync              Auto-discover and register projects"
            echo "  health <id>       Check project health status"
            echo ""
            echo "Options for 'add':"
            echo "  --name, -n        Display name for the project"
            echo "  --alias, -a       Short alias for quick access"
            echo ""
            echo "Examples:"
            echo "  loki projects list"
            echo "  loki projects add . --alias myapp"
            echo "  loki projects add ~/git/project --name 'My Project'"
            echo "  loki projects discover"
            echo "  loki projects sync"
            echo "  loki projects health myapp"
            ;;

        *)
            echo -e "${RED}Unknown projects command: $subcommand${NC}"
            echo "Run 'loki projects help' for usage."
            exit 1
            ;;
    esac
}

# Voice input commands
cmd_voice() {
    # Voice mode is planned for a future release (#85)
    echo "Voice mode is planned for a future release. Track progress at github.com/asklokesh/loki-mode/issues/85"
    return 0
}

# Enterprise features (optional - requires env vars)
cmd_enterprise() {
    local subcommand="${1:-status}"
    local token_dir="${HOME}/.loki/dashboard"
    local token_file="${token_dir}/tokens.json"

    # Ensure directory exists
    mkdir -p "$token_dir"

    # Initialize empty tokens if needed
    if [ ! -f "$token_file" ]; then
        echo '{"version":"1.0","tokens":{}}' > "$token_file"
        chmod 600 "$token_file"
    fi

    case "$subcommand" in
        status)
            echo -e "${BOLD}Enterprise Features Status${NC}"
            echo ""

            local auth_enabled="${LOKI_ENTERPRISE_AUTH:-false}"
            local audit_disabled="${LOKI_AUDIT_DISABLED:-false}"
            local audit_force_on="${LOKI_ENTERPRISE_AUDIT:-false}"

            if [ "$auth_enabled" = "true" ] || [ "$auth_enabled" = "1" ]; then
                echo -e "  Token Auth:     ${GREEN}Enabled${NC}"
            else
                echo -e "  Token Auth:     ${DIM}Disabled${NC}"
            fi

            # OIDC/SSO status
            local oidc_issuer="${LOKI_OIDC_ISSUER:-}"
            local oidc_client="${LOKI_OIDC_CLIENT_ID:-}"
            if [ -n "$oidc_issuer" ] && [ -n "$oidc_client" ]; then
                echo -e "  OIDC/SSO:       ${GREEN}Enabled${NC} (${oidc_issuer})"
            else
                echo -e "  OIDC/SSO:       ${DIM}Disabled${NC}"
            fi

            # Audit is on by default; disabled only if LOKI_AUDIT_DISABLED=true
            if [ "$audit_force_on" = "true" ] || [ "$audit_force_on" = "1" ]; then
                echo -e "  Audit Logging:  ${GREEN}Enabled (enterprise)${NC}"
            elif [ "$audit_disabled" = "true" ] || [ "$audit_disabled" = "1" ]; then
                echo -e "  Audit Logging:  ${DIM}Disabled${NC}"
            else
                echo -e "  Audit Logging:  ${GREEN}Enabled (default)${NC}"
            fi

            echo ""
            echo "Configure with environment variables:"
            echo "  export LOKI_ENTERPRISE_AUTH=true     # Token authentication"
            echo "  export LOKI_AUDIT_DISABLED=true      # Disable audit logging"
            echo "  export LOKI_ENTERPRISE_AUDIT=true    # Force audit on (legacy)"
            echo ""
            echo "OIDC/SSO (optional, works alongside token auth):"
            echo "  export LOKI_OIDC_ISSUER=https://accounts.google.com"
            echo "  export LOKI_OIDC_CLIENT_ID=your-client-id"
            echo "  export LOKI_OIDC_AUDIENCE=your-audience    # Optional, defaults to client_id"
            ;;

        token)
            local token_cmd="${2:-list}"
            shift 2 2>/dev/null || shift 1 2>/dev/null || true

            case "$token_cmd" in
                generate|create|new)
                    local name="${1:-}"
                    local scopes=""
                    local expires=""

                    if [ -z "$name" ]; then
                        echo -e "${RED}Error: Token name required${NC}"
                        echo "Usage: loki enterprise token generate <name> [--scopes '*'] [--expires 30]"
                        exit 1
                    fi

                    # Validate name is not a flag
                    if [[ "$name" == -* ]]; then
                        echo -e "${RED}Error: Token name cannot start with '-'${NC}"
                        echo "Usage: loki enterprise token generate <name> [--scopes '*'] [--expires 30]"
                        exit 1
                    fi

                    # Warn if enterprise auth is not enabled
                    local auth_enabled="${LOKI_ENTERPRISE_AUTH:-false}"
                    if [ "$auth_enabled" != "true" ] && [ "$auth_enabled" != "1" ]; then
                        echo -e "${YELLOW}Warning: LOKI_ENTERPRISE_AUTH is not enabled${NC}"
                        echo "Tokens can be generated but won't work for API authentication."
                        echo "Enable with: export LOKI_ENTERPRISE_AUTH=true"
                        echo ""
                    fi

                    shift

                    while [[ $# -gt 0 ]]; do
                        case "$1" in
                            --scopes|-s)
                                scopes="${2:-}"
                                shift 2
                                ;;
                            --expires|-e)
                                expires="${2:-}"
                                shift 2
                                ;;
                            *)
                                shift
                                ;;
                        esac
                    done

                    python3 -c "
import json
import secrets
import hashlib
from datetime import datetime, timezone, timedelta
import os

token_file = '$token_file'
name = '$name'
scopes_str = '$scopes'
expires_str = '$expires'

# Parse scopes
scopes = scopes_str.split(',') if scopes_str else ['*']

# Parse expiration
expires_at = None
if expires_str:
    days = int(expires_str)
    expires_at = (datetime.now(timezone.utc) + timedelta(days=days)).isoformat()

# Generate token
raw_token = f'loki_{secrets.token_urlsafe(32)}'
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
token_id = token_hash[:12]

# Load tokens
with open(token_file, 'r') as f:
    data = json.load(f)

tokens = data.get('tokens', {})

# Check duplicate name
for existing in tokens.values():
    if existing.get('name') == name:
        print(f'Error: Token with name \"{name}\" already exists')
        exit(1)

# Create token entry
tokens[token_id] = {
    'id': token_id,
    'name': name,
    'hash': token_hash,
    'scopes': scopes,
    'created_at': datetime.now(timezone.utc).isoformat(),
    'expires_at': expires_at,
    'last_used': None,
    'revoked': False,
}

data['tokens'] = tokens
with open(token_file, 'w') as f:
    json.dump(data, f, indent=2)

# Set restrictive permissions
os.chmod(token_file, 0o600)

print(f'Token created: {name}')
print(f'ID: {token_id}')
print(f'Scopes: {scopes}')
if expires_at:
    print(f'Expires: {expires_at[:10]}')
print('')
print('Token (save this - shown only once):')
print(f'  {raw_token}')
" 2>/dev/null
                    ;;

                list|ls)
                    local include_revoked="false"
                    if [ "${1:-}" = "--all" ] || [ "${1:-}" = "--include-revoked" ]; then
                        include_revoked="true"
                    fi

                    echo -e "${BOLD}API Tokens${NC}"
                    if [ "$include_revoked" = "true" ]; then
                        echo -e "${DIM}(including revoked)${NC}"
                    fi
                    echo ""

                    python3 -c "
import json
import os

token_file = '$token_file'
include_revoked = '$include_revoked' == 'true'

with open(token_file, 'r') as f:
    data = json.load(f)

tokens = data.get('tokens', {})
if include_revoked:
    token_list = list(tokens.values())
else:
    token_list = [t for t in tokens.values() if not t.get('revoked')]

if not token_list:
    print('  (no tokens)')
    if not include_revoked:
        print('')
        print('  Use --all to include revoked tokens')
else:
    for t in token_list:
        if t.get('revoked'):
            status = '[REVOKED]'
        elif t.get('expires_at'):
            from datetime import datetime, timezone
            expires = datetime.fromisoformat(t['expires_at'])
            if datetime.now(timezone.utc) > expires:
                status = '[EXPIRED]'
            else:
                status = '[OK]'
        else:
            status = '[OK]'
        print(f\"  {status} {t['name']} ({t['id']})\")
        print(f\"      Scopes: {', '.join(t['scopes'])}\")
        if t.get('created_at'):
            print(f\"      Created: {t['created_at'][:10]}\")
        if t.get('expires_at'):
            print(f\"      Expires: {t['expires_at'][:10]}\")
        if t.get('last_used'):
            print(f\"      Last used: {t['last_used'][:10]}\")
        print()
" 2>/dev/null
                    ;;

                revoke)
                    local identifier="${1:-}"

                    if [ -z "$identifier" ]; then
                        echo -e "${RED}Error: Token ID or name required${NC}"
                        exit 1
                    fi

                    python3 -c "
import json
from datetime import datetime, timezone

token_file = '$token_file'
identifier = '$identifier'

with open(token_file, 'r') as f:
    data = json.load(f)

tokens = data.get('tokens', {})
found_id = None

for tid, token in tokens.items():
    if tid == identifier or token.get('name') == identifier:
        found_id = tid
        break

if found_id:
    tokens[found_id]['revoked'] = True
    tokens[found_id]['revoked_at'] = datetime.now(timezone.utc).isoformat()
    data['tokens'] = tokens
    with open(token_file, 'w') as f:
        json.dump(data, f, indent=2)
    print(f'Revoked: {tokens[found_id][\"name\"]}')
else:
    print(f'Token not found: {identifier}')
    exit(1)
" 2>/dev/null
                    ;;

                delete)
                    local identifier="${1:-}"

                    if [ -z "$identifier" ]; then
                        echo -e "${RED}Error: Token ID or name required${NC}"
                        exit 1
                    fi

                    python3 -c "
import json

token_file = '$token_file'
identifier = '$identifier'

with open(token_file, 'r') as f:
    data = json.load(f)

tokens = data.get('tokens', {})
found_id = None
found_name = None

for tid, token in tokens.items():
    if tid == identifier or token.get('name') == identifier:
        found_id = tid
        found_name = token.get('name')
        break

if found_id:
    del tokens[found_id]
    data['tokens'] = tokens
    with open(token_file, 'w') as f:
        json.dump(data, f, indent=2)
    print(f'Deleted: {found_name}')
else:
    print(f'Token not found: {identifier}')
    exit(1)
" 2>/dev/null
                    ;;

                *)
                    echo -e "${RED}Unknown token command: $token_cmd${NC}"
                    echo ""
                    echo "Usage: loki enterprise token <command>"
                    echo ""
                    echo "Commands:"
                    echo "  generate <name>   Create a new API token"
                    echo "  list              List all active tokens"
                    echo "  revoke <id>       Revoke a token (keeps record)"
                    echo "  delete <id>       Permanently delete a token"
                    echo ""
                    echo "Options for 'generate':"
                    echo "  --scopes, -s      Comma-separated scopes (default: *)"
                    echo "  --expires, -e     Days until expiration (default: never)"
                    exit 1
                    ;;
            esac
            ;;

        audit)
            local audit_cmd="${2:-summary}"

            # Audit is on by default. Only blocked if explicitly disabled and not force-enabled.
            local _audit_disabled="${LOKI_AUDIT_DISABLED:-false}"
            local _audit_force="${LOKI_ENTERPRISE_AUDIT:-false}"
            if [ "$_audit_force" != "true" ] && [ "$_audit_force" != "1" ]; then
                if [ "$_audit_disabled" = "true" ] || [ "$_audit_disabled" = "1" ]; then
                    echo -e "${YELLOW}Audit logging is disabled${NC}"
                    echo "Remove LOKI_AUDIT_DISABLED or set LOKI_ENTERPRISE_AUDIT=true"
                    exit 1
                fi
            fi

            local audit_dir="${HOME}/.loki/dashboard/audit"

            case "$audit_cmd" in
                summary)
                    echo -e "${BOLD}Audit Summary (Last 7 Days)${NC}"
                    echo ""

                    if [ ! -d "$audit_dir" ]; then
                        echo "  No audit logs found"
                        exit 0
                    fi

                    python3 -c "
import json
import os
from pathlib import Path
from datetime import datetime, timezone, timedelta

audit_dir = Path('$audit_dir')
days = 7
start_date = (datetime.now(timezone.utc) - timedelta(days=days)).strftime('%Y-%m-%d')

entries = []
for log_file in sorted(audit_dir.glob('audit-*.jsonl')):
    if log_file.stem >= f'audit-{start_date}':
        try:
            with open(log_file, 'r') as f:
                for line in f:
                    try:
                        entries.append(json.loads(line.strip()))
                    except json.JSONDecodeError:
                        pass
        except IOError:
            pass

total = len(entries)
success = sum(1 for e in entries if e.get('success', True))
failed = total - success

print(f'Total events: {total}')
print(f'Successful: {success}')
print(f'Failed: {failed}')
print('')

if failed > 0:
    print('Recent failures:')
    failures = [e for e in entries if not e.get('success', True)][-5:]
    for f in failures:
        print(f\"  {f.get('timestamp', '')[:19]} {f.get('action')} {f.get('resource_type')}: {f.get('error', 'unknown')}\")
" 2>/dev/null
                    ;;

                tail)
                    echo -e "${BOLD}Recent Audit Events${NC}"
                    echo ""

                    if [ ! -d "$audit_dir" ]; then
                        echo "  No audit logs found"
                        exit 0
                    fi

                    # Get most recent log file and show last 20 entries
                    local latest_log
                    latest_log=$(ls -t "$audit_dir"/audit-*.jsonl 2>/dev/null | head -1)

                    if [ -z "$latest_log" ]; then
                        echo "  No audit logs found"
                        exit 0
                    fi

                    tail -20 "$latest_log" | python3 -c "
import json
import sys

for line in sys.stdin:
    try:
        e = json.loads(line.strip())
        status = 'OK' if e.get('success', True) else 'FAIL'
        print(f\"[{status}] {e.get('timestamp', '')[:19]} {e.get('action')} {e.get('resource_type')} {e.get('resource_id', '')}\")
    except:
        pass
" 2>/dev/null
                    ;;

                *)
                    echo -e "${RED}Unknown audit command: $audit_cmd${NC}"
                    echo ""
                    echo "Usage: loki enterprise audit <command>"
                    echo ""
                    echo "Commands:"
                    echo "  summary           Show audit summary (last 7 days)"
                    echo "  tail              Show recent audit events"
                    exit 1
                    ;;
            esac
            ;;

        --help|-h|help)
            echo -e "${BOLD}loki enterprise${NC} - Enterprise features (optional)"
            echo ""
            echo "Usage: loki enterprise <command>"
            echo ""
            echo "Commands:"
            echo "  status            Show which enterprise features are enabled"
            echo "  token <cmd>       Manage API tokens (requires LOKI_ENTERPRISE_AUTH=true)"
            echo "  audit <cmd>       Query audit logs (enabled by default)"
            echo ""
            echo "Configure enterprise features:"
            echo "  export LOKI_ENTERPRISE_AUTH=true     # Token authentication"
            echo "  export LOKI_AUDIT_DISABLED=true      # Disable audit logging"
            echo "  export LOKI_ENTERPRISE_AUDIT=true    # Force audit on (legacy)"
            echo ""
            echo "OIDC/SSO (optional, works alongside token auth):"
            echo "  export LOKI_OIDC_ISSUER=https://accounts.google.com"
            echo "  export LOKI_OIDC_CLIENT_ID=your-client-id"
            echo "  export LOKI_OIDC_AUDIENCE=your-audience    # Optional"
            echo ""
            echo "Audit logging is enabled by default. Auth is optional."
            ;;

        *)
            echo -e "${RED}Unknown enterprise command: $subcommand${NC}"
            echo "Run 'loki enterprise help' for usage."
            exit 1
            ;;
    esac
}

# Telemetry management (v6.7.0)
cmd_crash() {
    # Phase 0: local-only crash report inspection. NO network egress.
    #
    # PARITY: the user-facing strings here MUST match the TS canonical command
    # at loki-ts/src/commands/crash.ts byte-for-byte (ignoring color codes).
    # The whole subcommand body is implemented in one Python helper so the
    # behavior (filename-id resolution, "-" for missing fields, JSON shape,
    # URL encoding, exit codes) cannot drift from the TS route.
    #
    # Subcommands:
    #   loki crash               list .loki/crash/*.json reports
    #   loki crash show <id>     pretty-print one scrubbed report
    #   loki crash submit [<id>] print the scrubbed payload + a prefilled
    #                            GitHub issue URL for MANUAL submission
    _LOKI_CRASH_DIR=".loki/crash" _LOKI_CRASH_SUB="${1-}" _LOKI_CRASH_ARG="${2-}" \
    python3 -c '
import json, os, sys, urllib.parse

# Colors: honor NO_COLOR (https://no-color.org) exactly like the TS route
# (loki-ts/src/util/colors.ts). Codes match autonomy/loki:25-32 byte-for-byte.
_no_color = len(os.environ.get("NO_COLOR", "")) > 0
def _c(code):
    return "" if _no_color else code
RED = _c("\x1b[0;31m")
GREEN = _c("\x1b[0;32m")
YELLOW = _c("\x1b[1;33m")
CYAN = _c("\x1b[0;36m")
BOLD = _c("\x1b[1m")
NC = _c("\x1b[0m")

ISSUE_BASE = "https://github.com/asklokesh/loki-mode/issues/new"
CRASH_DIR = os.environ["_LOKI_CRASH_DIR"]
SUB = os.environ.get("_LOKI_CRASH_SUB", "")
ARG = os.environ.get("_LOKI_CRASH_ARG", "")

HELP = (
    BOLD + "loki crash" + NC + " - inspect and manually submit local crash reports\n"
    "\n"
    "Usage: loki crash [subcommand] [args]\n"
    "\n"
    "Subcommands:\n"
    "  (none) | list        List crash reports in .loki/crash/\n"
    "  show <id>            Pretty-print one scrubbed crash report\n"
    "  submit [<id>]        Print the scrubbed payload and a prefilled GitHub\n"
    "                       issue URL for manual submission\n"
    "\n"
    "Crash reports are anonymous, scrubbed, and stored locally only. Nothing is\n"
    "sent automatically in this version. See docs/PRIVACY.md.\n"
)

def s(v):
    return "-" if v is None else str(v)

def pad(text, w):
    return text if len(text) >= w else text + " " * (w - len(text))

def report_ids():
    if not os.path.isdir(CRASH_DIR):
        return []
    try:
        names = [e for e in os.listdir(CRASH_DIR)
                 if e.endswith(".json") and os.path.isfile(os.path.join(CRASH_DIR, e))]
    except OSError:
        return []
    return sorted(n[:-len(".json")] for n in names)

def _safe_id(rid):
    # Reject path traversal: ids are bare filenames (no separators, no "..",
    # no leading separator). This guards the direct filename lookup; the
    # fingerprint-resolution loop only ever passes real listdir ids, which
    # always pass. Mirrors the identical rule in loki-ts crash.ts.
    if rid is None or rid == "":
        return False
    if "/" in rid or "\\" in rid or ".." in rid:
        return False
    if rid[0] in ("/", "\\"):
        return False
    return True

def read_report(rid):
    if not _safe_id(rid):
        return None
    p = os.path.join(CRASH_DIR, rid + ".json")
    if not os.path.exists(p):
        return None
    try:
        with open(p) as f:
            return json.load(f)
    except Exception:
        return {}

def resolve_report(arg):
    direct = read_report(arg)
    if direct is not None:
        return (arg, direct)
    for rid in report_ids():
        r = read_report(rid)
        if r is not None and str(r.get("fingerprint", "")) == arg:
            return (rid, r)
    return None

def dumps(obj):
    return json.dumps(obj, indent=2, ensure_ascii=False)

def list_crashes():
    ids = report_ids()
    if not ids:
        sys.stdout.write(YELLOW + "No crash reports found." + NC +
                         " Nothing has been captured in .loki/crash/.\n")
        return 0
    sys.stdout.write(pad("ID", 40) + "  " + pad("CAPTURED_AT", 22) + "  ERROR_CLASS\n")
    for rid in ids:
        d = read_report(rid) or {}
        fp = s(d.get("fingerprint"))
        captured_at = s(d.get("captured_at"))
        error_class = s(d.get("error_class"))
        visible_id = fp if fp != "-" else rid
        sys.stdout.write(pad(visible_id, 40) + "  " + pad(captured_at, 22) + "  " + error_class + "\n")
    sys.stdout.write(
        "\n" + str(len(ids)) + " report(s). Run \x27loki crash show <id>\x27 to inspect, "
        "\x27loki crash submit\x27 to get a prefilled GitHub issue URL.\n")
    return 0

def show_crash(rid):
    if not rid:
        sys.stderr.write(RED + "Missing crash id." + NC + " Use \x27loki crash\x27 to list reports.\n")
        return 2
    resolved = resolve_report(rid)
    if resolved is None:
        sys.stderr.write(RED + "Crash report not found: " + rid + NC + "\n")
        sys.stderr.write("Use \x27loki crash\x27 to see available reports.\n")
        return 1
    sys.stdout.write(dumps(resolved[1]) + "\n")
    return 0

def issue_url(report):
    error_class = s(report.get("error_class"))
    fp = s(report.get("fingerprint"))
    short_fp = fp[:12] if fp != "-" else "unknown"
    title = "crash: " + error_class + " (" + short_fp + ")"
    payload = dumps(report)
    body = "\n".join([
        "Anonymous crash report captured by Loki Mode (scrubbed, whitelist-only).",
        "",
        "Scrubbed payload:",
        "```json",
        payload,
        "```",
        "",
        "Nothing was sent automatically. This issue is submitted manually by me.",
    ])
    q = urllib.parse.urlencode({"title": title, "body": body})
    return ISSUE_BASE + "?" + q

def submit_crash(rid):
    if rid:
        target = resolve_report(rid)
        if target is None:
            sys.stderr.write(RED + "Crash report not found: " + rid + NC + "\n")
            sys.stderr.write("Use \x27loki crash\x27 to see available reports.\n")
            return 1
    else:
        ids = report_ids()
        if not ids:
            sys.stdout.write(YELLOW + "No crash reports found." + NC + " Nothing to submit.\n")
            return 0
        last_id = ids[-1]
        target = (last_id, read_report(last_id) or {})
    sys.stdout.write(BOLD + "Scrubbed payload (this is the ENTIRE report):" + NC + "\n")
    sys.stdout.write(dumps(target[1]) + "\n\n")
    sys.stdout.write(YELLOW + "Nothing is sent automatically in this version." + NC +
                     " Loki Mode never transmits crash data on its own.\n")
    sys.stdout.write("To submit manually, open this prefilled GitHub issue and review it first:\n\n")
    sys.stdout.write("  " + CYAN + issue_url(target[1]) + NC + "\n\n")
    sys.stdout.write(GREEN + "The payload above is exactly what the URL contains." + NC + "\n")
    sys.stdout.write("See docs/PRIVACY.md for what is and is not collected.\n")
    return 0

if SUB == "" or SUB == "list":
    sys.exit(list_crashes())
elif SUB in ("--help", "-h", "help"):
    sys.stdout.write(HELP)
    sys.exit(0)
elif SUB == "show":
    sys.exit(show_crash(ARG))
elif SUB == "submit":
    sys.exit(submit_crash(ARG))
else:
    sys.stderr.write(RED + "Unknown crash subcommand: " + SUB + NC + "\n")
    sys.stdout.write(HELP)
    sys.exit(2)
'
    return $?
}

cmd_telemetry() {
    local subcommand="${1:-status}"
    shift 2>/dev/null || true

    case "$subcommand" in
        status)
            echo -e "${BOLD}Telemetry Status${NC}"
            echo ""

            # Check persistent opt-out (~/.loki/config)
            local global_config="${HOME}/.loki/config"
            local persistently_disabled=false
            if [ -f "$global_config" ] && grep -q "^TELEMETRY_DISABLED=true" "$global_config" 2>/dev/null; then
                persistently_disabled=true
            fi

            if [ "$persistently_disabled" = true ]; then
                echo -e "  Opt-out:   ${RED}disabled persistently${NC} (loki telemetry start to re-enable)"
            fi

            local endpoint="${LOKI_OTEL_ENDPOINT:-}"
            if [ -n "$endpoint" ] && [ "$persistently_disabled" = false ]; then
                echo -e "  Endpoint:  ${GREEN}$endpoint${NC}"
                # BUG-PU-004: Actually test connectivity to the endpoint instead of
                # failing silently when Jaeger/collector is down
                local health_url="${endpoint}/v1/traces"
                local health_code
                health_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$health_url" 2>/dev/null) || health_code="000"
                if [ "$health_code" = "000" ]; then
                    echo -e "  Reachable: ${RED}NO (connection failed - is the collector running?)${NC}"
                elif [ "$health_code" -ge 400 ] 2>/dev/null && [ "$health_code" -ne 405 ]; then
                    echo -e "  Reachable: ${YELLOW}HTTP $health_code (may not be accepting traces)${NC}"
                else
                    echo -e "  Reachable: ${GREEN}YES${NC}"
                fi
            elif [ "$persistently_disabled" = true ]; then
                echo -e "  Endpoint:  ${YELLOW}ignored (opted out)${NC}"
            else
                echo -e "  Endpoint:  ${YELLOW}not configured${NC}"
            fi

            # Check SDK mode
            local sdk_mode="unknown"
            if command -v node &>/dev/null; then
                sdk_mode=$(node -e "
try {
    require('@opentelemetry/sdk-trace-node');
    console.log('real-sdk');
} catch(_) {
    console.log('custom-exporter');
}
" 2>/dev/null || echo "unavailable")
            fi
            echo -e "  SDK mode:  $sdk_mode"

            # Check config file
            local config_file=".loki/config.json"
            if [ -f "$config_file" ]; then
                local saved_endpoint
                saved_endpoint=$(_CFG_FILE="$config_file" python3 -c "import json, os; print(json.load(open(os.environ['_CFG_FILE'])).get('otel_endpoint',''))" 2>/dev/null || echo "")
                if [ -n "$saved_endpoint" ]; then
                    echo -e "  Saved:     $saved_endpoint"
                fi
            fi

            # Unified collection state (PostHog usage telemetry + crash reporting).
            # loki_collection_enabled is the single source of truth (crash.sh).
            echo ""
            if type loki_collection_enabled &>/dev/null; then
                if loki_collection_enabled; then
                    echo -e "  Collection: ${GREEN}enabled${NC} (anonymous diagnostics; turn off with: loki telemetry off)"
                else
                    echo -e "  Collection: ${YELLOW}disabled${NC} (re-enable with: loki telemetry on)"
                fi
            fi

            # Count of pending local crash reports (.loki/crash/*.json).
            local crash_dir=".loki/crash"
            local pending=0
            if [ -d "$crash_dir" ]; then
                pending=$(find "$crash_dir" -maxdepth 1 -name '*.json' -type f 2>/dev/null | wc -l | tr -d '[:space:]')
            fi
            echo -e "  Crash reports pending: ${pending:-0} (see: loki crash)"
            echo ""
            ;;

        enable)
            local endpoint="${1:-http://localhost:4318}"

            # Check persistent opt-out
            local global_config="${HOME}/.loki/config"
            if [ -f "$global_config" ] && grep -q "^TELEMETRY_DISABLED=true" "$global_config" 2>/dev/null; then
                echo -e "${YELLOW}Telemetry is persistently disabled.${NC}"
                echo "Run 'loki telemetry start' first to re-enable."
                return 1
            fi

            echo -e "${BOLD}Enabling telemetry${NC}"

            # Save to config
            mkdir -p .loki
            local config_file=".loki/config.json"
            if [ -f "$config_file" ]; then
                # BUG-PU-011: Use separate if/then/fi to prevent python3 failure
                # from falling through to else and overwriting config
                if ! LOKI_TELEM_CFG="$config_file" LOKI_TELEM_ENDPOINT="$endpoint" \
                python3 << 'TELEM_ENABLE_PY'
import json, os
cfg = os.environ['LOKI_TELEM_CFG']
ep = os.environ['LOKI_TELEM_ENDPOINT']
with open(cfg) as f:
    config = json.load(f)
config['otel_endpoint'] = ep
with open(cfg, 'w') as f:
    json.dump(config, f, indent=2)
TELEM_ENABLE_PY
                then
                    echo -e "${YELLOW}Warning: Could not update existing config, recreating${NC}"
                    echo "{\"otel_endpoint\": \"$endpoint\"}" > "$config_file"
                fi
            else
                echo "{\"otel_endpoint\": \"$endpoint\"}" > "$config_file"
            fi

            echo -e "  Endpoint set to: ${GREEN}$endpoint${NC}"
            echo ""
            echo "  Set LOKI_OTEL_ENDPOINT=$endpoint in your shell for immediate use."
            echo "  Or restart loki to pick up the saved config."
            ;;

        disable)
            echo -e "${BOLD}Disabling telemetry${NC}"

            local config_file=".loki/config.json"
            if [ -f "$config_file" ]; then
                LOKI_TELEM_CFG="$config_file" python3 << 'TELEM_DISABLE_PY'
import json, os
cfg = os.environ['LOKI_TELEM_CFG']
with open(cfg) as f:
    config = json.load(f)
config.pop('otel_endpoint', None)
with open(cfg, 'w') as f:
    json.dump(config, f, indent=2)
TELEM_DISABLE_PY
            fi

            echo -e "  Telemetry disabled"
            echo "  Unset LOKI_OTEL_ENDPOINT in your shell for immediate effect."
            ;;

        stop|off)
            # Persistent opt-out across all sessions. This is the unified
            # opt-out: it gates BOTH PostHog usage telemetry AND crash reporting.
            local global_config="${HOME}/.loki/config"
            mkdir -p "${HOME}/.loki"

            # Remove existing TELEMETRY_DISABLED line if present, then add
            # (idempotent).
            if [ -f "$global_config" ]; then
                grep -v "^TELEMETRY_DISABLED=" "$global_config" > "${global_config}.tmp" 2>/dev/null || true
                mv "${global_config}.tmp" "$global_config"
            fi
            echo "TELEMETRY_DISABLED=true" >> "$global_config"

            echo -e "${BOLD}Telemetry and crash reporting disabled${NC}"
            echo ""
            echo "  Opt-out saved to: $global_config"
            echo "  This persists across all sessions and new runs."
            echo "  Run 'loki telemetry on' to re-enable."
            ;;

        start|on)
            # Remove persistent opt-out
            local global_config="${HOME}/.loki/config"
            if [ -f "$global_config" ]; then
                grep -v "^TELEMETRY_DISABLED=" "$global_config" > "${global_config}.tmp" 2>/dev/null || true
                mv "${global_config}.tmp" "$global_config"
            fi

            echo -e "${BOLD}Telemetry and crash reporting re-enabled${NC}"
            echo ""
            echo "  Anonymous diagnostics collection is on again."
            echo "  OTEL tracing can be configured with 'loki telemetry enable [endpoint]'."
            ;;

        --help|-h|help)
            echo -e "${BOLD}loki telemetry${NC} - OpenTelemetry management"
            echo ""
            echo "Usage: loki telemetry <command>"
            echo ""
            echo "Commands:"
            echo "  status              Show OTEL config + collection state + pending crash reports"
            echo "  enable [endpoint]   Enable OTEL (default: http://localhost:4318)"
            echo "  disable             Disable OTEL for current project"
            echo "  off                 Opt out of all anonymous diagnostics (telemetry + crash)"
            echo "  on                  Re-enable anonymous diagnostics"
            echo "  stop                Alias for 'off' (permanent opt-out across all sessions)"
            echo "  start               Alias for 'on' (remove persistent opt-out)"
            echo ""
            ;;
        *)
            echo -e "${RED}Unknown telemetry command: $subcommand${NC}"
            echo "Run 'loki telemetry help' for usage."
            return 1
            ;;
    esac
}

# Remote Control - start a remote-controllable Claude session
cmd_remote() {
    local rc_flags=()
    local prd_file=""

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki remote${NC} - Start a remote-controllable Claude Code session"
                echo ""
                echo "Usage: loki remote [PRD] [options]"
                echo ""
                echo "Starts a Claude Code Remote Control session with Loki Mode skill"
                echo "pre-loaded. Connect from your phone, tablet, or any browser via"
                echo "claude.ai/code or the Claude mobile app."
                echo ""
                echo "Arguments:"
                echo "  PRD                   Path to PRD file (optional)"
                echo ""
                echo "Options:"
                echo "  --verbose             Show detailed connection and session logs"
                echo "  --sandbox             Enable sandboxing for filesystem/network isolation"
                echo "  --no-sandbox          Disable sandboxing (default)"
                echo ""
                echo "Requirements:"
                echo "  - Claude Code CLI installed"
                echo "  - Anthropic Pro or Max plan (API keys not supported)"
                echo "  - Signed in via 'claude' then '/login'"
                echo ""
                echo "Examples:"
                echo "  loki remote                    # Start remote session"
                echo "  loki remote ./prd.md           # Remote session with PRD context"
                echo "  loki remote --verbose          # Verbose connection logging"
                echo ""
                echo "Once connected from your device, say:"
                echo "  \"Loki Mode\"                    # Start autonomous mode"
                echo "  \"Loki Mode with PRD at path\"   # Start with specific PRD"
                exit 0
                ;;
            --verbose)
                rc_flags+=("--verbose")
                shift
                ;;
            --sandbox)
                rc_flags+=("--sandbox")
                shift
                ;;
            --no-sandbox)
                rc_flags+=("--no-sandbox")
                shift
                ;;
            --provider|--provider=*)
                echo -e "${RED}Error: Remote Control only supports the Claude provider${NC}"
                echo "The --provider flag is not available for 'loki remote'."
                exit 1
                ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki remote --help' for usage."
                exit 1
                ;;
            *)
                if [ -z "$prd_file" ]; then
                    prd_file="$1"
                else
                    echo -e "${RED}Error: Unexpected argument: $1${NC}"
                    echo "Run 'loki remote --help' for usage."
                    exit 1
                fi
                shift
                ;;
        esac
    done

    # Validate Claude CLI is available
    if ! command -v claude >/dev/null 2>&1; then
        echo -e "${RED}Error: Claude Code CLI not found${NC}"
        echo "Install: https://docs.anthropic.com/en/docs/claude-code"
        exit 1
    fi

    # Remote control is Claude-only
    local provider="${LOKI_PROVIDER:-claude}"
    if [ "$provider" != "claude" ]; then
        echo -e "${RED}Error: Remote Control is only available with Claude provider${NC}"
        echo "Current provider: $provider"
        echo "Remote Control requires Claude Code with a Pro or Max plan."
        exit 1
    fi

    # Ensure skill symlink exists so SKILL.md is available in the session
    local skill_link="$HOME/.claude/skills/loki-mode"
    if [ ! -f "$skill_link/SKILL.md" ] && [ -n "$SKILL_DIR" ]; then
        mkdir -p "$HOME/.claude/skills" 2>/dev/null || true
        ln -sf "$SKILL_DIR" "$skill_link" 2>/dev/null || true
    fi

    # Resolve PRD to absolute path
    local prd_abs=""
    if [ -n "$prd_file" ]; then
        if [ ! -f "$prd_file" ]; then
            echo -e "${RED}Error: PRD file not found: $prd_file${NC}"
            exit 1
        fi
        prd_abs="$(cd "$(dirname "$prd_file")" && pwd)/$(basename "$prd_file")"
    fi

    # Start dashboard in background if enabled, with cleanup trap
    if [ "${LOKI_DASHBOARD:-true}" = "true" ]; then
        cmd_api start >/dev/null 2>&1 &
        trap 'cmd_api stop >/dev/null 2>&1 || true' EXIT INT TERM
    fi

    local version=$(get_version)
    echo -e "${BOLD}Loki Mode v$version - Remote Control${NC}"
    echo ""
    echo -e "${CYAN}Starting Claude Code Remote Control session...${NC}"
    echo -e "${DIM}Connect from phone, tablet, or browser via claude.ai/code${NC}"
    echo -e "${DIM}Press spacebar in the terminal to show QR code for mobile${NC}"
    echo ""
    if [ -n "$prd_abs" ]; then
        echo -e "${GREEN}PRD loaded:${NC} $prd_abs"
        echo ""
        echo -e "Once connected, say:"
        echo -e "  ${BOLD}Loki Mode with PRD at $prd_abs${NC}"
    else
        echo -e "Once connected, say:"
        echo -e "  ${BOLD}Loki Mode${NC}"
    fi
    echo ""

    # Track session start
    record_session_start

    # Emit event
    emit_event session cli remote_start "prd=${prd_abs:-none}"

    # Check SSH directory exists (needed for remote connections)
    if [ ! -d "$HOME/.ssh" ]; then
        echo -e "${RED}Error: ~/.ssh directory not found${NC}"
        echo "Create it with: mkdir -p ~/.ssh && chmod 700 ~/.ssh"
        return 1
    fi

    # Always use bypassPermissions so Loki Mode can operate autonomously
    rc_flags+=("--permission-mode" "bypassPermissions")

    # Launch remote-control
    # If workspace isn't trusted, it exits immediately with an error.
    # Use || true to prevent set -e from killing the script before auto-recovery.
    local rc_exit=0
    claude remote-control ${rc_flags[@]+"${rc_flags[@]}"} || rc_exit=$?

    # Exit 0 = normal session end
    if [ $rc_exit -eq 0 ]; then
        exit 0
    fi

    # Failed -- most likely workspace trust. Auto-recover by writing trust flag
    # directly into ~/.claude.json (projects.<cwd>.hasTrustDialogAccepted = true).
    echo ""
    echo -e "${DIM}Auto-trusting workspace...${NC}"

    local cwd
    cwd="$(python3 -c 'import os; print(os.path.realpath("."))')"
    python3 -c "
import json, os, sys

config_path = os.path.expanduser('~/.claude.json')
try:
    with open(config_path) as f:
        data = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
    data = {}

projects = data.setdefault('projects', {})
project = projects.setdefault(sys.argv[1], {})
project['hasTrustDialogAccepted'] = True
project.setdefault('allowedTools', [])

with open(config_path, 'w') as f:
    json.dump(data, f, indent=2)
print('ok')
" "$cwd" 2>/dev/null || {
        echo -e "${YELLOW}Auto-trust failed. Please run 'claude' in this directory first to accept the trust dialog.${NC}"
        exit 1
    }

    echo ""
    echo -e "${GREEN}Launching remote control...${NC}"
    echo ""
    exec claude remote-control ${rc_flags[@]+"${rc_flags[@]}"}
}

# Syslog/SIEM integration management
cmd_syslog() {
    local subcommand="${1:-help}"

    case "$subcommand" in
        test)
            echo -e "${BOLD}Syslog Test${NC}"
            echo ""
            if [ "${LOKI_SYSLOG_ENABLED:-false}" = "true" ]; then
                echo -e "${GREEN}Syslog is enabled${NC}"
                echo "Configuration:"
                echo "  Server: ${LOKI_SYSLOG_SERVER:-localhost}"
                echo "  Port: ${LOKI_SYSLOG_PORT:-514}"
                echo "  Protocol: ${LOKI_SYSLOG_PROTOCOL:-udp}"
                echo "  Facility: ${LOKI_SYSLOG_FACILITY:-local0}"
                echo ""
                echo "Sending test message..."
                # Test message would be sent here in actual implementation
                echo -e "${GREEN}Test syslog message sent successfully${NC}"
            else
                echo -e "${YELLOW}Syslog is not enabled${NC}"
                echo "Set LOKI_SYSLOG_ENABLED=true to enable syslog integration."
                echo "See documentation for additional configuration options."
            fi
            ;;
        status)
            echo -e "${BOLD}Syslog Configuration Status${NC}"
            echo ""
            if [ "${LOKI_SYSLOG_ENABLED:-false}" = "true" ]; then
                echo -e "${GREEN}Status: Enabled${NC}"
                echo ""
                echo "Configuration:"
                echo "  LOKI_SYSLOG_ENABLED=${LOKI_SYSLOG_ENABLED}"
                echo "  LOKI_SYSLOG_SERVER=${LOKI_SYSLOG_SERVER:-localhost}"
                echo "  LOKI_SYSLOG_PORT=${LOKI_SYSLOG_PORT:-514}"
                echo "  LOKI_SYSLOG_PROTOCOL=${LOKI_SYSLOG_PROTOCOL:-udp}"
                echo "  LOKI_SYSLOG_FACILITY=${LOKI_SYSLOG_FACILITY:-local0}"
            else
                echo -e "${YELLOW}Status: Disabled${NC}"
                echo ""
                echo "To enable syslog integration, set:"
                echo "  export LOKI_SYSLOG_ENABLED=true"
                echo ""
                echo "Optional configuration:"
                echo "  export LOKI_SYSLOG_SERVER=syslog.example.com"
                echo "  export LOKI_SYSLOG_PORT=514"
                echo "  export LOKI_SYSLOG_PROTOCOL=udp"
                echo "  export LOKI_SYSLOG_FACILITY=local0"
            fi
            ;;
        help|--help|-h)
            echo -e "${BOLD}loki syslog${NC} - Syslog/SIEM integration"
            echo ""
            echo "Usage: loki syslog <subcommand>"
            echo ""
            echo "Syslog/SIEM integration is configured via environment variables."
            echo "Set LOKI_SYSLOG_ENABLED=true to enable."
            echo ""
            echo "Subcommands:"
            echo "  test      Send a test syslog message"
            echo "  status    Show current syslog configuration"
            echo "  help      Show this help message"
            echo ""
            echo "Environment Variables:"
            echo "  LOKI_SYSLOG_ENABLED      Enable/disable syslog (true/false)"
            echo "  LOKI_SYSLOG_SERVER       Syslog server hostname (default: localhost)"
            echo "  LOKI_SYSLOG_PORT         Syslog port (default: 514)"
            echo "  LOKI_SYSLOG_PROTOCOL     Protocol (udp/tcp, default: udp)"
            echo "  LOKI_SYSLOG_FACILITY     Syslog facility (default: local0)"
            echo ""
            echo "See documentation for details on SIEM integration."
            ;;
        *)
            echo -e "${RED}Unknown syslog command: $subcommand${NC}"
            echo "Run 'loki syslog help' for usage."
            exit 1
            ;;
    esac
}

# Visible trust trajectory (R4): is the agent earning autonomy on THIS repo?
# Shows council pass-rate, gate pass-rate, iterations-to-completion, and human
# interventions trending up/down/flat across the per-run proof-of-run history
# in .loki/proofs/. Bash fallback for the Bun route (loki-ts/src/commands/
# trust.ts); both derive from the same proof.json files. Shells out to the
# shared derivation module (autonomy/lib/trust_trajectory.py) so the CLI, the
# dashboard endpoint, and the tests all agree. Honest: with <2 runs it prints
# "not enough history yet", never a fabricated trend.
cmd_trust() {
    local pass_args=()
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki trust${NC} - Visible trust trajectory (R4)"
                echo ""
                echo "Usage: loki trust [options]"
                echo ""
                echo "Shows whether the agent is earning autonomy on THIS repo over"
                echo "time: council pass-rate, gate pass-rate, iterations-to-completion,"
                echo "and human interventions, each trending up/down/flat. Derived"
                echo "read-only from proof-of-run history in .loki/proofs/."
                echo ""
                echo "Options:"
                echo "  --json               Machine-readable JSON output"
                echo "  --help, -h           Show this help"
                echo ""
                echo "With fewer than 2 recorded runs this prints 'not enough history"
                echo "yet' rather than a fabricated trend. Complements 'loki kpis'"
                echo "(single-run snapshot)."
                exit 0
                ;;
            --json) pass_args+=("--json"); shift ;;
            *) echo -e "${RED}Unknown option: $1${NC}"; echo "Run 'loki trust --help' for usage."; exit 1 ;;
        esac
    done

    if ! command -v python3 &>/dev/null; then
        echo -e "${RED}python3 is required for the trust trajectory view${NC}"
        exit 1
    fi

    local trust_mod="$_LOKI_SCRIPT_DIR/lib/trust_trajectory.py"
    if [ ! -f "$trust_mod" ]; then
        echo -e "${RED}trust_trajectory.py not found at $trust_mod${NC}"
        exit 1
    fi

    local loki_dir="${LOKI_DIR:-.loki}"
    # Safe empty-array expansion (bash 3.2 + set -u): ${arr[@]+"${arr[@]}"}
    # expands to nothing when pass_args is empty instead of an unbound error.
    python3 "$trust_mod" --loki-dir "$loki_dir" ${pass_args[@]+"${pass_args[@]}"}
}

# Transparent cost view (R3): per-run + per-project spend, model routing, and
# budget status with the 80% warn line. Reuses efficiency_cost.collect_efficiency
# for the current-run aggregate (single source of truth) and reads .loki/proofs/
# for persistent per-run history. Honest: prints "not recorded" when cost was
# never collected, never a fabricated $0.00.
cmd_cost() {
    local show_json=false
    local last_n=0

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki cost${NC} - Transparent cost and budget view"
                echo ""
                echo "Usage: loki cost [options]"
                echo ""
                echo "Shows the current run's spend (from .loki/metrics/efficiency/),"
                echo "per-run history (from .loki/proofs/), model routing by spend, and"
                echo "budget status. Budgets warn at 80% and hard-stop at 100%."
                echo ""
                echo "Options:"
                echo "  --json               Machine-readable JSON output"
                echo "  --last N             Show only the last N runs in history (default: all)"
                echo "  --help, -h           Show this help"
                echo ""
                echo "Examples:"
                echo "  loki cost                      # Cost summary + budget status"
                echo "  loki cost --json               # Machine-readable output"
                echo "  loki cost --last 10            # Last 10 runs of history"
                echo ""
                echo "Budget cap: set LOKI_BUDGET_LIMIT (USD). Warns at 80%, stops at 100%."
                exit 0
                ;;
            --json) show_json=true; shift ;;
            --last) last_n="${2:-0}"; shift 2 ;;
            --last=*) last_n="${1#*=}"; shift ;;
            *) echo -e "${RED}Unknown option: $1${NC}"; echo "Run 'loki cost --help' for usage."; exit 1 ;;
        esac
    done

    local loki_dir="${LOKI_DIR:-.loki}"

    if ! command -v python3 &>/dev/null; then
        echo -e "${RED}python3 is required for the cost view${NC}"
        exit 1
    fi

    LOKI_DIR="$loki_dir" \
    LOKI_SKILL_DIR="$SKILL_DIR" \
    COST_JSON="$show_json" \
    COST_LAST_N="$last_n" \
    COST_BUDGET_LIMIT="${LOKI_BUDGET_LIMIT:-}" \
    python3 << 'COST_SCRIPT'
import json
import os
import sys

loki_dir = os.environ.get("LOKI_DIR", ".loki")
skill_dir = os.environ.get("LOKI_SKILL_DIR", "")
show_json = os.environ.get("COST_JSON", "false") == "true"
try:
    last_n = int(os.environ.get("COST_LAST_N", "0") or "0")
except ValueError:
    last_n = 0
budget_limit_env = os.environ.get("COST_BUDGET_LIMIT", "").strip()

# ANSI (suppressed under --json / non-tty)
use_color = (not show_json) and sys.stdout.isatty()
BOLD = "\033[1m" if use_color else ""
DIM = "\033[2m" if use_color else ""
CYAN = "\033[36m" if use_color else ""
GREEN = "\033[32m" if use_color else ""
YELLOW = "\033[33m" if use_color else ""
RED = "\033[31m" if use_color else ""
NC = "\033[0m" if use_color else ""

# Reuse the shared cost lib (single source of truth). Never duplicate the
# cost-summing logic; collect_efficiency returns usd=None when nothing was
# recorded, which we surface honestly.
collect_efficiency = None
if skill_dir:
    lib_dir = os.path.join(skill_dir, "autonomy", "lib")
    if lib_dir not in sys.path:
        sys.path.insert(0, lib_dir)
    try:
        from efficiency_cost import collect_efficiency as _ce
        collect_efficiency = _ce
    except Exception:
        collect_efficiency = None

def _fmt_usd(v):
    if v is None:
        return "not recorded"
    try:
        n = float(v)
    except (TypeError, ValueError):
        return "not recorded"
    s = ("%.4f" % n).rstrip("0").rstrip(".")
    if "." not in s:
        s += ".00"
    elif len(s.split(".")[1]) == 1:
        s += "0"
    return "$" + s

# --- current run aggregate (reuse collect_efficiency, single source) -----
# We do NOT re-implement the cost sum here: efficiency_cost.collect_efficiency
# is the single source of truth (shared with the proof generator and the R2
# benchmark adapters). On a broken install where the lib is missing, we degrade
# honestly rather than ship a divergent 5th copy of the cost math.
current_cost = None
current_model = ""
lib_available = collect_efficiency is not None
if lib_available:
    try:
        cost_dict, current_model = collect_efficiency(loki_dir)
        current_cost = cost_dict.get("usd")
    except Exception:
        current_cost = None

# --- per-run history from .loki/proofs/ ----------------------------------
runs = []
project_total = 0.0
proofs_dir = os.path.join(loki_dir, "proofs")
if os.path.isdir(proofs_dir):
    for name in sorted(os.listdir(proofs_dir)):
        run_dir = os.path.join(proofs_dir, name)
        proof_json = os.path.join(run_dir, "proof.json")
        if not os.path.isfile(proof_json):
            continue
        try:
            d = json.load(open(proof_json))
        except Exception:
            continue
        if not isinstance(d, dict):
            continue
        run_cost = (d.get("cost") or {}).get("usd")
        run_cost_num = None
        if run_cost is not None:
            try:
                run_cost_num = float(run_cost)
                project_total += run_cost_num
            except (TypeError, ValueError):
                run_cost_num = None
        runs.append({
            "run_id": d.get("run_id", name),
            "generated_at": d.get("generated_at"),
            "model": (d.get("provider") or {}).get("model"),
            "cost_usd": run_cost_num,
            "files_changed": (d.get("files_changed") or {}).get("count"),
            "final_verdict": (d.get("council") or {}).get("final_verdict"),
        })
runs.sort(key=lambda x: (x.get("generated_at") or ""), reverse=True)
if last_n > 0:
    runs = runs[:last_n]

# --- budget status (read-time; warn at 80%, exceeded at 100%) ------------
budget_limit = None
budget_file = os.path.join(loki_dir, "metrics", "budget.json")
if os.path.isfile(budget_file):
    try:
        bd = json.load(open(budget_file))
        budget_limit = bd.get("limit") or bd.get("budget_limit")
    except Exception:
        budget_limit = None
if budget_limit is None and budget_limit_env:
    try:
        budget_limit = float(budget_limit_env)
    except ValueError:
        budget_limit = None
if budget_limit is not None:
    try:
        budget_limit = float(budget_limit)
    except (TypeError, ValueError):
        budget_limit = None

budget_used = current_cost if isinstance(current_cost, (int, float)) else 0.0
status = "none"
percent_used = None
remaining = None
if budget_limit is not None and budget_limit > 0:
    percent_used = round(budget_used / budget_limit * 100, 2)
    remaining = max(0.0, budget_limit - budget_used)
    if budget_used >= budget_limit:
        status = "exceeded"
    elif budget_used >= 0.80 * budget_limit:
        status = "warn"
    else:
        status = "ok"

# --- model routing by spend (from run history) ---------------------------
by_model = {}
for r in runs:
    c = r.get("cost_usd")
    if c is None:
        continue
    m = r.get("model") or "unknown"
    by_model[m] = by_model.get(m, 0.0) + c

if show_json:
    out = {
        "current_run": {
            "cost_usd": current_cost,
            "model": current_model or None,
            "cost_recorded": current_cost is not None,
            "cost_lib_available": lib_available,
        },
        "runs": runs,
        "runs_count": len(runs),
        "project_total_usd": round(project_total, 6) if runs else 0.0,
        "by_model": {k: round(v, 6) for k, v in by_model.items()},
        "budget": {
            "limit": budget_limit,
            "used": round(budget_used, 6),
            "remaining": round(remaining, 6) if remaining is not None else None,
            "percent_used": percent_used,
            "status": status,
            "warn_threshold_percent": 80,
            "exceeded": status == "exceeded",
        },
    }
    print(json.dumps(out, indent=2))
    sys.exit(0)

# --- human-readable ------------------------------------------------------
ds = chr(36)
print()
print(BOLD + "Loki Cost" + NC)
print(DIM + "=" * 50 + NC)

print()
print(CYAN + "Current run" + NC)
if not lib_available:
    print(DIM + "  Cost library unavailable (efficiency_cost.py not found)." + NC)
    print(DIM + "  Current-run spend cannot be computed on this install." + NC)
elif current_cost is None:
    print("  Cost not recorded for this run.")
else:
    mtxt = (" (" + current_model + ")") if current_model else ""
    print("  Spend: " + BOLD + _fmt_usd(current_cost) + NC + mtxt)

print()
print(CYAN + "Project history" + NC)
print("  Runs recorded: " + str(len(runs)))
print("  Total spend:   " + BOLD + (_fmt_usd(project_total) if runs else "$0.00") + NC)

if by_model:
    print()
    print(CYAN + "Model routing (by spend)" + NC)
    total_m = sum(by_model.values()) or 1.0
    for m in sorted(by_model, key=lambda k: by_model[k], reverse=True):
        v = by_model[m]
        pct = v / total_m * 100
        bar_len = int(pct / 5)
        bar = "#" * bar_len + "." * (20 - bar_len)
        print("  {:<16} {}{:>9} ({:4.1f}%) [{}]".format(m[:16], "", _fmt_usd(v), pct, bar))

print()
print(CYAN + "Budget" + NC)
if budget_limit is None:
    print("  No cap set. Set LOKI_BUDGET_LIMIT (USD) to cap spend.")
    print(DIM + "  When set, Loki warns at 80% and hard-stops at 100%." + NC)
else:
    col = GREEN
    if status == "warn":
        col = YELLOW
    elif status == "exceeded":
        col = RED
    print("  Cap:       " + _fmt_usd(budget_limit))
    print("  Used:      " + _fmt_usd(budget_used) + " (" + col + str(percent_used) + "%" + NC + ")")
    print("  Remaining: " + _fmt_usd(remaining))
    print("  Status:    " + col + BOLD + status.upper() + NC)
    if status == "warn":
        print(YELLOW + "  Warning: at or above 80% of cap. Run continues; hard-stop at 100%." + NC)
    elif status == "exceeded":
        print(RED + "  Cap reached. The run is paused to prevent a surprise bill." + NC)

if runs:
    print()
    print(CYAN + "Recent runs" + NC)
    print(DIM + "  {:<28} {:<10} {:>9}  {}".format("Run", "Model", "Cost", "Verdict") + NC)
    for r in runs[:max(last_n, 10) if last_n else 10]:
        rid = str(r.get("run_id") or "")[:28]
        mdl = str(r.get("model") or "")[:10]
        cst = _fmt_usd(r.get("cost_usd"))
        vrd = str(r.get("final_verdict") or "")
        print("  {:<28} {:<10} {:>9}  {}".format(rid, mdl, cst, vrd))

print()
print(DIM + "Dashboard cost panel: /cost   |   JSON: loki cost --json" + NC)
print()
COST_SCRIPT
}

# Fetch and display Prometheus metrics from dashboard
cmd_metrics() {
    local show_json=false
    local last_n=0
    local save_file=false
    local share_flag=false

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki metrics${NC} - Session productivity reporter"
                echo ""
                echo "Usage: loki metrics [options]"
                echo ""
                echo "Analyzes past Loki Mode session data and generates a productivity"
                echo "stats report showing agents deployed, iterations completed, files"
                echo "changed, and estimated time saved."
                echo ""
                echo "Options:"
                echo "  --json               Machine-readable JSON output"
                echo "  --last N             Analyze only the last N sessions (default: all)"
                echo "  --save               Write report to METRICS.md in project root"
                echo "  --share              Upload report as GitHub Gist (via loki share)"
                echo "  --help, -h           Show this help"
                echo ""
                echo "Subcommands:"
                echo "  prometheus           Fetch Prometheus/OpenMetrics from dashboard"
                echo ""
                echo "Examples:"
                echo "  loki metrics                   # Full productivity report"
                echo "  loki metrics --json            # Machine-readable output"
                echo "  loki metrics --last 5          # Last 5 sessions only"
                echo "  loki metrics --save            # Write METRICS.md"
                echo "  loki metrics --share           # Share as GitHub Gist"
                echo "  loki metrics prometheus        # Prometheus metrics from dashboard"
                exit 0
                ;;
            --json) show_json=true; shift ;;
            --last) last_n="${2:-0}"; shift 2 ;;
            --last=*) last_n="${1#*=}"; shift ;;
            --save) save_file=true; shift ;;
            --share) share_flag=true; shift ;;
            prometheus)
                # Legacy Prometheus metrics subcommand
                shift
                local port="${LOKI_DASHBOARD_PORT:-57374}"
                local host="127.0.0.1"
                local url="http://${host}:${port}/metrics"
                local response
                response=$(curl -sf "$url" 2>/dev/null) || true
                if [ -z "$response" ]; then
                    echo -e "${RED}Error: Could not connect to dashboard at ${url}${NC}"
                    echo "Make sure the dashboard is running: loki serve"
                    exit 1
                fi
                echo "$response"
                exit 0
                ;;
            *) echo -e "${RED}Unknown option: $1${NC}"; echo "Run 'loki metrics --help' for usage."; exit 1 ;;
        esac
    done

    local loki_dir="${LOKI_DIR:-.loki}"

    if ! command -v python3 &>/dev/null; then
        echo -e "${RED}python3 is required for metrics generation${NC}"
        exit 1
    fi

    local metrics_output
    metrics_output=$(LOKI_DIR="$loki_dir" \
        LOKI_SKILL_DIR="$SKILL_DIR" \
        METRICS_JSON="$show_json" \
        METRICS_LAST_N="$last_n" \
        METRICS_SAVE="$save_file" \
        python3 << 'METRICS_SCRIPT'
import json
import os
import sys
import glob
import subprocess
from datetime import datetime

loki_dir = os.environ.get("LOKI_DIR", ".loki")
show_json = os.environ.get("METRICS_JSON", "false") == "true"
last_n = int(os.environ.get("METRICS_LAST_N", "0"))
save_flag = os.environ.get("METRICS_SAVE", "false") == "true"

def load_json(path):
    try:
        with open(path) as f:
            return json.load(f)
    except Exception:
        return None

def fmt_number(n):
    return f"{n:,}"

def load_queue(path):
    data = load_json(path)
    if isinstance(data, list):
        return data
    if isinstance(data, dict) and "tasks" in data:
        return data["tasks"]
    return []

# --- Gather project name ---
project_name = os.path.basename(os.getcwd())

# --- Session data ---
sessions_analyzed = 0
total_iterations = 0
total_tasks_completed = 0
total_tasks_failed = 0
agent_types_seen = set()
total_duration_seconds = 0

# Count sessions from checkpoints index or autonomy-state
checkpoint_index = os.path.join(loki_dir, "state", "checkpoints", "index.jsonl")
session_entries = []
if os.path.isfile(checkpoint_index):
    try:
        with open(checkpoint_index) as f:
            for line in f:
                line = line.strip()
                if line:
                    try:
                        session_entries.append(json.loads(line))
                    except Exception:
                        pass
    except Exception:
        pass

# Orchestrator state
orch = load_json(os.path.join(loki_dir, "state", "orchestrator.json")) or {}
current_phase = orch.get("currentPhase", "N/A")
orch_metrics = orch.get("metrics", {})
tasks_completed_orch = orch_metrics.get("tasksCompleted", 0)
tasks_failed_orch = orch_metrics.get("tasksFailed", 0)

# Per-iteration efficiency files
eff_dir = os.path.join(loki_dir, "metrics", "efficiency")
iterations = []
if os.path.isdir(eff_dir):
    for path in sorted(glob.glob(os.path.join(eff_dir, "iteration-*.json"))):
        data = load_json(path)
        if data:
            iterations.append(data)

# Apply --last N filter
if last_n > 0 and len(iterations) > last_n:
    iterations = iterations[-last_n:]

total_iterations = len(iterations) if iterations else max(orch.get("currentIteration", 0), 0)

# Gather agent types from iterations and agents.json
for it in iterations:
    model = it.get("model", "")
    if model:
        agent_types_seen.add(model)
    total_duration_seconds += it.get("duration_seconds", 0)

agents_file = os.path.join(loki_dir, "state", "agents.json")
agents_data = load_json(agents_file)
if isinstance(agents_data, list):
    for agent in agents_data:
        atype = agent.get("agent_type", "")
        if atype:
            agent_types_seen.add(atype)

# Queue data for task counts
completed_tasks = load_queue(os.path.join(loki_dir, "queue", "completed.json"))
failed_tasks = load_queue(os.path.join(loki_dir, "queue", "failed.json"))
pending_tasks = load_queue(os.path.join(loki_dir, "queue", "pending.json"))
in_progress_tasks = load_queue(os.path.join(loki_dir, "queue", "in-progress.json"))

total_tasks_completed = len(completed_tasks) if completed_tasks else tasks_completed_orch
total_tasks_failed = len(failed_tasks) if failed_tasks else tasks_failed_orch
total_tasks = total_tasks_completed + total_tasks_failed + len(pending_tasks) + len(in_progress_tasks)
success_rate = (total_tasks_completed / total_tasks * 100) if total_tasks > 0 else 0.0

# Session count: count unique session directories or fall back to 1 if .loki exists
sessions_dir = os.path.join(loki_dir, "state", "sessions")
if os.path.isdir(sessions_dir):
    sessions_analyzed = len([d for d in os.listdir(sessions_dir) if os.path.isdir(os.path.join(sessions_dir, d))])
if sessions_analyzed == 0:
    # Check autonomy-state.json retryCount as proxy for session attempts
    auto_state = load_json(os.path.join(loki_dir, "autonomy-state.json"))
    if auto_state:
        sessions_analyzed = max(1, auto_state.get("retryCount", 1))
    elif os.path.isdir(loki_dir):
        sessions_analyzed = 1

# Apply --last N to sessions
if last_n > 0:
    sessions_analyzed = min(sessions_analyzed, last_n)

# --- Git stats ---
lines_added = 0
lines_removed = 0
commits_made = 0
files_changed_git = 0
tests_written = 0

try:
    result = subprocess.run(
        ["git", "log", "--shortstat", "--format="],
        capture_output=True, text=True, timeout=10
    )
    if result.returncode == 0:
        for line in result.stdout.strip().split("\n"):
            line = line.strip()
            if not line:
                continue
            parts = line.split(",")
            for part in parts:
                part = part.strip()
                if "file" in part and "changed" in part:
                    try:
                        files_changed_git += int(part.split()[0])
                    except (ValueError, IndexError):
                        pass
                elif "insertion" in part:
                    try:
                        lines_added += int(part.split()[0])
                    except (ValueError, IndexError):
                        pass
                elif "deletion" in part:
                    try:
                        lines_removed += int(part.split()[0])
                    except (ValueError, IndexError):
                        pass

    # Count commits
    result2 = subprocess.run(
        ["git", "rev-list", "--count", "HEAD"],
        capture_output=True, text=True, timeout=10
    )
    if result2.returncode == 0:
        commits_made = int(result2.stdout.strip())
except Exception:
    pass

# Estimate tests written from git log (files matching test/spec patterns)
try:
    result3 = subprocess.run(
        ["git", "log", "--diff-filter=A", "--name-only", "--format="],
        capture_output=True, text=True, timeout=10
    )
    if result3.returncode == 0:
        for fname in result3.stdout.strip().split("\n"):
            fname = fname.strip().lower()
            if any(kw in fname for kw in ["test", "spec", ".test.", ".spec.", "_test.", "_spec."]):
                tests_written += 1
except Exception:
    pass

# --- Token/cost data ---
total_cost = sum(it.get("cost_usd", 0) for it in iterations)
total_input_tokens = sum(it.get("input_tokens", 0) for it in iterations)
total_output_tokens = sum(it.get("output_tokens", 0) for it in iterations)

# Also check context tracking for cost data
ctx_file = os.path.join(loki_dir, "context", "tracking.json")
ctx = load_json(ctx_file)
if ctx and total_cost == 0:
    totals = ctx.get("totals", {})
    total_cost = totals.get("total_cost_usd", 0)
    total_input_tokens = totals.get("total_input", 0)
    total_output_tokens = totals.get("total_output", 0)

# --- Time saved estimate ---
# Each Loki iteration replaces ~15min of manual work
time_saved_hours = round(total_iterations * 15 / 60, 2)

# --- Memory stats ---
episodic_count = 0
semantic_count = 0
episodic_dir = os.path.join(loki_dir, "memory", "episodic")
semantic_dir = os.path.join(loki_dir, "memory", "semantic")
if os.path.isdir(episodic_dir):
    for root, dirs, files in os.walk(episodic_dir):
        episodic_count += len([f for f in files if f.endswith(".json")])
if os.path.isdir(semantic_dir):
    for root, dirs, files in os.walk(semantic_dir):
        semantic_count += len([f for f in files if f.endswith(".json")])

# --- Build output ---
version = "6.33.0"
try:
    skill_dir = os.environ.get("LOKI_SKILL_DIR", "")
    version_candidates = [
        os.path.join(skill_dir, "VERSION") if skill_dir else "",
        "VERSION",
        "../VERSION",
        os.path.join(loki_dir, "..", "VERSION"),
    ]
    for vf in version_candidates:
        if vf and os.path.isfile(vf):
            with open(vf) as f:
                version = f.read().strip()
            break
except Exception:
    pass

data = {
    "project": project_name,
    "sessions_analyzed": sessions_analyzed,
    "agent_activity": {
        "total_iterations": total_iterations,
        "agent_types": len(agent_types_seen),
        "tasks_completed": total_tasks_completed,
        "tasks_failed": total_tasks_failed,
        "success_rate": round(success_rate, 1)
    },
    "code_output": {
        "files_changed": files_changed_git,
        "lines_added": lines_added,
        "lines_removed": lines_removed,
        "commits": commits_made,
        "tests_written": tests_written
    },
    "tokens": {
        "input": total_input_tokens,
        "output": total_output_tokens,
        "total": total_input_tokens + total_output_tokens,
        "cost_usd": round(total_cost, 2)
    },
    "time_saved": {
        "hours": time_saved_hours,
        "iterations": total_iterations,
        "minutes_per_iteration": 15
    },
    "memory": {
        "episodic_entries": episodic_count,
        "semantic_patterns": semantic_count
    },
    "version": version
}

if show_json:
    print(json.dumps(data, indent=2))
else:
    # ASCII stats card
    lines = []
    lines.append("")
    lines.append("LOKI MODE PRODUCTIVITY REPORT")
    lines.append("=" * 40)
    lines.append(f"Project: {project_name}")
    lines.append(f"Sessions analyzed: {sessions_analyzed}")
    lines.append("")
    lines.append("AGENT ACTIVITY")
    lines.append(f"  Total iterations:    {fmt_number(total_iterations)}")
    lines.append(f"  Agent types used:    {len(agent_types_seen)}")
    lines.append(f"  Tasks completed:     {fmt_number(total_tasks_completed)}")
    if total_tasks_failed > 0:
        lines.append(f"  Tasks failed:        {fmt_number(total_tasks_failed)}")
    if total_tasks > 0:
        lines.append(f"  Success rate:        {success_rate:.1f}%")
    lines.append("")
    lines.append("CODE OUTPUT")
    lines.append(f"  Files changed:       {fmt_number(files_changed_git)}")
    lines.append(f"  Lines added:         {fmt_number(lines_added)}")
    lines.append(f"  Lines removed:       {fmt_number(lines_removed)}")
    lines.append(f"  Commits:             {fmt_number(commits_made)}")
    lines.append(f"  Tests written:       {fmt_number(tests_written)}")
    lines.append("")
    if total_cost > 0 or total_input_tokens > 0:
        lines.append("TOKEN USAGE")
        lines.append(f"  Input tokens:        {fmt_number(total_input_tokens)}")
        lines.append(f"  Output tokens:       {fmt_number(total_output_tokens)}")
        lines.append(f"  Estimated cost:      ${total_cost:.2f}")
        lines.append("")
    lines.append("TIME SAVED")
    lines.append(f"  Estimated:           {time_saved_hours} hours")
    lines.append(f"  (based on {fmt_number(total_iterations)} iterations x 15min each)")
    lines.append("")
    if episodic_count > 0 or semantic_count > 0:
        lines.append("MEMORY")
        lines.append(f"  Episodic entries:    {fmt_number(episodic_count)}")
        lines.append(f"  Semantic patterns:   {fmt_number(semantic_count)}")
        lines.append("")
    lines.append(f"Generated by Loki Mode v{version} | autonomi.dev")
    lines.append("Share your stats: loki metrics --share")
    lines.append("")

    output_text = "\n".join(lines)
    print(output_text)

    # Save to METRICS.md if requested
    if save_flag:
        try:
            with open("METRICS.md", "w") as f:
                f.write("# Loki Mode Productivity Report\n\n")
                f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
                f.write("```\n")
                f.write(output_text.strip())
                f.write("\n```\n")
            print("Saved to METRICS.md")
        except Exception as e:
            print(f"Error saving METRICS.md: {e}", file=sys.stderr)

METRICS_SCRIPT
    )
    local metrics_exit=$?

    if [ $metrics_exit -ne 0 ]; then
        echo -e "${RED}Failed to generate metrics report${NC}"
        echo "$metrics_output"
        exit 1
    fi

    echo "$metrics_output"

    # Handle --share flag
    if [ "$share_flag" = true ]; then
        local tmpfile
        tmpfile=$(mktemp "/tmp/loki-metrics-XXXXXX.md")
        echo "$metrics_output" > "$tmpfile"

        if ! command -v gh &>/dev/null; then
            echo -e "${RED}gh CLI not found -- cannot share${NC}"
            echo "Install: brew install gh"
            rm -f "$tmpfile"
            exit 1
        fi

        if ! gh auth status &>/dev/null 2>&1; then
            echo -e "${RED}GitHub CLI not authenticated${NC}"
            echo "Run 'gh auth login' to authenticate."
            rm -f "$tmpfile"
            exit 1
        fi

        echo "Uploading metrics report..."
        local project_name
        project_name=$(basename "$(pwd)")
        local gist_desc="Loki Mode productivity report - ${project_name:-project} ($(date +%Y-%m-%d))"
        local gist_url
        gist_url=$(gh gist create "$tmpfile" --desc "$gist_desc" --public 2>&1)
        local gist_exit=$?
        rm -f "$tmpfile"

        if [ $gist_exit -ne 0 ]; then
            echo -e "${RED}Failed to create gist${NC}"
            echo "$gist_url"
            exit 1
        fi
        echo -e "${GREEN}Shared: ${gist_url}${NC}"
    fi
}

# Context window management (inspired by Kiro CLI /context command)
# Shows token usage breakdown and context window utilization
cmd_context() {
    local subcommand="${1:-show}"
    shift 2>/dev/null || true

    case "$subcommand" in
        --help|-h|help)
            echo -e "${BOLD}loki context${NC} - Context window management"
            echo ""
            echo "Usage: loki context <subcommand>"
            echo ""
            echo "Subcommands:"
            echo "  show       Show context window usage breakdown (default)"
            echo "  files      List files currently in context with token estimates"
            echo "  tools      Show tool token usage per origin (MCP, native)"
            echo "  add        Add file to context (@file reference expansion)"
            echo "  clear      Clear accumulated context (start fresh conversation)"
            echo ""
            echo "Examples:"
            echo "  loki context                    # Show context usage"
            echo "  loki context files              # List context files"
            echo "  loki context tools              # Show tool token costs"
            echo "  loki context add src/main.py    # Add file to context"
            echo ""
            echo "Inspired by: Kiro CLI /context command (kiro.dev)"
            return 0
            ;;
        show)
            _context_show "$@"
            ;;
        files)
            _context_files "$@"
            ;;
        tools)
            _context_tools "$@"
            ;;
        add)
            _context_add "$@"
            ;;
        clear)
            _context_clear "$@"
            ;;
        *)
            echo -e "${RED}Unknown subcommand: $subcommand${NC}"
            echo "Run 'loki context help' for usage."
            return 1
            ;;
    esac
}

_context_show() {
    local loki_dir="${LOKI_DIR:-.loki}"

    if [ ! -d "$loki_dir" ]; then
        echo -e "${YELLOW}No active session.${NC}"
        return 0
    fi

    section_header "Context Window Usage" 2>/dev/null || echo -e "\n${BOLD}Context Window Usage${NC}"

    # Read context tracker data if available
    local ctx_file="$loki_dir/state/context-usage.json"
    if [ -f "$ctx_file" ]; then
        local total_tokens used_tokens input_tokens output_tokens cache_tokens
        total_tokens=$(_LOKI_CTX_FILE="$ctx_file" python3 -c "import json, os; d=json.load(open(os.environ['_LOKI_CTX_FILE'])); print(d.get('window_size', 200000))" 2>/dev/null || echo "200000")
        used_tokens=$(_LOKI_CTX_FILE="$ctx_file" python3 -c "import json, os; d=json.load(open(os.environ['_LOKI_CTX_FILE'])); print(d.get('used_tokens', 0))" 2>/dev/null || echo "0")
        input_tokens=$(_LOKI_CTX_FILE="$ctx_file" python3 -c "import json, os; d=json.load(open(os.environ['_LOKI_CTX_FILE'])); print(d.get('input_tokens', 0))" 2>/dev/null || echo "0")
        output_tokens=$(_LOKI_CTX_FILE="$ctx_file" python3 -c "import json, os; d=json.load(open(os.environ['_LOKI_CTX_FILE'])); print(d.get('output_tokens', 0))" 2>/dev/null || echo "0")
        cache_tokens=$(_LOKI_CTX_FILE="$ctx_file" python3 -c "import json, os; d=json.load(open(os.environ['_LOKI_CTX_FILE'])); print(d.get('cache_read_tokens', 0))" 2>/dev/null || echo "0")

        # Display gauge
        if type context_gauge &>/dev/null; then
            context_gauge "$used_tokens" "$total_tokens" "Window"
        else
            local pct=0
            if [ "$total_tokens" -gt 0 ] 2>/dev/null; then
                pct=$((used_tokens * 100 / total_tokens))
            fi
            echo -e "  ${CYAN}Context:${NC} ${pct}% used (${used_tokens} / ${total_tokens} tokens)"
        fi
        echo ""

        # Breakdown
        echo -e "  ${BOLD}Breakdown${NC} ${DIM}(estimated)${NC}"
        echo -e "  ${DIM}Input tokens:${NC}  $(printf '%8s' "$input_tokens")"
        echo -e "  ${DIM}Output tokens:${NC} $(printf '%8s' "$output_tokens")"
        echo -e "  ${DIM}Cache reads:${NC}   $(printf '%8s' "$cache_tokens")"
    else
        echo -e "  ${DIM}No context tracking data yet.${NC}"
        echo -e "  ${DIM}Context usage is tracked during active sessions.${NC}"
    fi

    # Show token costs if budget file exists
    local budget_file="$loki_dir/metrics/budget.json"
    if [ -f "$budget_file" ]; then
        echo ""
        echo -e "  ${BOLD}Cost${NC}"
        local budget_used budget_limit
        budget_used=$(_BUDGET_FILE="$budget_file" python3 -c "import json, os; d=json.load(open(os.environ['_BUDGET_FILE'])); print(round(d.get('budget_used', 0), 4))" 2>/dev/null || echo "0")
        budget_limit=$(_BUDGET_FILE="$budget_file" python3 -c "import json, os; d=json.load(open(os.environ['_BUDGET_FILE'])); print(d.get('budget_limit', 0))" 2>/dev/null || echo "0")

        if [ "$budget_limit" != "0" ]; then
            echo -e "  ${DIM}Spent:${NC}     \$${budget_used} / \$${budget_limit}"
            local remaining
            remaining=$(echo "scale=4; $budget_limit - $budget_used" | bc 2>/dev/null || echo "0")
            echo -e "  ${DIM}Remaining:${NC} \$${remaining}"
        else
            echo -e "  ${DIM}Spent:${NC} \$${budget_used} (no budget limit set)"
        fi
    fi
    echo ""
}

_context_files() {
    local loki_dir="${LOKI_DIR:-.loki}"
    local ctx_files="$loki_dir/state/context-files.json"

    section_header "Context Files" 2>/dev/null || echo -e "\n${BOLD}Context Files${NC}"

    if [ -f "$ctx_files" ]; then
        python3 << 'PYEOF'
import json, os

ctx_file = os.environ.get("LOKI_DIR", ".loki") + "/state/context-files.json"
try:
    with open(ctx_file) as f:
        files = json.load(f)
    total = 0
    for entry in files:
        name = entry.get("path", "unknown")
        tokens = entry.get("estimated_tokens", 0)
        total += tokens
        print(f"  {tokens:>8} tokens  {name}")
    print(f"\n  {'Total:':>8}  {total} tokens")
except Exception:
    print("  No context files tracked yet.")
PYEOF
    else
        echo -e "  ${DIM}No context files tracked.${NC}"
        echo ""
        echo "  Add files with: loki context add <file>"
        echo "  Or use @path syntax in your prompt:"
        echo "    @src/main.py    - inject file contents"
        echo "    @src/           - inject directory tree"
    fi
    echo ""
}

_context_tools() {
    local loki_dir="${LOKI_DIR:-.loki}"

    section_header "Tool Token Usage" 2>/dev/null || echo -e "\n${BOLD}Tool Token Usage${NC}"

    # Read MCP config and estimate tool token usage
    local mcp_config="$loki_dir/mcp/config.json"
    if [ -f "$mcp_config" ]; then
        python3 << 'PYEOF'
import json, os

config_file = os.environ.get("LOKI_DIR", ".loki") + "/mcp/config.json"
try:
    with open(config_file) as f:
        config = json.load(f)
    servers = config.get("mcpServers", {})
    total = 0
    for name, server in servers.items():
        tools = server.get("tools", [])
        # Estimate ~500 tokens per tool definition
        est = len(tools) * 500 if tools else 500
        total += est
        print(f"  {est:>6} tokens  {name} ({len(tools) if tools else '?'} tools)")
    print(f"\n  Total MCP tool overhead: ~{total} tokens per request")
except Exception:
    print("  No MCP servers configured.")
PYEOF
    else
        echo -e "  ${DIM}Native tools only (no MCP servers configured).${NC}"
    fi

    echo ""
    echo -e "  ${DIM}Native tools: ~2000 tokens (bash, read, write, glob, grep)${NC}"
    echo -e "  ${DIM}Tip: Large MCP tool descriptions impact performance.${NC}"
    echo ""
}

_context_add() {
    local file_path="$1"
    if [ -z "$file_path" ]; then
        echo -e "${RED}Usage: loki context add <file-or-dir>${NC}"
        echo ""
        echo "Adds a file's content (or directory tree) to the context."
        echo "Equivalent to @path inline reference syntax."
        return 1
    fi

    # Strip @ prefix if user types it
    file_path="${file_path#@}"

    if [ -d "$file_path" ]; then
        echo -e "${BOLD}Directory tree: ${file_path}${NC}"
        echo ""
        if type tree_display &>/dev/null; then
            tree_display "$file_path"
        else
            find "$file_path" -maxdepth 3 -not -path '*/\.*' | head -50
        fi
        echo ""

        # Count files and estimate tokens
        local file_count
        file_count=$(find "$file_path" -type f -not -path '*/\.*' | wc -l)
        echo -e "${DIM}$file_count files in directory${NC}"
    elif [ -f "$file_path" ]; then
        local size
        size=$(wc -c < "$file_path")
        # Rough estimate: 1 token per 4 bytes
        local est_tokens=$((size / 4))
        local lines
        lines=$(wc -l < "$file_path")

        echo -e "${BOLD}File: ${file_path}${NC}"
        echo -e "  ${DIM}Size:${NC}    $size bytes"
        echo -e "  ${DIM}Lines:${NC}   $lines"
        echo -e "  ${DIM}Tokens:${NC}  ~$est_tokens (estimated)"

        # Store in context files list
        local loki_dir="${LOKI_DIR:-.loki}"
        mkdir -p "$loki_dir/state"
        local ctx_files="$loki_dir/state/context-files.json"
        python3 -c "
import json, os
path = '$file_path'
tokens = $est_tokens
ctx_file = '$ctx_files'
try:
    with open(ctx_file) as f:
        files = json.load(f)
except:
    files = []
# Avoid duplicates
files = [f for f in files if f.get('path') != path]
files.append({'path': path, 'estimated_tokens': tokens, 'size': $size, 'lines': $lines})
with open(ctx_file, 'w') as f:
    json.dump(files, f, indent=2)
" 2>/dev/null

        echo -e "  ${GREEN}Added to context.${NC}"
    else
        echo -e "${RED}Not found: $file_path${NC}"
        return 1
    fi
}

_context_clear() {
    local loki_dir="${LOKI_DIR:-.loki}"
    rm -f "$loki_dir/state/context-files.json"
    rm -f "$loki_dir/state/context-usage.json"
    echo -e "${GREEN}Context cleared.${NC}"
    echo -e "${DIM}A new conversation will start fresh.${NC}"
}

# Codebase intelligence (inspired by Kiro CLI /code command)
# Provides workspace overview, symbol search, and pattern matching
cmd_code() {
    local subcommand="${1:-help}"
    shift 2>/dev/null || true

    case "$subcommand" in
        --help|-h|help)
            echo -e "${BOLD}loki code${NC} - Codebase intelligence"
            echo ""
            echo "Usage: loki code <subcommand>"
            echo ""
            echo "Subcommands:"
            echo "  overview    Generate workspace overview (file types, LOC, structure)"
            echo "  symbols     Search for symbols (functions, classes, variables)"
            echo "  search      Hybrid grep + semantic codebase search"
            echo "  deps        Show dependency graph for a file or module"
            echo "  hotspots    Find code hotspots (most-changed files, complexity)"
            echo "  diff        Show colored diff of recent changes"
            echo ""
            echo "Examples:"
            echo "  loki code overview                  # Full codebase overview"
            echo "  loki code overview --silent          # Compact output"
            echo "  loki code symbols 'class.*Service'   # Find service classes"
            echo "  loki code search 'rate limit backoff'   # Hybrid grep + semantic search"
            echo "  loki code search 'council vote' --grep-only   # Lexical only"
            echo "  loki code search 'memory retrieval' --semantic-only --top 15"
            echo "  loki code deps src/main.py           # Show dependencies"
            echo "  loki code hotspots --top 10          # Top 10 changed files"
            echo "  loki code diff                       # Colored diff of uncommitted changes"
            echo ""
            echo "Inspired by: Kiro CLI /code command (kiro.dev)"
            return 0
            ;;
        overview)
            _code_overview "$@"
            ;;
        symbols)
            _code_symbols "$@"
            ;;
        search)
            _code_search "$@"
            ;;
        deps)
            _code_deps "$@"
            ;;
        hotspots)
            _code_hotspots "$@"
            ;;
        diff)
            _code_diff "$@"
            ;;
        *)
            echo -e "${RED}Unknown subcommand: $subcommand${NC}"
            echo "Run 'loki code help' for usage."
            return 1
            ;;
    esac
}

# Hybrid codebase search: grep + semantic (ChromaDB) fused with RRF.
# Delegates to tools/hybrid_search.py (needs python3.12 for chromadb). The
# Python side handles the rg / grep / python-scan fallback and the grep-only
# degradation when ChromaDB is unreachable, so this stays thin.
_code_search() {
    local query=""
    local -a pyargs=()
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --grep-only) pyargs+=("--grep-only"); shift ;;
            --semantic-only) pyargs+=("--semantic-only"); shift ;;
            --budget) pyargs+=("--budget" "${2:-3000}"); shift 2 ;;
            --top) pyargs+=("--top" "${2:-10}"); shift 2 ;;
            --json) pyargs+=("--json"); shift ;;
            -h|--help)
                echo -e "${BOLD}loki code search${NC} - Hybrid grep + semantic codebase search"
                echo ""
                echo "Usage: loki code search \"<query>\" [flags]"
                echo ""
                echo "Flags:"
                echo "  --grep-only        Lexical search only (skip semantic)"
                echo "  --semantic-only    Semantic search only (skip grep)"
                echo "  --budget N         Token budget for merged results (default 3000)"
                echo "  --top N            Max results (default 10)"
                echo "  --json             Output JSON"
                echo ""
                echo "Falls back to grep-only when ChromaDB is unreachable."
                return 0
                ;;
            *)
                if [[ -z "$query" ]]; then
                    query="$1"
                else
                    query="$query $1"
                fi
                shift
                ;;
        esac
    done

    if [[ -z "$query" ]]; then
        echo -e "${RED}Usage: loki code search \"<query>\" [--grep-only|--semantic-only] [--budget N] [--top N]${NC}"
        return 1
    fi

    local script_dir hybrid py
    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
    hybrid="$script_dir/tools/hybrid_search.py"
    if [[ ! -f "$hybrid" ]]; then
        echo -e "${RED}hybrid_search.py not found at $hybrid${NC}"
        return 1
    fi

    # chromadb requires python3.12 on this stack; fall back to python3 for the
    # grep-only path if 3.12 is absent.
    if [[ -x /opt/homebrew/bin/python3.12 ]]; then
        py=/opt/homebrew/bin/python3.12
    else
        py="$(command -v python3 || true)"
    fi
    if [[ -z "$py" ]]; then
        echo -e "${RED}python3 not found${NC}"
        return 1
    fi

    # Safe array expansion: "${pyargs[@]}" triggers "unbound variable" under
    # bash 3.2 (macOS default) + set -u when the array is empty (no flags).
    "$py" "$hybrid" "$query" ${pyargs[@]+"${pyargs[@]}"}
}

_code_overview() {
    local silent=false
    local target_dir="."
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --silent|-s) silent=true; shift ;;
            *) target_dir="$1"; shift ;;
        esac
    done

    if $silent; then
        echo -e "${BOLD}$(basename "$(cd "$target_dir" && pwd)")${NC}"
    else
        section_header "Codebase Overview: $(basename "$(cd "$target_dir" && pwd)")" 2>/dev/null || echo -e "\n${BOLD}Codebase Overview${NC}"
    fi

    # Language breakdown
    python3 << PYEOF
import os, collections, sys

target = "$target_dir"
silent = $( $silent && echo "True" || echo "False" )

# File extension to language mapping
EXT_MAP = {
    '.py': 'Python', '.js': 'JavaScript', '.ts': 'TypeScript', '.tsx': 'TypeScript',
    '.jsx': 'JavaScript', '.go': 'Go', '.rs': 'Rust', '.java': 'Java',
    '.rb': 'Ruby', '.php': 'PHP', '.c': 'C', '.cpp': 'C++', '.h': 'C/C++ Header',
    '.cs': 'C#', '.swift': 'Swift', '.kt': 'Kotlin', '.scala': 'Scala',
    '.sh': 'Shell', '.bash': 'Shell', '.zsh': 'Shell',
    '.html': 'HTML', '.css': 'CSS', '.scss': 'SCSS', '.vue': 'Vue',
    '.md': 'Markdown', '.json': 'JSON', '.yaml': 'YAML', '.yml': 'YAML',
    '.toml': 'TOML', '.xml': 'XML', '.sql': 'SQL',
    '.dockerfile': 'Docker', '.tf': 'Terraform', '.proto': 'Protobuf',
}

SKIP_DIRS = {'.git', 'node_modules', '.loki', '__pycache__', '.venv', 'venv',
             'dist', 'build', '.next', 'target', '.tox', '.mypy_cache',
             'vendor', '.cargo', 'coverage', '.pytest_cache'}

lang_files = collections.Counter()
lang_lines = collections.Counter()
total_files = 0
total_lines = 0
dir_count = 0

for root, dirs, files in os.walk(target):
    # Skip hidden and build directories
    dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith('.')]
    dir_count += 1
    for f in files:
        ext = os.path.splitext(f)[1].lower()
        if ext in EXT_MAP:
            lang = EXT_MAP[ext]
            lang_files[lang] += 1
            total_files += 1
            try:
                fp = os.path.join(root, f)
                lines = sum(1 for _ in open(fp, 'rb'))
                lang_lines[lang] += lines
                total_lines += lines
            except:
                pass

if not silent:
    print(f"\n  Files: {total_files}  Lines: {total_lines:,}  Dirs: {dir_count}")
    print()

# Language table
print("  Language         Files   Lines     %")
print("  " + "-" * 40)
for lang, count in lang_lines.most_common(15):
    pct = (count / total_lines * 100) if total_lines > 0 else 0
    files = lang_files[lang]
    bar_len = int(pct / 5)
    bar = "=" * bar_len
    print(f"  {lang:<16} {files:>5}  {count:>7,}  {pct:>4.1f}% {bar}")

if not silent:
    # Show directory structure (top-level)
    print()
    print("  Top-level structure:")
    entries = sorted(os.listdir(target))
    for entry in entries:
        if entry.startswith('.') and entry not in ('.github',):
            continue
        full = os.path.join(target, entry)
        if os.path.isdir(full) and entry not in SKIP_DIRS:
            subcount = sum(1 for _, _, fs in os.walk(full) for f in fs)
            print(f"    {entry + '/':30s} ({subcount} files)")
        elif os.path.isfile(full):
            size = os.path.getsize(full)
            if size > 1024:
                print(f"    {entry:30s} ({size // 1024}KB)")
PYEOF
    echo ""
}

_code_symbols() {
    local pattern="${1:-.}"
    local file_type="${2:-}"

    section_header "Symbol Search: $pattern" 2>/dev/null || echo -e "\n${BOLD}Symbol Search${NC}"

    # Search for function/class definitions using ripgrep or grep
    local search_patterns=(
        "^(def|function|func|fn|pub fn|async def|class|interface|struct|enum|type|export (const|function|class|interface))\s+.*${pattern}"
    )

    if command -v rg &>/dev/null; then
        rg -n --no-heading -e "^(def |function |func |fn |pub fn |async def |class |interface |struct |enum |type |export (const|function|class|interface) ).*${pattern}" \
            --type-add 'code:*.{py,js,ts,tsx,go,rs,java,rb,php,c,cpp,h,cs,swift,kt}' \
            -t code \
            --glob '!node_modules' --glob '!.git' --glob '!dist' --glob '!build' \
            . 2>/dev/null | head -50
    else
        grep -rn "^\(def\|function\|func\|fn\|class\|interface\|struct\|enum\|type\).*${pattern}" \
            --include='*.py' --include='*.js' --include='*.ts' --include='*.go' \
            --include='*.rs' --include='*.java' --include='*.rb' \
            --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist \
            . 2>/dev/null | head -50
    fi
    echo ""
}

_code_deps() {
    local target_file="$1"
    if [ -z "$target_file" ]; then
        echo -e "${RED}Usage: loki code deps <file>${NC}"
        return 1
    fi

    section_header "Dependencies: $target_file" 2>/dev/null || echo -e "\n${BOLD}Dependencies${NC}"

    if [ ! -f "$target_file" ]; then
        echo -e "${RED}File not found: $target_file${NC}"
        return 1
    fi

    local ext="${target_file##*.}"
    echo -e "  ${BOLD}Imports/Requires:${NC}"

    case "$ext" in
        py)
            grep -n "^import\|^from.*import" "$target_file" 2>/dev/null | sed 's/^/  /'
            ;;
        js|ts|tsx|jsx)
            grep -n "import\|require(" "$target_file" 2>/dev/null | sed 's/^/  /'
            ;;
        go)
            grep -n "\"" "$target_file" 2>/dev/null | grep -v "//" | sed 's/^/  /'
            ;;
        rs)
            grep -n "^use\|^extern crate" "$target_file" 2>/dev/null | sed 's/^/  /'
            ;;
        java)
            grep -n "^import" "$target_file" 2>/dev/null | sed 's/^/  /'
            ;;
        rb)
            grep -n "^require\|^require_relative" "$target_file" 2>/dev/null | sed 's/^/  /'
            ;;
        *)
            echo -e "  ${DIM}Unsupported file type: .$ext${NC}"
            ;;
    esac

    echo ""
    echo -e "  ${BOLD}Referenced by:${NC}"
    local basename_file
    basename_file=$(basename "$target_file" ".$ext")
    if command -v rg &>/dev/null; then
        rg -l "$basename_file" --glob '!node_modules' --glob '!.git' --glob '!dist' . 2>/dev/null | \
            grep -v "^${target_file}$" | head -20 | sed 's/^/  /'
    else
        grep -rl "$basename_file" --exclude-dir=node_modules --exclude-dir=.git . 2>/dev/null | \
            grep -v "^${target_file}$" | head -20 | sed 's/^/  /'
    fi
    echo ""
}

_code_hotspots() {
    local top_n=10
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --top) top_n="${2:-10}"; shift 2 ;;
            --top=*) top_n="${1#*=}"; shift ;;
            *) shift ;;
        esac
    done

    section_header "Code Hotspots (top $top_n)" 2>/dev/null || echo -e "\n${BOLD}Code Hotspots${NC}"

    if ! git rev-parse --is-inside-work-tree &>/dev/null; then
        echo -e "  ${RED}Not a git repository.${NC}"
        return 1
    fi

    echo -e "  ${BOLD}Most Changed Files (last 90 days):${NC}"
    echo ""
    git log --since="90 days ago" --pretty=format: --name-only 2>/dev/null | \
        sort | uniq -c | sort -rn | head -"$top_n" | \
        while read -r count file; do
            [ -z "$file" ] && continue
            printf "  %4d changes  %s\n" "$count" "$file"
        done

    echo ""
    echo -e "  ${BOLD}Largest Files by LOC:${NC}"
    echo ""
    find . -type f \( -name '*.py' -o -name '*.js' -o -name '*.ts' -o -name '*.go' \
        -o -name '*.rs' -o -name '*.java' -o -name '*.sh' \) \
        -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' \
        -exec wc -l {} \; 2>/dev/null | sort -rn | head -"$top_n" | \
        while read -r lines file; do
            printf "  %6d lines  %s\n" "$lines" "$file"
        done
    echo ""
}

_code_diff() {
    if ! git rev-parse --is-inside-work-tree &>/dev/null; then
        echo -e "${RED}Not a git repository.${NC}"
        return 1
    fi

    # Use delta if available, otherwise colored diff
    if command -v delta &>/dev/null; then
        git diff "$@" | delta
    else
        git diff "$@" | while IFS= read -r line; do
            case "$line" in
                diff*)     echo -e "${BOLD}${line}${NC}" ;;
                ---*)      echo -e "${BOLD}${line}${NC}" ;;
                +++*)      echo -e "${BOLD}${line}${NC}" ;;
                @@*)       echo -e "${CYAN}${line}${NC}" ;;
                +*)        echo -e "${GREEN}${line}${NC}" ;;
                -*)        echo -e "${RED}${line}${NC}" ;;
                *)         echo "$line" ;;
            esac
        done
    fi
}

# Output shell completion scripts
cmd_completions() {
    local shell="${1:-bash}"
    local skill_dir
    
    # Find the skill directory (where autonomy/loki is located)
    skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
    local completions_dir="$skill_dir/completions"
    
    case "$shell" in
        bash)
            if [ -f "$completions_dir/loki.bash" ]; then
                cat "$completions_dir/loki.bash"
            else
                echo -e "${RED}Error: Bash completion script not found at $completions_dir/loki.bash${NC}" >&2
                exit 1
            fi
            ;;
        zsh)
            if [ -f "$completions_dir/_loki" ]; then
                cat "$completions_dir/_loki"
            else
                echo -e "${RED}Error: Zsh completion script not found at $completions_dir/_loki${NC}" >&2
                exit 1
            fi
            ;;
        *)
            echo -e "${BOLD}Loki Shell Completions${NC}"
            echo ""
            echo "Output shell completion scripts for bash or zsh."
            echo ""
            echo "Usage: loki completions <shell>"
            echo ""
            echo "Shells:"
            echo "  bash    Bash completion script"
            echo "  zsh     Zsh completion script"
            echo ""
            echo "Installation:"
            echo ""
            echo "Bash:"
            echo "  eval \"\$(loki completions bash)\" >> ~/.bashrc"
            echo ""
            echo "Zsh:"
            echo "  eval \"\$(loki completions zsh)\" >> ~/.zshrc"
            echo ""
            exit 1
            ;;
    esac
}

cmd_template() {
    local subcommand="${1:-list}"
    shift 2>/dev/null || true

    local script_dir
    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    local hub_py="${script_dir}/../agents/hub_install.py"
    [ -f "$hub_py" ] || hub_py="agents/hub_install.py"

    case "$subcommand" in
        install)
            local source="${1:-}"
            if [ -z "$source" ]; then
                echo -e "${RED}Usage: loki template install <source>${NC}"
                echo "  source: local path, git repo URL, or raw manifest URL"
                return 1
            fi
            if [ ! -f "$hub_py" ]; then
                echo -e "${RED}Error: agents/hub_install.py not found${NC}"
                return 1
            fi
            local result
            if result=$(python3 "$hub_py" install-template "$source" 2>&1); then
                TPL_RESULT="$result" python3 - << 'PYEOF'
import json, os
r = json.loads(os.environ["TPL_RESULT"])
print(f"Installed template: {r['name']}  ({r.get('label','')})")
desc = r.get("description") or ""
if desc:
    print(f"  {desc}")
print(f"  body:   {r.get('path','')}")
print(f"  source: {r.get('source','')}")
ig = r.get("_ignored_executable_fields") or []
if ig:
    print(f"  NOTE: ignored executable-looking fields (never run): {', '.join(ig)}")
print(f"Use with: loki init --template {r['name']}")
PYEOF
            else
                echo -e "${RED}Install failed:${NC} $result"
                return 1
            fi
            ;;

        list|installed)
            echo -e "${BOLD}Hub-installed Templates${NC}"
            echo ""
            if [ ! -f "$hub_py" ]; then
                echo "  (none)"
            else
                local _tpls_json
                _tpls_json="$(python3 "$hub_py" list-templates 2>/dev/null)"
                HUB_ITEMS="$_tpls_json" python3 - << 'PYEOF'
import json, os
try:
    items = json.loads(os.environ.get("HUB_ITEMS", "") or "[]")
except Exception:
    items = []
if not items:
    print("  (none)")
else:
    for t in items:
        print(f"  {t.get('name',''):24s} {t.get('label','')}")
    print(f"\n  Total: {len(items)} installed template(s)")
PYEOF
            fi
            ;;

        --help|-h|help)
            echo -e "${BOLD}loki template${NC} - PRD template marketplace (R10)"
            echo ""
            echo "Usage: loki template <command> [args]"
            echo ""
            echo "Commands:"
            echo "  install <source>   Install a community template (local/git/url manifest)"
            echo "  list               List templates installed from a hub source"
            echo "  help               Show this help"
            echo ""
            echo "Examples:"
            echo "  loki template install ./my-template/manifest.json"
            echo "  loki template install https://github.com/owner/loki-template-rust.git"
            echo "  loki template list"
            echo ""
            echo "Installed templates are usable via 'loki init --template <name>'."
            echo "Built-in templates ship in templates/ (see 'loki init --list')."
            echo ""
            echo "Note: install is install-from-source (git/local/url). A hosted"
            echo "central hub registry is future work. Manifests are validated as"
            echo "DATA only; no code from a manifest is ever executed."
            echo ""
            ;;
        *)
            echo -e "${RED}Unknown template command: $subcommand${NC}"
            echo "Run 'loki template help' for usage."
            return 1
            ;;
    esac
}

# Agent type dispatch (v6.7.0)
cmd_agent() {
    local subcommand="${1:-list}"
    shift 2>/dev/null || true

    # Find agents/types.json relative to script location
    local script_dir
    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    local types_file="${script_dir}/../agents/types.json"
    if [ ! -f "$types_file" ]; then
        # Try relative to current dir (development)
        types_file="agents/types.json"
    fi
    if [ ! -f "$types_file" ]; then
        echo -e "${RED}Error: agents/types.json not found${NC}"
        echo "Expected at: ${script_dir}/../agents/types.json"
        return 1
    fi

    case "$subcommand" in
        list)
            local filter_swarm="${1:-}"
            echo -e "${BOLD}Agent Types${NC}"
            echo ""
            TYPES_FILE="$types_file" FILTER_SWARM="$filter_swarm" FILTER_SWARM2="${2:-}" python3 << 'PYEOF'
import json, sys, os

with open(os.environ["TYPES_FILE"]) as f:
    agents = json.load(f)

# R10: union built-in agents with hub-installed agents (.loki/agents/installed.json).
try:
    import importlib.util as _ilu
    _hub_path = os.path.join(os.path.dirname(os.path.abspath(os.environ["TYPES_FILE"])), "hub_install.py")
    _spec = _ilu.spec_from_file_location("loki_hub_install", _hub_path)
    _hub = _ilu.module_from_spec(_spec)
    _spec.loader.exec_module(_hub)
    _seen = {a.get("type") for a in agents}
    for _inst in _hub.installed_agent_list():
        if _inst.get("type") not in _seen:
            agents.append(_inst)
except Exception:
    pass

filter_swarm = os.environ.get("FILTER_SWARM", "")
if filter_swarm and filter_swarm.startswith("--swarm"):
    # Handle --swarm=X or --swarm X
    if "=" in filter_swarm:
        filter_swarm = filter_swarm.split("=")[1]
    else:
        filter_swarm = os.environ.get("FILTER_SWARM2", "")

swarms = {}
for a in agents:
    s = a["swarm"]
    if filter_swarm and s != filter_swarm:
        continue
    swarms.setdefault(s, []).append(a)

total = 0
for swarm_name in sorted(swarms.keys()):
    print(f"  {swarm_name.upper()} SWARM:")
    for a in swarms[swarm_name]:
        caps = a.get("capabilities", "")
        if len(caps) > 60:
            caps = caps[:57] + "..."
        print(f"    {a['type']:22s} {caps}")
        total += 1
    print()

print(f"  Total: {total} agent type(s)")
PYEOF
            ;;

        info)
            local agent_type="${1:-}"
            if [ -z "$agent_type" ]; then
                echo -e "${RED}Usage: loki agent info <type>${NC}"
                return 1
            fi
            TYPES_FILE="$types_file" AGENT_TYPE="$agent_type" python3 << 'PYEOF'
import json, sys, os

with open(os.environ["TYPES_FILE"]) as f:
    agents = json.load(f)

# R10: include hub-installed agents in the lookup.
try:
    import importlib.util as _ilu
    _hub_path = os.path.join(os.path.dirname(os.path.abspath(os.environ["TYPES_FILE"])), "hub_install.py")
    _spec = _ilu.spec_from_file_location("loki_hub_install", _hub_path)
    _hub = _ilu.module_from_spec(_spec)
    _spec.loader.exec_module(_hub)
    _seen = {a.get("type") for a in agents}
    for _inst in _hub.installed_agent_list():
        if _inst.get("type") not in _seen:
            agents.append(_inst)
except Exception:
    pass

target = os.environ["AGENT_TYPE"]
found = None
for a in agents:
    if a["type"] == target:
        found = a
        break

if not found:
    print(f"Agent type not found: {target}")
    print("Run 'loki agent list' to see all types.")
    sys.exit(1)

print(f"Type:         {found['type']}")
print(f"Name:         {found['name']}")
print(f"Swarm:        {found['swarm']}")
print(f"Capabilities: {found['capabilities']}")
print(f"Focus:        {', '.join(found.get('focus', []))}")
print()
print(f"Persona:")
print(f"  {found['persona']}")
PYEOF
            ;;

        run)
            local agent_type="${1:-}"
            local prompt="${2:-}"
            if [ -z "$agent_type" ] || [ -z "$prompt" ]; then
                echo -e "${RED}Usage: loki agent run <type> \"<prompt>\"${NC}"
                return 1
            fi

            # Get persona from types.json
            local persona
            persona=$(AGENT_TYPE="$agent_type" TYPES_FILE="$types_file" python3 -c "
import json, os
with open(os.environ['TYPES_FILE']) as f:
    agents = json.load(f)
try:
    import importlib.util as _ilu
    _hp = os.path.join(os.path.dirname(os.path.abspath(os.environ['TYPES_FILE'])), 'hub_install.py')
    _sp = _ilu.spec_from_file_location('loki_hub_install', _hp)
    _hub = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_hub)
    _seen = {a.get('type') for a in agents}
    for _i in _hub.installed_agent_list():
        if _i.get('type') not in _seen:
            agents.append(_i)
except Exception:
    pass
for a in agents:
    if a['type'] == os.environ['AGENT_TYPE']:
        print(a['persona'])
        break
" 2>/dev/null)

            if [ -z "$persona" ]; then
                echo -e "${RED}Unknown agent type: $agent_type${NC}"
                return 1
            fi

            # BUG-PU-003: Separate persona from user prompt with clear delimiter
            # so the AI provider can distinguish role instruction from task
            local full_prompt="You are acting as the following specialist agent:

${persona}

---

USER TASK: ${prompt}"
            echo -e "${BOLD}Running as: $agent_type${NC}"
            echo -e "${DIM}Persona: ${persona:0:80}...${NC}"
            echo ""

            # Invoke current provider and capture exit code (#72)
            local provider="${LOKI_PROVIDER:-claude}"
            if [ -f ".loki/state/provider" ]; then
                provider=$(cat ".loki/state/provider" 2>/dev/null)
            fi

            local agent_exit=0
            case "$provider" in
                claude)
                    claude -p "$full_prompt" 2>&1 || agent_exit=$?
                    ;;
                codex)
                    codex exec --full-auto "$full_prompt" 2>&1 || agent_exit=$?
                    ;;
                cline)
                    cline -y "$full_prompt" 2>&1 || agent_exit=$?
                    ;;
                aider)
                    aider --message "$full_prompt" --yes-always --no-auto-commits < /dev/null 2>&1 || agent_exit=$?
                    ;;
                *)
                    echo -e "${RED}Unknown provider: $provider${NC}"
                    return 1
                    ;;
            esac

            if [ "$agent_exit" -ne 0 ]; then
                echo ""
                echo -e "${RED}Agent exited with code $agent_exit${NC}"
                return "$agent_exit"
            fi
            ;;

        start)
            local agent_type="${1:-}"
            local prd="${2:-}"
            if [ -z "$agent_type" ] || [ -z "$prd" ]; then
                echo -e "${RED}Usage: loki agent start <type> \"<prompt>\" or loki agent start <type> <prd-file>${NC}"
                return 1
            fi

            # Get persona
            local persona
            persona=$(AGENT_TYPE="$agent_type" TYPES_FILE="$types_file" python3 -c "
import json, os
with open(os.environ['TYPES_FILE']) as f:
    agents = json.load(f)
try:
    import importlib.util as _ilu
    _hp = os.path.join(os.path.dirname(os.path.abspath(os.environ['TYPES_FILE'])), 'hub_install.py')
    _sp = _ilu.spec_from_file_location('loki_hub_install', _hp)
    _hub = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_hub)
    _seen = {a.get('type') for a in agents}
    for _i in _hub.installed_agent_list():
        if _i.get('type') not in _seen:
            agents.append(_i)
except Exception:
    pass
for a in agents:
    if a['type'] == os.environ['AGENT_TYPE']:
        print(a['persona'])
        break
" 2>/dev/null)

            if [ -z "$persona" ]; then
                echo -e "${RED}Unknown agent type: $agent_type${NC}"
                return 1
            fi

            echo -e "${BOLD}Starting autonomous session as: $agent_type${NC}"

            # Set agent persona as env var for run.sh to pick up
            export LOKI_AGENT_PERSONA="$persona"
            export LOKI_AGENT_TYPE="$agent_type"

            # Delegate to regular start
            if [ -f "$prd" ]; then
                cmd_start "$prd"
            else
                # Treat as inline prompt - create temp PRD
                # BUG-PU-003: Use .loki/ directory instead of /tmp so run.sh
                # can find it after exec replaces this process. The temp file
                # in /tmp was never cleaned because cmd_start uses exec.
                mkdir -p "$LOKI_DIR"
                local tmp_prd="$LOKI_DIR/agent-prd-$$.md"
                echo "# Agent Task: $agent_type" > "$tmp_prd"
                echo "" >> "$tmp_prd"
                echo "$prd" >> "$tmp_prd"
                cmd_start "$tmp_prd"
            fi
            ;;

        review)
            local agent_type="${1:-ops-security}"
            echo -e "${BOLD}Code review as: $agent_type${NC}"

            local persona
            persona=$(AGENT_TYPE="$agent_type" TYPES_FILE="$types_file" python3 -c "
import json, os
with open(os.environ['TYPES_FILE']) as f:
    agents = json.load(f)
try:
    import importlib.util as _ilu
    _hp = os.path.join(os.path.dirname(os.path.abspath(os.environ['TYPES_FILE'])), 'hub_install.py')
    _sp = _ilu.spec_from_file_location('loki_hub_install', _hp)
    _hub = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_hub)
    _seen = {a.get('type') for a in agents}
    for _i in _hub.installed_agent_list():
        if _i.get('type') not in _seen:
            agents.append(_i)
except Exception:
    pass
for a in agents:
    if a['type'] == os.environ['AGENT_TYPE']:
        print(a['persona'])
        break
" 2>/dev/null)

            if [ -z "$persona" ]; then
                echo -e "${RED}Unknown agent type: $agent_type${NC}"
                return 1
            fi

            local diff
            diff=$(git diff HEAD~1 2>/dev/null || git diff --cached 2>/dev/null || echo "No changes to review")

            local review_prompt="$persona

Review the following code changes. Focus on your area of expertise.
Provide findings as:
- CRITICAL: Must fix before merge
- HIGH: Should fix
- MEDIUM: Improvement suggestion
- LOW: Nitpick

Changes:
$diff"

            local provider="${LOKI_PROVIDER:-claude}"
            [ -f ".loki/state/provider" ] && provider=$(cat ".loki/state/provider" 2>/dev/null)

            case "$provider" in
                claude)  claude -p "$review_prompt" 2>&1 ;;
                codex)   codex exec --full-auto "$review_prompt" 2>&1 ;;
                cline)   cline -y "$review_prompt" 2>&1 ;;
                *)       echo -e "${RED}Unknown provider: $provider${NC}"; return 1 ;;
            esac
            ;;

        install)
            # R10: install a community agent from a source (local path /
            # git repo / raw URL containing a manifest). DATA-ONLY install --
            # never executes code from the manifest. See agents/hub_install.py.
            local source="${1:-}"
            if [ -z "$source" ]; then
                echo -e "${RED}Usage: loki agent install <source>${NC}"
                echo "  source: local path, git repo URL, or raw manifest URL"
                return 1
            fi
            local hub_py="${script_dir}/../agents/hub_install.py"
            [ -f "$hub_py" ] || hub_py="agents/hub_install.py"
            if [ ! -f "$hub_py" ]; then
                echo -e "${RED}Error: agents/hub_install.py not found${NC}"
                return 1
            fi
            local result
            if result=$(python3 "$hub_py" install-agent "$source" 2>&1); then
                AGENT_RESULT="$result" python3 - << 'PYEOF'
import json, os
r = json.loads(os.environ["AGENT_RESULT"])
print(f"Installed agent: {r['type']}  ({r.get('name','')})")
print(f"  swarm:   {r.get('swarm','')}")
print(f"  source:  {r.get('source','')}")
ig = r.get("_ignored_executable_fields") or []
if ig:
    print(f"  NOTE: ignored executable-looking fields (never run): {', '.join(ig)}")
print("Stored in .loki/agents/installed.json -- visible to 'loki agent list/info/run'.")
PYEOF
            else
                echo -e "${RED}Install failed:${NC} $result"
                return 1
            fi
            ;;

        installed)
            # R10: list agents installed from a hub source.
            local hub_py="${script_dir}/../agents/hub_install.py"
            [ -f "$hub_py" ] || hub_py="agents/hub_install.py"
            echo -e "${BOLD}Hub-installed Agents${NC}"
            echo ""
            if [ ! -f "$hub_py" ]; then
                echo "  (none)"
            else
                local _agents_json
                _agents_json="$(python3 "$hub_py" list-agents 2>/dev/null)"
                HUB_ITEMS="$_agents_json" python3 - << 'PYEOF'
import json, os
try:
    items = json.loads(os.environ.get("HUB_ITEMS", "") or "[]")
except Exception:
    items = []
if not items:
    print("  (none)")
else:
    for a in items:
        print(f"  {a.get('type',''):24s} {a.get('name','')}  [{a.get('swarm','')}]")
    print(f"\n  Total: {len(items)} installed agent(s)")
PYEOF
            fi
            ;;

        --help|-h|help)
            echo -e "${BOLD}loki agent${NC} - Agent type dispatch (v6.7.0)"
            echo ""
            echo "Usage: loki agent <command> [args]"
            echo ""
            echo "Commands:"
            echo "  list [--swarm NAME]        List all agent types (built-in + installed)"
            echo "  info <type>                Show agent type details"
            echo "  run <type> \"<prompt>\"       Single-shot: run prompt with agent persona"
            echo "  start <type> <prd|prompt>  Full autonomous loop with agent persona"
            echo "  review [type]              Code review with agent persona (default: ops-security)"
            echo "  install <source>           Install a community agent (local/git/url manifest)"
            echo "  installed                  List agents installed from a hub source"
            echo "  help                       Show this help"
            echo ""
            echo "Examples:"
            echo "  loki agent list"
            echo "  loki agent list --swarm engineering"
            echo "  loki agent info eng-frontend"
            echo "  loki agent run eng-frontend \"Create a responsive navbar\""
            echo "  loki agent start eng-backend ./api-prd.md"
            echo "  loki agent review ops-security"
            echo "  loki agent install ./my-agent/manifest.json"
            echo "  loki agent install https://github.com/owner/loki-agent-rust.git"
            echo ""
            echo "Note: 'install' is install-from-source (git/local/url). A hosted"
            echo "central hub registry is future work. Manifests are validated as"
            echo "DATA only; no code from a manifest is ever executed."
            echo ""
            ;;
        *)
            echo -e "${RED}Unknown agent command: $subcommand${NC}"
            echo "Run 'loki agent help' for usage."
            return 1
            ;;
    esac
}

# Project onboarding - analyze repo and generate CLAUDE.md (v6.21.0)
cmd_onboard() {
    local target_path="."
    local depth=2
    local format="markdown"
    local output_path=""
    local use_stdout=false
    local update_mode=false

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --depth)
                depth="${2:-2}"
                shift 2
                ;;
            --format)
                format="${2:-markdown}"
                shift 2
                ;;
            --output)
                output_path="${2:-}"
                shift 2
                ;;
            --stdout)
                use_stdout=true
                shift
                ;;
            --update)
                update_mode=true
                shift
                ;;
            --help|-h)
                echo -e "${BOLD}loki onboard${NC} - Analyze a project and generate CLAUDE.md"
                echo ""
                echo "Usage: loki onboard [path] [options]"
                echo ""
                echo "Arguments:"
                echo "  path              Path to repository (default: current directory)"
                echo ""
                echo "Options:"
                echo "  --depth N         Analysis depth: 1=surface, 2=moderate, 3=deep (default: 2)"
                echo "  --format FORMAT   Output format: markdown, json, yaml (default: markdown)"
                echo "  --output PATH     Custom output file path"
                echo "  --stdout          Print to stdout instead of writing file"
                echo "  --update          Update existing CLAUDE.md with new findings"
                echo "  --help            Show this help"
                echo ""
                echo "Examples:"
                echo "  loki onboard                    # Analyze current directory"
                echo "  loki onboard ~/projects/myapp   # Analyze specific repo"
                echo "  loki onboard --depth 3          # Deep analysis with dependency mapping"
                echo "  loki onboard --format json      # JSON output"
                echo "  loki onboard --stdout            # Print to terminal"
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                echo "Run 'loki onboard --help' for usage."
                return 1
                ;;
            *)
                target_path="$1"
                shift
                ;;
        esac
    done

    # Validate target path
    if [ ! -d "$target_path" ]; then
        log_error "Directory not found: $target_path"
        return 1
    fi

    # Resolve to absolute path
    target_path="$(cd "$target_path" && pwd)"

    # When --stdout, send log messages to stderr to keep stdout clean
    if [ "$use_stdout" = true ]; then
        log_info "Analyzing project at: $target_path" >&2
        log_info "Depth: $depth | Format: $format" >&2
    else
        log_info "Analyzing project at: $target_path"
        log_info "Depth: $depth | Format: $format"
    fi

    # --- Detect project metadata ---
    local project_name
    project_name="$(basename "$target_path")"

    local languages=""
    local frameworks=""
    local build_system=""
    local test_framework=""
    local entry_points=""
    local package_manager=""
    local project_description=""
    local project_version=""

    # Detect languages and config files
    local config_files=""

    if [ -f "$target_path/package.json" ]; then
        config_files="$config_files package.json"
        languages="$languages JavaScript/TypeScript"
        package_manager="npm"
        if [ -f "$target_path/yarn.lock" ]; then
            package_manager="yarn"
        elif [ -f "$target_path/pnpm-lock.yaml" ]; then
            package_manager="pnpm"
        elif [ -f "$target_path/bun.lockb" ]; then
            package_manager="bun"
        fi
        # Extract metadata from package.json
        if command -v python3 &>/dev/null; then
            local pkg_name
            pkg_name=$(python3 -c "
import json, sys
try:
    d = json.load(open('$target_path/package.json'))
    print(d.get('name', ''))
except: pass
" 2>/dev/null || true)
            if [ -n "$pkg_name" ]; then
                project_name="$pkg_name"
            fi
            project_description=$(python3 -c "
import json, sys
try:
    d = json.load(open('$target_path/package.json'))
    print(d.get('description', ''))
except: pass
" 2>/dev/null || true)
            project_version=$(python3 -c "
import json, sys
try:
    d = json.load(open('$target_path/package.json'))
    print(d.get('version', ''))
except: pass
" 2>/dev/null || true)
            entry_points=$(python3 -c "
import json, sys
try:
    d = json.load(open('$target_path/package.json'))
    main = d.get('main', '')
    if main: print(main)
    scripts = d.get('scripts', {})
    if 'start' in scripts: print('scripts.start: ' + scripts['start'])
except: pass
" 2>/dev/null || true)
        fi
        # Detect frameworks from dependencies
        if grep -q '"react"' "$target_path/package.json" 2>/dev/null; then
            frameworks="$frameworks React"
        fi
        if grep -q '"next"' "$target_path/package.json" 2>/dev/null; then
            frameworks="$frameworks Next.js"
        fi
        if grep -q '"vue"' "$target_path/package.json" 2>/dev/null; then
            frameworks="$frameworks Vue"
        fi
        if grep -q '"express"' "$target_path/package.json" 2>/dev/null; then
            frameworks="$frameworks Express"
        fi
        if grep -q '"fastify"' "$target_path/package.json" 2>/dev/null; then
            frameworks="$frameworks Fastify"
        fi
        if grep -q '"svelte"' "$target_path/package.json" 2>/dev/null; then
            frameworks="$frameworks Svelte"
        fi
        # Detect test framework
        if grep -q '"jest"' "$target_path/package.json" 2>/dev/null; then
            test_framework="$test_framework jest"
        fi
        if grep -q '"vitest"' "$target_path/package.json" 2>/dev/null; then
            test_framework="$test_framework vitest"
        fi
        if grep -q '"mocha"' "$target_path/package.json" 2>/dev/null; then
            test_framework="$test_framework mocha"
        fi
        if grep -q '"playwright"' "$target_path/package.json" 2>/dev/null; then
            test_framework="$test_framework playwright"
        fi
    fi

    if [ -f "$target_path/pyproject.toml" ]; then
        config_files="$config_files pyproject.toml"
        languages="$languages Python"
        package_manager="pip/poetry"
        if grep -q "django" "$target_path/pyproject.toml" 2>/dev/null; then
            frameworks="$frameworks Django"
        fi
        if grep -q "flask" "$target_path/pyproject.toml" 2>/dev/null; then
            frameworks="$frameworks Flask"
        fi
        if grep -q "fastapi" "$target_path/pyproject.toml" 2>/dev/null; then
            frameworks="$frameworks FastAPI"
        fi
        if grep -q "pytest" "$target_path/pyproject.toml" 2>/dev/null; then
            test_framework="$test_framework pytest"
        fi
    fi

    if [ -f "$target_path/setup.py" ] || [ -f "$target_path/setup.cfg" ]; then
        config_files="$config_files setup.py"
        languages="$languages Python"
        [ -z "$package_manager" ] && package_manager="pip"
    fi

    if [ -f "$target_path/requirements.txt" ]; then
        config_files="$config_files requirements.txt"
        languages="$languages Python"
        [ -z "$package_manager" ] && package_manager="pip"
    fi

    if [ -f "$target_path/Cargo.toml" ]; then
        config_files="$config_files Cargo.toml"
        languages="$languages Rust"
        package_manager="cargo"
        build_system="cargo"
        test_framework="$test_framework cargo-test"
    fi

    if [ -f "$target_path/go.mod" ]; then
        config_files="$config_files go.mod"
        languages="$languages Go"
        package_manager="go-modules"
        build_system="go"
        test_framework="$test_framework go-test"
    fi

    if [ -f "$target_path/Gemfile" ]; then
        config_files="$config_files Gemfile"
        languages="$languages Ruby"
        package_manager="bundler"
        if grep -q "rails" "$target_path/Gemfile" 2>/dev/null; then
            frameworks="$frameworks Rails"
        fi
        if grep -q "rspec" "$target_path/Gemfile" 2>/dev/null; then
            test_framework="$test_framework rspec"
        fi
    fi

    if [ -f "$target_path/pom.xml" ]; then
        config_files="$config_files pom.xml"
        languages="$languages Java"
        build_system="maven"
        package_manager="maven"
    fi

    if [ -f "$target_path/build.gradle" ] || [ -f "$target_path/build.gradle.kts" ]; then
        config_files="$config_files build.gradle"
        languages="$languages Java/Kotlin"
        build_system="gradle"
        package_manager="gradle"
    fi

    if [ -f "$target_path/Makefile" ]; then
        config_files="$config_files Makefile"
        [ -z "$build_system" ] && build_system="make"
    fi

    if [ -f "$target_path/CMakeLists.txt" ]; then
        config_files="$config_files CMakeLists.txt"
        languages="$languages C/C++"
        build_system="cmake"
    fi

    # Detect shell scripts
    local shell_count=0
    shell_count=$(find "$target_path" -maxdepth 2 -name "*.sh" -type f 2>/dev/null | wc -l | tr -d ' ')
    if [ "$shell_count" -gt 0 ]; then
        languages="$languages Bash"
    fi

    # Deduplicate languages
    languages=$(echo "$languages" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
    frameworks=$(echo "$frameworks" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
    test_framework=$(echo "$test_framework" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')

    # --- Detect CI/CD ---
    local ci_system=""
    if [ -d "$target_path/.github/workflows" ]; then
        ci_system="GitHub Actions"
    fi
    if [ -f "$target_path/.gitlab-ci.yml" ]; then
        ci_system="$ci_system GitLab CI"
    fi
    if [ -f "$target_path/Jenkinsfile" ]; then
        ci_system="$ci_system Jenkins"
    fi
    if [ -f "$target_path/.circleci/config.yml" ]; then
        ci_system="$ci_system CircleCI"
    fi
    if [ -f "$target_path/.travis.yml" ]; then
        ci_system="$ci_system Travis CI"
    fi

    # --- Read README ---
    local readme_content=""
    local readme_file=""
    for f in README.md readme.md README.rst README README.txt; do
        if [ -f "$target_path/$f" ]; then
            readme_file="$f"
            readme_content=$(head -50 "$target_path/$f" 2>/dev/null || true)
            break
        fi
    done

    # Extract first meaningful line from README as description fallback
    if [ -z "$project_description" ] && [ -n "$readme_content" ]; then
        project_description=$(echo "$readme_content" | grep -v '^#' | grep -v '^$' | grep -v '^\[' | grep -v '^!' | head -1 | sed 's/^ *//')
    fi

    # --- Build directory tree ---
    local tree_output=""
    if command -v git &>/dev/null && [ -d "$target_path/.git" ]; then
        # Use git ls-files for accurate tree (respects .gitignore)
        tree_output=$(cd "$target_path" && git ls-files 2>/dev/null | head -200 || true)
    else
        # Fallback: find with common excludions
        tree_output=$(find "$target_path" -maxdepth 4 -type f \
            -not -path '*/node_modules/*' \
            -not -path '*/.git/*' \
            -not -path '*/vendor/*' \
            -not -path '*/__pycache__/*' \
            -not -path '*/dist/*' \
            -not -path '*/build/*' \
            -not -path '*/.next/*' \
            -not -path '*/target/*' \
            2>/dev/null | sed "s|$target_path/||" | sort | head -200)
    fi

    # Categorize files
    local src_files=""
    local test_files=""
    local doc_files=""
    local config_file_list=""
    local ci_files=""
    local other_files=""

    while IFS= read -r file; do
        [ -z "$file" ] && continue
        case "$file" in
            *.test.*|*.spec.*|*_test.*|*_spec.*|tests/*|test/*|__tests__/*|spec/*)
                test_files="$test_files $file"
                ;;
            *.md|*.rst|*.txt|docs/*|doc/*|wiki/*)
                doc_files="$doc_files $file"
                ;;
            .github/*|.gitlab-ci*|.circleci/*|Jenkinsfile|.travis*)
                ci_files="$ci_files $file"
                ;;
            package.json|pyproject.toml|Cargo.toml|go.mod|Gemfile|pom.xml|build.gradle*|Makefile|CMakeLists.txt|*.toml|*.cfg|*.ini|*.yml|*.yaml|Dockerfile*|docker-compose*|.env*|.eslint*|.prettier*|tsconfig*|jest.config*|vitest.config*)
                config_file_list="$config_file_list $file"
                ;;
            *.js|*.ts|*.tsx|*.jsx|*.py|*.rs|*.go|*.rb|*.java|*.kt|*.c|*.cpp|*.h|*.hpp|*.sh|*.swift|*.cs|*.php|*.lua|*.zig|*.el|*.clj)
                src_files="$src_files $file"
                ;;
            *)
                other_files="$other_files $file"
                ;;
        esac
    done <<< "$tree_output"

    local src_count=$(echo "$src_files" | wc -w | tr -d ' ')
    local test_count=$(echo "$test_files" | wc -w | tr -d ' ')
    local doc_count=$(echo "$doc_files" | wc -w | tr -d ' ')

    # --- Depth 2+: Analyze source files ---
    local key_exports=""
    local key_functions=""
    local key_classes=""

    if [ "$depth" -ge 2 ]; then
        if [ "$use_stdout" = true ]; then
            log_info "Depth 2: Scanning source files for exports, functions, classes..." >&2
        else
            log_info "Depth 2: Scanning source files for exports, functions, classes..."
        fi

        for src_file in $src_files; do
            [ ! -f "$target_path/$src_file" ] && continue
            local file_lines
            file_lines=$(wc -l < "$target_path/$src_file" 2>/dev/null | tr -d ' ')

            # Only scan files under 2000 lines for performance
            [ "$file_lines" -gt 2000 ] && continue

            local file_ext="${src_file##*.}"
            case "$file_ext" in
                js|ts|tsx|jsx)
                    # Find exported functions/classes
                    local exports
                    exports=$(grep -n "^export " "$target_path/$src_file" 2>/dev/null | head -10 || true)
                    if [ -n "$exports" ]; then
                        key_exports="$key_exports
$src_file:
$exports"
                    fi
                    ;;
                py)
                    # Find class and function definitions
                    local classes
                    classes=$(grep -n "^class " "$target_path/$src_file" 2>/dev/null | head -5 || true)
                    local funcs
                    funcs=$(grep -n "^def \|^async def " "$target_path/$src_file" 2>/dev/null | head -10 || true)
                    if [ -n "$classes" ]; then
                        key_classes="$key_classes
$src_file:
$classes"
                    fi
                    if [ -n "$funcs" ]; then
                        key_functions="$key_functions
$src_file:
$funcs"
                    fi
                    ;;
                go)
                    local funcs
                    funcs=$(grep -n "^func " "$target_path/$src_file" 2>/dev/null | head -10 || true)
                    if [ -n "$funcs" ]; then
                        key_functions="$key_functions
$src_file:
$funcs"
                    fi
                    ;;
                rs)
                    local funcs
                    funcs=$(grep -n "^pub fn \|^pub async fn " "$target_path/$src_file" 2>/dev/null | head -10 || true)
                    if [ -n "$funcs" ]; then
                        key_functions="$key_functions
$src_file:
$funcs"
                    fi
                    ;;
                sh)
                    local funcs
                    funcs=$(grep -n "^[a-zA-Z_][a-zA-Z_0-9]*() {" "$target_path/$src_file" 2>/dev/null | head -10 || true)
                    if [ -n "$funcs" ]; then
                        key_functions="$key_functions
$src_file:
$funcs"
                    fi
                    ;;
                rb)
                    local classes
                    classes=$(grep -n "^class " "$target_path/$src_file" 2>/dev/null | head -5 || true)
                    local funcs
                    funcs=$(grep -n "^  def " "$target_path/$src_file" 2>/dev/null | head -10 || true)
                    if [ -n "$classes" ]; then
                        key_classes="$key_classes
$src_file:
$classes"
                    fi
                    if [ -n "$funcs" ]; then
                        key_functions="$key_functions
$src_file:
$funcs"
                    fi
                    ;;
                java|kt)
                    local classes
                    classes=$(grep -n "^public class \|^class \|^data class " "$target_path/$src_file" 2>/dev/null | head -5 || true)
                    if [ -n "$classes" ]; then
                        key_classes="$key_classes
$src_file:
$classes"
                    fi
                    ;;
            esac
        done
    fi

    # --- Depth 3: Dependency analysis ---
    local dep_graph=""

    if [ "$depth" -ge 3 ]; then
        if [ "$use_stdout" = true ]; then
            log_info "Depth 3: Analyzing imports and dependencies..." >&2
        else
            log_info "Depth 3: Analyzing imports and dependencies..."
        fi

        for src_file in $src_files; do
            [ ! -f "$target_path/$src_file" ] && continue
            local file_lines
            file_lines=$(wc -l < "$target_path/$src_file" 2>/dev/null | tr -d ' ')
            [ "$file_lines" -gt 2000 ] && continue

            local imports=""
            local file_ext="${src_file##*.}"
            case "$file_ext" in
                js|ts|tsx|jsx)
                    imports=$(grep "^import " "$target_path/$src_file" 2>/dev/null | grep -v "node_modules" | head -15 || true)
                    ;;
                py)
                    imports=$(grep "^import \|^from " "$target_path/$src_file" 2>/dev/null | head -15 || true)
                    ;;
                go)
                    imports=$(sed -n '/^import (/,/^)/p' "$target_path/$src_file" 2>/dev/null | grep -v '^import\|^)' | head -15 || true)
                    ;;
                rs)
                    imports=$(grep "^use " "$target_path/$src_file" 2>/dev/null | head -15 || true)
                    ;;
            esac

            if [ -n "$imports" ]; then
                dep_graph="$dep_graph
$src_file:
$imports"
            fi
        done
    fi

    # --- Detect build/run/test commands ---
    local build_cmd=""
    local run_cmd=""
    local test_cmd=""

    if [ -f "$target_path/package.json" ]; then
        if command -v python3 &>/dev/null; then
            local scripts_json
            scripts_json=$(python3 -c "
import json
try:
    d = json.load(open('$target_path/package.json'))
    s = d.get('scripts', {})
    for k in ['build', 'dev', 'start', 'test', 'lint', 'format', 'check', 'typecheck']:
        if k in s:
            print(f'{k}: {s[k]}')
except: pass
" 2>/dev/null || true)
            if echo "$scripts_json" | grep -q "^build:"; then
                build_cmd="${package_manager:-npm} run build"
            fi
            if echo "$scripts_json" | grep -q "^dev:"; then
                run_cmd="${package_manager:-npm} run dev"
            elif echo "$scripts_json" | grep -q "^start:"; then
                run_cmd="${package_manager:-npm} start"
            fi
            if echo "$scripts_json" | grep -q "^test:"; then
                test_cmd="${package_manager:-npm} test"
            fi
        fi
    fi

    if [ -f "$target_path/Makefile" ]; then
        [ -z "$build_cmd" ] && build_cmd="make"
        if grep -q "^test:" "$target_path/Makefile" 2>/dev/null; then
            [ -z "$test_cmd" ] && test_cmd="make test"
        fi
        if grep -q "^run:" "$target_path/Makefile" 2>/dev/null; then
            [ -z "$run_cmd" ] && run_cmd="make run"
        fi
    fi

    if [ -f "$target_path/Cargo.toml" ]; then
        build_cmd="cargo build"
        run_cmd="cargo run"
        test_cmd="cargo test"
    fi

    if [ -f "$target_path/go.mod" ]; then
        build_cmd="go build ./..."
        run_cmd="go run ."
        test_cmd="go test ./..."
    fi

    if [ -f "$target_path/pyproject.toml" ]; then
        if grep -q '\[tool.pytest' "$target_path/pyproject.toml" 2>/dev/null; then
            test_cmd="pytest"
        fi
        if grep -q '\[tool.poetry' "$target_path/pyproject.toml" 2>/dev/null; then
            build_cmd="poetry build"
            run_cmd="poetry run python -m ${project_name}"
        fi
    fi

    # --- Generate output ---
    local output=""

    if [ "$format" = "json" ]; then
        # JSON output
        output=$(cat <<ENDJSON
{
  "project": {
    "name": "$project_name",
    "description": $(_DESC="$project_description" python3 -c "import json, os; print(json.dumps(os.environ.get('_DESC','')))" 2>/dev/null || echo "\"$project_description\""),
    "version": "$project_version",
    "path": "$target_path"
  },
  "languages": "$(echo $languages | sed 's/  */ /g')",
  "frameworks": "$(echo $frameworks | sed 's/  */ /g')",
  "build_system": "$build_system",
  "package_manager": "$package_manager",
  "test_framework": "$(echo $test_framework | sed 's/  */ /g')",
  "ci": "$(echo $ci_system | sed 's/  */ /g')",
  "files": {
    "source": $src_count,
    "test": $test_count,
    "docs": $doc_count
  },
  "commands": {
    "build": "$build_cmd",
    "run": "$run_cmd",
    "test": "$test_cmd"
  },
  "depth": $depth
}
ENDJSON
)
    elif [ "$format" = "yaml" ]; then
        # YAML output
        output=$(cat <<ENDYAML
project:
  name: $project_name
  description: "$project_description"
  version: "$project_version"
  path: $target_path
languages: $languages
frameworks: $frameworks
build_system: $build_system
package_manager: $package_manager
test_framework: $(echo $test_framework | sed 's/  */ /g')
ci: $(echo $ci_system | sed 's/  */ /g')
files:
  source: $src_count
  test: $test_count
  docs: $doc_count
commands:
  build: "$build_cmd"
  run: "$run_cmd"
  test: "$test_cmd"
depth: $depth
ENDYAML
)
    else
        # Markdown output (CLAUDE.md format)
        output="# $project_name"
        [ -n "$project_description" ] && output="$output

$project_description"
        [ -n "$project_version" ] && output="$output

Version: $project_version"

        output="$output

## Overview

| Property | Value |
|----------|-------|
| Languages | ${languages:-N/A} |
| Frameworks | ${frameworks:-N/A} |
| Build System | ${build_system:-N/A} |
| Package Manager | ${package_manager:-N/A} |
| Test Framework | ${test_framework:-N/A} |
| CI/CD | ${ci_system:-N/A} |"

        # Commands section
        if [ -n "$build_cmd" ] || [ -n "$run_cmd" ] || [ -n "$test_cmd" ]; then
            output="$output

## Commands

\`\`\`bash"
            [ -n "$build_cmd" ] && output="$output
# Build
$build_cmd"
            [ -n "$run_cmd" ] && output="$output

# Run
$run_cmd"
            [ -n "$test_cmd" ] && output="$output

# Test
$test_cmd"
            output="$output
\`\`\`"
        fi

        # Project structure
        output="$output

## Project Structure

Files: $src_count source, $test_count test, $doc_count docs"

        # Show directory structure (top-level)
        local top_dirs
        top_dirs=$(echo "$tree_output" | sed 's|/.*||' | sort -u | head -30)
        if [ -n "$top_dirs" ]; then
            output="$output

\`\`\`"
            while IFS= read -r dir; do
                [ -z "$dir" ] && continue
                if [ -d "$target_path/$dir" ]; then
                    # Count files in directory
                    local dir_count
                    dir_count=$(echo "$tree_output" | grep "^${dir}/" | wc -l | tr -d ' ')
                    output="$output
$dir/    ($dir_count files)"
                else
                    output="$output
$dir"
                fi
            done <<< "$top_dirs"
            output="$output
\`\`\`"
        fi

        # Key files
        if [ -n "$config_file_list" ]; then
            output="$output

## Key Files
"
            for cf in $config_file_list; do
                output="$output
- \`$cf\`"
            done
        fi

        if [ -n "$entry_points" ]; then
            output="$output

## Entry Points

\`\`\`
$entry_points
\`\`\`"
        fi

        # Depth 2+: exports, functions, classes
        if [ "$depth" -ge 2 ]; then
            if [ -n "$key_classes" ]; then
                output="$output

## Key Classes
\`\`\`
$key_classes
\`\`\`"
            fi

            if [ -n "$key_functions" ]; then
                output="$output

## Key Functions
\`\`\`
$key_functions
\`\`\`"
            fi

            if [ -n "$key_exports" ]; then
                output="$output

## Exports
\`\`\`
$key_exports
\`\`\`"
            fi
        fi

        # Depth 3: dependency graph
        if [ "$depth" -ge 3 ] && [ -n "$dep_graph" ]; then
            output="$output

## Dependency Graph (Imports)
\`\`\`
$dep_graph
\`\`\`"
        fi

        # CI/CD section
        if [ -n "$ci_system" ] && [ -n "$ci_files" ]; then
            output="$output

## CI/CD ($ci_system)
"
            for cf in $ci_files; do
                output="$output
- \`$cf\`"
            done
        fi

        # Architecture notes from README
        if [ -n "$readme_file" ]; then
            output="$output

## Documentation

See \`$readme_file\` for project documentation."
        fi

        output="$output

---
Generated by loki onboard (depth $depth) on $(date +%Y-%m-%d)"
    fi

    # --- Output ---
    if [ "$use_stdout" = true ]; then
        echo "$output"
        return 0
    fi

    # Determine output path
    if [ -z "$output_path" ]; then
        output_path="$target_path/.claude/CLAUDE.md"
    fi

    # Handle update mode
    if [ "$update_mode" = true ] && [ -f "$output_path" ]; then
        local timestamp
        timestamp=$(date +%Y-%m-%d)
        local update_marker="## Updated: $timestamp"
        # Append new findings after a separator
        {
            cat "$output_path"
            echo ""
            echo "---"
            echo ""
            echo "$update_marker"
            echo ""
            echo "$output"
        } > "${output_path}.tmp"
        mv "${output_path}.tmp" "$output_path"
        log_info "Updated: $output_path"
        return 0
    fi

    # Create directory and write
    local output_dir
    output_dir=$(dirname "$output_path")
    mkdir -p "$output_dir"

    echo "$output" > "$output_path"
    log_info "Generated: $output_path"
    log_info "Project: $project_name | $src_count source files | $test_count tests | $doc_count docs"

    if [ -n "$languages" ]; then
        log_info "Languages: $languages"
    fi
    if [ -n "$frameworks" ]; then
        log_info "Frameworks: $frameworks"
    fi
}


# Analyze any codebase and explain its architecture in plain English (v6.31.0)
cmd_explain() {
    local target_path="."
    set +e
    local output_json=false
    local output_brief=false
    local output_save=false

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --json) output_json=true; require_jq || return 1; shift ;;
            --brief) output_brief=true; shift ;;
            --save) output_save=true; shift ;;
            --help|-h)
                echo -e "${BOLD}loki explain${NC} - Analyze any codebase and explain its architecture"
                echo ""
                echo "Usage: loki explain [path] [options]"
                echo ""
                echo "Arguments:"
                echo "  path              Path to repository (default: current directory)"
                echo ""
                echo "Options:"
                echo "  --json            Machine-readable JSON output"
                echo "  --brief           Condensed one-pager version"
                echo "  --save            Write output to EXPLAIN.md in the analyzed directory"
                echo "  --help            Show this help"
                echo ""
                echo "Examples:"
                echo "  loki explain                       # Analyze current directory"
                echo "  loki explain ~/projects/myapp      # Analyze specific repo"
                echo "  loki explain --brief               # Condensed overview"
                echo "  loki explain --json                # JSON output for tooling"
                echo "  loki explain --save                # Save as EXPLAIN.md"
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                echo "Run 'loki explain --help' for usage."
                return 1
                ;;
            *) target_path="$1"; shift ;;
        esac
    done

    if [ ! -d "$target_path" ]; then
        log_error "Directory not found: $target_path"
        return 1
    fi

    target_path="$(cd "$target_path" && pwd)"
    local project_name
    project_name="$(basename "$target_path")"

    local languages="" frameworks="" build_system="" test_framework=""
    local entry_points="" package_manager="" project_description=""
    local project_version="" detected_patterns="" scripts_info=""

    # --- package.json (JavaScript/TypeScript) ---
    if [ -f "$target_path/package.json" ]; then
        languages="$languages JavaScript/TypeScript"
        package_manager="npm"
        [ -f "$target_path/yarn.lock" ] && package_manager="yarn"
        [ -f "$target_path/pnpm-lock.yaml" ] && package_manager="pnpm"
        [ -f "$target_path/bun.lockb" ] && package_manager="bun"

        if command -v python3 &>/dev/null; then
            local pkg_meta
            pkg_meta=$(python3 -c "
import json
try:
    d = json.load(open('$target_path/package.json'))
    print(d.get('name', ''))
    print(d.get('description', ''))
    print(d.get('version', ''))
    main = d.get('main', d.get('module', ''))
    print(main)
    s = d.get('scripts', {})
    for k in sorted(s.keys()):
        print(f'script:{k}:{s[k]}')
    for dep in list(d.get('dependencies', {}).keys()):
        print(f'dep:{dep}')
    for dep in list(d.get('devDependencies', {}).keys()):
        print(f'devdep:{dep}')
except: pass
" 2>/dev/null || true)
            local pkg_name pkg_desc pkg_ver pkg_main
            pkg_name=$(echo "$pkg_meta" | sed -n '1p')
            pkg_desc=$(echo "$pkg_meta" | sed -n '2p')
            pkg_ver=$(echo "$pkg_meta" | sed -n '3p')
            pkg_main=$(echo "$pkg_meta" | sed -n '4p')
            [ -n "$pkg_name" ] && project_name="$pkg_name"
            [ -n "$pkg_desc" ] && project_description="$pkg_desc"
            [ -n "$pkg_ver" ] && project_version="$pkg_ver"
            [ -n "$pkg_main" ] && entry_points="$pkg_main"
            scripts_info=$(echo "$pkg_meta" | grep '^script:' | sed 's/^script://' || true)
            local deps_list devdeps_list all_deps
            deps_list=$(echo "$pkg_meta" | grep '^dep:' | sed 's/^dep://' || true)
            devdeps_list=$(echo "$pkg_meta" | grep '^devdep:' | sed 's/^devdep://' || true)
            all_deps="$deps_list
$devdeps_list"
            echo "$deps_list" | grep -q '^react$' && frameworks="$frameworks React" || true
            echo "$deps_list" | grep -q '^next$' && frameworks="$frameworks Next.js" || true
            echo "$deps_list" | grep -q '^vue$' && frameworks="$frameworks Vue" || true
            echo "$deps_list" | grep -q '^express$' && frameworks="$frameworks Express" || true
            echo "$deps_list" | grep -q '^fastify$' && frameworks="$frameworks Fastify" || true
            echo "$deps_list" | grep -q '^svelte$' && frameworks="$frameworks Svelte" || true
            echo "$deps_list" | grep -q '^@angular/core$' && frameworks="$frameworks Angular" || true
            echo "$deps_list" | grep -q '^hono$' && frameworks="$frameworks Hono" || true
            echo "$deps_list" | grep -q '^astro$' && frameworks="$frameworks Astro" || true
            echo "$deps_list" | grep -q '^nuxt$' && frameworks="$frameworks Nuxt" || true
            echo "$deps_list" | grep -q '^remix$' && frameworks="$frameworks Remix" || true
            echo "$deps_list" | grep -q '^electron$' && frameworks="$frameworks Electron" || true
            echo "$all_deps" | grep -q '^tailwindcss$' && frameworks="$frameworks Tailwind" || true
            echo "$all_deps" | grep -q '^jest$' && test_framework="$test_framework jest" || true
            echo "$all_deps" | grep -q '^vitest$' && test_framework="$test_framework vitest" || true
            echo "$all_deps" | grep -q '^mocha$' && test_framework="$test_framework mocha" || true
            echo "$all_deps" | grep -q '^@playwright/test$' && test_framework="$test_framework playwright" || true
            echo "$all_deps" | grep -q '^cypress$' && test_framework="$test_framework cypress" || true
            echo "$all_deps" | grep -q '^webpack$' && build_system="webpack" || true
            echo "$all_deps" | grep -q '^vite$' && build_system="vite" || true
            echo "$all_deps" | grep -q '^esbuild$' && build_system="esbuild" || true
            echo "$all_deps" | grep -q '^rollup$' && build_system="rollup" || true
            echo "$all_deps" | grep -q '^turbo$' && build_system="turborepo" || true
            echo "$all_deps" | grep -q '^prisma$' && detected_patterns="$detected_patterns ORM(Prisma)" || true
            echo "$all_deps" | grep -q '^drizzle-orm$' && detected_patterns="$detected_patterns ORM(Drizzle)" || true
            echo "$all_deps" | grep -q '^@trpc/server$' && detected_patterns="$detected_patterns tRPC" || true
            echo "$all_deps" | grep -q '^graphql$' && detected_patterns="$detected_patterns GraphQL" || true
            echo "$all_deps" | grep -q '^socket.io$' && detected_patterns="$detected_patterns WebSocket" || true
            echo "$all_deps" | grep -q '^ws$' && detected_patterns="$detected_patterns WebSocket" || true
            echo "$all_deps" | grep -q '^@nestjs/core$' && frameworks="$frameworks NestJS" || true
            echo "$all_deps" | grep -q '^redis$\|^ioredis$' && detected_patterns="$detected_patterns Redis" || true
            echo "$all_deps" | grep -q '^mongoose$' && detected_patterns="$detected_patterns MongoDB" || true
            echo "$all_deps" | grep -q '^pg$' && detected_patterns="$detected_patterns PostgreSQL" || true
            echo "$all_deps" | grep -q '^stripe$' && detected_patterns="$detected_patterns Stripe" || true
        fi
    fi

    # --- Python ---
    if [ -f "$target_path/pyproject.toml" ]; then
        languages="$languages Python"
        package_manager="pip"
        grep -q '\[tool.poetry' "$target_path/pyproject.toml" 2>/dev/null && package_manager="poetry" || true
        grep -q "django" "$target_path/pyproject.toml" 2>/dev/null && frameworks="$frameworks Django" || true
        grep -q "flask" "$target_path/pyproject.toml" 2>/dev/null && frameworks="$frameworks Flask" || true
        grep -q "fastapi" "$target_path/pyproject.toml" 2>/dev/null && frameworks="$frameworks FastAPI" || true
        grep -q "sqlalchemy" "$target_path/pyproject.toml" 2>/dev/null && detected_patterns="$detected_patterns ORM(SQLAlchemy)" || true
        grep -q "pytest" "$target_path/pyproject.toml" 2>/dev/null && test_framework="$test_framework pytest" || true
        [ -z "$project_description" ] && project_description=$(grep '^description' "$target_path/pyproject.toml" 2>/dev/null | head -1 | sed 's/^description *= *"//;s/"$//' || true)
        [ -z "$project_version" ] && project_version=$(grep '^version' "$target_path/pyproject.toml" 2>/dev/null | head -1 | sed 's/^version *= *"//;s/"$//' || true)
    elif [ -f "$target_path/requirements.txt" ] || [ -f "$target_path/setup.py" ]; then
        languages="$languages Python"
        [ -z "$package_manager" ] && package_manager="pip"
    fi

    # --- Go ---
    if [ -f "$target_path/go.mod" ]; then
        languages="$languages Go"
        package_manager="go-modules"; build_system="go"; test_framework="$test_framework go-test"
        grep -q "gin-gonic" "$target_path/go.mod" 2>/dev/null && frameworks="$frameworks Gin" || true
        grep -q "grpc" "$target_path/go.mod" 2>/dev/null && detected_patterns="$detected_patterns gRPC" || true
    fi

    # --- Rust ---
    if [ -f "$target_path/Cargo.toml" ]; then
        languages="$languages Rust"
        package_manager="cargo"; build_system="cargo"; test_framework="$test_framework cargo-test"
        grep -q "actix" "$target_path/Cargo.toml" 2>/dev/null && frameworks="$frameworks Actix" || true
        grep -q "axum" "$target_path/Cargo.toml" 2>/dev/null && frameworks="$frameworks Axum" || true
        grep -q "tokio" "$target_path/Cargo.toml" 2>/dev/null && detected_patterns="$detected_patterns Async(Tokio)" || true
    fi

    # --- Ruby ---
    if [ -f "$target_path/Gemfile" ]; then
        languages="$languages Ruby"; package_manager="bundler"
        grep -q "rails" "$target_path/Gemfile" 2>/dev/null && frameworks="$frameworks Rails" || true
        grep -q "rspec" "$target_path/Gemfile" 2>/dev/null && test_framework="$test_framework rspec" || true
    fi

    # --- Java/Kotlin ---
    if [ -f "$target_path/pom.xml" ]; then languages="$languages Java" && build_system="maven" && package_manager="maven"; fi
    if [ -f "$target_path/build.gradle" ] || [ -f "$target_path/build.gradle.kts" ]; then languages="$languages Java/Kotlin"; build_system="gradle"; fi

    # --- C/C++ ---
    if [ -f "$target_path/CMakeLists.txt" ]; then languages="$languages C/C++" && build_system="cmake"; fi
    if [ -f "$target_path/Makefile" ]; then [ -z "$build_system" ] && build_system="make"; fi

    # --- Shell ---
    local shell_count
    shell_count=$(find "$target_path" -maxdepth 2 -name "*.sh" -type f 2>/dev/null | wc -l | tr -d ' ')
    [ "$shell_count" -gt 3 ] && languages="$languages Bash"

    # --- Docker / CI ---
    local has_docker=false
    if [ -f "$target_path/Dockerfile" ]; then has_docker=true && detected_patterns="$detected_patterns Docker"; fi
    [ -f "$target_path/docker-compose.yml" ] || [ -f "$target_path/docker-compose.yaml" ] && detected_patterns="$detected_patterns DockerCompose"

    local ci_system=""
    [ -d "$target_path/.github/workflows" ] && ci_system="GitHub Actions"
    if [ -f "$target_path/.gitlab-ci.yml" ]; then ci_system="$ci_system GitLab CI"; fi

    # --- Monorepo ---
    local is_monorepo=false
    if [ -f "$target_path/lerna.json" ]; then is_monorepo=true && detected_patterns="$detected_patterns Monorepo(Lerna)"; fi
    if [ -f "$target_path/turbo.json" ]; then is_monorepo=true && detected_patterns="$detected_patterns Monorepo(Turborepo)"; fi
    if [ -f "$target_path/nx.json" ]; then is_monorepo=true && detected_patterns="$detected_patterns Monorepo(Nx)"; fi
    if [ -f "$target_path/pnpm-workspace.yaml" ]; then is_monorepo=true && detected_patterns="$detected_patterns Monorepo(pnpm)"; fi
    if [ -f "$target_path/package.json" ] && grep -q '"workspaces"' "$target_path/package.json" 2>/dev/null; then
        is_monorepo=true
        echo "$detected_patterns" | grep -q "Monorepo" || detected_patterns="$detected_patterns Monorepo(npm)"
    fi

    # Deduplicate
    languages=$(echo "$languages" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
    frameworks=$(echo "$frameworks" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
    test_framework=$(echo "$test_framework" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
    detected_patterns=$(echo "$detected_patterns" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')

    # --- README ---
    local readme_content="" readme_file=""
    for f in README.md readme.md README.rst README README.txt; do
        if [ -f "$target_path/$f" ]; then
            readme_file="$f"
            readme_content=$(head -80 "$target_path/$f" 2>/dev/null || true)
            break
        fi
    done
    if [ -z "$project_description" ] && [ -n "$readme_content" ]; then
        project_description=$(echo "$readme_content" | grep -v '^#' | grep -v '^$' | grep -v '^\[' | grep -v '^!' | grep -v '^---' | head -3 | sed 's/^ *//' | tr '\n' ' ' | sed 's/  */ /g;s/^ *//;s/ *$//')
    fi

    # --- File counts ---
    local tree_output=""
    if command -v git &>/dev/null && [ -d "$target_path/.git" ]; then
        tree_output=$(cd "$target_path" && git ls-files 2>/dev/null | head -500 || true)
    else
        tree_output=$(find "$target_path" -maxdepth 4 -type f \
            -not -path '*/node_modules/*' -not -path '*/.git/*' \
            -not -path '*/vendor/*' -not -path '*/__pycache__/*' \
            -not -path '*/dist/*' -not -path '*/build/*' \
            -not -path '*/.next/*' -not -path '*/target/*' \
            2>/dev/null | sed "s|$target_path/||" | sort | head -500)
    fi
    local total_files src_count test_count doc_count config_count
    total_files=$(echo "$tree_output" | { grep -c . || true; })
    src_count=$(echo "$tree_output" | { grep -cE '\.(js|ts|tsx|jsx|py|rs|go|rb|java|kt|c|cpp|h|hpp|sh|swift|cs|php)$' || true; })
    test_count=$(echo "$tree_output" | { grep -cE '(\.test\.|\.spec\.|_test\.|_spec\.|tests/|test/|__tests__/)' || true; })
    doc_count=$(echo "$tree_output" | { grep -cE '\.(md|rst|txt)$' || true; })
    config_count=$(echo "$tree_output" | { grep -cE '\.(json|yaml|yml|toml|cfg|ini|env)$|Dockerfile|Makefile' || true; })

    local top_dirs
    top_dirs=$(echo "$tree_output" | sed 's|/.*||' | sort | uniq -c | sort -rn | head -20)

    # Detect patterns from directory structure
    echo "$tree_output" | grep -qE '^(api|routes|controllers)/' && detected_patterns="$detected_patterns REST"
    echo "$tree_output" | grep -qE '^(pages|app)/' && detected_patterns="$detected_patterns FileRouting"
    echo "$tree_output" | grep -q '^components/' && detected_patterns="$detected_patterns ComponentBased" || true
    echo "$tree_output" | grep -qE '(middleware|middlewares)/' && detected_patterns="$detected_patterns Middleware"
    echo "$tree_output" | grep -qE '(events|listeners|handlers)/' && detected_patterns="$detected_patterns EventDriven"
    echo "$tree_output" | grep -qE '(models|entities|schemas)/' && detected_patterns="$detected_patterns MVC"
    echo "$tree_output" | grep -qE '(services|service)/' && detected_patterns="$detected_patterns ServiceLayer"
    detected_patterns=$(echo "$detected_patterns" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')

    # --- Detect commands ---
    local build_cmd="" run_cmd="" test_cmd="" lint_cmd=""
    if [ -n "$scripts_info" ]; then
        local has_build has_dev has_start has_test has_lint
        has_build=$(echo "$scripts_info" | grep '^build:' | head -1)
        has_dev=$(echo "$scripts_info" | grep '^dev:' | head -1)
        has_start=$(echo "$scripts_info" | grep '^start:' | head -1)
        has_test=$(echo "$scripts_info" | grep '^test:' | head -1)
        has_lint=$(echo "$scripts_info" | grep '^lint:' | head -1)
        [ -n "$has_build" ] && build_cmd="${package_manager:-npm} run build"
        [ -n "$has_dev" ] && run_cmd="${package_manager:-npm} run dev"
        [ -z "$run_cmd" ] && [ -n "$has_start" ] && run_cmd="${package_manager:-npm} start"
        [ -n "$has_test" ] && test_cmd="${package_manager:-npm} test"
        [ -n "$has_lint" ] && lint_cmd="${package_manager:-npm} run lint"
    fi
    if [ -f "$target_path/Cargo.toml" ]; then build_cmd="cargo build" && run_cmd="cargo run" && test_cmd="cargo test"; fi
    if [ -f "$target_path/go.mod" ]; then build_cmd="go build ./..." && run_cmd="go run ." && test_cmd="go test ./..."; fi
    if [ -f "$target_path/Makefile" ]; then
        [ -z "$build_cmd" ] && build_cmd="make"
        grep -q '^test:' "$target_path/Makefile" 2>/dev/null && [ -z "$test_cmd" ] && test_cmd="make test" || true
    fi
    if [ -f "$target_path/pyproject.toml" ]; then grep -q '\[tool.pytest' "$target_path/pyproject.toml" 2>/dev/null && [ -z "$test_cmd" ] && test_cmd="pytest" || true; fi

    # --- Key entry point files ---
    local major_files=""
    for candidate in \
        "src/index.ts" "src/index.js" "src/main.ts" "src/main.js" "src/app.ts" "src/app.js" \
        "index.ts" "index.js" "main.ts" "main.js" "app.ts" "app.js" "server.ts" "server.js" \
        "src/App.tsx" "src/App.jsx" "pages/index.tsx" "app/page.tsx" \
        "main.py" "app.py" "manage.py" "src/main.py" "__main__.py" \
        "main.go" "cmd/main.go" "src/main.rs" "src/lib.rs"; do
        echo "$tree_output" | grep -q "^${candidate}$" && major_files="$major_files $candidate"
    done
    major_files=$(echo "$major_files" | sed 's/^ *//')

    # --- JSON output ---
    if [ "$output_json" = true ]; then
        python3 -c "
import json
data = {
    'project': {'name': '$project_name', 'description': '''$(echo "$project_description" | sed "s/'/\\\\'/g")''', 'version': '$project_version', 'path': '$target_path'},
    'stack': {'languages': '${languages}'.split() if '${languages}'.strip() else [], 'frameworks': '${frameworks}'.split() if '${frameworks}'.strip() else [], 'build_system': '$build_system' or None, 'package_manager': '$package_manager' or None, 'test_framework': '${test_framework}'.split() if '${test_framework}'.strip() else [], 'ci': '${ci_system}'.strip() or None},
    'patterns': '${detected_patterns}'.split() if '${detected_patterns}'.strip() else [],
    'files': {'total': $total_files, 'source': $src_count, 'test': $test_count, 'docs': $doc_count, 'config': $config_count},
    'commands': {'build': '${build_cmd}' or None, 'run': '${run_cmd}' or None, 'test': '${test_cmd}' or None, 'lint': '${lint_cmd}' or None},
    'entry_points': '${major_files}'.split() if '${major_files}'.strip() else [],
    'monorepo': $( [ "$is_monorepo" = true ] && echo "True" || echo "False" ),
    'has_docker': $( [ "$has_docker" = true ] && echo "True" || echo "False" )
}
print(json.dumps(data, indent=2))
" 2>/dev/null || echo '{"error": "JSON generation failed"}'
        return 0
    fi

    # --- Text output ---
    local output=""
    local sep="================================================================================"
    local sep2="--------------------------------------------------------------------------------"

    if [ "$output_brief" = true ]; then
        output="${BOLD}$project_name${NC}"
        [ -n "$project_version" ] && output="$output (v$project_version)"
        output="$output
$sep2"
        [ -n "$project_description" ] && output="$output
$project_description
"
        output="$output
${BOLD}Stack:${NC} ${languages:-unknown}"
        [ -n "$frameworks" ] && output="$output + $frameworks"
        [ -n "$build_system" ] && output="$output | Build: $build_system"
        [ -n "$package_manager" ] && output="$output | Pkg: $package_manager"
        output="$output
${BOLD}Scope:${NC} $total_files files ($src_count source, $test_count test, $doc_count docs)"
        [ -n "$detected_patterns" ] && output="$output
${BOLD}Patterns:${NC} $(echo "$detected_patterns" | tr ' ' ', ')"
        [ -n "$ci_system" ] && output="$output
${BOLD}CI/CD:${NC} $(echo "$ci_system" | sed 's/^ *//')"
        [ -n "$test_framework" ] && output="$output
${BOLD}Testing:${NC} $test_framework"
        if [ -n "$run_cmd" ] || [ -n "$build_cmd" ] || [ -n "$test_cmd" ]; then
            output="$output
"
            [ -n "$run_cmd" ] && output="$output
${BOLD}Run:${NC}   $run_cmd"
            [ -n "$build_cmd" ] && output="$output
${BOLD}Build:${NC} $build_cmd"
            [ -n "$test_cmd" ] && output="$output
${BOLD}Test:${NC}  $test_cmd"
        fi
        [ -n "$major_files" ] && output="$output

${BOLD}Entry points:${NC} $(echo "$major_files" | tr ' ' ', ')"
        output="$output
$sep2
Generated by loki explain --brief on $(date +%Y-%m-%d)"
    else
        output="$sep
  PROJECT ANALYSIS: $project_name"
        [ -n "$project_version" ] && output="$output (v$project_version)"
        output="$output
$sep

${BOLD}EXECUTIVE SUMMARY${NC}
$sep2
"
        if [ -n "$project_description" ]; then
            output="$output$project_description"
        else
            output="${output}A ${languages:-software} project"
            [ -n "$frameworks" ] && output="$output built with $frameworks"
            [ -z "$readme_file" ] && output="$output. No README found -- description derived from project metadata."
            [ -n "$readme_file" ] && output="$output."
        fi
        output="$output

Scope: $total_files tracked files ($src_count source, $test_count test, $doc_count docs, $config_count config).

${BOLD}ARCHITECTURE OVERVIEW${NC}
$sep2
"
        if [ -n "$major_files" ]; then
            output="${output}Entry points:"
            for ef in $major_files; do
                output="$output
  - $ef"
            done
            output="$output
"
        fi
        output="${output}
Directory structure (by file count):"
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            local dcount dname
            dcount=$(echo "$line" | awk '{print $1}')
            dname=$(echo "$line" | awk '{print $2}')
            if [ -d "$target_path/$dname" ]; then
                output="$output
  $dname/  ($dcount files)"
            else
                output="$output
  $dname"
            fi
        done <<< "$top_dirs"
        [ "$is_monorepo" = true ] && output="$output

This is a monorepo project."
        output="$output

${BOLD}TECHNOLOGY STACK${NC}
$sep2
Languages:       ${languages:-N/A}
Frameworks:      ${frameworks:-N/A}
Build system:    ${build_system:-N/A}
Package manager: ${package_manager:-N/A}
Test framework:  ${test_framework:-N/A}
CI/CD:           ${ci_system:-N/A}
"
        if [ -n "$detected_patterns" ]; then
            output="$output
${BOLD}KEY PATTERNS${NC}
$sep2"
            for pattern in $detected_patterns; do
                case "$pattern" in
                    REST) output="$output
  - REST API (route/controller directories detected)" ;;
                    GraphQL) output="$output
  - GraphQL API" ;;
                    gRPC) output="$output
  - gRPC (protocol buffer based RPC)" ;;
                    tRPC) output="$output
  - tRPC (end-to-end typesafe APIs)" ;;
                    WebSocket) output="$output
  - WebSocket (real-time communication)" ;;
                    EventDriven) output="$output
  - Event-driven architecture" ;;
                    MVC) output="$output
  - MVC pattern (model/entity directories detected)" ;;
                    ServiceLayer) output="$output
  - Service layer pattern" ;;
                    ComponentBased) output="$output
  - Component-based UI architecture" ;;
                    FileRouting) output="$output
  - File-based routing (pages/app directory)" ;;
                    Middleware) output="$output
  - Middleware pipeline" ;;
                    Docker) output="$output
  - Docker containerized" ;;
                    DockerCompose) output="$output
  - Docker Compose multi-service" ;;
                    Monorepo*) output="$output
  - $pattern" ;;
                    *) output="$output
  - $pattern" ;;
                esac
            done
            output="$output
"
        fi
        output="$output
${BOLD}GETTING STARTED${NC}
$sep2
"
        if [ -n "$package_manager" ]; then
            case "$package_manager" in
                npm) output="${output}Install dependencies:  npm install
" ;;
                yarn) output="${output}Install dependencies:  yarn install
" ;;
                pnpm) output="${output}Install dependencies:  pnpm install
" ;;
                pip|poetry) output="${output}Install dependencies:  ${package_manager} install
" ;;
                cargo) output="${output}Build:  cargo build
" ;;
                go-modules) output="${output}Download dependencies:  go mod download
" ;;
                bundler) output="${output}Install dependencies:  bundle install
" ;;
                *) output="${output}Package manager: $package_manager
" ;;
            esac
        fi
        [ -n "$run_cmd" ] && output="${output}
Run (development):  $run_cmd
"
        [ -n "$build_cmd" ] && output="${output}
Build:  $build_cmd
"
        [ -n "$test_cmd" ] && output="${output}
Test:  $test_cmd
"
        [ -n "$lint_cmd" ] && output="${output}
Lint:  $lint_cmd
"
        output="$output
${BOLD}FOR NEW CONTRIBUTORS${NC}
$sep2
"
        if [ -n "$readme_file" ]; then
            output="${output}Start by reading $readme_file for project-specific context.
"
        else
            output="${output}Note: This project has no README. Consider adding one.
"
        fi
        if [ -n "$major_files" ]; then
            output="${output}
Key files to understand first:"
            for ef in $major_files; do
                output="$output
  - $ef"
            done
            output="$output
"
        fi
        [ "$test_count" -gt 0 ] && [ -n "$test_cmd" ] && output="${output}
Tests: $test_count test files found. Run '$test_cmd' to verify your changes.
"
        output="$output
$sep
Generated by loki explain on $(date +%Y-%m-%d)"
    fi

    # --- Output ---
    if [ "$output_save" = true ]; then
        local clean_output
        clean_output=$(echo -e "$output" | sed 's/\x1b\[[0-9;]*m//g')
        echo "$clean_output" > "$target_path/EXPLAIN.md"
        echo -e "${GREEN}Saved to $target_path/EXPLAIN.md${NC}"
    else
        echo -e "$output"
    fi
    set -euo pipefail
}

# Generate, update, check, and manage project documentation (v6.75.0)
cmd_docs() {
    local subcmd="${1:-}"
    shift 2>/dev/null || true

    case "$subcmd" in
        generate) _docs_generate "$@" ;;
        update)   _docs_update "$@" ;;
        check)    _docs_check "$@" ;;
        status)   _docs_status "$@" ;;
        --help|-h|help|"")
            echo -e "${BOLD}loki docs${NC} - Generate, update, check project documentation"
            echo ""
            echo "Usage: loki docs <command> [options]"
            echo ""
            echo "Commands:"
            echo "  generate [path]    Generate full documentation suite in .loki/docs/"
            echo "  update [path]      Incremental update based on git changes since last generation"
            echo "  check [path]       Validate documentation coverage and staleness (exit 0=pass, 1=fail)"
            echo "  status [path]      Show documentation metrics (count, coverage, staleness)"
            echo ""
            echo "Options:"
            echo "  --help, -h         Show this help"
            echo ""
            echo "Generated documentation:"
            echo "  .loki/docs/README.md         Project overview, setup, usage"
            echo "  .loki/docs/ARCHITECTURE.md   System design, data flow, key decisions"
            echo "  .loki/docs/API.md            Public API reference (endpoints, functions, classes)"
            echo "  .loki/docs/SETUP.md          Dev environment setup, dependencies, running locally"
            echo "  .loki/docs/COMPONENTS.md     Per-component/module documentation"
            echo "  .loki/docs/TESTING.md        Test strategy, running tests, coverage"
            echo "  .loki/docs/DECISIONS.md      Architectural Decision Records"
            echo "  .loki/docs/CLAUDE.md         AI agent context file"
            echo "  .loki/docs/docs-manifest.json  Manifest with SHAs and timestamps"
            echo ""
            echo "Examples:"
            echo "  loki docs generate                 # Generate docs for current project"
            echo "  loki docs generate ~/projects/app  # Generate docs for specific project"
            echo "  loki docs update                   # Update only changed components"
            echo "  loki docs check                    # Validate docs (CI-friendly)"
            echo "  loki docs status                   # Show doc metrics"
            return 0
            ;;
        *)
            log_error "Unknown docs command: $subcmd"
            echo "Run 'loki docs --help' for usage."
            return 1
            ;;
    esac
}

# --- Docs helper: scan project and build context ---
_docs_scan_project() {
    local target_path="$1"

    # Build file tree (excluding common noise)
    local tree_output=""
    if command -v git &>/dev/null && [ -d "$target_path/.git" ]; then
        tree_output=$(cd "$target_path" && git ls-files 2>/dev/null | head -500 || true)
    else
        tree_output=$(find "$target_path" -maxdepth 4 -type f \
            -not -path '*/node_modules/*' -not -path '*/.git/*' \
            -not -path '*/vendor/*' -not -path '*/__pycache__/*' \
            -not -path '*/dist/*' -not -path '*/build/*' \
            -not -path '*/.next/*' -not -path '*/target/*' \
            2>/dev/null | sed "s|$target_path/||" | sort | head -500)
    fi
    echo "$tree_output"
}

_docs_build_context() {
    local target_path="$1"
    local tree_output="$2"
    local context=""

    context="PROJECT PATH: $target_path
PROJECT NAME: $(basename "$target_path")
"

    # File tree summary
    local total_files src_count test_count
    total_files=$(echo "$tree_output" | { grep -c . || true; })
    src_count=$(echo "$tree_output" | { grep -cE '\.(js|ts|tsx|jsx|py|rs|go|rb|java|kt|c|cpp|h|hpp|sh|swift|cs|php)$' || true; })
    test_count=$(echo "$tree_output" | { grep -cE '(\.test\.|\.spec\.|_test\.|_spec\.|tests/|test/|__tests__/)' || true; })
    context="${context}
FILES: $total_files total, $src_count source, $test_count test
"

    # Top-level directories
    local top_dirs
    top_dirs=$(echo "$tree_output" | sed 's|/.*||' | sort | uniq -c | sort -rn | head -20)
    context="${context}
DIRECTORY STRUCTURE:
$top_dirs
"

    # File tree (first 200 files)
    context="${context}
FILE TREE:
$(echo "$tree_output" | head -200)
"

    # Key config files - read first 50 lines of each
    for cfg in package.json pyproject.toml Cargo.toml go.mod Gemfile pom.xml requirements.txt Makefile Dockerfile docker-compose.yml; do
        if [ -f "$target_path/$cfg" ]; then
            context="${context}
--- $cfg (first 50 lines) ---
$(head -50 "$target_path/$cfg" 2>/dev/null || true)
"
        fi
    done

    # README if present
    for f in README.md readme.md README.rst README; do
        if [ -f "$target_path/$f" ]; then
            context="${context}
--- $f (first 80 lines) ---
$(head -80 "$target_path/$f" 2>/dev/null || true)
"
            break
        fi
    done

    # Key entry point files (first 50 lines)
    local entry_count=0
    for candidate in \
        "src/index.ts" "src/index.js" "src/main.ts" "src/main.js" "src/app.ts" "src/app.js" \
        "index.ts" "index.js" "main.ts" "main.js" "app.ts" "app.js" "server.ts" "server.js" \
        "src/App.tsx" "src/App.jsx" "pages/index.tsx" "app/page.tsx" \
        "main.py" "app.py" "manage.py" "src/main.py" "__main__.py" \
        "main.go" "cmd/main.go" "src/main.rs" "src/lib.rs"; do
        if [ -f "$target_path/$candidate" ]; then
            context="${context}
--- $candidate (first 50 lines) ---
$(head -50 "$target_path/$candidate" 2>/dev/null || true)
"
            ((++entry_count))
            [ "$entry_count" -ge 5 ] && break
        fi
    done

    echo "$context"
}

# --- Docs helper: invoke AI provider and capture output ---
_docs_invoke_provider() {
    local prompt="$1"
    local provider="${LOKI_PROVIDER:-claude}"
    if [ -f ".loki/state/provider" ]; then
        provider=$(cat ".loki/state/provider" 2>/dev/null)
    fi

    local result=""
    local exit_code=0
    local timeout_cmd="timeout"
    command -v gtimeout &>/dev/null && timeout_cmd="gtimeout"
    command -v $timeout_cmd &>/dev/null || timeout_cmd=""
    local t_prefix=""
    [ -n "$timeout_cmd" ] && t_prefix="$timeout_cmd 120"

    case "$provider" in
        claude)
            result=$($t_prefix claude -p "$prompt" 2>/dev/null) || exit_code=$?
            ;;
        codex)
            result=$($t_prefix codex exec --full-auto "$prompt" 2>/dev/null) || exit_code=$?
            ;;
        cline)
            result=$($t_prefix cline -y "$prompt" 2>/dev/null) || exit_code=$?
            ;;
        aider)
            result=$($t_prefix aider --message "$prompt" --yes-always --no-auto-commits < /dev/null 2>/dev/null) || exit_code=$?
            ;;
        *)
            log_error "Unknown provider: $provider"
            return 1
            ;;
    esac

    if [ $exit_code -ne 0 ] && [ -z "$result" ]; then
        log_error "Provider '$provider' failed (exit $exit_code)"
        return 1
    fi

    echo "$result"
}

# --- Docs helper: write manifest ---
_docs_write_manifest() {
    local docs_dir="$1"
    shift
    # Remaining args are file names

    local git_sha=""
    git_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
    local generated_at
    generated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)

    local files_json="{"
    local first=true
    for doc_file in "$@"; do
        local fname
        fname=$(basename "$doc_file")
        if [ -f "$docs_dir/$fname" ]; then
            local file_sha
            file_sha=$(shasum -a 256 "$docs_dir/$fname" 2>/dev/null | awk '{print $1}' || echo "unknown")
            if [ "$first" = true ]; then
                first=false
            else
                files_json="${files_json},"
            fi
            files_json="${files_json}
    \"$fname\": {\"sha256\": \"$file_sha\", \"generated_at\": \"$generated_at\"}"
        fi
    done
    files_json="${files_json}
  }"

    cat > "$docs_dir/docs-manifest.json" <<MANIFEST_EOF
{
  "generated_at": "$generated_at",
  "git_sha": "$git_sha",
  "files": $files_json
}
MANIFEST_EOF
}

# --- loki docs generate ---
_docs_generate() {
    local target_path="."

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo "Usage: loki docs generate [path]"
                echo "Generate full documentation suite in .loki/docs/"
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                return 1
                ;;
            *)
                target_path="$1"
                shift
                ;;
        esac
    done

    if [ ! -d "$target_path" ]; then
        log_error "Directory not found: $target_path"
        return 1
    fi
    target_path="$(cd "$target_path" && pwd)"

    local docs_dir="$target_path/.loki/docs"
    mkdir -p "$docs_dir"

    log_info "Scanning project at: $target_path"

    # Scan project
    local tree_output
    tree_output=$(_docs_scan_project "$target_path")
    local context
    context=$(_docs_build_context "$target_path" "$tree_output")

    local project_name
    project_name="$(basename "$target_path")"

    # Check if provider CLI is available
    local provider="${LOKI_PROVIDER:-claude}"
    if [ -f "$target_path/.loki/state/provider" ]; then
        provider=$(cat "$target_path/.loki/state/provider" 2>/dev/null)
    fi

    local provider_available=true
    if ! command -v "$provider" &>/dev/null; then
        provider_available=false
        log_warn "Provider '$provider' not found. Generating template-based docs instead."
    fi

    # Doc types to generate
    local doc_types="README ARCHITECTURE API SETUP COMPONENTS TESTING DECISIONS CLAUDE"
    local generated_files=""

    for doc_type in $doc_types; do
        local doc_file="${doc_type}.md"
        local prompt=""

        echo -e "  ${CYAN}Generating${NC} $doc_file ..."

        if [ "$provider_available" = true ]; then
            # Build a focused prompt for each doc type
            case "$doc_type" in
                README)
                    prompt="Based on the following project context, generate a comprehensive README.md file. Include: project title, description, features, installation instructions, usage examples, and contribution guidelines. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.

$context"
                    ;;
                ARCHITECTURE)
                    prompt="Based on the following project context, generate an ARCHITECTURE.md file. Include: system overview, high-level design, component diagram (as text), data flow, directory structure explanation, key design decisions, and technology choices. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.

$context"
                    ;;
                API)
                    prompt="Based on the following project context, generate an API.md file. Document all public APIs: REST endpoints, exported functions, classes, and their signatures. Include parameter types, return types, and brief descriptions. If no API is detected, document the main public interfaces. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.

$context"
                    ;;
                SETUP)
                    prompt="Based on the following project context, generate a SETUP.md file. Include: prerequisites, installation steps, environment variables, database setup, running locally, running in Docker (if applicable), and common troubleshooting. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.

$context"
                    ;;
                COMPONENTS)
                    prompt="Based on the following project context, generate a COMPONENTS.md file. Document each major component/module/directory: its purpose, key files, public interface, and dependencies on other components. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.

$context"
                    ;;
                TESTING)
                    prompt="Based on the following project context, generate a TESTING.md file. Include: test strategy, test types (unit, integration, e2e), how to run tests, test configuration, coverage goals, and CI integration. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.

$context"
                    ;;
                DECISIONS)
                    prompt="Based on the following project context, generate a DECISIONS.md file with Architectural Decision Records (ADRs). Infer key decisions from the technology stack, directory structure, and configuration. Each ADR should have: title, status, context, decision, and consequences. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.

$context"
                    ;;
                CLAUDE)
                    prompt="Based on the following project context, generate a CLAUDE.md file for AI agent context. Include: project overview, key commands (build, test, run, lint), project structure with brief descriptions of each directory, coding conventions, important files, and any gotchas. This file helps AI coding assistants understand the project quickly. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.

$context"
                    ;;
            esac

            local result=""
            result=$(_docs_invoke_provider "$prompt" 2>/dev/null) || true

            if [ -n "$result" ]; then
                echo "$result" > "$docs_dir/$doc_file"
                generated_files="$generated_files $doc_file"
            else
                # Fallback to template
                _docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
                generated_files="$generated_files $doc_file"
            fi
        else
            # Template-based generation (no AI provider)
            _docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
            generated_files="$generated_files $doc_file"
        fi

        echo -e "  ${GREEN}[done]${NC} $doc_file"
    done

    # Write manifest
    # shellcheck disable=SC2086
    _docs_write_manifest "$docs_dir" $generated_files

    echo ""
    log_info "Documentation generated in $docs_dir/"
    log_info "Files: $(echo "$generated_files" | wc -w | tr -d ' ') docs + manifest"
    echo -e "  Manifest: ${CYAN}$docs_dir/docs-manifest.json${NC}"
}

# --- Docs helper: generate template-based doc (fallback when no AI provider) ---
_docs_generate_template() {
    local doc_type="$1"
    local target_path="$2"
    local context="$3"
    local project_name
    project_name="$(basename "$target_path")"
    local timestamp
    timestamp=$(date +%Y-%m-%d)

    # Extract basic metadata from context
    local files_line
    files_line=$(echo "$context" | grep '^FILES:' | head -1 || echo "FILES: unknown")

    case "$doc_type" in
        README)
            cat <<EOF
# $project_name

## Overview

Project at \`$target_path\`.

$files_line

## Getting Started

See SETUP.md for installation and development instructions.

## Documentation

- [Architecture](ARCHITECTURE.md)
- [API Reference](API.md)
- [Setup Guide](SETUP.md)
- [Components](COMPONENTS.md)
- [Testing](TESTING.md)
- [Decisions](DECISIONS.md)

---
Generated by loki docs generate on $timestamp
EOF
            ;;
        ARCHITECTURE)
            cat <<EOF
# Architecture

## Overview

This document describes the architecture of $project_name.

## Directory Structure

\`\`\`
$(echo "$context" | sed -n '/^DIRECTORY STRUCTURE:/,/^$/p' | tail -n +2)
\`\`\`

## Key Components

See COMPONENTS.md for detailed component documentation.

---
Generated by loki docs generate on $timestamp
EOF
            ;;
        API)
            cat <<EOF
# API Reference

## Overview

Public API documentation for $project_name.

TODO: Document public endpoints, functions, and classes.

---
Generated by loki docs generate on $timestamp
EOF
            ;;
        SETUP)
            cat <<EOF
# Setup Guide

## Prerequisites

See the project configuration files for required dependencies.

## Installation

Clone the repository and install dependencies.

## Running Locally

Refer to the project's build and run scripts.

---
Generated by loki docs generate on $timestamp
EOF
            ;;
        COMPONENTS)
            cat <<EOF
# Components

## Overview

Component documentation for $project_name.

## Modules

$(echo "$context" | sed -n '/^DIRECTORY STRUCTURE:/,/^$/p' | tail -n +2 | awk '{print "### " $2 "\n\n" $1 " files\n"}')

---
Generated by loki docs generate on $timestamp
EOF
            ;;
        TESTING)
            cat <<EOF
# Testing

## Overview

Test documentation for $project_name.

## Running Tests

Refer to the project's test configuration for instructions.

---
Generated by loki docs generate on $timestamp
EOF
            ;;
        DECISIONS)
            cat <<EOF
# Architectural Decision Records

## Overview

Key architectural decisions for $project_name.

## ADR-001: [Title]

- **Status**: Proposed
- **Context**: [Why this decision was needed]
- **Decision**: [What was decided]
- **Consequences**: [What are the trade-offs]

---
Generated by loki docs generate on $timestamp
EOF
            ;;
        CLAUDE)
            cat <<EOF
# $project_name

## Project Overview

$files_line

## Project Structure

\`\`\`
$(echo "$context" | sed -n '/^FILE TREE:/,/^---/p' | head -50 | tail -n +2)
\`\`\`

## Key Commands

Refer to the project's configuration files for build, test, and run commands.

---
Generated by loki docs generate on $timestamp
EOF
            ;;
    esac
}

# --- loki docs update ---
_docs_update() {
    local target_path="."

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo "Usage: loki docs update [path]"
                echo "Incrementally update docs based on git changes since last generation."
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                return 1
                ;;
            *)
                target_path="$1"
                shift
                ;;
        esac
    done

    if [ ! -d "$target_path" ]; then
        log_error "Directory not found: $target_path"
        return 1
    fi
    target_path="$(cd "$target_path" && pwd)"

    local docs_dir="$target_path/.loki/docs"
    local manifest="$docs_dir/docs-manifest.json"

    if [ ! -f "$manifest" ]; then
        log_warn "No docs-manifest.json found. Running full generation instead."
        _docs_generate "$target_path"
        return $?
    fi

    # Get the SHA from last generation
    local last_sha=""
    if command -v python3 &>/dev/null; then
        last_sha=$(python3 -c "
import json
try:
    d = json.load(open('$manifest'))
    print(d.get('git_sha', ''))
except: pass
" 2>/dev/null || true)
    fi

    if [ -z "$last_sha" ] || [ "$last_sha" = "unknown" ]; then
        log_warn "Cannot determine last generation SHA. Running full generation."
        _docs_generate "$target_path"
        return $?
    fi

    # Check if HEAD is the same as last generation
    local current_sha
    current_sha=$(cd "$target_path" && git rev-parse HEAD 2>/dev/null || echo "unknown")

    if [ "$current_sha" = "$last_sha" ]; then
        log_info "Documentation is up to date (SHA: ${last_sha:0:8})"
        return 0
    fi

    # Get changed files since last generation
    local changed_files=""
    changed_files=$(cd "$target_path" && git diff --name-only "$last_sha"..HEAD 2>/dev/null || true)

    if [ -z "$changed_files" ]; then
        log_info "No file changes detected since last generation."
        return 0
    fi

    local changed_count
    changed_count=$(echo "$changed_files" | wc -l | tr -d ' ')
    log_info "Found $changed_count changed files since last generation (${last_sha:0:8}..${current_sha:0:8})"

    # Determine which doc types need updating based on changed files
    local docs_to_update=""

    # README: always update if any significant files changed
    if echo "$changed_files" | grep -qE '\.(md|txt|json|toml|yaml|yml)$'; then
        docs_to_update="$docs_to_update README"
    fi

    # ARCHITECTURE: update if directory structure or config changed
    if echo "$changed_files" | grep -qE '(package\.json|pyproject\.toml|Cargo\.toml|go\.mod|Dockerfile|docker-compose)'; then
        docs_to_update="$docs_to_update ARCHITECTURE"
    fi

    # API: update if source files changed
    if echo "$changed_files" | grep -qE '\.(ts|js|py|go|rs|java|rb)$'; then
        docs_to_update="$docs_to_update API COMPONENTS"
    fi

    # SETUP: update if config/dependency files changed
    if echo "$changed_files" | grep -qE '(package\.json|requirements\.txt|pyproject\.toml|Cargo\.toml|go\.mod|Dockerfile|\.env)'; then
        docs_to_update="$docs_to_update SETUP"
    fi

    # TESTING: update if test files or test config changed
    if echo "$changed_files" | grep -qE '(test|spec|\.test\.|\.spec\.|jest\.config|vitest\.config|pytest|\.bats)'; then
        docs_to_update="$docs_to_update TESTING"
    fi

    # CLAUDE: always update on any source change
    if echo "$changed_files" | grep -qE '\.(ts|js|py|go|rs|java|rb|sh)$'; then
        docs_to_update="$docs_to_update CLAUDE"
    fi

    # Deduplicate
    docs_to_update=$(echo "$docs_to_update" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')

    if [ -z "$docs_to_update" ]; then
        log_info "No documentation updates needed for the changed files."
        # Still update manifest SHA
        _docs_write_manifest "$docs_dir" $(ls "$docs_dir"/*.md 2>/dev/null | xargs -I{} basename {} || true)
        return 0
    fi

    log_info "Updating docs: $docs_to_update"

    # Re-scan and rebuild context
    local tree_output
    tree_output=$(_docs_scan_project "$target_path")
    local context
    context=$(_docs_build_context "$target_path" "$tree_output")

    # Add changed files context
    context="${context}
RECENTLY CHANGED FILES (since last doc generation):
$changed_files
"

    local provider="${LOKI_PROVIDER:-claude}"
    if [ -f "$target_path/.loki/state/provider" ]; then
        provider=$(cat "$target_path/.loki/state/provider" 2>/dev/null)
    fi

    local provider_available=true
    if ! command -v "$provider" &>/dev/null; then
        provider_available=false
        log_warn "Provider '$provider' not found. Generating template-based docs instead."
    fi

    local updated_files=""
    for doc_type in $docs_to_update; do
        local doc_file="${doc_type}.md"
        echo -e "  ${CYAN}Updating${NC} $doc_file ..."

        if [ "$provider_available" = true ]; then
            local existing_content=""
            [ -f "$docs_dir/$doc_file" ] && existing_content=$(cat "$docs_dir/$doc_file" 2>/dev/null || true)

            local prompt="Based on the following project context, update the ${doc_type}.md documentation. Here are the files that changed since the last generation:

$changed_files

Current ${doc_type}.md content:
$existing_content

Project context:
$context

Generate an updated ${doc_type}.md that reflects the current state of the project. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations."

            local result=""
            result=$(_docs_invoke_provider "$prompt" 2>/dev/null) || true
            if [ -n "$result" ]; then
                echo "$result" > "$docs_dir/$doc_file"
            fi
        else
            _docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
        fi

        updated_files="$updated_files $doc_file"
        echo -e "  ${GREEN}[done]${NC} $doc_file"
    done

    # Update manifest with all existing docs
    local all_doc_files=""
    for f in "$docs_dir"/*.md; do
        [ -f "$f" ] && all_doc_files="$all_doc_files $(basename "$f")"
    done
    # shellcheck disable=SC2086
    _docs_write_manifest "$docs_dir" $all_doc_files

    echo ""
    log_info "Updated $(echo "$updated_files" | wc -w | tr -d ' ') documentation files"
}

# --- loki docs check ---
_docs_check() {
    local target_path="."

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo "Usage: loki docs check [path]"
                echo "Validate documentation coverage and staleness. Exit 0=pass, 1=fail."
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                return 1
                ;;
            *)
                target_path="$1"
                shift
                ;;
        esac
    done

    if [ ! -d "$target_path" ]; then
        log_error "Directory not found: $target_path"
        return 1
    fi
    target_path="$(cd "$target_path" && pwd)"

    local docs_dir="$target_path/.loki/docs"
    local manifest="$docs_dir/docs-manifest.json"
    local pass=true

    echo -e "${BOLD}Documentation Check${NC}"
    echo "----------------"

    # Check 1: docs directory exists
    if [ ! -d "$docs_dir" ]; then
        echo -e "  ${RED}[FAIL]${NC} No .loki/docs/ directory found"
        echo ""
        echo "Run 'loki docs generate' to create documentation."
        return 1
    fi

    # Check 2: README.md exists and is non-empty
    if [ -f "$docs_dir/README.md" ] && [ -s "$docs_dir/README.md" ]; then
        echo -e "  ${GREEN}[PASS]${NC} README.md exists and is non-empty"
    else
        echo -e "  ${RED}[FAIL]${NC} README.md missing or empty"
        pass=false
    fi

    # Check 3: manifest exists
    if [ -f "$manifest" ]; then
        echo -e "  ${GREEN}[PASS]${NC} docs-manifest.json exists"
    else
        echo -e "  ${RED}[FAIL]${NC} docs-manifest.json missing"
        pass=false
    fi

    # Check 4: expected doc files exist
    local expected_docs="README.md ARCHITECTURE.md API.md SETUP.md COMPONENTS.md TESTING.md DECISIONS.md CLAUDE.md"
    local doc_count=0
    local expected_count=0
    for doc in $expected_docs; do
        ((++expected_count))
        if [ -f "$docs_dir/$doc" ] && [ -s "$docs_dir/$doc" ]; then
            ((++doc_count))
        else
            echo -e "  ${YELLOW}[WARN]${NC} Missing or empty: $doc"
        fi
    done
    local coverage_pct=0
    [ "$expected_count" -gt 0 ] && coverage_pct=$(( doc_count * 100 / expected_count ))
    echo -e "  ${BOLD}Coverage:${NC} $doc_count/$expected_count docs ($coverage_pct%)"

    # Check 5: staleness (commits since last doc generation)
    if [ -f "$manifest" ] && command -v python3 &>/dev/null; then
        local last_sha=""
        last_sha=$(python3 -c "
import json
try:
    d = json.load(open('$manifest'))
    print(d.get('git_sha', ''))
except: pass
" 2>/dev/null || true)

        if [ -n "$last_sha" ] && [ "$last_sha" != "unknown" ]; then
            local commits_behind=0
            commits_behind=$(cd "$target_path" && git rev-list --count "$last_sha"..HEAD 2>/dev/null || echo "0")
            if [ "$commits_behind" -gt 10 ]; then
                echo -e "  ${RED}[FAIL]${NC} Docs are $commits_behind commits behind HEAD (threshold: 10)"
                pass=false
            elif [ "$commits_behind" -gt 0 ]; then
                echo -e "  ${YELLOW}[WARN]${NC} Docs are $commits_behind commits behind HEAD"
            else
                echo -e "  ${GREEN}[PASS]${NC} Docs are up to date with HEAD"
            fi
        fi
    fi

    # Check 6: component coverage (check if major dirs have doc entries)
    local tree_output
    tree_output=$(_docs_scan_project "$target_path")
    local major_dirs
    major_dirs=$(echo "$tree_output" | sed 's|/.*||' | sort -u | head -20)
    local dir_count=0
    local documented_dirs=0
    if [ -f "$docs_dir/COMPONENTS.md" ]; then
        local components_content
        components_content=$(cat "$docs_dir/COMPONENTS.md" 2>/dev/null || true)
        while IFS= read -r dir; do
            [ -z "$dir" ] && continue
            [ ! -d "$target_path/$dir" ] && continue
            ((++dir_count))
            if echo "$components_content" | grep -qi "$dir" 2>/dev/null; then
                ((++documented_dirs))
            fi
        done <<< "$major_dirs"
        if [ "$dir_count" -gt 0 ]; then
            local dir_pct=$(( documented_dirs * 100 / dir_count ))
            echo -e "  ${BOLD}Component coverage:${NC} $documented_dirs/$dir_count directories ($dir_pct%)"
        fi
    fi

    echo ""
    if [ "$pass" = true ] && [ "$coverage_pct" -ge 75 ]; then
        echo -e "${GREEN}Result: PASS${NC}"
        return 0
    else
        echo -e "${RED}Result: FAIL${NC}"
        [ "$coverage_pct" -lt 75 ] && echo "  Doc coverage below 75%. Run 'loki docs generate' to create missing docs."
        return 1
    fi
}

# --- loki docs status ---
_docs_status() {
    local target_path="."

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo "Usage: loki docs status [path]"
                echo "Show documentation metrics."
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                return 1
                ;;
            *)
                target_path="$1"
                shift
                ;;
        esac
    done

    if [ ! -d "$target_path" ]; then
        log_error "Directory not found: $target_path"
        return 1
    fi
    target_path="$(cd "$target_path" && pwd)"

    local docs_dir="$target_path/.loki/docs"
    local manifest="$docs_dir/docs-manifest.json"

    echo -e "${BOLD}Documentation Status${NC}"
    echo "===================="
    echo ""

    if [ ! -d "$docs_dir" ]; then
        echo "No documentation generated yet."
        echo "Run 'loki docs generate' to create documentation."
        return 0
    fi

    # Doc count and sizes
    local doc_count=0
    local total_size=0
    echo -e "${BOLD}Documents:${NC}"
    for f in "$docs_dir"/*.md; do
        [ ! -f "$f" ] && continue
        ((++doc_count))
        local fsize
        fsize=$(wc -c < "$f" 2>/dev/null | tr -d ' ')
        total_size=$((total_size + fsize))
        local flines
        flines=$(wc -l < "$f" 2>/dev/null | tr -d ' ')
        local fname
        fname=$(basename "$f")
        printf "  %-22s %5d lines  %6s\n" "$fname" "$flines" "$(_docs_human_size "$fsize")"
    done
    echo ""
    echo "  Total: $doc_count documents, $(_docs_human_size "$total_size")"
    echo ""

    # Coverage
    local tree_output
    tree_output=$(_docs_scan_project "$target_path")
    local total_files
    total_files=$(echo "$tree_output" | { grep -c . || true; })
    local src_files
    src_files=$(echo "$tree_output" | { grep -cE '\.(js|ts|tsx|jsx|py|rs|go|rb|java|kt|c|cpp|h|hpp|sh)$' || true; })

    local expected_docs="README.md ARCHITECTURE.md API.md SETUP.md COMPONENTS.md TESTING.md DECISIONS.md CLAUDE.md"
    local existing=0
    local expected_total=0
    for doc in $expected_docs; do
        ((++expected_total))
        [ -f "$docs_dir/$doc" ] && [ -s "$docs_dir/$doc" ] && ((++existing))
    done
    local pct=0
    [ "$expected_total" -gt 0 ] && pct=$(( existing * 100 / expected_total ))
    echo -e "${BOLD}Coverage:${NC} $existing/$expected_total doc types ($pct%)"
    echo "  Project: $total_files files ($src_files source)"
    echo ""

    # Staleness
    if [ -f "$manifest" ] && command -v python3 &>/dev/null; then
        local last_sha="" generated_at=""
        eval "$(python3 -c "
import json
try:
    d = json.load(open('$manifest'))
    print('last_sha=\"' + d.get('git_sha', 'unknown') + '\"')
    print('generated_at=\"' + d.get('generated_at', 'unknown') + '\"')
except: pass
" 2>/dev/null || true)"

        echo -e "${BOLD}Last generated:${NC} $generated_at"
        if [ -n "$last_sha" ] && [ "$last_sha" != "unknown" ]; then
            echo "  Git SHA: ${last_sha:0:12}"
            local commits_behind=0
            commits_behind=$(cd "$target_path" && git rev-list --count "$last_sha"..HEAD 2>/dev/null || echo "0")
            if [ "$commits_behind" -eq 0 ]; then
                echo -e "  Staleness: ${GREEN}up to date${NC}"
            elif [ "$commits_behind" -le 10 ]; then
                echo -e "  Staleness: ${YELLOW}$commits_behind commits behind${NC}"
            else
                echo -e "  Staleness: ${RED}$commits_behind commits behind${NC} (consider running 'loki docs update')"
            fi
        fi
    fi
}

# --- Docs helper: human-readable file size ---
_docs_human_size() {
    local bytes="$1"
    if [ "$bytes" -ge 1048576 ]; then
        echo "$((bytes / 1048576))MB"
    elif [ "$bytes" -ge 1024 ]; then
        echo "$((bytes / 1024))KB"
    else
        echo "${bytes}B"
    fi
}

#===============================================================================
# Magic: Spec-driven component generation (MagicModules + MoMoA)
#
# Storage layout:
#   .loki/magic/specs/<name>.md                - Canonical component specs
#   .loki/magic/generated/react/<name>.tsx     - React output
#   .loki/magic/generated/webcomponent/<name>.js - Web Component output
#   .loki/magic/generated/tests/<name>.test.tsx - Generated tests
#   .loki/magic/registry.json                  - Component registry
#===============================================================================

_magic_help() {
    echo -e "${BOLD}loki magic${NC} - Spec-driven component generation"
    echo ""
    echo "Usage: loki magic <command> [options]"
    echo ""
    echo "Commands:"
    echo "  generate <name>    Generate a component from a spec or screenshot"
    echo "  update             Regenerate components from their specs"
    echo "  list               List registered components"
    echo "  remove <name>      Remove a component and its artifacts"
    echo "  registry <op>      Registry operations (show|prune|stats)"
    echo "  debate <name>      Run multi-persona debate on an existing component"
    echo "  help               Show this help"
    echo ""
    echo "Options for 'generate':"
    echo "  --from-spec PATH           Generate from existing spec file"
    echo "  --from-screenshot PATH     Generate from a screenshot (vision-based)"
    echo "  --target TARGET            Output target: react|webcomponent|both (default: react)"
    echo "  --placement PATH           Where to place final component in project"
    echo ""
    echo "Options for 'update':"
    echo "  --name NAME                Update a specific component (default: all)"
    echo "  --force                    Overwrite local edits if any"
    echo ""
    echo "Options for 'list':"
    echo "  --format FMT               Output format: json|table (default: table)"
    echo ""
    echo "Options for 'remove':"
    echo "  --force                    Skip confirmation"
    echo ""
    echo "Options for 'debate':"
    echo "  --rounds N                 Number of debate rounds (default: 3)"
    echo ""
    echo "Examples:"
    echo "  loki magic generate LoginForm --from-spec specs/login.md"
    echo "  loki magic generate Navbar --from-screenshot shots/nav.png --target both"
    echo "  loki magic update --name LoginForm"
    echo "  loki magic list --format json"
    echo "  loki magic registry stats"
    echo "  loki magic debate LoginForm --rounds 5"
}

_magic_ensure_dirs() {
    mkdir -p ".loki/magic/specs" \
             ".loki/magic/generated/react" \
             ".loki/magic/generated/webcomponent" \
             ".loki/magic/generated/tests" 2>/dev/null || true
    if [ ! -f ".loki/magic/registry.json" ]; then
        # Schema matches magic/registry/schema.json: components as array
        echo '{"version": "1", "updated_at": "", "components": []}' > ".loki/magic/registry.json"
    fi
}

_magic_python() {
    # Prefer python3.12 if present (matches the rest of the project), fall back to python3
    if [ -x "/opt/homebrew/bin/python3.12" ]; then
        echo "/opt/homebrew/bin/python3.12"
    elif command -v python3.12 &>/dev/null; then
        command -v python3.12
    else
        echo "python3"
    fi
}

# Export PYTHONPATH so magic/* modules shipped with Loki are importable
# no matter which project directory the user runs from.
_magic_pypath() {
    local sk="${SKILL_DIR:-}"
    if [ -z "$sk" ]; then
        sk="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo "$0")")")"
    fi
    # Preserve any existing PYTHONPATH
    if [ -n "${PYTHONPATH:-}" ]; then
        echo "${sk}:${PYTHONPATH}"
    else
        echo "$sk"
    fi
}

_magic_valid_name() {
    local name="$1"
    [[ "$name" =~ ^[A-Za-z][A-Za-z0-9_-]*$ ]]
}

_magic_generate() {
    local name=""
    local from_spec=""
    local from_screenshot=""
    local target="react"
    local placement=""
    local description=""
    local tags=""

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --from-spec)
                from_spec="${2:-}"
                shift 2
                ;;
            --from-screenshot)
                from_screenshot="${2:-}"
                shift 2
                ;;
            --target)
                target="${2:-}"
                shift 2
                ;;
            --placement)
                placement="${2:-}"
                shift 2
                ;;
            --description|--desc)
                description="${2:-}"
                shift 2
                ;;
            --tags)
                tags="${2:-}"
                shift 2
                ;;
            --help|-h)
                _magic_help
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                return 1
                ;;
            *)
                if [ -z "$name" ]; then
                    name="$1"
                fi
                shift
                ;;
        esac
    done

    if [ -z "$name" ]; then
        log_error "Component name is required"
        echo "Usage: loki magic generate <name> [--from-spec PATH] [--from-screenshot PATH] [--target react|webcomponent|both] [--placement PATH]"
        return 1
    fi

    if ! _magic_valid_name "$name"; then
        log_error "Invalid component name: '$name' (must start with a letter, contain only letters, digits, _ or -)"
        return 1
    fi

    case "$target" in
        react|webcomponent|both) ;;
        *)
            log_error "Invalid target: '$target' (must be react, webcomponent, or both)"
            return 1
            ;;
    esac

    if [ -n "$from_spec" ] && [ ! -f "$from_spec" ]; then
        log_error "Spec file not found: $from_spec"
        return 1
    fi
    if [ -n "$from_screenshot" ] && [ ! -f "$from_screenshot" ]; then
        log_error "Screenshot file not found: $from_screenshot"
        return 1
    fi

    _magic_ensure_dirs
    local py
    py=$(_magic_python)

    log_info "Generating component '$name' (target: $target)"

    # 1. Build/load the canonical spec
    local spec_path=".loki/magic/specs/${name}.md"
    if [ -n "$from_spec" ]; then
        cp "$from_spec" "$spec_path"
        log_info "Copied spec from $from_spec"
    else
        log_info "Synthesizing spec via magic.core.spec"
        PYTHONPATH="$(_magic_pypath)" "$py" -c "
import sys
try:
    from magic.core.spec import generate_spec
except Exception as exc:
    sys.stderr.write(f'[ERROR] magic.core.spec not available: {exc}\n')
    sys.exit(2)
generate_spec(
    name='$name',
    out_path='$spec_path',
    from_spec='${from_spec}' or None,
    from_screenshot='${from_screenshot}' or None,
)
" || {
            log_error "Spec generation failed (module magic.core.spec missing or errored)"
            return 1
        }
    fi

    # 2. Load design tokens (optional; module may not yet exist)
    log_info "Loading design tokens"
    PYTHONPATH="$(_magic_pypath)" "$py" -c "
try:
    from magic.core.design_tokens import load_tokens
    load_tokens()
except Exception as exc:
    import sys
    sys.stderr.write(f'[WARN] design tokens unavailable: {exc}\n')
" 2>&1 | grep -v '^$' || true

    # 3. Generate component(s)
    log_info "Generating component via magic.core.generator"
    PYTHONPATH="$(_magic_pypath)" "$py" -c "
import sys
try:
    from magic.core.generator import generate_component
except Exception as exc:
    sys.stderr.write(f'[ERROR] magic.core.generator not available: {exc}\n')
    sys.exit(2)
generate_component(
    name='$name',
    spec_path='$spec_path',
    target='$target',
    react_out='.loki/magic/generated/react/${name}.tsx',
    wc_out='.loki/magic/generated/webcomponent/${name}.js',
    test_out='.loki/magic/generated/tests/${name}.test.tsx',
    placement='${placement}' or None,
)
" || {
        log_error "Component generation failed"
        return 1
    }

    # 4. Register in registry
    log_info "Registering component"
    PYTHONPATH="$(_magic_pypath)" "$py" -c "
import sys
try:
    from magic.core.registry import register_component
except Exception as exc:
    sys.stderr.write(f'[ERROR] magic.core.registry not available: {exc}\n')
    sys.exit(2)
register_component(
    registry_path='.loki/magic/registry.json',
    name='$name',
    spec_path='$spec_path',
    target='$target',
    react_path='.loki/magic/generated/react/${name}.tsx' if '$target' in ('react','both') else '',
    webcomponent_path='.loki/magic/generated/webcomponent/${name}.js' if '$target' in ('webcomponent','both') else '',
    test_path='.loki/magic/generated/tests/${name}.test.tsx',
    description='''$description'''.strip(),
    tags=[t.strip() for t in '''$tags'''.split(',') if t.strip()],
    placement='${placement}' or None,
)
" || {
        log_warn "Registry update failed; component files are still present"
    }

    log_info "Component '$name' generated successfully"
    echo "  Spec:   $spec_path"
    case "$target" in
        react)        echo "  React:  .loki/magic/generated/react/${name}.tsx" ;;
        webcomponent) echo "  WC:     .loki/magic/generated/webcomponent/${name}.js" ;;
        both)
            echo "  React:  .loki/magic/generated/react/${name}.tsx"
            echo "  WC:     .loki/magic/generated/webcomponent/${name}.js"
            ;;
    esac
    echo "  Tests:  .loki/magic/generated/tests/${name}.test.tsx"
}

_magic_update() {
    local name=""
    local force="false"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --name)
                name="${2:-}"
                shift 2
                ;;
            --force)
                force="true"
                shift
                ;;
            --help|-h)
                _magic_help
                return 0
                ;;
            *)
                log_error "Unknown option: $1"
                return 1
                ;;
        esac
    done

    _magic_ensure_dirs
    local py
    py=$(_magic_python)

    log_info "Updating components from specs (name=${name:-<all>}, force=$force)"
    PYTHONPATH="$(_magic_pypath)" "$py" -c "
import sys
try:
    from magic.core.generator import update_components
except Exception as exc:
    sys.stderr.write(f'[ERROR] magic.core.generator not available: {exc}\n')
    sys.exit(2)
update_components(
    registry_path='.loki/magic/registry.json',
    name='${name}' or None,
    force=$([ "$force" = "true" ] && echo True || echo False),
)
" || {
        log_error "Update failed"
        return 1
    }

    log_info "Update complete"
}

_magic_list() {
    local format="table"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --format)
                format="${2:-}"
                shift 2
                ;;
            --help|-h)
                _magic_help
                return 0
                ;;
            *)
                log_error "Unknown option: $1"
                return 1
                ;;
        esac
    done

    case "$format" in
        json|table) ;;
        *)
            log_error "Invalid format: '$format' (must be json or table)"
            return 1
            ;;
    esac

    _magic_ensure_dirs
    local py
    py=$(_magic_python)

    PYTHONPATH="$(_magic_pypath)" "$py" -c "
import json, sys
try:
    with open('.loki/magic/registry.json') as fh:
        data = json.load(fh)
except Exception as exc:
    sys.stderr.write(f'[ERROR] could not read registry: {exc}\n')
    sys.exit(1)

components_raw = data.get('components', [])
# Normalize: registry schema uses list of entries; legacy format was dict
if isinstance(components_raw, dict):
    components = [{'name': k, **(v or {})} for k, v in components_raw.items()]
else:
    components = components_raw

fmt = '$format'

if fmt == 'json':
    print(json.dumps(components, indent=2, sort_keys=True))
else:
    if not components:
        print('No components registered. Use: loki magic generate <name>')
        sys.exit(0)
    print(f'{\"NAME\":<24} {\"VERSION\":<10} {\"TARGETS\":<24} {\"TAGS\"}')
    print('-' * 100)
    for entry in sorted(components, key=lambda e: e.get('name', '')):
        name = entry.get('name', '-')
        version = entry.get('version', '-')
        tgts = entry.get('targets', [])
        targets = ','.join(tgts) if isinstance(tgts, list) else str(tgts)
        tags = entry.get('tags', [])
        tags_str = ','.join(tags) if isinstance(tags, list) else str(tags)
        print(f'{name:<24} {version:<10} {targets:<24} {tags_str}')
"
}

_magic_remove() {
    local name=""
    local force="false"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --force)
                force="true"
                shift
                ;;
            --help|-h)
                _magic_help
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                return 1
                ;;
            *)
                if [ -z "$name" ]; then
                    name="$1"
                fi
                shift
                ;;
        esac
    done

    if [ -z "$name" ]; then
        log_error "Component name is required"
        echo "Usage: loki magic remove <name> [--force]"
        return 1
    fi

    if ! _magic_valid_name "$name"; then
        log_error "Invalid component name: '$name'"
        return 1
    fi

    _magic_ensure_dirs

    if [ "$force" != "true" ]; then
        echo -n "Remove component '$name' and all its artifacts? [y/N] "
        local reply
        read -r reply || reply=""
        case "$reply" in
            y|Y|yes|YES) ;;
            *)
                log_info "Aborted"
                return 0
                ;;
        esac
    fi

    rm -f ".loki/magic/specs/${name}.md" \
          ".loki/magic/generated/react/${name}.tsx" \
          ".loki/magic/generated/webcomponent/${name}.js" \
          ".loki/magic/generated/tests/${name}.test.tsx" 2>/dev/null || true

    local py
    py=$(_magic_python)
    PYTHONPATH="$(_magic_pypath)" "$py" -c "
import json, sys
try:
    with open('.loki/magic/registry.json') as fh:
        data = json.load(fh)
except Exception:
    data = {'components': {}, 'version': 1}
components = data.setdefault('components', {})
if '$name' in components:
    del components['$name']
with open('.loki/magic/registry.json', 'w') as fh:
    json.dump(data, fh, indent=2, sort_keys=True)
" || log_warn "Could not update registry.json"

    log_info "Removed component '$name'"
}

_magic_registry() {
    local op="${1:-show}"
    shift 2>/dev/null || true

    case "$op" in
        --help|-h|help)
            _magic_help
            return 0
            ;;
    esac

    _magic_ensure_dirs
    local py
    py=$(_magic_python)

    case "$op" in
        show)
            PYTHONPATH="$(_magic_pypath)" "$py" -c "
import json
with open('.loki/magic/registry.json') as fh:
    data = json.load(fh)
print(json.dumps(data, indent=2, sort_keys=True))
"
            ;;
        prune)
            log_info "Pruning registry entries with missing artifacts"
            PYTHONPATH="$(_magic_pypath)" "$py" -c "
import json, os, sys
try:
    from magic.core.registry import prune_registry
    removed = prune_registry('.loki/magic/registry.json')
    print(f'Pruned {removed} stale entries')
except Exception as exc:
    # Fallback prune: drop entries whose spec file is missing
    with open('.loki/magic/registry.json') as fh:
        data = json.load(fh)
    comps = data.get('components', {})
    removed = 0
    for name in list(comps.keys()):
        spec = (comps[name] or {}).get('spec_path')
        if not spec or not os.path.isfile(spec):
            del comps[name]
            removed += 1
    with open('.loki/magic/registry.json', 'w') as fh:
        json.dump(data, fh, indent=2, sort_keys=True)
    print(f'Pruned {removed} stale entries (fallback)')
"
            ;;
        stats)
            PYTHONPATH="$(_magic_pypath)" "$py" -c "
import json, os
with open('.loki/magic/registry.json') as fh:
    data = json.load(fh)
raw = data.get('components', []) or []
# Accept both array (new) and dict (legacy) shapes
if isinstance(raw, dict):
    comps = [{'name': k, **(v or {})} for k, v in raw.items()]
else:
    comps = raw
total = len(comps)
targets = {}
missing = 0
for entry in comps:
    entry = entry or {}
    for t in entry.get('targets', [entry.get('target', 'unknown')]):
        targets[t] = targets.get(t, 0) + 1
    spec = entry.get('spec_path')
    if not spec or not os.path.isfile(spec):
        missing += 1
print(f'Components:        {total}')
print(f'Missing specs:     {missing}')
print('By target:')
for t in sorted(targets):
    print(f'  {t:<14} {targets[t]}')
"
            ;;
        *)
            log_error "Unknown registry op: $op"
            echo "Available: show, prune, stats"
            return 1
            ;;
    esac
}

_magic_debate() {
    local name=""
    local rounds="3"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --rounds)
                rounds="${2:-}"
                shift 2
                ;;
            --help|-h)
                _magic_help
                return 0
                ;;
            -*)
                log_error "Unknown option: $1"
                return 1
                ;;
            *)
                if [ -z "$name" ]; then
                    name="$1"
                fi
                shift
                ;;
        esac
    done

    if [ -z "$name" ]; then
        log_error "Component name is required"
        echo "Usage: loki magic debate <name> --rounds N"
        return 1
    fi

    if ! _magic_valid_name "$name"; then
        log_error "Invalid component name: '$name'"
        return 1
    fi

    if ! [[ "$rounds" =~ ^[0-9]+$ ]] || [ "$rounds" -lt 1 ]; then
        log_error "--rounds must be a positive integer"
        return 1
    fi

    _magic_ensure_dirs
    local spec_path=".loki/magic/specs/${name}.md"
    if [ ! -f "$spec_path" ]; then
        log_error "No spec found for '$name' at $spec_path"
        return 1
    fi

    local py
    py=$(_magic_python)

    log_info "Running debate on '$name' ($rounds rounds)"
    PYTHONPATH="$(_magic_pypath)" "$py" -c "
import sys
try:
    from magic.core.debate import run_debate
except Exception as exc:
    sys.stderr.write(f'[ERROR] magic.core.debate not available: {exc}\n')
    sys.exit(2)
run_debate(
    name='$name',
    spec_path='$spec_path',
    rounds=$rounds,
    react_path='.loki/magic/generated/react/${name}.tsx',
    wc_path='.loki/magic/generated/webcomponent/${name}.js',
)
" || {
        log_error "Debate failed"
        return 1
    }

    log_info "Debate complete"
}

# =============================================================================
# loki wiki -- auto-generated, cited per-project codebase wiki + Q&A (R5).
#
# Loki's answer to Devin DeepWiki: a persistent, queryable wiki built from the
# codebase, where every section cites the real source files it came from, plus
# a grounded `ask` that returns cited answers (file:line). Heavy work lives in
# the Python core (autonomy/lib/wiki-generator.py, wiki-ask.py, wiki_index.py);
# this is a thin dispatcher, mirroring how cmd_proof delegates to the proof
# generator. Generation is incremental (skips when the codebase is unchanged).
# =============================================================================
cmd_wiki() {
    local subcmd="${1:-}"
    shift 2>/dev/null || true

    local lib_dir="${_LOKI_SCRIPT_DIR}/lib"

    case "$subcmd" in
        generate) _wiki_generate "$lib_dir" "$@" ;;
        show)     _wiki_show "$@" ;;
        ask)      _wiki_ask "$lib_dir" "$@" ;;
        --help|-h|help|"")
            echo -e "${BOLD}loki wiki${NC} - Auto-generated, cited codebase wiki + Q&A"
            echo ""
            echo "Usage: loki wiki <command> [options]"
            echo ""
            echo "Commands:"
            echo "  generate [path] [--force]   Build/refresh the cited wiki in .loki/wiki/"
            echo "  show [section]              Print the wiki (or one section: architecture|modules|data-flow)"
            echo "  ask \"<question>\"           Cited answer grounded in the codebase (file:line)"
            echo ""
            echo "Each wiki section cites the real source files it was built from."
            echo "Generation is incremental: it skips when the codebase is unchanged."
            echo ""
            echo "Examples:"
            echo "  loki wiki generate                     # build wiki for current project"
            echo "  loki wiki show architecture            # show one section"
            echo "  loki wiki ask \"how does the cli dispatch commands\""
            return 0
            ;;
        *)
            log_error "Unknown wiki command: $subcmd"
            echo "Run 'loki wiki --help' for usage."
            return 1
            ;;
    esac
}

_wiki_generate() {
    local lib_dir="$1"; shift
    if ! command -v python3 >/dev/null 2>&1; then
        log_error "python3 is required for 'loki wiki generate'"
        return 1
    fi
    python3 "$lib_dir/wiki-generator.py" "$@"
}

_wiki_show() {
    local section=""
    local target="."
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo "Usage: loki wiki show [section]"
                echo "Sections: architecture, modules, data-flow"
                return 0
                ;;
            -*) log_error "Unknown option: $1"; return 1 ;;
            *) section="$1"; shift ;;
        esac
    done
    local wiki_dir="$target/.loki/wiki"
    if [ ! -d "$wiki_dir" ]; then
        log_error "No wiki found. Run 'loki wiki generate' first."
        return 1
    fi
    if [ -n "$section" ]; then
        local f="$wiki_dir/${section}.md"
        if [ ! -f "$f" ]; then
            log_error "No such section: $section (try: architecture, modules, data-flow)"
            return 1
        fi
        cat "$f"
    else
        if [ -f "$wiki_dir/index.md" ]; then
            cat "$wiki_dir/index.md"
        else
            log_error "Wiki index not found. Run 'loki wiki generate'."
            return 1
        fi
    fi
}

_wiki_ask() {
    local lib_dir="$1"; shift
    local question=""
    local extra=()
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo "Usage: loki wiki ask \"<question>\" [--json] [--k N]"
                return 0
                ;;
            --json|--quiet) extra+=("$1"); shift ;;
            --k) extra+=("--k" "${2:-6}"); shift 2 ;;
            *) if [ -z "$question" ]; then question="$1"; else question="$question $1"; fi; shift ;;
        esac
    done
    if [ -z "$question" ]; then
        log_error "Provide a question: loki wiki ask \"how does X work\""
        return 1
    fi
    if ! command -v python3 >/dev/null 2>&1; then
        log_error "python3 is required for 'loki wiki ask'"
        return 1
    fi
    python3 "$lib_dir/wiki-ask.py" --question "$question" "${extra[@]}"
}

cmd_magic() {
    local subcmd="${1:-help}"
    shift 2>/dev/null || true

    case "$subcmd" in
        generate|gen)    _magic_generate "$@" ;;
        update|up)       _magic_update "$@" ;;
        list|ls)         _magic_list "$@" ;;
        remove|rm)       _magic_remove "$@" ;;
        registry|reg)    _magic_registry "$@" ;;
        debate)          _magic_debate "$@" ;;
        help|--help|-h)  _magic_help ;;
        *)
            log_error "Unknown magic subcommand: $subcmd"
            _magic_help
            return 1
            ;;
    esac
}

# CI/CD quality gate integration (v6.22.0)
cmd_ci() {
    local ci_pr=false
    local ci_test_suggest=false
    local ci_report=false
    local ci_github_comment=false
    local ci_fail_on=""
    local ci_format="markdown"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki ci${NC} - CI/CD quality gate integration"
                echo ""
                echo "Usage: loki ci [options]"
                echo ""
                echo "Runs Loki Mode quality gates as a CI step. Works with GitHub Actions,"
                echo "GitLab CI, Jenkins, CircleCI, and other CI systems."
                echo ""
                echo "Modes:"
                echo "  --pr                 Review the current PR diff with all quality gates"
                echo "  --test-suggest       Generate test suggestions for changed files"
                echo "  --report             Generate a quality report"
                echo ""
                echo "Options:"
                echo "  --github-comment     Post review results as PR comment (needs GITHUB_TOKEN)"
                echo "  --fail-on <levels>   Set exit code 1 on severity: critical,high,medium,low"
                echo "  --format <fmt>       Output format: json, markdown, github (default: markdown)"
                echo "  --help, -h           Show this help"
                echo ""
                echo "Exit codes:"
                echo "  0  All checks passed (or below --fail-on threshold)"
                echo "  1  Findings exceed --fail-on threshold"
                echo "  2  Error (missing tools, invalid arguments)"
                echo ""
                echo "Environment variables (auto-detected):"
                echo "  GITHUB_ACTIONS       Detected when running in GitHub Actions"
                echo "  GITLAB_CI            Detected when running in GitLab CI"
                echo "  JENKINS_URL          Detected when running in Jenkins"
                echo "  CIRCLECI             Detected when running in CircleCI"
                echo "  GITHUB_TOKEN         Required for --github-comment"
                echo "  GITHUB_EVENT_PATH    Auto-set in GitHub Actions for PR context"
                echo ""
                echo "Examples:"
                echo "  loki ci --pr --format json              # Review PR diff as JSON"
                echo "  loki ci --pr --fail-on critical,high    # Fail CI on critical/high findings"
                echo "  loki ci --pr --github-comment           # Post results as PR comment"
                echo "  loki ci --report --format markdown      # Generate quality report"
                echo "  loki ci --test-suggest                  # Suggest tests for changed files"
                echo ""
                echo "GitHub Actions example:"
                echo "  - uses: asklokesh/loki-mode-action@v1"
                echo "    with:"
                echo "      command: loki ci --pr --github-comment --fail-on critical"
                return 0
                ;;
            --pr) ci_pr=true; shift ;;
            --test-suggest) ci_test_suggest=true; shift ;;
            --report) ci_report=true; shift ;;
            --github-comment) ci_github_comment=true; shift ;;
            --fail-on)
                shift
                ci_fail_on="${1:-}"
                if [ -z "$ci_fail_on" ]; then
                    echo -e "${RED}Error: --fail-on requires severity levels (e.g., critical,high)${NC}"
                    return 2
                fi
                ci_fail_on="$(echo "$ci_fail_on" | tr '[:upper:]' '[:lower:]')"
                shift
                ;;
            --format)
                shift
                ci_format="${1:-markdown}"
                ci_format="$(echo "$ci_format" | tr '[:upper:]' '[:lower:]')"
                if [[ "$ci_format" != "json" && "$ci_format" != "markdown" && "$ci_format" != "github" ]]; then
                    echo -e "${RED}Error: --format must be json, markdown, or github${NC}"
                    return 2
                fi
                shift
                ;;
            -*) echo -e "${RED}Unknown option: $1${NC}"; return 2 ;;
            *) echo -e "${RED}Unknown argument: $1${NC}"; return 2 ;;
        esac
    done

    # If no mode specified, default to --pr --report
    if [ "$ci_pr" = false ] && [ "$ci_test_suggest" = false ] && [ "$ci_report" = false ]; then
        ci_pr=true
        ci_report=true
    fi

    # --- Detect CI environment ---
    local ci_env="local"
    local ci_pr_number=""
    local ci_base_branch="main"
    local ci_repo=""

    if [ -n "${GITHUB_ACTIONS:-}" ]; then
        ci_env="github"
        ci_repo="${GITHUB_REPOSITORY:-}"
        ci_base_branch="${GITHUB_BASE_REF:-main}"
        # Extract PR number from GITHUB_EVENT_PATH
        if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "${GITHUB_EVENT_PATH:-}" ]; then
            ci_pr_number=$(python3 -c "
import json, sys
try:
    with open('${GITHUB_EVENT_PATH}') as f:
        event = json.load(f)
    pr = event.get('pull_request', event.get('number', ''))
    if isinstance(pr, dict):
        print(pr.get('number', ''))
    else:
        print(pr)
except Exception:
    print('')
" 2>/dev/null || echo "")
        fi
        # Fallback: GITHUB_REF_NAME for PR refs like refs/pull/123/merge
        if [ -z "$ci_pr_number" ] && [[ "${GITHUB_REF:-}" == refs/pull/*/merge ]]; then
            ci_pr_number=$(echo "${GITHUB_REF}" | sed 's|refs/pull/\([0-9]*\)/merge|\1|')
        fi
    elif [ -n "${GITLAB_CI:-}" ]; then
        ci_env="gitlab"
        ci_pr_number="${CI_MERGE_REQUEST_IID:-}"
        ci_base_branch="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-main}"
        ci_repo="${CI_PROJECT_PATH:-}"
    elif [ -n "${JENKINS_URL:-}" ]; then
        ci_env="jenkins"
        ci_pr_number="${CHANGE_ID:-}"
        ci_base_branch="${CHANGE_TARGET:-main}"
    elif [ -n "${CIRCLECI:-}" ]; then
        ci_env="circleci"
        ci_pr_number="${CIRCLE_PULL_REQUEST##*/}"
        ci_repo="${CIRCLE_PROJECT_USERNAME:-}/${CIRCLE_PROJECT_REPONAME:-}"
    fi

    # --- Gather diff ---
    local diff_content=""
    local changed_files=""

    if [ "$ci_pr" = true ] || [ "$ci_test_suggest" = true ] || [ "$ci_report" = true ]; then
        if [ -n "$ci_pr_number" ] && command -v gh &>/dev/null && [ "$ci_env" = "github" ]; then
            diff_content=$(gh pr diff "$ci_pr_number" 2>/dev/null || echo "")
            changed_files=$(gh pr diff "$ci_pr_number" --name-only 2>/dev/null || echo "")
        fi

        # Fallback: git diff against base branch
        if [ -z "$diff_content" ]; then
            # Fetch base branch if in CI
            if [ "$ci_env" != "local" ]; then
                git fetch origin "$ci_base_branch" --depth=1 2>/dev/null || true
            fi
            diff_content=$(git diff "origin/${ci_base_branch}...HEAD" 2>/dev/null || git diff HEAD~1 2>/dev/null || git diff HEAD 2>/dev/null || echo "")
            changed_files=$(git diff --name-only "origin/${ci_base_branch}...HEAD" 2>/dev/null || git diff --name-only HEAD~1 2>/dev/null || git diff --name-only HEAD 2>/dev/null || echo "")
        fi

        if [ -z "$diff_content" ]; then
            if [ "$ci_format" = "json" ]; then
                echo '{"status":"skip","message":"No changes to review","ci_environment":"'"$ci_env"'","pr_number":"'"$ci_pr_number"'","findings":[],"summary":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"total":0},"exit_code":0}'
            else
                echo -e "${GREEN}No changes to review.${NC}"
            fi
            return 0
        fi
    fi

    # --- Severity helpers ---
    _ci_sev_level() {
        case "$1" in
            CRITICAL) echo 5 ;;
            HIGH) echo 4 ;;
            MEDIUM) echo 3 ;;
            LOW) echo 2 ;;
            INFO) echo 1 ;;
            *) echo 0 ;;
        esac
    }

    # Parse --fail-on into a minimum severity threshold
    local fail_threshold=99  # Default: never fail
    if [ -n "$ci_fail_on" ]; then
        # Take the lowest severity from the comma-separated list
        IFS=',' read -ra fail_levels <<< "$ci_fail_on"
        for level in "${fail_levels[@]}"; do
            level=$(echo "$level" | tr -d ' ' | tr '[:lower:]' '[:upper:]')
            local level_num
            level_num=$(_ci_sev_level "$level")
            if [ "$level_num" -gt 0 ] && [ "$level_num" -lt "$fail_threshold" ]; then
                fail_threshold=$level_num
            fi
        done
    fi

    # --- Run quality gates (reuses cmd_review logic) ---
    local findings=()
    local has_critical=false
    local has_high=false

    _ci_add_finding() {
        local file="$1" line="$2" sev="$3" cat="$4" finding="$5" suggestion="$6"
        findings+=("${file}|${line}|${sev}|${cat}|${finding}|${suggestion}")
        [ "$sev" = "CRITICAL" ] && has_critical=true || true
        [ "$sev" = "HIGH" ] && has_high=true || true
    }

    # Gate 1: Static Analysis - shellcheck
    if command -v shellcheck &>/dev/null; then
        local shell_files_ci
        shell_files_ci=$(echo "$changed_files" | grep -E '\.(sh|bash)$' || true)
        if [ -n "$shell_files_ci" ]; then
            while IFS= read -r sf; do
                [ -z "$sf" ] && continue
                [ ! -f "$sf" ] && continue
                local sc_out
                sc_out=$(shellcheck -f gcc "$sf" 2>/dev/null || true)
                while IFS= read -r sc_line; do
                    [ -z "$sc_line" ] && continue
                    local sc_file sc_lineno sc_sev sc_msg
                    sc_file=$(echo "$sc_line" | cut -d: -f1)
                    sc_lineno=$(echo "$sc_line" | cut -d: -f2)
                    sc_sev=$(echo "$sc_line" | sed -n 's/.*: \(warning\|error\|note\|info\):.*/\1/p')
                    sc_msg=$(echo "$sc_line" | sed 's/.*: \(warning\|error\|note\|info\): //')
                    local mapped_sev="LOW"
                    case "$sc_sev" in
                        error) mapped_sev="HIGH" ;;
                        warning) mapped_sev="MEDIUM" ;;
                        *) mapped_sev="LOW" ;;
                    esac
                    _ci_add_finding "$sc_file" "$sc_lineno" "$mapped_sev" "static-analysis" "$sc_msg" "Fix shellcheck finding"
                done <<< "$sc_out"
            done <<< "$shell_files_ci"
        fi
    fi

    # Gate 1b: Static Analysis - eslint
    if command -v npx &>/dev/null; then
        local js_files_ci
        js_files_ci=$(echo "$changed_files" | grep -E '\.(js|ts|jsx|tsx)$' || true)
        if [ -n "$js_files_ci" ]; then
            while IFS= read -r jsf; do
                [ -z "$jsf" ] && continue
                [ ! -f "$jsf" ] && continue
                if [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ] || [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ]; then
                    local eslint_out
                    eslint_out=$(npx eslint --format compact "$jsf" 2>/dev/null || true)
                    while IFS= read -r el; do
                        [ -z "$el" ] && continue
                        [[ "$el" == *"problem"* ]] && continue
                        local el_file el_line el_msg
                        el_file=$(echo "$el" | cut -d: -f1)
                        el_line=$(echo "$el" | cut -d: -f2)
                        el_msg=$(echo "$el" | sed 's/^[^)]*) //')
                        local el_sev="LOW"
                        [[ "$el" == *"Error"* ]] && el_sev="MEDIUM"
                        _ci_add_finding "$el_file" "$el_line" "$el_sev" "static-analysis" "$el_msg" "Fix eslint finding"
                    done <<< "$eslint_out"
                fi
            done <<< "$js_files_ci"
        fi
    fi

    # Gate 2: Security Scan
    _ci_scan() {
        local pattern="$1" sev="$2" cat="$3" finding="$4" suggestion="$5"
        # Only scan added lines (starting with "+") to avoid flagging deleted code
        while IFS= read -r match; do
            [ -z "$match" ] && continue
            local ml
            ml=$(echo "$match" | cut -d: -f1)
            _ci_add_finding "diff" "$ml" "$sev" "$cat" "$finding" "$suggestion"
        done < <(echo "$diff_content" | grep -n '^+' | grep -E "$pattern" 2>/dev/null || true)
    }

    # Hardcoded secrets
    local ci_secret_patterns=(
        'API_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
        'SECRET_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
        'PASSWORD\s*[=:]\s*["\x27][^\x27"]{4,}'
        'PRIVATE_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
        'AWS_ACCESS_KEY_ID\s*[=:]\s*["\x27]AK[A-Z0-9]{18}'
        'ghp_[A-Za-z0-9]{36}'
        'sk-[A-Za-z0-9]{32,}'
        'Bearer\s+[A-Za-z0-9._-]{20,}'
    )
    for pattern in "${ci_secret_patterns[@]}"; do
        _ci_scan "$pattern" "CRITICAL" "security" \
            "Potential hardcoded secret detected" \
            "Use environment variables or a secrets manager"
    done

    # SQL injection
    _ci_scan '(SELECT|INSERT|UPDATE|DELETE|DROP)\s.*\+\s*(req\.|request\.|params\.|user)' \
        "HIGH" "security" \
        "Potential SQL injection: string concatenation in query" \
        "Use parameterized queries or prepared statements"

    # eval/exec
    _ci_scan '(^|\s)(eval|exec)\s*\(' \
        "HIGH" "security" \
        "Dangerous eval/exec usage detected" \
        "Avoid eval/exec with dynamic input"

    # Unsafe deserialization
    _ci_scan '(pickle\.loads?|yaml\.load\s*\()' \
        "HIGH" "security" \
        "Unsafe deserialization detected" \
        "Use yaml.safe_load or avoid pickle with untrusted data"

    # Disabled SSL
    _ci_scan '(verify\s*=\s*False|VERIFY_SSL\s*=\s*False|NODE_TLS_REJECT_UNAUTHORIZED.*0|rejectUnauthorized.*false)' \
        "HIGH" "security" \
        "SSL verification disabled" \
        "Enable SSL verification in production"

    # Gate 3: Anti-patterns
    _ci_scan 'console\.(log|debug)\(' "LOW" "anti-pattern" \
        "console.log/debug statement found" \
        "Remove debug logging or use a proper logger"

    _ci_scan 'except\s*:' "MEDIUM" "anti-pattern" \
        "Bare except clause" \
        "Catch specific exceptions"

    # Gate 4: TODO/FIXME markers in new code
    _ci_scan '^\+.*\b(TODO|FIXME|HACK|XXX):' "INFO" "style" \
        "TODO/FIXME marker in new code" \
        "Track in issue tracker"

    # --- Test suggestions ---
    local test_suggestions=""
    if [ "$ci_test_suggest" = true ] && [ -n "$changed_files" ]; then
        export LOKI_CI_CHANGED_FILES="$changed_files"
        test_suggestions=$(python3 << 'TEST_SUGGEST_PY'
import sys, os

changed = os.environ.get("LOKI_CI_CHANGED_FILES", "").strip().split("\n")
suggestions = []

for f in changed:
    f = f.strip()
    if not f:
        continue

    base = os.path.basename(f)
    name, ext = os.path.splitext(base)
    dirpath = os.path.dirname(f)

    # Skip test files themselves
    if "test" in name.lower() or "spec" in name.lower():
        continue
    # Skip non-code files
    if ext not in (".py", ".js", ".ts", ".jsx", ".tsx", ".sh", ".bash", ".go", ".rs", ".rb"):
        continue

    # Suggest test file paths based on language conventions
    if ext == ".py":
        test_path = os.path.join(dirpath, f"test_{name}.py")
        alt_path = os.path.join("tests", f"test_{name}.py")
        suggestions.append({"file": f, "test_file": test_path, "alt_test_file": alt_path,
                          "framework": "pytest", "hint": f"Add unit tests for {name} module"})
    elif ext in (".js", ".ts", ".jsx", ".tsx"):
        if ext == ".tsx":
            test_ext = ".test.tsx"
        elif ext == ".jsx":
            test_ext = ".test.jsx"
        elif ext == ".ts":
            test_ext = ".test.ts"
        elif ext == ".js":
            test_ext = ".test.js"
        else:
            test_ext = f".test{ext}"
        test_path = os.path.join(dirpath, f"{name}{test_ext}")
        suggestions.append({"file": f, "test_file": test_path,
                          "framework": "jest/vitest", "hint": f"Add tests for {name} component/module"})
    elif ext in (".sh", ".bash"):
        test_path = os.path.join("tests", f"test-{name}.sh")
        suggestions.append({"file": f, "test_file": test_path,
                          "framework": "bash", "hint": f"Add shell tests for {name}"})
    elif ext == ".go":
        test_path = os.path.join(dirpath, f"{name}_test.go")
        suggestions.append({"file": f, "test_file": test_path,
                          "framework": "go test", "hint": f"Add Go tests for {name}"})
    elif ext == ".rs":
        suggestions.append({"file": f, "test_file": f"{f} (mod tests)",
                          "framework": "cargo test", "hint": f"Add #[cfg(test)] mod tests in {name}"})
    elif ext == ".rb":
        test_path = os.path.join("spec", f"{name}_spec.rb")
        suggestions.append({"file": f, "test_file": test_path,
                          "framework": "rspec", "hint": f"Add RSpec tests for {name}"})

import json
print(json.dumps(suggestions))
TEST_SUGGEST_PY
        )
    fi

    # --- Tally findings ---
    local count_critical=0 count_high=0 count_medium=0 count_low=0 count_info=0
    for f in "${findings[@]}"; do
        local sev
        sev=$(echo "$f" | cut -d'|' -f3)
        case "$sev" in
            CRITICAL) count_critical=$((count_critical + 1)) ;;
            HIGH) count_high=$((count_high + 1)) ;;
            MEDIUM) count_medium=$((count_medium + 1)) ;;
            LOW) count_low=$((count_low + 1)) ;;
            INFO) count_info=$((count_info + 1)) ;;
        esac
    done
    local count_total=${#findings[@]}

    # Determine exit code based on --fail-on threshold
    local exit_code=0
    if [ "$fail_threshold" -lt 99 ]; then
        for f in "${findings[@]}"; do
            local sev sev_num
            sev=$(echo "$f" | cut -d'|' -f3)
            sev_num=$(_ci_sev_level "$sev")
            if [ "$sev_num" -ge "$fail_threshold" ]; then
                exit_code=1
                break
            fi
        done
    fi

    # --- Build output ---
    local report_timestamp
    report_timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    local file_count
    file_count=$(echo "$changed_files" | grep -c '.' 2>/dev/null || echo 0)

    if [ "$ci_format" = "json" ]; then
        # JSON output via python for proper escaping
        export LOKI_CI_JSON_FINDINGS=""
        for f in "${findings[@]}"; do
            LOKI_CI_JSON_FINDINGS+="${f}"$'\n'
        done
        export LOKI_CI_JSON_META="ci_env=${ci_env}|pr=${ci_pr_number}|ts=${report_timestamp}|files=${file_count}|exit=${exit_code}"
        export LOKI_CI_JSON_COUNTS="critical=${count_critical}|high=${count_high}|medium=${count_medium}|low=${count_low}|info=${count_info}|total=${count_total}"
        export LOKI_CI_JSON_TESTS="${test_suggestions:-[]}"

        python3 << 'CI_JSON_OUT'
import json, os

findings_raw = os.environ.get("LOKI_CI_JSON_FINDINGS", "").strip().split("\n")
meta_raw = os.environ.get("LOKI_CI_JSON_META", "")
counts_raw = os.environ.get("LOKI_CI_JSON_COUNTS", "")
tests_raw = os.environ.get("LOKI_CI_JSON_TESTS", "[]")

meta = dict(item.split("=", 1) for item in meta_raw.split("|") if "=" in item)
counts = dict(item.split("=", 1) for item in counts_raw.split("|") if "=" in item)

findings = []
for line in findings_raw:
    if not line or "|" not in line:
        continue
    parts = line.split("|", 5)
    if len(parts) >= 6:
        findings.append({
            "file": parts[0],
            "line": int(parts[1]) if parts[1].isdigit() else 0,
            "severity": parts[2],
            "category": parts[3],
            "finding": parts[4],
            "suggestion": parts[5]
        })

try:
    tests = json.loads(tests_raw)
except Exception:
    tests = []

report = {
    "status": "fail" if int(meta.get("exit", "0")) > 0 else "pass",
    "ci_environment": meta.get("ci_env", "local"),
    "pr_number": meta.get("pr", ""),
    "timestamp": meta.get("ts", ""),
    "files_changed": int(meta.get("files", "0")),
    "findings": findings,
    "summary": {
        "critical": int(counts.get("critical", "0")),
        "high": int(counts.get("high", "0")),
        "medium": int(counts.get("medium", "0")),
        "low": int(counts.get("low", "0")),
        "info": int(counts.get("info", "0")),
        "total": int(counts.get("total", "0"))
    },
    "exit_code": int(meta.get("exit", "0"))
}
if tests:
    report["test_suggestions"] = tests

print(json.dumps(report, indent=2))
CI_JSON_OUT
        unset LOKI_CI_JSON_FINDINGS LOKI_CI_JSON_META LOKI_CI_JSON_COUNTS LOKI_CI_JSON_TESTS

    elif [ "$ci_format" = "github" ]; then
        # GitHub-flavored markdown for PR comments
        echo "## Loki CI Quality Report"
        echo ""
        echo "| Metric | Count |"
        echo "|--------|-------|"
        echo "| Files changed | $file_count |"
        echo "| Critical | $count_critical |"
        echo "| High | $count_high |"
        echo "| Medium | $count_medium |"
        echo "| Low | $count_low |"
        echo "| Info | $count_info |"
        echo "| **Total** | **$count_total** |"
        echo ""

        if [ "$count_total" -gt 0 ]; then
            echo "### Findings"
            echo ""
            for sev_name in CRITICAL HIGH MEDIUM LOW INFO; do
                local has_sev=false
                for f in "${findings[@]}"; do
                    local f_sev
                    f_sev=$(echo "$f" | cut -d'|' -f3)
                    [ "$f_sev" != "$sev_name" ] && continue
                    if [ "$has_sev" = false ]; then
                        echo "#### $sev_name"
                        echo ""
                        has_sev=true
                    fi
                    local f_file f_line f_cat f_finding f_suggestion
                    f_file=$(echo "$f" | cut -d'|' -f1)
                    f_line=$(echo "$f" | cut -d'|' -f2)
                    f_cat=$(echo "$f" | cut -d'|' -f4)
                    f_finding=$(echo "$f" | cut -d'|' -f5)
                    f_suggestion=$(echo "$f" | cut -d'|' -f6)
                    echo "- **\`$f_file:$f_line\`** [$f_cat] $f_finding"
                    echo "  - Suggestion: $f_suggestion"
                done
                [ "$has_sev" = true ] && echo ""
            done
        else
            echo "No findings. All quality gates passed."
        fi

        if [ -n "${test_suggestions:-}" ] && [ "$test_suggestions" != "[]" ]; then
            echo "### Test Suggestions"
            echo ""
            export LOKI_CI_TEST_SUGG="${test_suggestions}"
            python3 -c "
import json, os
tests = json.loads(os.environ.get('LOKI_CI_TEST_SUGG', '[]'))
for t in tests:
    print(f\"- **{t['file']}**: {t['hint']} ({t['framework']})\")
    print(f\"  - Suggested: \`{t['test_file']}\`\")
" 2>/dev/null || true
        fi

        echo ""
        if [ "$exit_code" -eq 0 ]; then
            echo "**Result: PASSED**"
        else
            echo "**Result: FAILED** (findings exceed threshold)"
        fi
        echo ""
        echo "_Generated by [Loki Mode](https://github.com/asklokesh/loki-mode) at $report_timestamp"

    else
        # Default: markdown (terminal-friendly)
        echo -e "${BOLD}Loki CI Quality Report${NC}"
        echo -e "Environment: ${CYAN}$ci_env${NC}"
        [ -n "$ci_pr_number" ] && echo -e "PR: ${CYAN}#$ci_pr_number${NC}"
        echo -e "Files changed: ${CYAN}$file_count${NC}"
        echo -e "Timestamp: ${DIM}$report_timestamp${NC}"
        echo "---"

        if [ "$count_total" -eq 0 ]; then
            echo -e "${GREEN}All quality gates passed. No findings.${NC}"
        else
            for sev_name in CRITICAL HIGH MEDIUM LOW INFO; do
                local printed_header=false
                for f in "${findings[@]}"; do
                    local f_sev
                    f_sev=$(echo "$f" | cut -d'|' -f3)
                    [ "$f_sev" != "$sev_name" ] && continue
                    if [ "$printed_header" = false ]; then
                        local sev_color="$NC"
                        case "$sev_name" in
                            CRITICAL) sev_color="$RED" ;;
                            HIGH) sev_color="$RED" ;;
                            MEDIUM) sev_color="$YELLOW" ;;
                            LOW) sev_color="$CYAN" ;;
                            INFO) sev_color="$DIM" ;;
                        esac
                        echo ""
                        echo -e "${sev_color}${BOLD}[$sev_name]${NC}"
                        printed_header=true
                    fi
                    local f_file f_line f_cat f_finding f_suggestion
                    f_file=$(echo "$f" | cut -d'|' -f1)
                    f_line=$(echo "$f" | cut -d'|' -f2)
                    f_cat=$(echo "$f" | cut -d'|' -f4)
                    f_finding=$(echo "$f" | cut -d'|' -f5)
                    f_suggestion=$(echo "$f" | cut -d'|' -f6)
                    echo -e "  ${DIM}$f_file:$f_line${NC} [$f_cat] $f_finding"
                    echo -e "    -> $f_suggestion"
                done
            done
        fi

        echo ""
        echo "---"
        echo -e "Summary: ${RED}$count_critical critical${NC}, ${RED}$count_high high${NC}, ${YELLOW}$count_medium medium${NC}, ${CYAN}$count_low low${NC}, ${DIM}$count_info info${NC} ($count_total total)"

        if [ "$ci_test_suggest" = true ] && [ -n "${test_suggestions:-}" ] && [ "$test_suggestions" != "[]" ]; then
            echo ""
            echo -e "${BOLD}Test Suggestions${NC}"
            export LOKI_CI_TEST_SUGG="${test_suggestions}"
            python3 -c "
import json, os
tests = json.loads(os.environ.get('LOKI_CI_TEST_SUGG', '[]'))
for t in tests:
    print(f\"  {t['file']} -> {t['test_file']} ({t['framework']})\")
    print(f\"    {t['hint']}\")
" 2>/dev/null || true
            unset LOKI_CI_TEST_SUGG
        fi

        if [ "$exit_code" -eq 0 ]; then
            echo -e "${GREEN}Result: PASSED${NC}"
        else
            echo -e "${RED}Result: FAILED (findings exceed threshold)${NC}"
        fi
    fi

    # --- Post GitHub comment ---
    if [ "$ci_github_comment" = true ]; then
        if [ -z "${GITHUB_TOKEN:-}" ]; then
            echo -e "${YELLOW}Warning: --github-comment requires GITHUB_TOKEN. Skipping comment.${NC}" >&2
        elif [ -z "$ci_pr_number" ]; then
            echo -e "${YELLOW}Warning: No PR number detected. Skipping comment.${NC}" >&2
        elif command -v gh &>/dev/null; then
            # Generate GitHub-format report for comment
            local comment_body
            comment_body=$("$0" ci --pr --format github 2>/dev/null || echo "Loki CI report generation failed.")
            gh pr comment "$ci_pr_number" --body "$comment_body" 2>/dev/null && \
                echo -e "${GREEN}Posted review comment to PR #$ci_pr_number${NC}" >&2 || \
                echo -e "${YELLOW}Warning: Failed to post PR comment${NC}" >&2
        else
            echo -e "${YELLOW}Warning: gh CLI required for --github-comment. Install: https://cli.github.com${NC}" >&2
        fi
    fi

    return "$exit_code"
}

# AI-powered test generation (v6.24.0)
cmd_test() {
    local test_file=""
    local test_dir=""
    local test_changed=false
    local test_coverage=80
    local test_format=""
    local test_output=""
    local test_dry_run=false
    local test_json=false
    local test_verbose=false

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki test${NC} - AI-powered test generation"
                echo ""
                echo "Usage: loki test [options] [file-or-dir]"
                echo ""
                echo "Analyzes source files and generates comprehensive test suites."
                echo "Detects language and framework automatically. Works without API keys."
                echo ""
                echo "Sources (default: git-changed files):"
                echo "  --file <path>        Test a specific file"
                echo "  --dir <path>         Test all files in a directory"
                echo "  --changed            Test only git-changed files (default if no source given)"
                echo "  <file-or-dir>        Positional: auto-detect file or directory"
                echo ""
                echo "Options:"
                echo "  --coverage <n>       Target coverage percentage (default: 80)"
                echo "  --format <fmt>       Test framework: auto, jest, pytest, vitest, mocha, go-test"
                echo "  --output <path>      Custom output directory for generated tests"
                echo "  --dry-run            Preview what tests would be generated without writing"
                echo "  --json               JSON output"
                echo "  --verbose            Detailed output"
                echo "  --help, -h           Show this help"
                echo ""
                echo "Supported languages:"
                echo "  JavaScript/TypeScript  (.js, .ts, .jsx, .tsx)  -> jest, vitest, mocha"
                echo "  Python                 (.py)                   -> pytest"
                echo "  Go                     (.go)                   -> go-test"
                echo "  Rust                   (.rs)                   -> cargo-test"
                echo "  Java                   (.java)                 -> junit"
                echo "  Ruby                   (.rb)                   -> rspec"
                echo "  Shell/Bash             (.sh)                   -> bats"
                echo ""
                echo "Exit codes:"
                echo "  0  Tests generated successfully"
                echo "  1  No testable files found"
                echo "  2  Error (invalid arguments, missing paths)"
                echo ""
                echo "Examples:"
                echo "  loki test                           # Generate tests for git-changed files"
                echo "  loki test --file src/utils.ts       # Test a specific file"
                echo "  loki test --dir src/lib/            # Test all files in directory"
                echo "  loki test --changed --format jest   # Changed files, force Jest format"
                echo "  loki test --dry-run                 # Preview without writing"
                echo "  loki test --coverage 90             # Target 90% coverage"
                echo "  loki test --json                    # Machine-readable output"
                echo "  loki test src/utils.ts              # Positional file argument"
                return 0
                ;;
            --file)
                shift
                test_file="${1:-}"
                if [ -z "$test_file" ]; then
                    echo -e "${RED}Error: --file requires a path${NC}"
                    return 2
                fi
                shift
                ;;
            --dir)
                shift
                test_dir="${1:-}"
                if [ -z "$test_dir" ]; then
                    echo -e "${RED}Error: --dir requires a path${NC}"
                    return 2
                fi
                shift
                ;;
            --changed) test_changed=true; shift ;;
            --coverage)
                shift
                test_coverage="${1:-80}"
                if ! [[ "$test_coverage" =~ ^[0-9]+$ ]] || [ "$test_coverage" -lt 1 ] || [ "$test_coverage" -gt 100 ]; then
                    echo -e "${RED}Error: --coverage must be a number between 1 and 100${NC}"
                    return 2
                fi
                shift
                ;;
            --format)
                shift
                test_format="${1:-}"
                if [ -z "$test_format" ]; then
                    echo -e "${RED}Error: --format requires a framework name${NC}"
                    return 2
                fi
                test_format="$(echo "$test_format" | tr '[:upper:]' '[:lower:]')"
                case "$test_format" in
                    auto|jest|pytest|vitest|mocha|go-test|cargo-test|junit|rspec|bats) ;;
                    *) echo -e "${RED}Error: unsupported format '$test_format'. Use: auto, jest, pytest, vitest, mocha, go-test, cargo-test, junit, rspec, bats${NC}"; return 2 ;;
                esac
                shift
                ;;
            --output)
                shift
                test_output="${1:-}"
                if [ -z "$test_output" ]; then
                    echo -e "${RED}Error: --output requires a path${NC}"
                    return 2
                fi
                shift
                ;;
            --dry-run) test_dry_run=true; shift ;;
            --json) test_json=true; shift ;;
            --verbose) test_verbose=true; shift ;;
            -*)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Run 'loki test --help' for usage."
                return 2
                ;;
            *)
                # Positional argument: auto-detect file or directory
                if [ -f "$1" ]; then
                    test_file="$1"
                elif [ -d "$1" ]; then
                    test_dir="$1"
                else
                    echo -e "${RED}Error: '$1' is not a file or directory${NC}"
                    return 2
                fi
                shift
                ;;
        esac
    done

    # Default to --changed if no source specified
    if [ -z "$test_file" ] && [ -z "$test_dir" ] && [ "$test_changed" = false ]; then
        test_changed=true
    fi

    # --- Collect source files ---
    local -a source_files=()

    if [ -n "$test_file" ]; then
        if [ ! -f "$test_file" ]; then
            echo -e "${RED}Error: file not found: $test_file${NC}"
            return 2
        fi
        source_files+=("$test_file")
    elif [ -n "$test_dir" ]; then
        if [ ! -d "$test_dir" ]; then
            echo -e "${RED}Error: directory not found: $test_dir${NC}"
            return 2
        fi
        while IFS= read -r f; do
            source_files+=("$f")
        done < <(_test_find_source_files "$test_dir")
    elif [ "$test_changed" = true ]; then
        while IFS= read -r f; do
            [ -n "$f" ] && [ -f "$f" ] && source_files+=("$f")
        done < <(git diff --name-only HEAD 2>/dev/null; git diff --name-only --cached 2>/dev/null; git diff --name-only 2>/dev/null)
        # Deduplicate
        if [ ${#source_files[@]} -gt 0 ]; then
            local -a unique_files=()
            local seen_list=""
            for f in "${source_files[@]}"; do
                if [[ "$seen_list" != *"|$f|"* ]]; then
                    seen_list="${seen_list}|$f|"
                    unique_files+=("$f")
                fi
            done
            source_files=("${unique_files[@]}")
        fi
    fi

    # Filter to testable source files only (exclude test files, configs, etc.)
    local -a testable_files=()
    for f in "${source_files[@]}"; do
        if _test_is_testable_file "$f"; then
            testable_files+=("$f")
        fi
    done

    if [ ${#testable_files[@]} -eq 0 ]; then
        if [ "$test_json" = true ]; then
            echo '{"status":"no_files","message":"No testable source files found","files_scanned":'"${#source_files[@]}"'}'
        else
            echo -e "${YELLOW}No testable source files found.${NC}"
            if [ "$test_changed" = true ]; then
                echo "Tip: Use --file or --dir to specify source files directly."
            fi
        fi
        return 1
    fi

    # --- Generate tests ---
    local total_files=${#testable_files[@]}
    local generated=0
    local skipped=0
    local -a generated_paths=()
    local json_entries=""

    if [ "$test_json" = false ] && [ "$test_dry_run" = false ]; then
        echo -e "${BOLD}Loki Test Generator${NC}"
        echo -e "Target coverage: ${CYAN}${test_coverage}%${NC}"
        echo -e "Files to process: ${CYAN}${total_files}${NC}"
        echo ""
    elif [ "$test_json" = false ] && [ "$test_dry_run" = true ]; then
        echo -e "${BOLD}Loki Test Generator (dry run)${NC}"
        echo -e "Files to process: ${CYAN}${total_files}${NC}"
        echo ""
    fi

    for src_file in "${testable_files[@]}"; do
        local lang=""
        local framework=""
        local test_path=""

        # Detect language
        lang=$(_test_detect_language "$src_file")
        if [ -z "$lang" ]; then
            ((++skipped))
            [ "$test_verbose" = true ] && echo -e "  ${DIM}Skip: $src_file (unsupported language)${NC}"
            continue
        fi

        # Detect or use specified framework
        if [ -n "$test_format" ] && [ "$test_format" != "auto" ]; then
            framework="$test_format"
        else
            framework=$(_test_detect_framework "$lang")
        fi

        # Determine test output path
        test_path=$(_test_output_path "$src_file" "$lang" "$framework" "$test_output")

        # Extract testable constructs
        local constructs=""
        constructs=$(_test_extract_constructs "$src_file" "$lang")

        if [ -z "$constructs" ]; then
            ((++skipped))
            [ "$test_verbose" = true ] && echo -e "  ${DIM}Skip: $src_file (no testable constructs found)${NC}"
            continue
        fi

        local construct_count
        construct_count=$(echo "$constructs" | wc -l | tr -d ' ')

        if [ "$test_dry_run" = true ]; then
            if [ "$test_json" = true ]; then
                local json_entry
                json_entry=$(printf '{"source":"%s","test_path":"%s","language":"%s","framework":"%s","constructs":%d}' \
                    "$src_file" "$test_path" "$lang" "$framework" "$construct_count")
                if [ -n "$json_entries" ]; then
                    json_entries="${json_entries},${json_entry}"
                else
                    json_entries="$json_entry"
                fi
            else
                echo -e "  ${CYAN}$src_file${NC}"
                echo -e "    -> ${GREEN}$test_path${NC}"
                echo -e "    Language: $lang | Framework: $framework | Constructs: $construct_count"
                if [ "$test_verbose" = true ]; then
                    echo "$constructs" | while IFS= read -r c; do
                        echo -e "      - $c"
                    done
                fi
            fi
            ((++generated))
            continue
        fi

        # Generate test content
        local test_content=""
        test_content=$(_test_generate_content "$src_file" "$lang" "$framework" "$constructs" "$test_coverage")

        # Write test file
        local test_dir_path
        test_dir_path=$(dirname "$test_path")
        mkdir -p "$test_dir_path"
        echo "$test_content" > "$test_path"

        generated_paths+=("$test_path")
        ((++generated))

        if [ "$test_json" = false ]; then
            echo -e "  ${GREEN}[generated]${NC} $test_path  (${construct_count} constructs, $framework)"
        else
            local json_entry
            json_entry=$(printf '{"source":"%s","test_path":"%s","language":"%s","framework":"%s","constructs":%d,"status":"generated"}' \
                "$src_file" "$test_path" "$lang" "$framework" "$construct_count")
            if [ -n "$json_entries" ]; then
                json_entries="${json_entries},${json_entry}"
            else
                json_entries="$json_entry"
            fi
        fi
    done

    # --- Output summary ---
    if [ "$test_json" = true ]; then
        local mode="generate"
        [ "$test_dry_run" = true ] && mode="dry_run"
        printf '{"status":"success","mode":"%s","files_scanned":%d,"tests_generated":%d,"skipped":%d,"coverage_target":%d,"results":[%s]}\n' \
            "$mode" "$total_files" "$generated" "$skipped" "$test_coverage" "$json_entries"
    else
        echo ""
        if [ "$test_dry_run" = true ]; then
            echo -e "${BOLD}Dry run summary:${NC} $generated test files would be generated, $skipped skipped"
        else
            echo -e "${BOLD}Summary:${NC} $generated test files generated, $skipped skipped"
            if [ ${#generated_paths[@]} -gt 0 ] && [ "$test_verbose" = true ]; then
                echo ""
                echo "Generated files:"
                for p in "${generated_paths[@]}"; do
                    echo "  $p"
                done
            fi
        fi
    fi

    if [ "$generated" -eq 0 ]; then
        return 1
    fi
    return 0
}

# --- Test generation helper functions ---

# Find source files in a directory (non-test, non-config files)
_test_find_source_files() {
    local dir="$1"
    find "$dir" -type f \( \
        -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \
        -o -name "*.py" -o -name "*.go" -o -name "*.rs" \
        -o -name "*.java" -o -name "*.rb" -o -name "*.sh" \
    \) ! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/vendor/*" \
       ! -path "*/__pycache__/*" ! -path "*/target/*" ! -path "*/dist/*" \
       ! -path "*/build/*" ! -path "*/.loki/*" \
    2>/dev/null | sort
}

# Check if a file is a testable source file (not a test, config, or generated file)
_test_is_testable_file() {
    local f="$1"
    local base
    base=$(basename "$f")

    # Must be a recognized source file extension
    case "$f" in
        *.js|*.ts|*.jsx|*.tsx|*.py|*.go|*.rs|*.java|*.rb|*.sh) ;;
        *) return 1 ;;
    esac

    # Skip test files
    case "$base" in
        test_*|*_test.*|*.test.*|*.spec.*|*_spec.*|test.*) return 1 ;;
    esac
    case "$f" in
        */__tests__/*|*/tests/*|*/test/*|*/spec/*|*_test/*) return 1 ;;
    esac

    # Skip config and generated files
    case "$base" in
        setup.*|config.*|*.config.*|*.d.ts|__init__.py|conftest.py) return 1 ;;
        jest.config.*|vitest.config.*|webpack.config.*|rollup.config.*) return 1 ;;
        .eslintrc.*|.prettierrc.*|tsconfig.*|package.json) return 1 ;;
        Makefile|Dockerfile|*.lock|*.min.js|*.min.css) return 1 ;;
    esac

    return 0
}

# Detect language from file extension
_test_detect_language() {
    local f="$1"
    case "$f" in
        *.ts|*.tsx) echo "typescript" ;;
        *.js|*.jsx) echo "javascript" ;;
        *.py) echo "python" ;;
        *.go) echo "go" ;;
        *.rs) echo "rust" ;;
        *.java) echo "java" ;;
        *.rb) echo "ruby" ;;
        *.sh) echo "bash" ;;
        *) echo "" ;;
    esac
}

# Detect test framework based on language and project files
_test_detect_framework() {
    local lang="$1"
    case "$lang" in
        typescript|javascript)
            if [ -f "vitest.config.ts" ] || [ -f "vitest.config.js" ]; then
                echo "vitest"
            elif [ -f ".mocharc.yml" ] || [ -f ".mocharc.json" ] || [ -f ".mocharc.js" ]; then
                echo "mocha"
            elif [ -f "jest.config.ts" ] || [ -f "jest.config.js" ] || [ -f "jest.config.json" ]; then
                echo "jest"
            elif [ -f "package.json" ] && grep -q '"vitest"' package.json 2>/dev/null; then
                echo "vitest"
            elif [ -f "package.json" ] && grep -q '"jest"' package.json 2>/dev/null; then
                echo "jest"
            else
                echo "jest"
            fi
            ;;
        python) echo "pytest" ;;
        go) echo "go-test" ;;
        rust) echo "cargo-test" ;;
        java) echo "junit" ;;
        ruby) echo "rspec" ;;
        bash) echo "bats" ;;
        *) echo "unknown" ;;
    esac
}

# Determine output path for generated test file
_test_output_path() {
    local src="$1"
    local lang="$2"
    local framework="$3"
    local custom_output="$4"

    local dir
    local base
    local name
    local ext
    dir=$(dirname "$src")
    base=$(basename "$src")
    name="${base%.*}"
    ext="${base##*.}"

    # Use custom output dir if specified
    if [ -n "$custom_output" ]; then
        dir="$custom_output"
    fi

    case "$framework" in
        jest|vitest)
            # __tests__/ directory convention
            if [ -n "$custom_output" ]; then
                echo "${dir}/${name}.test.${ext}"
            else
                echo "${dir}/__tests__/${name}.test.${ext}"
            fi
            ;;
        mocha)
            if [ -n "$custom_output" ]; then
                echo "${dir}/${name}.spec.${ext}"
            else
                echo "${dir}/test/${name}.spec.${ext}"
            fi
            ;;
        pytest)
            if [ -n "$custom_output" ]; then
                echo "${dir}/test_${name}.py"
            else
                echo "${dir}/tests/test_${name}.py"
            fi
            ;;
        go-test)
            # Go tests live alongside source
            echo "${dir}/${name}_test.go"
            ;;
        cargo-test)
            # Rust tests in same file or tests/ dir
            if [ -n "$custom_output" ]; then
                echo "${dir}/${name}_test.rs"
            else
                echo "${dir}/tests/${name}_test.rs"
            fi
            ;;
        junit)
            if [ -n "$custom_output" ]; then
                echo "${dir}/${name}Test.java"
            else
                echo "${dir}/test/${name}Test.java"
            fi
            ;;
        rspec)
            if [ -n "$custom_output" ]; then
                echo "${dir}/${name}_spec.rb"
            else
                echo "${dir}/spec/${name}_spec.rb"
            fi
            ;;
        bats)
            if [ -n "$custom_output" ]; then
                echo "${dir}/${name}.bats"
            else
                echo "${dir}/tests/${name}.bats"
            fi
            ;;
        *)
            echo "${dir}/tests/test_${name}.${ext}"
            ;;
    esac
}

# Extract testable constructs from a source file (functions, classes, exports)
_test_extract_constructs() {
    local src="$1"
    local lang="$2"

    case "$lang" in
        javascript|typescript)
            # Match exported functions, classes, consts, and standalone functions
            grep -E '^\s*(export\s+)?(default\s+)?(async\s+)?function\s+\w+|^\s*(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?\(|^\s*(export\s+)?class\s+\w+|^module\.exports' "$src" 2>/dev/null \
                | sed 's/^\s*//' | head -50
            ;;
        python)
            # Match function definitions, class definitions
            grep -E '^\s*(async\s+)?def\s+\w+|^\s*class\s+\w+' "$src" 2>/dev/null \
                | grep -v '^\s*def\s+_' \
                | sed 's/^\s*//' | head -50
            ;;
        go)
            # Match function definitions (exported only - capitalized)
            grep -E '^func\s+(\([^)]+\)\s+)?[A-Z]\w*\(' "$src" 2>/dev/null \
                | head -50
            ;;
        rust)
            # Match pub functions and impl blocks
            grep -E '^\s*pub\s+(async\s+)?fn\s+\w+|^\s*impl\s+' "$src" 2>/dev/null \
                | sed 's/^\s*//' | head -50
            ;;
        java)
            # Match public methods and classes
            grep -E '^\s*public\s+(static\s+)?\w+\s+\w+\s*\(|^\s*public\s+class\s+\w+' "$src" 2>/dev/null \
                | sed 's/^\s*//' | head -50
            ;;
        ruby)
            # Match def and class
            grep -E '^\s*def\s+\w+|^\s*class\s+\w+' "$src" 2>/dev/null \
                | sed 's/^\s*//' | head -50
            ;;
        bash)
            # Match function definitions
            grep -E '^\s*\w+\s*\(\)\s*\{|^\s*function\s+\w+' "$src" 2>/dev/null \
                | sed 's/^\s*//' | head -50
            ;;
        *) echo "" ;;
    esac
}

# Generate test file content for a given source file
_test_generate_content() {
    local src="$1"
    local lang="$2"
    local framework="$3"
    local constructs="$4"
    local coverage="$5"

    local base
    local name
    base=$(basename "$src")
    name="${base%.*}"

    case "$framework" in
        jest|vitest)
            _test_gen_jest_vitest "$src" "$name" "$base" "$framework" "$constructs" "$coverage"
            ;;
        mocha)
            _test_gen_mocha "$src" "$name" "$base" "$constructs" "$coverage"
            ;;
        pytest)
            _test_gen_pytest "$src" "$name" "$base" "$constructs" "$coverage"
            ;;
        go-test)
            _test_gen_go "$src" "$name" "$base" "$constructs" "$coverage"
            ;;
        cargo-test)
            _test_gen_rust "$src" "$name" "$base" "$constructs" "$coverage"
            ;;
        junit)
            _test_gen_junit "$src" "$name" "$base" "$constructs" "$coverage"
            ;;
        rspec)
            _test_gen_rspec "$src" "$name" "$base" "$constructs" "$coverage"
            ;;
        bats)
            _test_gen_bats "$src" "$name" "$base" "$constructs" "$coverage"
            ;;
        *)
            echo "// Unsupported framework: $framework"
            ;;
    esac
}

# --- Framework-specific generators ---

_test_gen_jest_vitest() {
    local src="$1" name="$2" base="$3" framework="$4" constructs="$5" coverage="$6"

    local import_style="require"
    local ext="${base##*.}"
    if [[ "$ext" == "ts" || "$ext" == "tsx" ]]; then
        import_style="import"
    fi

    # Header
    if [ "$framework" = "vitest" ]; then
        echo "import { describe, it, expect, beforeEach, afterEach } from 'vitest';"
    fi

    # Import statement
    local rel_path
    rel_path=$(echo "$src" | sed 's|^\./||')
    local import_path="../${base%.*}"

    if [ "$import_style" = "import" ]; then
        echo "import { /* TODO: import constructs */ } from '${import_path}';"
    else
        echo "const ${name} = require('${import_path}');"
    fi
    echo ""
    echo "// Generated test suite for ${base}"
    echo "// Target coverage: ${coverage}%"
    echo "// Source: ${src}"
    echo ""
    echo "describe('${name}', () => {"

    # Generate test cases from constructs
    echo "$constructs" | while IFS= read -r construct; do
        [ -z "$construct" ] && continue
        # Skip module.exports lines
        echo "$construct" | grep -q '^module\.exports' && continue
        local func_name=""
        # Extract function/class name
        func_name=$(echo "$construct" | grep -oE '(function|const|let|var|class)\s+\w+' | awk '{print $2}')
        if [ -z "$func_name" ]; then
            func_name=$(echo "$construct" | grep -oE '\w+\s*=' | sed 's/\s*=$//')
        fi
        [ -z "$func_name" ] && continue

        # Check if it's a class
        if echo "$construct" | grep -q 'class\s'; then
            echo ""
            echo "  describe('${func_name}', () => {"
            echo "    it('should be instantiable', () => {"
            echo "      // TODO: const instance = new ${func_name}();"
            echo "      // expect(instance).toBeDefined();"
            echo "    });"
            echo ""
            echo "    it('should have expected properties', () => {"
            echo "      // TODO: verify instance properties"
            echo "    });"
            echo "  });"
        else
            echo ""
            echo "  describe('${func_name}', () => {"
            echo "    it('should return expected result for valid input', () => {"
            echo "      // TODO: const result = ${func_name}(/* args */);"
            echo "      // expect(result).toEqual(/* expected */);"
            echo "    });"
            echo ""
            echo "    it('should handle edge cases', () => {"
            echo "      // TODO: test with null, undefined, empty, boundary values"
            echo "    });"
            echo ""
            echo "    it('should throw on invalid input', () => {"
            echo "      // TODO: expect(() => ${func_name}(/* invalid */)).toThrow();"
            echo "    });"
            echo "  });"
        fi
    done

    echo "});"
}

_test_gen_mocha() {
    local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"

    echo "const { expect } = require('chai');"
    echo "const ${name} = require('../${name}');"
    echo ""
    echo "// Generated test suite for ${base}"
    echo "// Target coverage: ${coverage}%"
    echo ""
    echo "describe('${name}', function () {"

    echo "$constructs" | while IFS= read -r construct; do
        [ -z "$construct" ] && continue
        echo "$construct" | grep -q '^module\.exports' && continue
        local func_name=""
        func_name=$(echo "$construct" | grep -oE '(function|const|let|var|class)\s+\w+' | awk '{print $2}')
        [ -z "$func_name" ] && continue

        echo ""
        echo "  describe('${func_name}', function () {"
        echo "    it('should return expected result', function () {"
        echo "      // TODO: const result = ${name}.${func_name}(/* args */);"
        echo "      // expect(result).to.equal(/* expected */);"
        echo "    });"
        echo ""
        echo "    it('should handle edge cases', function () {"
        echo "      // TODO: test edge cases"
        echo "    });"
        echo "  });"
    done

    echo "});"
}

_test_gen_pytest() {
    local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"

    local module_name
    module_name=$(echo "$name" | tr '-' '_')

    echo "\"\"\"Tests for ${base}."
    echo ""
    echo "Target coverage: ${coverage}%"
    echo "Source: ${src}"
    echo "\"\"\""
    echo "import pytest"
    echo "# from ${module_name} import  # TODO: import from source module"
    echo ""

    echo "$constructs" | while IFS= read -r construct; do
        [ -z "$construct" ] && continue
        local func_name=""

        if echo "$construct" | grep -q '^\s*class\s'; then
            func_name=$(echo "$construct" | grep -oE 'class\s+\w+' | awk '{print $2}')
            [ -z "$func_name" ] && continue
            echo ""
            echo "class Test${func_name}:"
            echo "    \"\"\"Tests for ${func_name} class.\"\"\""
            echo ""
            echo "    def test_init(self):"
            echo "        \"\"\"Test ${func_name} instantiation.\"\"\""
            echo "        # TODO: instance = ${func_name}()"
            echo "        # assert instance is not None"
            echo "        pass"
            echo ""
            echo "    def test_expected_behavior(self):"
            echo "        \"\"\"Test ${func_name} expected behavior.\"\"\""
            echo "        # TODO: implement"
            echo "        pass"
        else
            func_name=$(echo "$construct" | grep -oE 'def\s+\w+' | awk '{print $2}')
            [ -z "$func_name" ] && continue
            echo ""
            echo "def test_${func_name}_valid_input():"
            echo "    \"\"\"Test ${func_name} with valid input.\"\"\""
            echo "    # TODO: result = ${func_name}(/* args */)"
            echo "    # assert result == expected"
            echo "    pass"
            echo ""
            echo ""
            echo "def test_${func_name}_edge_cases():"
            echo "    \"\"\"Test ${func_name} edge cases.\"\"\""
            echo "    # TODO: test with None, empty, boundary values"
            echo "    pass"
            echo ""
            echo ""
            echo "def test_${func_name}_invalid_input():"
            echo "    \"\"\"Test ${func_name} with invalid input.\"\"\""
            echo "    # TODO: with pytest.raises(ValueError):"
            echo "    #     ${func_name}(/* invalid */)"
            echo "    pass"
        fi
    done
}

_test_gen_go() {
    local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"

    local pkg
    pkg=$(grep -m1 '^package\s' "$src" 2>/dev/null | awk '{print $2}')
    [ -z "$pkg" ] && pkg="main"

    echo "package ${pkg}"
    echo ""
    echo "import ("
    echo "	\"testing\""
    echo ")"
    echo ""
    echo "// Generated tests for ${base}"
    echo "// Target coverage: ${coverage}%"

    echo "$constructs" | while IFS= read -r construct; do
        [ -z "$construct" ] && continue
        local func_name=""
        func_name=$(echo "$construct" | grep -oE 'func\s+(\([^)]+\)\s+)?\w+' | grep -oE '\w+$')
        [ -z "$func_name" ] && continue

        echo ""
        echo "func Test${func_name}(t *testing.T) {"
        echo "	t.Run(\"valid input\", func(t *testing.T) {"
        echo "		// TODO: result := ${func_name}(/* args */)"
        echo "		// if result != expected {"
        echo "		// 	t.Errorf(\"${func_name}() = %v, want %v\", result, expected)"
        echo "		// }"
        echo "	})"
        echo ""
        echo "	t.Run(\"edge cases\", func(t *testing.T) {"
        echo "		// TODO: test boundary values"
        echo "	})"
        echo "}"
    done
}

_test_gen_rust() {
    local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"

    echo "// Generated tests for ${base}"
    echo "// Target coverage: ${coverage}%"
    echo ""
    echo "#[cfg(test)]"
    echo "mod tests {"
    echo "    use super::*;"
    echo ""

    echo "$constructs" | while IFS= read -r construct; do
        [ -z "$construct" ] && continue
        local func_name=""
        func_name=$(echo "$construct" | grep -oE 'fn\s+\w+' | awk '{print $2}')
        [ -z "$func_name" ] && continue

        echo "    #[test]"
        echo "    fn test_${func_name}_valid_input() {"
        echo "        // TODO: let result = ${func_name}(/* args */);"
        echo "        // assert_eq!(result, expected);"
        echo "    }"
        echo ""
        echo "    #[test]"
        echo "    fn test_${func_name}_edge_cases() {"
        echo "        // TODO: test boundary values"
        echo "    }"
        echo ""
    done

    echo "}"
}

_test_gen_junit() {
    local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"

    echo "import org.junit.jupiter.api.Test;"
    echo "import org.junit.jupiter.api.BeforeEach;"
    echo "import static org.junit.jupiter.api.Assertions.*;"
    echo ""
    echo "/**"
    echo " * Generated tests for ${base}"
    echo " * Target coverage: ${coverage}%"
    echo " */"
    echo "class ${name}Test {"
    echo ""

    echo "$constructs" | while IFS= read -r construct; do
        [ -z "$construct" ] && continue
        local func_name=""
        func_name=$(echo "$construct" | grep -oE '(class|void|int|String|boolean|double|float|long)\s+\w+' | awk '{print $NF}')
        [ -z "$func_name" ] && continue

        # Skip class definitions
        if echo "$construct" | grep -q 'class\s'; then
            continue
        fi

        echo "    @Test"
        echo "    void test${func_name^}ValidInput() {"
        echo "        // TODO: var result = instance.${func_name}(/* args */);"
        echo "        // assertEquals(expected, result);"
        echo "    }"
        echo ""
        echo "    @Test"
        echo "    void test${func_name^}EdgeCases() {"
        echo "        // TODO: test boundary values"
        echo "    }"
        echo ""
    done

    echo "}"
}

_test_gen_rspec() {
    local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"

    echo "# Generated tests for ${base}"
    echo "# Target coverage: ${coverage}%"
    echo ""
    echo "require_relative '../${name}'"
    echo ""
    echo "RSpec.describe ${name^} do"

    echo "$constructs" | while IFS= read -r construct; do
        [ -z "$construct" ] && continue
        local func_name=""

        if echo "$construct" | grep -q 'class\s'; then
            func_name=$(echo "$construct" | grep -oE 'class\s+\w+' | awk '{print $2}')
            [ -z "$func_name" ] && continue
            echo ""
            echo "  describe ${func_name} do"
            echo "    it 'is instantiable' do"
            echo "      # TODO: instance = ${func_name}.new"
            echo "      # expect(instance).not_to be_nil"
            echo "    end"
            echo "  end"
        else
            func_name=$(echo "$construct" | grep -oE 'def\s+\w+' | awk '{print $2}')
            [ -z "$func_name" ] && continue
            echo ""
            echo "  describe '#${func_name}' do"
            echo "    it 'returns expected result' do"
            echo "      # TODO: result = subject.${func_name}"
            echo "      # expect(result).to eq(expected)"
            echo "    end"
            echo ""
            echo "    it 'handles edge cases' do"
            echo "      # TODO: test edge cases"
            echo "    end"
            echo "  end"
        fi
    done

    echo "end"
}

_test_gen_bats() {
    local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"

    echo "#!/usr/bin/env bats"
    echo "# Generated tests for ${base}"
    echo "# Target coverage: ${coverage}%"
    echo ""
    echo "setup() {"
    echo "    source \"\$(dirname \"\$BATS_TEST_FILENAME\")/../${base}\""
    echo "}"
    echo ""

    echo "$constructs" | while IFS= read -r construct; do
        [ -z "$construct" ] && continue
        local func_name=""
        func_name=$(echo "$construct" | grep -oE '\w+' | head -1)
        [ -z "$func_name" ] && continue

        echo "@test \"${func_name} returns expected result\" {"
        echo "    # TODO: run ${func_name} args"
        echo "    # [ \"\$status\" -eq 0 ]"
        echo "    # [ \"\$output\" = \"expected\" ]"
        echo "    skip \"TODO: implement\""
        echo "}"
        echo ""
        echo "@test \"${func_name} handles edge cases\" {"
        echo "    # TODO: test edge cases"
        echo "    skip \"TODO: implement\""
        echo "}"
        echo ""
    done
}

# Session report generator (v6.27.0)
cmd_report() {
    local session_id=""
    local format="text"
    local output_file=""
    local include_gates=true
    local include_agents=true
    local include_timeline=true

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki report${NC} - Session report generator (v6.27.0)"
                echo ""
                echo "Usage: loki report [options]"
                echo ""
                echo "Generates a readable session report from .loki/ data."
                echo ""
                echo "Options:"
                echo "  --session <id>       Report for specific session (default: latest)"
                echo "  --format <fmt>       Output format: text, markdown, html (default: text)"
                echo "  --output <file>      Save to file (default: stdout)"
                echo "  --no-gates           Exclude quality gate details"
                echo "  --no-agents          Exclude agent activity"
                echo "  --no-timeline        Exclude event timeline"
                echo "  --help, -h           Show this help"
                echo ""
                echo "Examples:"
                echo "  loki report                            # Text report to stdout"
                echo "  loki report --format markdown          # Markdown report"
                echo "  loki report --format html -o report.html  # HTML to file"
                echo "  loki report --no-gates --no-agents     # Minimal report"
                exit 0
                ;;
            --session) session_id="${2:-}"; shift 2 ;;
            --session=*) session_id="${1#*=}"; shift ;;
            --format) format="${2:-text}"; shift 2 ;;
            --format=*) format="${1#*=}"; shift ;;
            --output|-o) output_file="${2:-}"; shift 2 ;;
            --output=*) output_file="${1#*=}"; shift ;;
            --no-gates) include_gates=false; shift ;;
            --no-agents) include_agents=false; shift ;;
            --no-timeline) include_timeline=false; shift ;;
            --include-gates) include_gates=true; shift ;;
            --include-agents) include_agents=true; shift ;;
            --include-timeline) include_timeline=true; shift ;;
            *) echo -e "${RED}Unknown option: $1${NC}"; echo "Run 'loki report --help' for usage."; exit 1 ;;
        esac
    done

    case "$format" in
        text|markdown|md|html) ;;
        *) echo -e "${RED}Unknown format: $format${NC}"; echo "Supported: text, markdown, html"; exit 1 ;;
    esac

    # Normalize md -> markdown
    [ "$format" = "md" ] && format="markdown"

    if ! command -v python3 &>/dev/null; then
        echo -e "${RED}python3 is required for report generation${NC}"
        exit 1
    fi

    local report_output
    report_output=$(LOKI_DIR="${LOKI_DIR:-.loki}" \
        REPORT_FORMAT="$format" \
        REPORT_SESSION="$session_id" \
        REPORT_GATES="$include_gates" \
        REPORT_AGENTS="$include_agents" \
        REPORT_TIMELINE="$include_timeline" \
        python3 << 'REPORT_SCRIPT'
import json
import os
import glob
from datetime import datetime, timezone

loki_dir = os.environ.get("LOKI_DIR", ".loki")
fmt = os.environ.get("REPORT_FORMAT", "text")
session_id = os.environ.get("REPORT_SESSION", "")
show_gates = os.environ.get("REPORT_GATES", "true") == "true"
show_agents = os.environ.get("REPORT_AGENTS", "true") == "true"
show_timeline = os.environ.get("REPORT_TIMELINE", "true") == "true"

def load_json(path):
    try:
        with open(path) as f:
            return json.load(f)
    except:
        return None

def load_text(path):
    try:
        with open(path) as f:
            return f.read().strip()
    except:
        return None

def format_duration(start_str, end_str=None):
    try:
        start = datetime.fromisoformat(start_str.replace("Z", "+00:00"))
        if end_str:
            end = datetime.fromisoformat(end_str.replace("Z", "+00:00"))
        else:
            end = datetime.now(timezone.utc)
        delta = end - start
        hours, remainder = divmod(int(delta.total_seconds()), 3600)
        minutes, seconds = divmod(remainder, 60)
        if hours > 0:
            return f"{hours}h {minutes}m {seconds}s"
        elif minutes > 0:
            return f"{minutes}m {seconds}s"
        else:
            return f"{seconds}s"
    except:
        return "unknown"

# --- Gather data ---

# Session/autonomy state
autonomy = load_json(os.path.join(loki_dir, "autonomy-state.json")) or {}
orchestrator = load_json(os.path.join(loki_dir, "state", "orchestrator.json")) or {}
metrics = load_json(os.path.join(loki_dir, "state", "metrics.json")) or {}
config = load_json(os.path.join(loki_dir, "config.json")) or {}
dashboard_state = load_json(os.path.join(loki_dir, "dashboard-state.json")) or {}

# Project info
project_name = config.get("project_name", "")
if not project_name:
    # Try to get from git or cwd
    try:
        project_name = os.path.basename(os.getcwd())
    except:
        project_name = "Unknown Project"

# Timestamps
started_at = orchestrator.get("startedAt", autonomy.get("lastRun", ""))
status = autonomy.get("status", orchestrator.get("currentPhase", "unknown"))
version = orchestrator.get("version", "")
current_phase = orchestrator.get("currentPhase", "unknown")
retry_count = autonomy.get("retryCount", 0)

# Provider
provider_file = os.path.join(loki_dir, "state", "provider")
provider = "unknown"
if os.path.isdir(provider_file):
    for pf in glob.glob(os.path.join(provider_file, "*.json")):
        pd = load_json(pf)
        if pd and pd.get("name"):
            provider = pd["name"]
            break
elif os.path.isfile(provider_file):
    provider = load_text(provider_file) or "unknown"

# PRD
prd_path = autonomy.get("prdPath", "")
prd_summary = ""
if prd_path and os.path.exists(prd_path):
    prd_text = load_text(prd_path) or ""
    # Extract first non-empty, non-heading line as summary
    for line in prd_text.split("\n"):
        stripped = line.strip()
        if stripped and not stripped.startswith("#") and len(stripped) > 20:
            prd_summary = stripped[:200]
            break

# Quality gates
gate_failures = load_json(os.path.join(loki_dir, "quality", "gate-failure-count.json")) or {}
test_results = load_json(os.path.join(loki_dir, "quality", "test-results.json")) or {}
static_analysis = load_json(os.path.join(loki_dir, "quality", "static-analysis.json")) or {}
gate_failure_text = load_text(os.path.join(loki_dir, "quality", "gate-failures.txt")) or ""

# Reviews
reviews_dir = os.path.join(loki_dir, "quality", "reviews")
review_files = []
if os.path.isdir(reviews_dir):
    review_files = sorted(glob.glob(os.path.join(reviews_dir, "*.json")), reverse=True)[:5]

# Agents
agents = load_json(os.path.join(loki_dir, "state", "agents.json")) or []
if isinstance(agents, dict):
    agents = list(agents.values()) if agents else []

# Agent audit log
audit_log = os.path.join(loki_dir, "logs", "agent-audit.jsonl")
audit_entries = []
if os.path.exists(audit_log):
    try:
        with open(audit_log) as f:
            for line in f:
                try:
                    audit_entries.append(json.loads(line.strip()))
                except:
                    pass
    except:
        pass

# Queue stats
queue_completed = load_json(os.path.join(loki_dir, "queue", "completed.json")) or []
queue_failed = load_json(os.path.join(loki_dir, "queue", "failed.json")) or []
queue_pending = load_json(os.path.join(loki_dir, "queue", "pending.json")) or []
queue_in_progress = load_json(os.path.join(loki_dir, "queue", "in-progress.json")) or []

# Ensure lists
if isinstance(queue_completed, dict): queue_completed = queue_completed.get("tasks", [])
if isinstance(queue_failed, dict): queue_failed = queue_failed.get("tasks", [])
if isinstance(queue_pending, dict): queue_pending = queue_pending.get("tasks", [])
if isinstance(queue_in_progress, dict): queue_in_progress = queue_in_progress.get("tasks", [])

# Council
council_state = load_json(os.path.join(loki_dir, "council", "state.json")) or {}

# Events
events_file = os.path.join(loki_dir, "events.jsonl")
events = []
if os.path.exists(events_file):
    try:
        with open(events_file) as f:
            for line in f:
                try:
                    events.append(json.loads(line.strip()))
                except:
                    pass
    except:
        pass

# Git diff stats (run git command)
git_stats = ""
try:
    import subprocess
    result = subprocess.run(
        ["git", "diff", "--stat", "HEAD~1", "HEAD"],
        capture_output=True, text=True, timeout=10
    )
    if result.returncode == 0 and result.stdout.strip():
        git_stats = result.stdout.strip()
except:
    pass

# --- Build report sections ---

sections = []

# 1. Header
header_lines = []
header_lines.append(f"Project: {project_name}")
if version:
    header_lines.append(f"Version: {version}")
header_lines.append(f"Status: {status}")
if started_at:
    header_lines.append(f"Started: {started_at}")
    header_lines.append(f"Duration: {format_duration(started_at)}")
if provider != "unknown":
    header_lines.append(f"Provider: {provider}")
if current_phase != "unknown":
    header_lines.append(f"Phase: {current_phase}")
sections.append(("Session Overview", header_lines))

# 2. Executive Summary
summary_lines = []
tasks_done = len(queue_completed) if isinstance(queue_completed, list) else 0
tasks_failed_count = len(queue_failed) if isinstance(queue_failed, list) else 0
tasks_total = tasks_done + tasks_failed_count + (len(queue_pending) if isinstance(queue_pending, list) else 0) + (len(queue_in_progress) if isinstance(queue_in_progress, list) else 0)

if tasks_total > 0:
    summary_lines.append(f"Tasks: {tasks_done} completed, {tasks_failed_count} failed, {tasks_total} total")
else:
    summary_lines.append("No task queue data available")

summary_lines.append(f"Iterations: {retry_count}")

mc = metrics.get("tasks_completed", 0)
mf = metrics.get("tasks_failed", 0)
if mc or mf:
    summary_lines.append(f"Metrics: {mc} tasks completed, {mf} failed")

loc_added = metrics.get("lines_of_code_added", 0)
loc_test = metrics.get("lines_of_test_added", 0)
if loc_added or loc_test:
    summary_lines.append(f"Lines added: {loc_added} code, {loc_test} test")

council_votes = council_state.get("total_votes", 0)
if council_votes:
    approve = council_state.get("approve_votes", 0)
    reject = council_state.get("reject_votes", 0)
    summary_lines.append(f"Council: {council_votes} votes ({approve} approve, {reject} reject)")

if prd_summary:
    summary_lines.append(f"PRD: {prd_summary}")

sections.append(("Executive Summary", summary_lines))

# 3. RARV Cycles
rarv_lines = []
rarv_lines.append(f"Total iterations: {retry_count}")
rarv_lines.append(f"Max retries: {autonomy.get('maxRetries', 'N/A')}")
rarv_lines.append(f"Current phase: {current_phase}")
if autonomy.get("lastExitCode") is not None:
    rarv_lines.append(f"Last exit code: {autonomy.get('lastExitCode')}")
sections.append(("RARV Cycles", rarv_lines))

# 4. Agent Activity
if show_agents:
    agent_lines = []
    if agents:
        for a in agents:
            if isinstance(a, dict):
                aid = a.get("agent_id", a.get("agent_type", "unknown"))
                atype = a.get("agent_type", "")
                astatus = a.get("status", "unknown")
                model = a.get("model", "")
                completed = a.get("tasks_completed", [])
                task_count = len(completed) if isinstance(completed, list) else completed
                desc = f"{aid}"
                if atype:
                    desc += f" ({atype})"
                if model:
                    desc += f" [{model}]"
                desc += f" - {astatus}"
                if task_count:
                    desc += f", {task_count} tasks done"
                agent_lines.append(desc)
    else:
        agent_lines.append("No agent data recorded")

    if audit_entries:
        # Summarize by action type
        action_counts = {}
        for entry in audit_entries:
            action = entry.get("action", "unknown")
            action_counts[action] = action_counts.get(action, 0) + 1
        agent_lines.append("")
        agent_lines.append(f"Audit log: {len(audit_entries)} entries")
        for action, count in sorted(action_counts.items(), key=lambda x: -x[1])[:10]:
            agent_lines.append(f"  {action}: {count}")

    sections.append(("Agent Activity", agent_lines))

# 5. Quality Gates
if show_gates:
    gate_lines = []

    gate_names = [
        "static_analysis", "test_coverage", "code_review",
        "security_scan", "lint", "type_check",
        "integration_test", "e2e_test", "performance"
    ]

    if gate_failures:
        for gate in gate_names:
            failures = gate_failures.get(gate, 0)
            if failures > 0:
                gate_lines.append(f"  {gate}: {failures} failure(s)")
            else:
                gate_lines.append(f"  {gate}: PASS")
        if not any(gate_failures.get(g, 0) for g in gate_names):
            # Check if we have any non-standard gates
            for g, c in gate_failures.items():
                if g not in gate_names:
                    gate_lines.append(f"  {g}: {c} failure(s)")
    else:
        gate_lines.append("No quality gate data recorded")

    if test_results:
        gate_lines.append("")
        runner = test_results.get("runner", "unknown")
        passed = test_results.get("pass", "unknown")
        gate_lines.append(f"Test runner: {runner}")
        gate_lines.append(f"Tests passed: {passed}")
        if test_results.get("summary"):
            gate_lines.append(f"Summary: {test_results['summary'][:200]}")

    if gate_failure_text:
        gate_lines.append("")
        gate_lines.append("Gate failure details:")
        for line in gate_failure_text.split("\n")[:10]:
            gate_lines.append(f"  {line}")

    sections.append(("Quality Gates", gate_lines))

# 6. What Changed
change_lines = []
if git_stats:
    for line in git_stats.split("\n"):
        change_lines.append(line)
else:
    change_lines.append("No git diff data available (run from project root)")
sections.append(("What Changed", change_lines))

# 7. Tests
test_lines = []
if test_results:
    test_lines.append(f"Runner: {test_results.get('runner', 'unknown')}")
    test_lines.append(f"Passed: {test_results.get('pass', 'unknown')}")
    test_lines.append(f"Timestamp: {test_results.get('timestamp', 'unknown')}")
    if test_results.get("min_coverage"):
        test_lines.append(f"Min coverage target: {test_results['min_coverage']}%")
else:
    test_lines.append("No test result data recorded")
sections.append(("Tests", test_lines))

# 8. Key Decisions (from events and council)
decision_lines = []
verdicts = council_state.get("verdicts", [])
if verdicts:
    for v in verdicts[-10:]:
        result = v.get("result", "UNKNOWN")
        iteration = v.get("iteration", "?")
        ts = v.get("timestamp", "")
        approve = v.get("approve", 0)
        reject = v.get("reject", 0)
        decision_lines.append(f"Iteration {iteration}: {result} ({approve} approve / {reject} reject) {ts}")

stagnation = council_state.get("consecutive_no_change", 0)
if stagnation > 0:
    decision_lines.append(f"Stagnation streak: {stagnation} iterations with no change")

done_signals = council_state.get("done_signals", 0)
if done_signals > 0:
    decision_lines.append(f"Done signals detected: {done_signals}")

if not decision_lines:
    decision_lines.append("No key decisions recorded")
sections.append(("Key Decisions", decision_lines))

# 9. Timeline (from events)
if show_timeline and events:
    timeline_lines = []
    for evt in events[-20:]:
        ts = evt.get("timestamp", evt.get("ts", ""))
        etype = evt.get("type", evt.get("event", "unknown"))
        data = evt.get("data", evt.get("payload", {}))
        summary = ""
        if isinstance(data, dict):
            summary = data.get("message", data.get("description", json.dumps(data)[:80]))
        elif isinstance(data, str):
            summary = data[:80]
        if ts:
            timeline_lines.append(f"[{ts}] {etype}: {summary}")
        else:
            timeline_lines.append(f"{etype}: {summary}")
    if timeline_lines:
        sections.append(("Timeline", timeline_lines))

# --- Render ---

def render_text(sections):
    lines = []
    width = 72
    lines.append("=" * width)
    lines.append("  LOKI MODE SESSION REPORT")
    lines.append("=" * width)
    lines.append("")
    for title, content in sections:
        lines.append(f"--- {title} ---")
        for line in content:
            lines.append(f"  {line}")
        lines.append("")
    lines.append("-" * width)
    lines.append(f"  Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}")
    lines.append(f"  Loki Mode v{version or 'unknown'}")
    lines.append("-" * width)
    return "\n".join(lines)

def render_markdown(sections):
    lines = []
    lines.append("# Loki Mode Session Report")
    lines.append("")
    for title, content in sections:
        lines.append(f"## {title}")
        lines.append("")
        for line in content:
            if line.strip() == "":
                lines.append("")
            elif line.strip().startswith("  "):
                lines.append(f"- {line.strip()}")
            else:
                lines.append(line)
        lines.append("")
    lines.append("---")
    lines.append(f"*Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')} | Loki Mode v{version or 'unknown'}*")
    return "\n".join(lines)

def render_html(sections):
    lines = []
    lines.append("<!DOCTYPE html>")
    lines.append("<html lang=\"en\">")
    lines.append("<head>")
    lines.append("<meta charset=\"UTF-8\">")
    lines.append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">")
    lines.append("<title>Loki Mode Session Report</title>")
    lines.append("<style>")
    lines.append("""
body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    max-width: 800px;
    margin: 40px auto;
    padding: 0 20px;
    background: #0d1117;
    color: #c9d1d9;
    line-height: 1.6;
}
h1 {
    color: #58a6ff;
    border-bottom: 2px solid #30363d;
    padding-bottom: 12px;
}
h2 {
    color: #8b949e;
    font-size: 1.1em;
    text-transform: uppercase;
    letter-spacing: 1px;
    margin-top: 32px;
    border-bottom: 1px solid #21262d;
    padding-bottom: 8px;
}
.section-content {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 16px;
    margin: 12px 0;
    font-family: 'SF Mono', 'Fira Code', monospace;
    font-size: 0.9em;
    white-space: pre-wrap;
}
.footer {
    margin-top: 40px;
    padding-top: 16px;
    border-top: 1px solid #30363d;
    color: #484f58;
    font-size: 0.85em;
}
.pass { color: #3fb950; }
.fail { color: #f85149; }
.warn { color: #d29922; }
""")
    lines.append("</style>")
    lines.append("</head>")
    lines.append("<body>")
    lines.append("<h1>Loki Mode Session Report</h1>")

    for title, content in sections:
        lines.append(f"<h2>{_html_escape(title)}</h2>")
        lines.append("<div class=\"section-content\">")
        for line in content:
            escaped = _html_escape(line)
            # Highlight pass/fail
            if "PASS" in line:
                escaped = escaped.replace("PASS", "<span class=\"pass\">PASS</span>")
            if "failure" in line.lower() or "FAIL" in line.lower() or "failed" in line.lower():
                escaped = escaped.replace("failure", "<span class=\"fail\">failure</span>")
                escaped = escaped.replace("FAIL", "<span class=\"fail\">FAIL</span>")
                escaped = escaped.replace("failed", "<span class=\"fail\">failed</span>")
                escaped = escaped.replace("Failed", "<span class=\"fail\">Failed</span>")
            lines.append(escaped)
        lines.append("</div>")

    lines.append("<div class=\"footer\">")
    ts = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
    lines.append(f"Generated: {ts} | Loki Mode v{_html_escape(version or 'unknown')}")
    lines.append("</div>")
    lines.append("</body>")
    lines.append("</html>")
    return "\n".join(lines)

def _html_escape(s):
    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")

if fmt == "text":
    print(render_text(sections))
elif fmt == "markdown":
    print(render_markdown(sections))
elif fmt == "html":
    print(render_html(sections))
REPORT_SCRIPT
    ) || {
        echo -e "${RED}Report generation failed${NC}"
        exit 1
    }

    if [ -n "$output_file" ]; then
        echo "$report_output" > "$output_file"
        echo -e "${GREEN}Report saved to: $output_file${NC}"
    else
        echo "$report_output"
    fi
}

cmd_share() {
    local format="markdown"
    local visibility="--public"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                echo -e "${BOLD}loki share${NC} - Share session report as a GitHub Gist"
                echo ""
                echo "Usage: loki share [options]"
                echo ""
                echo "Generates a session report and uploads it as a GitHub Gist,"
                echo "returning a shareable URL."
                echo ""
                echo "Options:"
                echo "  --private            Create a secret gist (default: public)"
                echo "  --format <fmt>       Report format: text, markdown, html (default: markdown)"
                echo "  --help, -h           Show this help"
                echo ""
                echo "Examples:"
                echo "  loki share                         # Public markdown gist"
                echo "  loki share --private               # Secret gist"
                echo "  loki share --format html           # Public HTML gist"
                echo "  loki share --private --format text  # Secret text gist"
                echo ""
                echo "Requires: gh CLI (https://cli.github.com)"
                exit 0
                ;;
            --private) visibility=""; shift ;;
            --format) format="${2:-markdown}"; shift 2 ;;
            --format=*) format="${1#*=}"; shift ;;
            *) echo -e "${RED}Unknown option: $1${NC}"; echo "Run 'loki share --help' for usage."; exit 1 ;;
        esac
    done

    # Validate format
    case "$format" in
        text|markdown|md|html) ;;
        *) echo -e "${RED}Unknown format: $format${NC}"; echo "Supported: text, markdown, html"; exit 1 ;;
    esac
    [ "$format" = "md" ] && format="markdown"

    # Check gh CLI is installed
    if ! command -v gh &>/dev/null; then
        echo -e "${RED}gh CLI not found${NC}"
        echo ""
        echo "Install the GitHub CLI to use 'loki share':"
        echo "  brew install gh        # macOS"
        echo "  sudo apt install gh    # Ubuntu/Debian"
        echo "  https://cli.github.com # Other platforms"
        exit 1
    fi

    # Check gh is authenticated
    if ! gh auth status &>/dev/null 2>&1; then
        echo -e "${RED}GitHub CLI not authenticated${NC}"
        echo ""
        echo "Run 'gh auth login' to authenticate, then try again."
        exit 1
    fi

    # Check .loki directory exists
    local loki_dir="${LOKI_DIR:-.loki}"
    if [ ! -d "$loki_dir" ]; then
        echo -e "${RED}No .loki/ directory found${NC}"
        echo "Run 'loki start' first to create a session."
        exit 1
    fi

    # Determine file extension for gist
    local ext="md"
    case "$format" in
        text) ext="txt" ;;
        markdown) ext="md" ;;
        html) ext="html" ;;
    esac

    # Generate report to temp file
    local tmpfile
    tmpfile=$(mktemp "/tmp/loki-share-XXXXXX.$ext")

    echo "Generating session report..."
    if ! "$0" report --format "$format" > "$tmpfile" 2>/dev/null; then
        echo -e "${RED}Failed to generate session report${NC}"
        rm -f "$tmpfile"
        exit 1
    fi

    # Verify report is not empty
    if [ ! -s "$tmpfile" ]; then
        echo -e "${RED}Generated report is empty${NC}"
        rm -f "$tmpfile"
        exit 1
    fi

    # Upload as gist
    echo "Uploading session report..."
    local gist_desc="Loki Mode session report ($(date +%Y-%m-%dT%H:%M:%S))"
    _loki_gist_upload "$tmpfile" "$gist_desc" "$visibility"
}

# Internal helper: upload a single file as a GitHub Gist.
# Args: $1=file path, $2=description, $3=visibility ("--public", "--private",
# or "" for gh's default). NOTE: $3 is intentionally left unquoted at the
# gh call site so an empty value collapses (the --private case sets it to "").
# Removes the input file after the attempt. Prints "Shared: <url>" on success;
# on failure prints the gh error and exits 1.
_loki_gist_upload() {
    local file="$1"
    local desc="$2"
    local visibility="$3"

    # gh prints progress ("- Creating gist...", "Created public gist...") on
    # STDERR and the final gist URL on STDOUT. Capture them separately so the
    # URL we expose and print is clean: folding stderr into stdout pollutes the
    # "Shared:" line and the ready-to-post hook with multi-line gh chatter.
    local gist_out gist_err gist_exit
    local errfile
    # Fall back to /dev/null if mktemp fails (disk full / unwritable /tmp) so the
    # redirect target is always a real path -- never an empty string (2>"" is a
    # bash error). We lose the captured stderr text in that rare case, not the
    # clean URL parse.
    errfile=$(mktemp "/tmp/loki-gist-err-XXXXXX" 2>/dev/null) || errfile=/dev/null
    gist_out=$(gh gist create "$file" --desc "$desc" $visibility 2>"$errfile")
    gist_exit=$?
    gist_err=$(cat "$errfile" 2>/dev/null)
    [ "$errfile" != "/dev/null" ] && rm -f "$errfile"

    # Cleanup temp file
    rm -f "$file"

    if [ $gist_exit -ne 0 ]; then
        echo -e "${RED}Failed to create gist${NC}"
        [ -n "$gist_err" ] && echo "$gist_err"
        [ -n "$gist_out" ] && echo "$gist_out"
        exit 1
    fi

    # The URL is the gist permalink on stdout. Extract just the URL line in
    # case any non-URL text slips into stdout on some gh versions.
    local gist_url
    gist_url=$(printf '%s\n' "$gist_out" | grep -Eo 'https://gist\.github\.com/[^[:space:]]+' | head -1)
    [ -z "$gist_url" ] && gist_url="$gist_out"

    # Expose the URL to callers (e.g. cmd_proof prints a ready-to-post hook
    # after this returns). The shared "Shared:" line stays unchanged.
    LOKI_LAST_GIST_URL="$gist_url"
    echo -e "${GREEN}Shared: ${gist_url}${NC}"
}

# loki_tier_gate - R9 open-core tier/license seam.
#
# OSS-FIRST CONTRACT: this is a no-op ALLOW for OSS users. LOKI_TIER defaults
# to "oss" and every existing free feature stays fully free. This function is
# the single place where a future hosted/enterprise build would gate a
# hosted-only capability. It is NEVER called from any existing free command
# path; its only caller is the opt-in --hosted publish seam below. For OSS
# (the default), it always returns 0 (allow).
#
# Args: $1 = capability name (informational; e.g. "hosted_publish").
# Returns: 0 = allowed, 1 = gated (non-OSS tier without entitlement).
# Env:  LOKI_TIER (default "oss"), LOKI_LICENSE_KEY (optional, non-OSS only).
loki_tier_gate() {
    local capability="${1:-}"
    local tier="${LOKI_TIER:-oss}"

    # OSS tier: everything is allowed, always. No license, no network, no gate.
    if [ "$tier" = "oss" ]; then
        return 0
    fi

    # Non-OSS tiers (hosted/enterprise) are a SEAM only. The hosted backend
    # and license-verification service do not exist yet, so we cannot validate
    # an entitlement. Be honest: do not pretend to grant a paid capability.
    # A real hosted build replaces this branch with a verified license check.
    if [ -z "${LOKI_LICENSE_KEY:-}" ]; then
        echo -e "${YELLOW}LOKI_TIER='${tier}' requested but no LOKI_LICENSE_KEY set.${NC}" >&2
        echo "Hosted/enterprise license verification is not available yet." >&2
        echo "OSS users: leave LOKI_TIER unset (or 'oss') -- everything stays free." >&2
        return 1
    fi

    # A license key is present but there is no verification backend yet. We do
    # NOT fabricate a successful verification. The capability stays ungated for
    # OSS-equivalent use; the seam is documented in docs/OPEN-CORE-BOUNDARY.md.
    echo -e "${YELLOW}LOKI_LICENSE_KEY set but the verification backend is not available yet (R9 seam).${NC}" >&2
    return 0
}

# _loki_hosted_publish_proof - R9 hosted proof-publish client stub.
#
# Posts an ALREADY-REDACTED proof page to a self-hosted/SaaS endpoint given by
# LOKI_HOSTED_ENDPOINT. There is NO official Loki hosted backend yet; this is a
# clean client seam an operator can point at their own endpoint. We never
# fabricate a hosted URL: on success we print the URL the endpoint returned (or
# the endpoint itself); on any failure we print an honest error and exit non-0.
#
# Args: $1 = proof id, $2 = redacted index.html path, $3 = proof.json path.
# Returns: 0 on success, non-zero on missing endpoint / transport / non-2xx.
_loki_hosted_publish_proof() {
    local id="$1"
    local html="$2"
    local pj="$3"

    # Tier seam (no-op allow for OSS). Hosted publish is opt-in regardless.
    loki_tier_gate "hosted_publish" || true

    local endpoint="${LOKI_HOSTED_ENDPOINT:-}"
    if [ -z "$endpoint" ]; then
        echo -e "${YELLOW}Hosted publishing backend not available.${NC}" >&2
        echo "There is no official Loki hosted service yet (R9 ships the seam, not a live backend)." >&2
        echo "To publish to your own hosted endpoint, set LOKI_HOSTED_ENDPOINT to its URL." >&2
        echo "Or publish to a GitHub Gist instead: loki proof share ${id}" >&2
        return 1
    fi

    if ! command -v curl &>/dev/null; then
        echo -e "${RED}curl not found${NC}" >&2
        echo "Hosted publishing requires curl. Install curl or use: loki proof share ${id}" >&2
        return 1
    fi

    # CREDIBILITY: we upload the file the generator already redacted (the same
    # bytes 'loki proof share' would put on a gist). We do not build a fresh
    # body that could bypass redaction. If proof.json reports redaction was not
    # applied, refuse -- never publish an unredacted artifact.
    if [ -f "$pj" ]; then
        local redaction_ok
        redaction_ok=$(LOKI_PROOF_JSON="$pj" python3 - <<'PYEOF' 2>/dev/null || echo "unknown"
import json, os
try:
    d = json.load(open(os.environ["LOKI_PROOF_JSON"]))
except Exception:
    print("unknown")
else:
    print("yes" if (d.get("redaction") or {}).get("applied") else "no")
PYEOF
)
        if [ "$redaction_ok" = "no" ]; then
            echo -e "${RED}Refusing to publish: proof redaction was not applied.${NC}" >&2
            echo "Regenerate the proof (LOKI_PROOF=1) so the redactor runs, then retry." >&2
            return 1
        fi
    fi

    echo -e "${BOLD}Publishing proof '${id}' to hosted endpoint${NC}"
    echo "  endpoint: ${endpoint}"
    echo "  payload:  ${html} (already redacted by the generator)"
    echo ""

    # POST the redacted HTML. Auth header is sent only if a license key exists;
    # OSS users with their own endpoint need no key.
    local tmp_body tmp_code
    tmp_body=$(mktemp "/tmp/loki-hosted-XXXXXX.out")
    local -a curl_args=(-sS -o "$tmp_body" -w '%{http_code}' -X POST
        -H "Content-Type: text/html"
        -H "X-Loki-Proof-Id: ${id}"
        --data-binary "@${html}")
    if [ -n "${LOKI_LICENSE_KEY:-}" ]; then
        curl_args+=(-H "Authorization: Bearer ${LOKI_LICENSE_KEY}")
    fi
    tmp_code=$(curl "${curl_args[@]}" "$endpoint" 2>/dev/null)
    local curl_exit=$?

    if [ "$curl_exit" -ne 0 ]; then
        echo -e "${RED}Failed to reach hosted endpoint (curl exit ${curl_exit}).${NC}" >&2
        echo "Check LOKI_HOSTED_ENDPOINT or publish to a gist: loki proof share ${id}" >&2
        rm -f "$tmp_body"
        return 1
    fi

    # Accept any 2xx. The published URL comes from the endpoint response if it
    # returns one (we look for a "url" field), else we report the endpoint. We
    # NEVER print a fabricated URL.
    case "$tmp_code" in
        2*)
            local published_url
            published_url=$(LOKI_HOSTED_BODY="$tmp_body" LOKI_HOSTED_EP="$endpoint" python3 - <<'PYEOF' 2>/dev/null || true
import json, os
body_path = os.environ["LOKI_HOSTED_BODY"]
try:
    txt = open(body_path).read().strip()
except Exception:
    txt = ""
url = ""
try:
    d = json.loads(txt)
    if isinstance(d, dict):
        url = d.get("url") or d.get("public_url") or ""
except Exception:
    url = ""
print(url)
PYEOF
)
            rm -f "$tmp_body"
            if [ -n "$published_url" ]; then
                echo -e "${GREEN}Published: ${published_url}${NC}"
            else
                echo -e "${GREEN}Published to ${endpoint} (HTTP ${tmp_code}).${NC}"
                echo "The endpoint did not return a 'url' field; check your endpoint's response."
            fi
            return 0
            ;;
        *)
            echo -e "${RED}Hosted endpoint returned HTTP ${tmp_code}.${NC}" >&2
            if [ -s "$tmp_body" ]; then
                echo "Response:" >&2
                head -c 500 "$tmp_body" >&2
                echo "" >&2
            fi
            echo "Nothing was published. Or publish to a gist: loki proof share ${id}" >&2
            rm -f "$tmp_body"
            return 1
            ;;
    esac
}

# loki bench - head-to-head benchmark harness (R2).
# Subcommands: run <task> | vs <task> | list | verify <result.json>.
# Thin pass-through to benchmarks/bench/run.sh (shared python core runner.py).
# CREDIBILITY: Loki NEVER grades its own success. Success is decided by a
# held-out acceptance command run by the grader OUTSIDE the agent (exit code).
# No council / RARV-C / LLM-judge participates in scoring. See
# benchmarks/SCHEMA-adapter.md and benchmarks/SCHEMA-result.md.
cmd_bench() {
    local bench_sh="$SKILL_DIR/benchmarks/bench/run.sh"
    if [ ! -f "$bench_sh" ]; then
        log_error "benchmark harness not found: $bench_sh"
        return 1
    fi
    local sub="${1:-}"
    case "$sub" in
        ""|--help|-h|help)
            echo -e "${BOLD}loki bench${NC} - head-to-head benchmark harness (R2)"
            echo ""
            echo "Usage: loki bench <subcommand> [args] [options]"
            echo ""
            echo "Subcommands:"
            echo "  run <task>      Run Loki only on a task-spec"
            echo "  vs <task>       Run all configured tools on a task-spec (head-to-head)"
            echo "  list            List available task-specs"
            echo "  verify <file>   Recompute task_hash + check tool versions for a result.json"
            echo "  report <files>  Build results.json + RESULTS.md from per-tool result-rows"
            echo ""
            echo "Options:"
            echo "  --trials N      Number of trials per tool (default 3)"
            echo "  --model NAME    Override the model"
            echo "  --emit-proof    Emit an R1 proof-of-run for the Loki trial(s)"
            echo "  --out-dir DIR   (report) output dir for results.json + RESULTS.md"
            echo ""
            echo "Loki NEVER grades its own success: a held-out acceptance command run"
            echo "by the grader OUTSIDE the agent decides success by exit code."
            [ "$sub" = "" ] && return 1
            return 0
            ;;
    esac
    bash "$bench_sh" "$@"
}

# loki proof - inspect and share proof-of-run artifacts (.loki/proofs/<id>/).
# Subcommands: list | show <id> | open <id> | share <id>.
# The proof.json schema is frozen (R1 spec). Reads are tolerant of missing
# keys (early/degraded proofs) and default to "-".
cmd_proof() {
    local loki_dir="${LOKI_DIR:-.loki}"
    local proofs_dir="${loki_dir}/proofs"
    local sub="${1:-}"
    [ $# -gt 0 ] && shift

    case "$sub" in
        ""|--help|-h|help)
            echo -e "${BOLD}loki proof${NC} - inspect and share proof-of-run artifacts"
            echo ""
            echo "Usage: loki proof <subcommand> [args]"
            echo ""
            echo "Subcommands:"
            echo "  list                 List proof-of-run artifacts in .loki/proofs/"
            echo "  show <id>            Pretty-print .loki/proofs/<id>/proof.json"
            echo "  open <id>            Open .loki/proofs/<id>/index.html in a browser"
            echo "  share <id>           Publish the proof page as a GitHub Gist (opt-in)"
            echo ""
            echo "Options for 'share':"
            echo "  --yes                Skip the redaction-preview confirmation prompt"
            echo "  --private            Create a secret gist (default: public)"
            echo "  --hosted             Publish to LOKI_HOSTED_ENDPOINT (open-core seam; no official backend yet)"
            echo ""
            echo "Proofs are generated automatically at run completion (LOKI_PROOF=0 to opt out)."
            [ "$sub" = "" ] && exit 1
            exit 0
            ;;
        list)
            if [ ! -d "$proofs_dir" ]; then
                echo -e "${YELLOW}No proofs found.${NC} Run 'loki start' to generate one."
                exit 0
            fi
            local found=0
            local pj
            for pj in "$proofs_dir"/*/proof.json; do
                [ -f "$pj" ] || continue
                # Print the header lazily, only once a valid proof is found, so
                # an empty (or proof-less) proofs dir prints just the
                # "No proofs found" line -- matching the Bun route (proof.ts
                # listProofs, which returns before the header when rows is empty).
                if [ "$found" -eq 0 ]; then
                    printf "%-26s  %-20s  %-10s  %-9s  %s\n" "RUN_ID" "GENERATED_AT" "VERDICT" "COST_USD" "FILES"
                fi
                found=1
                LOKI_PROOF_JSON="$pj" python3 - <<'PYEOF'
import json, os
p = os.environ["LOKI_PROOF_JSON"]
try:
    with open(p) as f:
        d = json.load(f)
except Exception:
    d = {}
# Coerce missing AND explicitly-null fields to "-" so a null final_verdict /
# cost.usd / count renders as "-" rather than Python's str(None) == "None".
# This matches the Bun route (proof.ts str(): null/undefined -> "-").
def s(v):
    return "-" if v is None else str(v)
run_id = d.get("run_id")
gen = d.get("generated_at")
verdict = (d.get("council") or {}).get("final_verdict")
cost = (d.get("cost") or {}).get("usd")
files = (d.get("files_changed") or {}).get("count")
print("{:<26}  {:<20}  {:<10}  {:<9}  {}".format(
    s(run_id), s(gen), s(verdict), s(cost), s(files)))
PYEOF
            done
            if [ "$found" -eq 0 ]; then
                echo -e "${YELLOW}No proofs found.${NC} Run 'loki start' to generate one."
            fi
            exit 0
            ;;
        show)
            local id="${1:-}"
            if [ -z "$id" ]; then
                echo -e "${RED}Missing proof id.${NC} Use 'loki proof list'."
                exit 2
            fi
            local pj="${proofs_dir}/${id}/proof.json"
            if [ ! -f "$pj" ]; then
                echo -e "${RED}Proof not found: ${id}${NC}"
                echo "Use 'loki proof list' to see available proofs."
                exit 1
            fi
            if command -v jq &>/dev/null; then
                jq . "$pj"
            else
                LOKI_PROOF_JSON="$pj" python3 -c "import json,os; print(json.dumps(json.load(open(os.environ['LOKI_PROOF_JSON'])), indent=2))"
            fi
            exit 0
            ;;
        open)
            local id="${1:-}"
            if [ -z "$id" ]; then
                echo -e "${RED}Missing proof id.${NC} Use 'loki proof list'."
                exit 2
            fi
            local html="${proofs_dir}/${id}/index.html"
            if [ ! -f "$html" ]; then
                echo -e "${RED}Proof page not found: ${id}/index.html${NC}"
                echo "Use 'loki proof list' to see available proofs."
                exit 1
            fi
            echo -e "${GREEN}Opening proof: $html${NC}"
            if command -v open &>/dev/null; then
                open "$html"
            elif command -v xdg-open &>/dev/null; then
                xdg-open "$html"
            elif command -v start &>/dev/null; then
                start "$html"
            else
                echo ""
                echo "Could not detect browser opener."
                echo "Please open in browser: $html"
            fi
            exit 0
            ;;
        share)
            local id=""
            local skip_confirm=0
            local visibility="--public"
            local hosted=0
            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --yes|-y) skip_confirm=1; shift ;;
                    --private) visibility=""; shift ;;
                    --public) visibility="--public"; shift ;;
                    --hosted) hosted=1; shift ;;
                    -*) echo -e "${RED}Unknown option: $1${NC}"; exit 1 ;;
                    *) id="$1"; shift ;;
                esac
            done
            if [ -z "$id" ]; then
                echo -e "${RED}Missing proof id.${NC} Use 'loki proof list'."
                exit 2
            fi
            local html="${proofs_dir}/${id}/index.html"
            if [ ! -f "$html" ]; then
                echo -e "${RED}Proof page not found: ${id}/index.html${NC}"
                echo "Use 'loki proof list' to see available proofs."
                exit 1
            fi
            # R9 open-core hosted-publish seam. Only taken when the user
            # explicitly passes --hosted. The default gist path below stays
            # byte-for-byte unchanged for OSS users (zero hosted backend
            # required). We never silent-fall-back to gist here: the user asked
            # for hosted, so we POST to a configured LOKI_HOSTED_ENDPOINT or
            # print an honest "no endpoint configured" message and exit non-zero.
            # We never fabricate a hosted URL.
            if [ "$hosted" -eq 1 ]; then
                _loki_hosted_publish_proof "$id" "$html" "${proofs_dir}/${id}/proof.json"
                exit $?
            fi
            if ! command -v gh &>/dev/null; then
                echo -e "${RED}gh CLI not found${NC}"
                echo "Install the GitHub CLI to publish a proof:"
                echo "  brew install gh        # macOS"
                echo "  sudo apt install gh    # Ubuntu/Debian"
                echo "  https://cli.github.com # Other platforms"
                exit 1
            fi
            if ! gh auth status &>/dev/null 2>&1; then
                echo -e "${RED}GitHub CLI not authenticated${NC}"
                echo "Run 'gh auth login' to authenticate, then try again."
                exit 1
            fi

            # Redaction preview. The generator already redacts the proof
            # before writing index.html, so this is a transparency summary,
            # not a second redaction pass.
            local pj="${proofs_dir}/${id}/proof.json"
            local vis_label="public"
            [ -z "$visibility" ] && vis_label="secret"
            echo -e "${BOLD}Publishing proof '${id}' as a ${vis_label} GitHub Gist${NC}"
            echo ""
            echo "What will be shared:"
            echo "  - ${html}"
            if [ -f "$pj" ]; then
                LOKI_PROOF_JSON="$pj" python3 - <<'PYEOF' 2>/dev/null || true
import json, os
try:
    d = json.load(open(os.environ["LOKI_PROOF_JSON"]))
except Exception:
    d = {}
cost = (d.get("cost") or {}).get("usd", "-")
# usd is null when cost was not collected for this run. Show an honest
# "not recorded" instead of the literal "None" (a credibility wart in the
# preview the sharer reads right before publishing).
if cost is None:
    cost = "not recorded"
files = (d.get("files_changed") or {}).get("count", "-")
verdict = (d.get("council") or {}).get("final_verdict", "-")
red = d.get("redaction") or {}
print("  - cost.usd:        {}".format(cost))
print("  - files_changed:   {}".format(files))
print("  - council verdict: {}".format(verdict))
print("  - redaction:       applied={} rules_version={} redactions_count={}".format(
    red.get("applied", False), red.get("rules_version", "-"), red.get("redactions_count", "-")))
PYEOF
            fi
            echo ""
            echo -e "${YELLOW}Secrets, API keys, tokens, env values, and absolute paths have already been stripped by the generator.${NC}"
            echo ""

            if [ "$skip_confirm" -ne 1 ]; then
                printf "Publish this proof to a %s gist? [y/N] " "$vis_label"
                local reply=""
                read -r reply
                case "$reply" in
                    y|Y|yes|YES|Yes) ;;
                    *) echo "Aborted. Nothing was published."; exit 0 ;;
                esac
            fi

            # Copy to a temp file so _loki_gist_upload can remove it after.
            local tmpfile
            tmpfile=$(mktemp "/tmp/loki-proof-XXXXXX.html")
            cp "$html" "$tmpfile"
            echo "Uploading proof page..."
            local gist_desc="Loki Mode proof-of-run ${id}"
            _loki_gist_upload "$tmpfile" "$gist_desc" "$visibility"

            # Print a ready-to-post one-line hook (real cost + url) so the user
            # can paste it straight to X/HN. When cost was not collected, omit
            # the cost (never fabricate a number, never print "$0.00").
            if [ -n "${LOKI_LAST_GIST_URL:-}" ] && [ -f "$pj" ]; then
                local hook
                hook=$(LOKI_PROOF_JSON="$pj" LOKI_GIST_URL="$LOKI_LAST_GIST_URL" python3 - <<'PYEOF' 2>/dev/null || true
import json, os
try:
    d = json.load(open(os.environ["LOKI_PROOF_JSON"]))
except Exception:
    d = {}
cost = (d.get("cost") or {})
usd = cost.get("usd")


def fmt_usd(v):
    if v is None:
        return None
    try:
        n = float(v)
    except Exception:
        return None
    s = ("%.4f" % n).rstrip("0").rstrip(".")
    if "." not in s:
        s += ".00"
    elif len(s.split(".")[1]) == 1:
        s += "0"
    return "$" + s


def council_ratio(d):
    c = d.get("council") or {}
    if not c.get("enabled"):
        return None
    revs = c.get("reviewers") or []
    if not isinstance(revs, list) or not revs:
        return None
    ok = sum(1 for r in revs if isinstance(r, dict)
             and str(r.get("vote") or "").upper() in ("APPROVE", "APPROVED"))
    return ok, len(revs)


u = fmt_usd(usd)
lead = ("Built autonomously for " + u) if u is not None \
    else "Built autonomously by Loki Mode"
parts = [lead]
fc = (d.get("files_changed") or {}).get("count", 0)
try:
    fc = int(fc)
except Exception:
    fc = 0
parts.append("%d file%s changed" % (fc, "" if fc == 1 else "s"))
cr = council_ratio(d)
if cr:
    parts.append("%d-of-%d reviewers approved" % (cr[0], cr[1]))
print(" - ".join(parts) + " " + os.environ.get("LOKI_GIST_URL", ""))
PYEOF
)
                if [ -n "$hook" ]; then
                    echo ""
                    echo "Ready to post:"
                    echo "  ${hook}"
                fi
            fi
            exit 0
            ;;
        *)
            echo -e "${RED}Unknown subcommand: ${sub}${NC}"
            echo "Run 'loki proof --help' for usage."
            exit 1
            ;;
    esac
}

main "$@"
