#!/usr/bin/env bash
# Yana AI CLI — entry point
# Usage: yana-ai audit [target] [flags]
set -euo pipefail

# Resolve symlink so REPO_ROOT is correct even when yana is run via ~/.local/bin/yana
_script="${BASH_SOURCE[0]}"
while [[ -L "$_script" ]]; do
  _dir="$(cd -P "$(dirname "$_script")" && pwd)"
  _script="$(readlink "$_script")"
  [[ "$_script" == /* ]] || _script="$_dir/$_script"
done
SCRIPT_DIR="$(cd -P "$(dirname "$_script")" && pwd)"
unset _script _dir
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# Read live from .claude-plugin/plugin.json (same source of truth the banner's
# agents/skills/rules counts already use) so this can't drift from the real
# release version the way the old hardcoded "0.17.0" did. Falls back to a
# fixed string only if the file or python3 is missing.
YANA_VERSION="$(
  if [[ -f "$REPO_ROOT/.claude-plugin/plugin.json" ]] && command -v python3 &>/dev/null; then
    python3 -c "import json; print(json.load(open('$REPO_ROOT/.claude-plugin/plugin.json')).get('version','0.0.0'))" 2>/dev/null || true
  fi
)"
[[ -n "$YANA_VERSION" ]] || YANA_VERSION="0.0.0"

# ── yana-rt binary (prefer release, fall back to debug) ────────────────────
_find_rt() {
  if [[ -x "$REPO_ROOT/target/release/yana-rt" ]]; then
    echo "$REPO_ROOT/target/release/yana-rt"
  elif [[ -x "$REPO_ROOT/target/debug/yana-rt" ]]; then
    echo "$REPO_ROOT/target/debug/yana-rt"
  elif command -v yana-rt &>/dev/null; then
    echo "yana-rt"
  else
    echo ""
  fi
}
rt() {
  local bin; bin=$(_find_rt)
  if [[ -z "$bin" ]]; then
    echo -e "\n${RED}  ✗ yana-rt not found${RESET} — the Rust runtime is required for this command.\n" >&2
    echo    "  Install options:" >&2
    echo    "    cargo install yana-rt          # from crates.io (recommended)" >&2
    echo    "    cargo build --release          # build from source (run in this repo)" >&2
    echo -e "\n  Commands that work without yana-rt:" >&2
    echo    "    yana-ai verify / init / policy / guard / router / watch / stats" >&2
    echo    "    yana-ai explain / badge / harness / export / report / upgrade" >&2
    echo    "" >&2
    exit 3
  fi
  exec "$bin" "$@"
}

# ── Python scripts (not yet ported) ──────────────────────────────────────────
SCANNER_PY="$REPO_ROOT/core/scripts/audit_scanner.py"
ROUTER_PY="$REPO_ROOT/core/scripts/router_suggest.py"
CHECK_CONTEXT_PY="$REPO_ROOT/core/scripts/check_context_pack.py"
VALIDATE_SPEC_PY="$REPO_ROOT/core/scripts/validate_spec.py"
POLICY_PY="$REPO_ROOT/core/scripts/policy_manager.py"
GUARD_PY="$REPO_ROOT/core/scripts/guard_installer.py"
INIT_POLICY_PY="$REPO_ROOT/core/scripts/init_policy.py"
EXPLAIN_PY="$REPO_ROOT/core/scripts/explain_rule.py"
SCORE_PY="$REPO_ROOT/core/scripts/score_explain.py"
DIFF_REPORT_PY="$REPO_ROOT/core/scripts/diff_report.py"
REPORT_HTML_PY="$REPO_ROOT/core/scripts/report_html.py"
SCAN_URL_PY="$REPO_ROOT/core/scripts/scan_url.py"
RULE_IMPORT_PY="$REPO_ROOT/core/scripts/rule_import.py"
UPGRADE_PY="$REPO_ROOT/core/scripts/upgrade.py"
INIT_WIZ_PY="$REPO_ROOT/core/scripts/init_wizard.py"
VERIFY_PY="$REPO_ROOT/core/scripts/verify_hooks.py"
MONITOR_PY="$REPO_ROOT/core/scripts/monitor.py"
STATS_PY="$REPO_ROOT/core/scripts/stats.py"
LINT_PY="$REPO_ROOT/core/scripts/lint_rules.py"
SNAPSHOT_PY="$REPO_ROOT/core/scripts/snapshot.py"
POLICY_CHECK_PY="$REPO_ROOT/core/scripts/policy_check.py"
EXPORT_PY="$REPO_ROOT/core/scripts/export_findings.py"
HARNESS_PY="$REPO_ROOT/core/scripts/harness_export.py"
CHECK_FILE_PY="$REPO_ROOT/core/scripts/check_file.py"
TEMPLATE_PY="$REPO_ROOT/core/scripts/template_list.py"
RULE_PY="$REPO_ROOT/core/scripts/rule_add.py"
INSTALL_PY="$REPO_ROOT/core/scripts/install_project.py"
GIAMTHI_SERVICE_PY="$REPO_ROOT/core/scripts/giamthi_service.py"
BADGE_PY="$REPO_ROOT/core/scripts/badge_gen.py"
WATCH_PY="$REPO_ROOT/core/scripts/watch.py"
REPORT_PDF_PY="$REPO_ROOT/core/scripts/report_pdf.py"
CONFIG_PY="$REPO_ROOT/core/scripts/config_manager.py"
RULE_TEST_PY="$REPO_ROOT/core/scripts/rule_test.py"
CHECK_COUNTS_PY="$REPO_ROOT/core/scripts/check_counts.py"

# ── helpers ──────────────────────────────────────────────────────────────────

RED='\033[31m'; CYAN='\033[36m'; BOLD='\033[1m'; RESET='\033[0m'
# Pink-into-blue palette — box border/text in pink, numbers/highlights in
# blue. The wordmark gets a per-row pink→blue gradient (PINK_GRADIENT).
PINK='\033[38;5;213m'
PINK_VER='\033[38;5;75m'; PINK_TXT='\033[38;5;225m'; PINK_PATH='\033[38;5;146m'
PINK_GRADIENT=('\033[1;38;5;213m' '\033[1;38;5;207m' '\033[1;38;5;171m' '\033[1;38;5;135m' '\033[1;38;5;99m' '\033[1;38;5;75m')
# Light-pink background fill for the banner box interior (256-color index
# 224 = #ffd7d7). FG-only codes (PINK_VER/PINK_TXT/PINK_GRADIENT/PINK_PATH
# above) never touch the background byte, so they layer on top of this
# correctly as long as BG_PINK is set before them and RESET only happens
# at the end of each row's own printf -- never mid-row.
BG_PINK='\033[48;5;224m'

err()  { echo -e "${RED}Error:${RESET} $*" >&2; }
info() { echo -e "  ${CYAN}→${RESET} $*"; }

# ── banner ───────────────────────────────────────────────────────────────────
# Shown on bare `yana-ai` / `--help` invocation, mirrors how `claude` greets you
# on startup — own layout though, not a copy of Claude Code's. Stats are read
# live from .claude-plugin/plugin.json so the banner never goes stale. Terminals
# can't render the real mascot (girl-with-carrot logo in docs/yana-logo.png),
# so the bunny+carrot emoji stands in for it here. A chafa-generated truecolor
# block-art version was tried and reverted (2026-06-19) — it degrades to
# unrecognizable noise on any terminal/viewer that doesn't render 24-bit ANSI
# color, which isn't safe to assume. 🦀 sits next to it as a nod to yana-rt,
# the Rust runtime this CLI shells out to (rt() above).

_banner_pad() {
  local s="$1" w="$2" len=${#1}
  if [ "$w" -le 0 ]; then printf ''; return; fi
  if [ "$len" -gt "$w" ]; then
    s="${s:0:$((w>1?w-1:0))}…"
    len=$w
  fi
  printf '%s%*s' "$s" "$((w-len))" ""
}

# Truncates plain text to fit width $2 (with an ellipsis), used before
# colorizing any dynamic value that might be longer than the box — release
# notes, cwd, branch names. Keeps _banner_pad's padding math from going
# negative, which previously caused an overflowing line + a stray "…".
_banner_fit() {
  local s="$1" w="$2" len=${#1}
  if [ "$w" -le 0 ]; then printf ''; return; fi
  if [ "$len" -gt "$w" ]; then
    printf '%s…' "${s:0:$((w>1?w-1:0))}"
  else
    printf '%s' "$s"
  fi
}

_banner_dashes() {
  local i out="" n="$1"
  for ((i=0; i<n; i++)); do out="${out}─"; done
  printf '%s' "$out"
}

# Prints one boxed content row, right-padded to $BANNER_W using the plain-text
# length so ANSI color codes in $2 never throw off the alignment math.
_banner_row() {
  local plain="$1" colored="$2" indent="${3:-2}"
  printf "${PINK}${BG_PINK}  │%*s%b${PINK}${BG_PINK}%s│${RESET}\n" "$indent" "" "$colored" "$(_banner_pad '' "$((BANNER_W-indent-${#plain}))")"
}

# Two-column row: left cell ($LEFT_W wide) + divider + right cell ($RIGHT_W
# wide). Falls back to plain (uncolored) text for a cell if it had to be
# truncated, so an ellipsis never lands mid-escape-sequence.
_banner_row2() {
  local lplain="$1" lcolored="$2" rplain="$3" rcolored="$4"
  local lfit rfit
  lfit="$(_banner_fit "$lplain" "$((LEFT_W-1))")"
  rfit="$(_banner_fit "$rplain" "$((RIGHT_W-1))")"
  [[ "$lfit" != "$lplain" ]] && { lcolored="$lfit"; lplain="$lfit"; }
  [[ "$rfit" != "$rplain" ]] && { rcolored="$rfit"; rplain="$rfit"; }
  printf "${PINK}${BG_PINK}  │ %b${PINK}${BG_PINK}%s│ %b${PINK}${BG_PINK}%s│${RESET}\n" \
    "$lcolored" "$(_banner_pad '' "$((LEFT_W-1-${#lplain}))")" \
    "$rcolored" "$(_banner_pad '' "$((RIGHT_W-1-${#rplain}))")"
}

_banner_plugin_counts() {
  [[ -f "$REPO_ROOT/.claude-plugin/plugin.json" ]] || return 1
  command -v python3 &>/dev/null || return 1
  python3 -c "
import json
d = json.load(open('$REPO_ROOT/.claude-plugin/plugin.json')).get('contents', {})
print(d.get('agents',''), d.get('skills',''), d.get('rules',''), d.get('hooks',''), d.get('scripts',''), d.get('checks',''))
" 2>/dev/null
}

_banner_release_note() {
  local raw
  raw="$(git -C "$REPO_ROOT" log --format=%s -1 --grep='^release:' 2>/dev/null)"
  printf '%s' "${raw#release: }"
}

# Bunny hops left-to-right-and-back once before the banner settles. Set
# YANA_NO_ANIM=1 to skip it (e.g. scripted/CI use, or if it gets annoying).
_banner_hop() {
  [[ -n "${YANA_NO_ANIM:-}" ]] && return 0
  local positions=(0 3 6 9 12 9 6 3 0) p
  for p in "${positions[@]}"; do
    printf "\r  %*s${PINK}🐇${RESET}" "$p" ""
    sleep 0.06
  done
  printf "\r\033[K"
}

_banner_art() {
  # 6-row block-letter "YANA AI" wordmark — pure box-drawing chars, all
  # single-width, safe to pad/center alongside the rest of the box.
  printf '%s\n' \
    '██╗   ██╗ █████╗ ███╗   ██╗ █████╗     █████╗ ██╗' \
    '╚██╗ ██╔╝██╔══██╗████╗  ██║██╔══██╗   ██╔══██╗██║' \
    ' ╚████╔╝ ███████║██╔██╗ ██║███████║   ███████║██║' \
    '  ╚██╔╝  ██╔══██║██║╚██╗██║██╔══██║   ██╔══██║██║' \
    '   ██║   ██║  ██║██║ ╚████║██║  ██║   ██║  ██║██║' \
    '   ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝  ╚═╝   ╚═╝  ╚═╝╚═╝'
}

banner() {
  # Skip only when piped and not forced — explicit `yana banner` passes force=1.
  local _force="${1:-0}"
  [[ -t 1 ]] || [[ "${YANA_FORCE_BANNER:-0}" == "1" ]] || [[ "$_force" == "1" ]] || return 0
  # Fit the box to the real terminal instead of a fixed width — a hardcoded
  # wide box (previously 170) overflows on normal-size terminals and wraps
  # into a mess. Clamp so it stays tidy on narrow terminals but still
  # stretches out on wide ones instead of leaving the box looking narrow
  # and tall in the middle of a wide window.
  local cols
  cols="$(tput cols 2>/dev/null || echo 80)"
  BANNER_W=$((cols - 6))
  [[ "$BANNER_W" -gt 140 ]] && BANNER_W=140
  [[ "$BANNER_W" -lt 76 ]] && BANNER_W=76
  LEFT_W=$((BANNER_W * 32 / 100))
  # Floor LEFT_W so stats lines like "101 agents · 1980 skills" (24 chars)
  # never get ellipsis-truncated on narrower terminals.
  [[ "$LEFT_W" -lt 28 ]] && LEFT_W=28
  RIGHT_W=$((BANNER_W - LEFT_W - 1))
  local cwd="${PWD/#$HOME/~}"
  local branch dirty status_txt
  branch="$(git branch --show-current 2>/dev/null)"
  [[ -n "$branch" ]] || branch="(no branch)"
  dirty="$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')"
  [[ "$dirty" == "0" ]] && status_txt="clean" || status_txt="${dirty} changed"

  local counts agents skills rules hooks scripts checks
  counts="$(_banner_plugin_counts)"
  read -r agents skills rules hooks scripts checks <<< "$counts"

  local dashes blank
  dashes="$(_banner_dashes "$BANNER_W")"
  blank="$(_banner_pad '' "$BANNER_W")"

  _banner_hop
  printf "${PINK}  🐰🥕  🦀${RESET}\n\n"
  printf "${PINK}${BG_PINK}  ╭%s╮${RESET}\n" "$dashes"
  printf "${PINK}${BG_PINK}  │%s│${RESET}\n" "$blank"

  local art_i=0
  while IFS= read -r art_line; do
    _banner_row "$art_line" "${PINK_GRADIENT[$art_i]}${art_line}${RESET}" 3
    # NOT `((art_i++))` — with `set -e`, a bare arithmetic command whose
    # result is 0 (true on the first iteration, when art_i is still 0)
    # exits non-zero and kills the whole script under errexit.
    art_i=$((art_i + 1))
  done < <(_banner_art)

  printf "${PINK}${BG_PINK}  │%s│${RESET}\n" "$blank"

  local username
  username="$(git config user.name 2>/dev/null)"
  [[ -n "$username" ]] || username="$(whoami 2>/dev/null)"
  [[ -n "$username" ]] || username="bạn"

  local release
  release="$(_banner_release_note)"
  [[ -n "$release" ]] || release="(no release notes found)"

  # left column: identity + live stats + git/path
  local left_plain=(
    "v${YANA_VERSION} · chào ${username}"
    "Personal Agent OS"
    ""
  )
  local left_colored=(
    "${PINK_VER}v${YANA_VERSION}${RESET}${PINK}${BG_PINK} · ${PINK_TXT}chào ${username}${RESET}"
    "Personal Agent OS"
    ""
  )
  if [[ -n "$agents" ]]; then
    left_plain+=("${agents} agents · ${skills} skills" "${rules} rules · ${hooks} hooks" "${scripts} scripts · ${checks} checks" "")
    left_colored+=(
      "${PINK_VER}${agents}${RESET}${PINK}${BG_PINK} agents · ${PINK_VER}${skills}${RESET}${PINK}${BG_PINK} skills"
      "${PINK_VER}${rules}${RESET}${PINK}${BG_PINK} rules · ${PINK_VER}${hooks}${RESET}${PINK}${BG_PINK} hooks"
      "${PINK_VER}${scripts}${RESET}${PINK}${BG_PINK} scripts · ${PINK_VER}${checks}${RESET}${PINK}${BG_PINK} checks"
      ""
    )
  fi
  left_plain+=("${branch} (${status_txt})" "${cwd}")
  left_colored+=("${PINK_PATH}${branch}${RESET}${PINK}${BG_PINK} (${status_txt})" "${PINK_PATH}${cwd}${RESET}")

  # right column: tips + what's new (release note word-wrapped to fit)
  local right_plain=("Tips for getting started" "yana-ai doctor" "yana-ai init" "")
  local right_colored=("${PINK_TXT}Tips for getting started${RESET}" "${PINK_VER}yana-ai doctor${RESET}" "${PINK_VER}yana-ai init${RESET}" "")
  right_plain+=("What's new")
  right_colored+=("${PINK_TXT}What's new${RESET}")
  # Use `|| [[ -n "$wline" ]]` to capture fold's last line when it has no
  # trailing newline (bash 3.2 compat — no mapfile).
  local wline
  while IFS= read -r wline || [[ -n "$wline" ]]; do
    right_plain+=("$wline")
    right_colored+=("$wline")
  done < <(printf '%s' "$release" | fold -s -w "$((RIGHT_W-2))")

  local nrows=${#left_plain[@]}
  [[ ${#right_plain[@]} -gt $nrows ]] && nrows=${#right_plain[@]}

  local i
  for ((i=0; i<nrows; i++)); do
    _banner_row2 "${left_plain[$i]:-}" "${left_colored[$i]:-}" "${right_plain[$i]:-}" "${right_colored[$i]:-}"
  done

  printf "${PINK}${BG_PINK}  │%s│${RESET}\n" "$blank"
  printf "${PINK}${BG_PINK}  ╰%s╯${RESET}\n" "$dashes"
}

usage() {
  banner 1
  cat <<EOF

Usage: yana-ai <command> [target] [flags]

Commands:
  chat   [--provider p]   Interactive chat REPL — cloud (Anthropic/OpenAI) or local (Ollama)
  doctor [target]         Check environment health before starting an agent session
  doctor dispatch [target] Cross-check src/main.rs Commands enum vs bin/yana dispatch table
  audit  [target]         Scan AI agent setup for risk patterns (default target: .)
  policy <subcommand>     Policy Kit — safe config templates for audit findings
  guard  <subcommand>     Control Layer — install runtime enforcement hooks
  task   <subcommand>     Runtime — task lifecycle (create/list/done/status/drop)
  eval   <subcommand>     Runtime — evaluate task evidence (run/schema)
  memory <subcommand>     L3 shared memory — workspace-level facts across sessions
  bus    <subcommand>     Agent message bus — emit/read/reply/inbox
  cost   <subcommand>     Cost dashboard — token usage and spend tracking
  explain <rule-id>       Explain a finding — what it means, why it's risky, how to fix
  map    [target]         Agent Blast Radius Map — what your agent can reach
  init-policy <tool>      Generate safe config template (claude/mcp/github-actions/gitignore/env)
  score  [target]         Show score and optional deduction breakdown (--explain)
  badge  [target]         Generate shields.io badge URL for README
  watch  [target]         Watch config files and re-audit on change
  fix    <rule-id>        Auto-apply safe fix for a finding (opt-in, --dry-run)
  ci-check [target]      CI/CD pipeline health check (missing gates, weak perms)
  diff-report <b> <a>   Compare two audit JSON runs — show what changed
  rule   <subcommand>    Custom rule management (add/list/remove)
  install [target]       One-command yana-ai setup for a project
  giamthi <action>       OS supervisor: install/status/repair/uninstall/run
  report html [target]   Export audit report as standalone HTML
  report pdf  [target]   Export audit report as PDF
  scan-url <url>         Scan a GitHub repo URL (no permanent clone)
  rule import <src>      Import rule pack from URL or file
  upgrade                Self-update yana-ai to latest release
  init   [target]        Interactive setup wizard (engine, profile, guards)
  verify [target]        Verify all safety hooks are wired and active
  monitor [target]       Real-time audit log tail with color output
  stats  [target]        Audit score trend over time (--record to add scan)
  lint   [path]          Lint rule YAML files for schema correctness
  snapshot <sub>         Save/list/diff/delete audit snapshots
  export [target]        Export findings to CSV/Markdown/JUnit/JSON
  harness                Generate harness adapter files from core/rules/ (cursor/opencode/zed/all)
  policy check [target]  Verify applied configs match policy templates
  check  <file>          Scan a single file against all matching rules
  template list/show     List and preview policy templates
  graph  <subcommand>     Knowledge graph (build/show/search/onboard/diff)
  hunt   [target]         Active security scanner (secrets/code/deps/supply-chain)
  design <subcommand>     Design context (extract/show/init)
  filescan check <file>   On-demand malware check for a downloaded file (VirusTotal
                          hash lookup — needs VT_API_KEY, no file content uploaded)
  config <subcommand>     Manage .yana-ai/config.yml (list/get/set/reset/show)
  su-gia [--fix]          Sứ Giả — check/fix release-surface staleness
                          (docs version badges, component counts). Fires
                          automatically on tag push via herald.yml; runs
                          identically local or CI, any tool.
  plugin <subcommand>     Plugin hooks — register custom guards without forking
  vault  <subcommand>     Vietnamese-first knowledge vault with translation links
  os     <subcommand>     Yana OS (Program K) — agent/credential/resource visibility
  workspace <subcommand>  Unified workspace — blocks, links, inbox, memory, and governed actions
  router suggest          Look up recommended model tier for a task type
  validate-spec <file>    Validate task spec file against spec schema (legacy Python validator)
  spec   <subcommand>     Validate task spec files against the yana-ai schema (Rust — see also validate-spec)
  mission <subcommand>    Parallel mission orchestrator — create/task/dispatch/track
  route <subcommand>      Classify a task description — simple/complex/external
  check-context <dir>     Validate context-pack directory structure
  provenance check [target] Verify ported code (core/lib/*_adapted) has vendor source + attribution
  evidence run <cmd...>   Run a command, emit an HMAC-signed receipt as Truth Gate evidence
  evidence verify         Verify a signed receipt from stdin
  observability <subcommand> Audit activity dashboard — tool-call volume, allow/deny/warn rate
  skill-quality <subcommand> Per-skill outcome ledger from real task verdicts (human-gated)
  mcp                     Program J MCP server over stdio (opt-in build, not wired into any client)
  remote <subcommand>     Remote interfaces (Discord adapter — opt-in build; setup/test/serve)
  version                 Print version
  banner                  Print the welcome banner only (no command list)
  help                    Show this help

Doctor flags:
  --fix                    Show fix suggestions for WARN and FAIL items
  --json                   Output as JSON
  --no-color               Disable ANSI color output
  --quiet                  Only print health summary

Audit flags:
  --json                   Output as JSON
  --markdown <file>        Write Markdown report to file
  --sarif <file>           Write SARIF 2.1.0 report to file (GitHub Code Scanning)
  --diff <base>            Only scan files changed since base (e.g. origin/main)
  --fail-on <level>        Exit non-zero on findings at level+ (low/medium/high/critical)
  --only <category>        Run one scanner only
  --ignore <id>            Suppress a finding ID (repeatable)
  --no-color               Disable ANSI color output
  --quiet                  Only print score + risk level
  --watch                  Re-audit on file change (Ctrl-C to stop)
  --include-skills         Also scan core/skills/** + markdown docs (off by
                            default — high false-positive rate from skill
                            library docs/demo scripts, not production code)
  (reads .yana-aiignore if present — suppress known/accepted findings)

Router flags:
  --task <task>            Task type to look up (e.g. pr_review, security_audit)
  --list                   List all tasks and their assigned tiers
  --json                   Output as JSON
  --no-color               Disable ANSI color output

Examples:
  yana-ai doctor .
  yana-ai doctor . --fix
  yana-ai audit .
  yana-ai audit . --markdown report.md
  yana-ai audit . --sarif yana-ai.sarif
  yana-ai audit . --diff origin/main --fail-on high
  yana-ai audit . --fail-on high
  yana-ai policy list
  yana-ai policy show claude-settings
  yana-ai policy apply claude-settings
  yana-ai policy fixes AC001
  yana-ai guard list
  yana-ai guard install all
  yana-ai guard status
  yana-ai giamthi status .
  yana-ai giamthi repair .
  yana-ai task create "Fix auth bug" --scope "src/auth/"
  yana-ai task list
  yana-ai task done <id> --evidence "12 tests passed, build ok"
  yana-ai eval run <id>
  yana-ai eval schema
  yana-ai explain CI001
  yana-ai explain AC002
  yana-ai map .
  yana-ai map . --json
  yana-ai init-policy claude
  yana-ai init-policy mcp --dry-run
  yana-ai init-policy list
  yana-ai score .
  yana-ai score . --explain
  yana-ai badge .
  yana-ai badge . --url-only
  yana-ai watch .
  yana-ai fix AC002 --dry-run
  yana-ai fix CI007
  yana-ai router suggest --task pr_review
  yana-ai router suggest --list

EOF
}

# ── version ───────────────────────────────────────────────────────────────────

cmd_version() {
  echo "yana-ai $YANA_VERSION"
}

# ── doctor ────────────────────────────────────────────────────────────────────
# `doctor` has no explicit subcommand in the public CLI — `yana doctor .`
# always meant `doctor run .`, so that's the default. `dispatch` is the one
# named exception (cross-checks this dispatch table itself against
# src/main.rs) and must pass through as its own DoctorAction subcommand
# instead of being swallowed as a positional arg to `run`.

cmd_doctor() {
  local first="${1:-}"
  case "$first" in
    dispatch) rt doctor "$@" ;;
    *)        rt doctor run "$@" ;;
  esac
}

# ── router ────────────────────────────────────────────────────────────────────

cmd_router() {
  local subcmd="${1:-}"
  shift 2>/dev/null || true

  if [[ "$subcmd" != "suggest" ]]; then
    err "Usage: yana-ai router suggest --task <task> | --list"
    exit 1
  fi

  if ! command -v python3 &>/dev/null; then
    err "python3 is required to run yana-ai router."
    exit 3
  fi

  if [[ ! -f "$ROUTER_PY" ]]; then
    err "Router not found at $ROUTER_PY"
    exit 3
  fi

  python3 "$ROUTER_PY" "$@"
}

# ── audit ─────────────────────────────────────────────────────────────────────

cmd_audit() {
  rt scan "$@"
}


# ── policy ────────────────────────────────────────────────────────────────────

cmd_policy() {
  if ! command -v python3 &>/dev/null; then
    err "python3 is required to run yana-ai policy."
    exit 3
  fi

  if [[ ! -f "$POLICY_PY" ]]; then
    err "Policy manager not found at $POLICY_PY"
    exit 3
  fi

  python3 "$POLICY_PY" "$@"
}

# ── guard ─────────────────────────────────────────────────────────────────────

cmd_guard() {
  if ! command -v python3 &>/dev/null; then
    err "python3 is required to run yana-ai guard."
    exit 3
  fi

  if [[ ! -f "$GUARD_PY" ]]; then
    err "Guard installer not found at $GUARD_PY"
    exit 3
  fi

  python3 "$GUARD_PY" "$@"
}

# ── validate-spec ─────────────────────────────────────────────────────────────
# Routed to Python, not `rt spec validate` — the Rust port validates against
# an invented ad-hoc schema (requires a `tasks` array) instead of the real
# .yana-ai/schemas/spec.schema.json (id/title/goal/scope/acceptance_criteria/
# verification_plan), has no --context-pack support, and maps invalid->exit 2
# instead of 1. core/scripts/validate_spec.py is schema-correct; use that
# until the Rust side is rewritten to match.

cmd_validate_spec() {
  if ! command -v python3 &>/dev/null; then
    err "python3 is required to run yana-ai validate-spec."
    exit 3
  fi

  if [[ ! -f "$VALIDATE_SPEC_PY" ]]; then
    err "validate-spec script not found at $VALIDATE_SPEC_PY"
    exit 3
  fi

  python3 "$VALIDATE_SPEC_PY" "$@"
}


# ── check-context ─────────────────────────────────────────────────────────────

cmd_check_context() {
  if ! command -v python3 &>/dev/null; then
    err "python3 is required to run yana-ai check-context."
    exit 3
  fi

  if [[ ! -f "$CHECK_CONTEXT_PY" ]]; then
    err "check-context script not found at $CHECK_CONTEXT_PY"
    exit 3
  fi

  python3 "$CHECK_CONTEXT_PY" "$@"
}

# ── dispatch ──────────────────────────────────────────────────────────────────

COMMAND="${1:-banner}"
shift 2>/dev/null || true

case "$COMMAND" in
  doctor)
    cmd_doctor "$@"
    ;;
  audit)
    cmd_audit "$@"
    ;;
  policy)
    SUBCMD="${1:-}"; shift 2>/dev/null || true
    case "$SUBCMD" in
      check) python3 "$POLICY_CHECK_PY" "$@" ;;
      *)     cmd_policy "$SUBCMD" "$@" ;;
    esac
    ;;
  guard)
    cmd_guard "$@"
    ;;
  task|eval|bus|memory|plugin|cost|vault|os|workspace|spec|provenance|mission|route|evidence|chat|observability|skill-quality|mcp|remote)
    rt "$COMMAND" "$@"
    ;;
  explain)
    python3 "$EXPLAIN_PY" "$@"
    ;;
  map)
    rt map show "$@"
    ;;
  init-policy)
    python3 "$INIT_POLICY_PY" "$@"
    ;;
  score)
    rt score show "$@"
    ;;
  badge)
    python3 "$BADGE_PY" "$@"
    ;;
  watch)
    python3 "$WATCH_PY" "$@"
    ;;
  fix)
    rt fix apply "$@"
    ;;
  ci-check)
    rt ci check "$@"
    ;;
  diff-report)
    python3 "$DIFF_REPORT_PY" "$@"
    ;;
  rule)
    SUBCMD="${1:-}"; shift 2>/dev/null || true
    case "$SUBCMD" in
      import) python3 "$RULE_IMPORT_PY" "$@" ;;
      test)   python3 "$RULE_TEST_PY"   "$@" ;;
      *)      python3 "$RULE_PY" "$SUBCMD" "$@" ;;
    esac
    ;;
  install)
    python3 "$INSTALL_PY" "$@"
    ;;
  giamthi)
    python3 "$GIAMTHI_SERVICE_PY" "$@"
    ;;
  report)
    SUBCMD="${1:-}"; shift 2>/dev/null || true
    case "$SUBCMD" in
      html) python3 "$REPORT_HTML_PY" "$@" ;;
      pdf)  python3 "$REPORT_PDF_PY"  "$@" ;;
      *)    err "Unknown report subcommand: $SUBCMD (available: html, pdf)"; exit 1 ;;
    esac
    ;;
  scan-url)
    python3 "$SCAN_URL_PY" "$@"
    ;;
  upgrade)
    python3 "$UPGRADE_PY" "$@"
    ;;
  init)
    python3 "$INIT_WIZ_PY" "$@"
    ;;
  verify)
    python3 "$VERIFY_PY" "$@"
    ;;
  monitor)
    python3 "$MONITOR_PY" "$@"
    ;;
  stats)
    python3 "$STATS_PY" "$@"
    ;;
  lint)
    python3 "$LINT_PY" "$@"
    ;;
  snapshot)
    python3 "$SNAPSHOT_PY" "$@"
    ;;
  export)
    python3 "$EXPORT_PY" "$@"
    ;;
  harness)
    python3 "$HARNESS_PY" "$@"
    ;;
  check)
    python3 "$CHECK_FILE_PY" "$@"
    ;;
  template)
    python3 "$TEMPLATE_PY" "$@"
    ;;
  router)
    cmd_router "$@"
    ;;
  validate-spec)
    cmd_validate_spec "$@"
    ;;
  check-context)
    cmd_check_context "$@"
    ;;
  graph)
    rt graph "$@"
    ;;
  hunt)
    rt hunt run "$@"
    ;;
  design)
    rt design "$@"
    ;;
  filescan)
    rt filescan "$@"
    ;;
  config)
    python3 "$CONFIG_PY" "$@"
    ;;
  su-gia)
    python3 "$CHECK_COUNTS_PY" "$@"
    ;;
  version|--version|-v)
    cmd_version
    ;;
  banner)
    banner 1
    ;;
  help|--help|-h|"")
    usage
    ;;
  *)
    err "Unknown command: $COMMAND"
    usage
    exit 1
    ;;
esac
