#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

#echo "starting: $0 <LOG_LEVEL=$1>"

### new.method

# ============================================================================
# hiveMind - Multi-agent orchestrator for oosh
# Manages REAL Claude Code agents in tmux panes using agentRoom backend
# ============================================================================

# ─────────────────────────────────────────────────────────────────────────────
# CONFIGURATION
# ─────────────────────────────────────────────────────────────────────────────

: ${HIVEMIND_SESSION:=hivemind}
: ${HIVEMIND_MAIN_PANE_SIZE:=60}
: ${HIVEMIND_BASE_PORT:=11080}

# ── Naming conventions ────────────────────────────────────────────────────────
# tmux session  : <teamName>            e.g. cursorOrchestrator, ooshTeam
# tmux window   : team                  window 0 is always "team"
# env per pane  : HIVEMIND_ROLE=<role>  exported before Claude Code starts
# role registry : ~/config/hivemind.roles.env  write-through cache: pane targets → role names
#                 format: target|role  (fallback — live discovery is primary)
# session store : ~/config/hivemind.sessions.env  maps pane → Claude session UUID
#                 format: pane|session-uuid  (allows session rejoin, pane-scoped)
: ${HIVEMIND_WINDOW_NAME:=team}
: ${HIVEMIND_REGISTRY:=${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}
: ${HIVEMIND_SESSIONS:=${CONFIG_PATH:-$HOME/config}/hivemind.sessions.env}
: ${HIVEMIND_TEAMS:=${CONFIG_PATH:-$HOME/config}/hivemind.teams.env}
: ${HIVEMIND_ACTIVE_TEAM_FILE:=${CONFIG_PATH:-$HOME/config}/hivemind.active.team}

# Default agent roles
HIVEMIND_DEFAULT_AGENTS=(
  "orchestrator"
  "coder"
  "tester"
  "reviewer"
)

# OOSH specialized agent roles
HIVEMIND_OOSH_AGENTS=(
  "oosh-expert"
  "oosh-tester"
)

# Canonical location for agent role definitions
# Resolved at runtime by private.hiveMind.find.agents.dir (searches upward from OOSH_DIR)
# Override via env or config: HIVEMIND_AGENTS_DIR=/path/to/.claude/agents

# Agent role descriptions (used for system prompts)
# Bash 3.2 compatible — uses function lookup instead of declare -A
# Roles with SKILL.md files get auto-loaded from .claude/agents/<role>/SKILL.md
private.hiveMind.get.role.prompt() {
  local role="$1"
  [ -z "$role" ] && return 1

  # Resolve agents dir if not set
  local agents_dir="$HIVEMIND_AGENTS_DIR"
  if [ -z "$agents_dir" ]; then
    agents_dir=$(private.hiveMind.find.agents.dir 2>/dev/null) || true
  fi

  # Role aliases: map alternate names to canonical role directories
  case "$role" in
    orchestrator|oosh-orchestrator) role="agent-teacher" ;;
  esac

  # Dynamic SKILL.md lookup
  local skill_file="$agents_dir/$role/SKILL.md"
  if [ -n "$agents_dir" ] && [ -f "$skill_file" ]; then
    # Extract description from YAML frontmatter (line starting with description:)
    local desc
    desc=$(sed -n '/^---$/,/^---$/p' "$skill_file" | grep '^description:' | head -1 | sed 's/^description: *//; s/^"//; s/"$//')
    if [ -n "$desc" ]; then
      echo "You are the $role. Read .claude/agents/$role/SKILL.md to learn your role. $desc"
    else
      echo "You are the $role. Read .claude/agents/$role/SKILL.md to learn your role."
    fi
    return 0
  fi

  # No SKILL.md found — unknown role
  return 1
}

# ─────────────────────────────────────────────────────────────────────────────
# PRIVATE HELPERS
# ─────────────────────────────────────────────────────────────────────────────

private.hiveMind.current.session() { # # return current tmux session name
  otmux pane.get '#{session_name}' 2>/dev/null || return 1
}

private.hiveMind.role.isGeneric() { # <role> # true if role is a generic placeholder
  local r="$1"
  [ -z "$r" ] || [ "$r" = "unknown" ] || [ "$r" = "ClaudeCode" ] || [ "$r" = "claude" ]
}

private.hiveMind.role.fromTitle() { # <title> # extract role name from pane title, stripping prefixes
  local r="$1"
  r="${r#✳ }"; r="${r#⠐ }"; r="${r#⠂ }"
  r="${r#✻ }"; r="${r#✢ }"; r="${r#✶ }"
  r="${r%%@*}"
  r=$(echo "$r" | tr -d '[:space:]' | head -c 30)
  private.hiveMind.role.isGeneric "$r" && return 1
  echo "$r"
}

private.hiveMind.env.set() { # <file> <key> <value> # set key|value in env file (add or replace)
  local file="$1" key="$2" value="$3"
  local old sedFlag="-i"
  [ "$(uname -s)" = "Darwin" ] && sedFlag="-i ''"
  old=$(grep "^${key}|" "$file" 2>/dev/null | head -1 | cut -d'|' -f2)
  if [ -z "$old" ]; then
    echo "${key}|${value}" >> "$file"
  elif [ "$old" != "$value" ]; then
    eval sed $sedFlag "'s#^${key}|${old}\$#${key}|${value}#'" "'$file'"
  else
    return 1  # no change needed
  fi
  return 0
}

private.hiveMind.env.del() { # <file> <key> <?value> # delete matching line from env file
  local file="$1" key="$2" value="$3"
  local sedFlag="-i"
  [ "$(uname -s)" = "Darwin" ] && sedFlag="-i ''"
  if [ -n "$value" ]; then
    eval sed $sedFlag "'/^${key}|${value}\$/d'" "'$file'"
  else
    eval sed $sedFlag "'/^${key}|/d'" "'$file'"
  fi
}

private.hiveMind.liveUuid() { # <paneTarget> <?procArgs> <?probe> # get live session UUID, skipping fork parents
  local target="$1" procArgs="$2" probe="${3:-}"
  local isForked=""
  [ -n "$procArgs" ] && echo "$procArgs" | grep -qE '\-\-fork-session|claudeCode fork' && isForked="yes"
  # Non-forked: extract UUID from ps args
  if [ -z "$isForked" ] && [ -n "$procArgs" ]; then
    local uuid
    uuid=$(echo "$procArgs" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1)
    [ -n "$uuid" ] && echo "$uuid" && return 0
  fi
  # Non-forked: try session.id (includes sessions.env fallback)
  if [ -z "$isForked" ]; then
    local sid
    sid=$("$OOSH_DIR/claudeCode" session.id "$target" 2>/dev/null)
    [ -n "$sid" ] && echo "$sid" && return 0
  fi
  # Forked or no UUID found: invasive /status probe (slow ~3s, only in fix mode)
  if [ "$probe" = "probe" ]; then
    local sid
    sid=$("$OOSH_DIR/claudeCode" session.probe "$target" 2>/dev/null)
    [ -n "$sid" ] && echo "$sid" && return 0
  fi
  return 1
}

private.hiveMind.pane.count() { # <target> # count panes in a window or session
  otmux panes -t "$1" ${2:+-s} 2>/dev/null | wc -l | tr -d ' '
}

private.hiveMind.claude.processes() { # # list Claude processes with pane targets: pid|tty|paneTarget|title|args
  local -A ttyMap titleMap
  while IFS='|' read -r paneTty paneRef paneTitle; do
    ttyMap["$paneTty"]="$paneRef"
    titleMap["$paneRef"]="$paneTitle"
  done < <(private.hiveMind.list.panes tty+title)
  while read -r pid tty comm rest; do
    [ -z "$pid" ] && continue
    [[ "$comm" == "bash" || "$comm" == "zsh" || "$comm" == "node" ]] && continue
    local ttyDev="/dev/$tty"
    local paneTarget="${ttyMap[$ttyDev]:-}"
    [ -z "$paneTarget" ] && continue
    local title="${titleMap[$paneTarget]:-}"
    echo "${pid}|${tty}|${paneTarget}|${title}|${rest}"
  done < <(ps -eo pid,tty,comm,args 2>/dev/null | grep -i 'claude' | grep -v grep | awk '{print $1, $2, $3, substr($0, index($0,$4))}')
}

private.hiveMind.ensure.pane() { # <session:window.pane> # create session/window/pane if missing
  local target="$1"
  local sess="${target%%:*}"
  local rest="${target#*:}"
  local win="${rest%%.*}"
  local pane="${rest#*.}"
  # Ensure session
  if ! otmux has "$sess" 2>/dev/null; then
    otmux new "$sess" -d -x 200 -y 50 2>/dev/null || return 1
  fi
  # Ensure window at exact index
  if ! otmux windows -t "$sess" -F "#{window_index}" 2>/dev/null | grep -qx "$win"; then
    otmux window.new -d -t "${sess}:${win}" 2>/dev/null || return 1
  fi
  # Ensure pane (split until enough)
  local current needed=$((pane + 1))
  current=$(private.hiveMind.pane.count "${sess}:${win}")
  while [ "$current" -lt "$needed" ]; do
    otmux split -d -t "${sess}:${win}" 2>/dev/null || break
    otmux tiled "${sess}:${win}" 2>/dev/null
    current=$(private.hiveMind.pane.count "${sess}:${win}")
  done
  [ "$current" -ge "$needed" ]
}

private.hiveMind.list.panes() { # <format> <?scope> # list panes with standard format strings
  # format: tty, tty+title, addr+cmd, addr, or raw tmux format string
  # scope: -a (all sessions, default) or session name
  local format="$1" scope="${2:--a}"
  local fmtStr
  case "$format" in
    tty)       fmtStr="#{pane_tty} #{session_name}:#{window_index}.#{pane_index}" ;;
    tty+title) fmtStr="#{pane_tty}|#{session_name}:#{window_index}.#{pane_index}|#{pane_title}" ;;
    addr+cmd)  fmtStr="#{window_index}.#{pane_index}|#{pane_current_command}" ;;
    addr)      fmtStr="#{session_name}:#{window_index}.#{pane_index}" ;;
    *)         fmtStr="$format" ;;
  esac
  if [ "$scope" = "-a" ]; then
    otmux panes -a -F "$fmtStr" 2>/dev/null
  else
    otmux panes -t "$scope" -s -F "$fmtStr" 2>/dev/null
  fi
}

private.hiveMind.find.agents.dir() {
  # Search upward from multiple starting points for .claude/agents with SKILL.md files
  # Tries: CWD, OOSH_DIR (resolved), symlink source — handles cross-repo layouts
  local search_dirs=()
  [ -n "$PWD" ] && search_dirs+=("$PWD")
  local resolved
  resolved=$(realpath "${OOSH_DIR:-.}" 2>/dev/null)
  [ -n "$resolved" ] && search_dirs+=("$resolved")
  [ -n "$OOSH_DIR" ] && [ "$OOSH_DIR" != "$resolved" ] && search_dirs+=("$OOSH_DIR")

  local start_dir
  for start_dir in "${search_dirs[@]}"; do
    local dir="$start_dir"
    while [ "$dir" != "/" ]; do
      if [ -d "$dir/.claude/agents" ]; then
        local has_skills=false
        for f in "$dir/.claude/agents"/*/SKILL.md; do
          [ -f "$f" ] && has_skills=true && break
        done
        [ "$has_skills" = "true" ] && echo "$dir/.claude/agents" && return 0
      fi
      dir=$(dirname "$dir")
    done
  done
  return 0
}

private.hiveMind.pane.identify() {
  # Register role for a pane: write to registry, set pane title, export env var
  # Usage: private.hiveMind.pane.identify <target> <role>
  local target="$1"
  local role="$2"
  private.hiveMind.registry.set "$target" "$role"
  otmux pane.title "$target" "$role"
  otmux send.enter "$target" "export FORCE_COLOR=2; unset COLORTERM; unset CLAUDECODE"
  otmux send.enter "$target" "export HIVEMIND_ROLE=$role"
}

# ── Role Registry ─────────────────────────────────────────────────────────────
# Write-through cache: pane target → role name.
# PRIMARY source of truth is live discovery (process → session UUID → customTitle).
# This file is the FALLBACK for panes where Claude isn't running yet.
# Format: target|role  (one per line, e.g. cursorOrchestrator:0.2|oosh-expert)
# Location: ~/config/hivemind.roles.env (persistent, OOSH config pattern)

private.hiveMind.registry.migrate() {
  # Migrate old /tmp/ registry to ~/config/ pattern (one-time, backward compat)
  local old_registry="/tmp/hivemind.roles"
  [ -f "$old_registry" ] || return 0
  [ "$HIVEMIND_REGISTRY" = "$old_registry" ] && return 0
  if [ ! -f "$HIVEMIND_REGISTRY" ] || [ ! -s "$HIVEMIND_REGISTRY" ]; then
    local config_dir
    config_dir=$(dirname "$HIVEMIND_REGISTRY")
    mkdir -p "$config_dir" 2>/dev/null
    cp "$old_registry" "$HIVEMIND_REGISTRY"
    info.log "Migrated registry from $old_registry to $HIVEMIND_REGISTRY"
  fi
}

private.hiveMind.sessions.migrate() {
  # Migrate old /tmp/ sessions to ~/config/ pattern (one-time, backward compat)
  local old_sessions="/tmp/hivemind.sessions"
  [ -f "$old_sessions" ] || return 0
  [ "$HIVEMIND_SESSIONS" = "$old_sessions" ] && return 0
  if [ ! -f "$HIVEMIND_SESSIONS" ] || [ ! -s "$HIVEMIND_SESSIONS" ]; then
    local config_dir
    config_dir=$(dirname "$HIVEMIND_SESSIONS")
    mkdir -p "$config_dir" 2>/dev/null
    cp "$old_sessions" "$HIVEMIND_SESSIONS"
    info.log "Migrated sessions from $old_sessions to $HIVEMIND_SESSIONS"
  fi
}

private.hiveMind.registry.set() {
  # Add or update a target→role mapping
  local target="$1"
  local role="$2"
  [ -z "$target" ] || [ -z "$role" ] && return 1
  # Validate pane target format: session:window.pane (e.g. projectTeam:0.3)
  if ! echo "$target" | grep -qE '^[a-zA-Z0-9_-]+:[0-9]+\.[0-9]+$'; then
    warn.log "registry.set: invalid pane target '$target' — expected session:window.pane"
    return 1
  fi
  # Validate role name: reject boot prompt garbage (>30 chars or contains spaces)
  if [ "${#role}" -gt 30 ] || echo "$role" | grep -q ' '; then
    warn.log "registry.set: rejecting invalid role name '${role:0:40}...' — too long or contains spaces"
    return 1
  fi
  private.hiveMind.registry.migrate
  # Ensure config directory exists
  local config_dir
  config_dir=$(dirname "$HIVEMIND_REGISTRY")
  mkdir -p "$config_dir" 2>/dev/null
  # Remove old entry for this target, then append new one
  if [ -f "$HIVEMIND_REGISTRY" ]; then
    grep -v "^${target}|" "$HIVEMIND_REGISTRY" > "${HIVEMIND_REGISTRY}.tmp" 2>/dev/null
    mv "${HIVEMIND_REGISTRY}.tmp" "$HIVEMIND_REGISTRY"
  fi
  echo "${target}|${role}" >> "$HIVEMIND_REGISTRY"
}

private.hiveMind.registry.get() {
  # Look up role for a given pane target
  # PRIMARY: live discovery from running Claude process
  # FALLBACK: static file (for panes where Claude isn't running yet)
  local target="$1"
  local role
  role=$(private.hiveMind.live.discover "$target" 2>/dev/null)
  [ -n "$role" ] && echo "$role" && return 0
  # Fallback to static registry file
  private.hiveMind.registry.migrate
  [ -f "$HIVEMIND_REGISTRY" ] || return 1
  grep "^${target}|" "$HIVEMIND_REGISTRY" 2>/dev/null | head -1 | cut -d'|' -f2
}

private.hiveMind.registry.find() {
  # Find pane target by role name (case-insensitive partial match)
  # PRIMARY: live discovery — scan panes for matching role
  # FALLBACK: static file
  local name="$1"
  local session="$2"
  if [ -n "$session" ]; then
    local match
    match=$(private.hiveMind.live.discover.session "$session" 2>/dev/null | grep -i "$name" | head -1 | cut -d'|' -f1)
    [ -n "$match" ] && echo "$match" && return 0
  fi
  # Fallback to static registry file
  private.hiveMind.registry.migrate
  [ -f "$HIVEMIND_REGISTRY" ] || return 1
  if [ -n "$session" ]; then
    grep "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null | grep -i "$name" | head -1 | cut -d'|' -f1
  else
    grep -i "$name" "$HIVEMIND_REGISTRY" 2>/dev/null | head -1 | cut -d'|' -f1
  fi
}

private.hiveMind.registry.list() {
  # List all entries, optionally filtered by session
  # PRIMARY: live discovery from running Claude processes
  # FALLBACK: static file (for listing across all sessions or offline sessions)
  local session="$1"
  if [ -n "$session" ] && otmux has "$session" 2>/dev/null; then
    local live
    live=$(private.hiveMind.live.discover.session "$session" 2>/dev/null)
    [ -n "$live" ] && echo "$live" && return 0
  fi
  # Fallback to static registry file
  private.hiveMind.registry.migrate
  [ -f "$HIVEMIND_REGISTRY" ] || return 0
  if [ -n "$session" ]; then
    grep "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null
  else
    cat "$HIVEMIND_REGISTRY" 2>/dev/null
  fi
}

# ── Live Discovery ────────────────────────────────────────────────────────────
# Discover agent roles from live process state (tmux panes + Claude session names).
# Primary source of truth — no static file needed.
# A pane is just a view; an agent is a Claude process with a session UUID.
# The role is stored in the Claude session's customTitle (via /rename).

private.hiveMind.live.discover() {
  # Discover role for a pane from live Claude process + session data
  # Returns: role name (e.g. "hiveMind-expert") or empty
  local target="$1"
  [ -z "$target" ] && return 1

  # 1. Find Claude PID on this pane's TTY
  local pid
  pid=$(claudeCode process.find "$target" 2>/dev/null) || return 1

  # 2. Extract session UUID from process args
  local sid
  sid=$(ps -p "$pid" -o args= 2>/dev/null | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
  [ -z "$sid" ] && return 1

  # 3. Get session name (customTitle from /rename, or firstPrompt)
  local name
  name=$(claudeCode session.name "$sid" 2>/dev/null)
  [ -z "$name" ] && return 1

  # 4. Extract role from role@model format (set by /rename during bootstrap)
  if [[ "$name" == *"@"* ]]; then
    local role="${name%%@*}"
    # Validate: reject garbage (same guards as registry.refresh)
    [[ "$role" == *" "* ]] && return 1
    [[ ${#role} -gt 30 ]] && return 1
    [[ "$role" =~ ^(Read|You|I\ am|Write|Edit|Help|Search|Find|Look|Check|Run|Show) ]] && return 1
    echo "$role"
    return 0
  fi

  return 1
}

private.hiveMind.live.discover.session() {
  # Discover all roles in a tmux session from live process state
  # Outputs: pane_target|role per line (same format as registry file)
  local session="$1"
  [ -z "$session" ] && return 1

  local pane_list
  pane_list=$(private.hiveMind.list.panes addr "$session")
  [ -z "$pane_list" ] && return 1

  while IFS= read -r pane_target; do
    [ -z "$pane_target" ] && continue
    local role
    role=$(private.hiveMind.live.discover "$pane_target" 2>/dev/null)
    if [ -n "$role" ]; then
      echo "${pane_target}|${role}"
    fi
  done <<< "$pane_list"
}

# ── Session Tracking ──────────────────────────────────────────────────────────
# File-based mapping: pane target → Claude session UUID.
# Allows rejoining a session after the Claude process exits.
# Format: pane|session-uuid  (one per line, e.g. projectTeam:0.2|a2c6b6c4-...)

private.hiveMind.session.store() {
  # Store or update pane → session UUID mapping
  local pane="$1"
  local sessionId="$2"
  [ -z "$pane" ] || [ -z "$sessionId" ] && return 1
  private.hiveMind.sessions.migrate
  # Ensure config directory exists
  local configDir
  configDir=$(dirname "$HIVEMIND_SESSIONS")
  mkdir -p "$configDir" 2>/dev/null
  # Remove old entry for this pane, then append new one
  if [ -f "$HIVEMIND_SESSIONS" ]; then
    grep -v "^${pane}|" "$HIVEMIND_SESSIONS" > "${HIVEMIND_SESSIONS}.tmp" 2>/dev/null
    mv "${HIVEMIND_SESSIONS}.tmp" "$HIVEMIND_SESSIONS"
  fi
  echo "${pane}|${sessionId}" >> "$HIVEMIND_SESSIONS"
}

private.hiveMind.session.lookup() {
  # Look up stored session UUID for a given pane target
  local pane="$1"
  private.hiveMind.sessions.migrate
  [ -f "$HIVEMIND_SESSIONS" ] || return 1
  grep "^${pane}|" "$HIVEMIND_SESSIONS" 2>/dev/null | tail -1 | cut -d'|' -f2
}

# ── Active Team ───────────────────────────────────────────────────────────────
# Single source of truth for which team session is "current".
# Replaces all hardcoded cursorOrchestrator defaults.

private.hiveMind.active.team() {
  # Return the active team name. Falls back to first registered team, then cursorOrchestrator.
  if [ -f "$HIVEMIND_ACTIVE_TEAM_FILE" ] && [ -s "$HIVEMIND_ACTIVE_TEAM_FILE" ]; then
    cat "$HIVEMIND_ACTIVE_TEAM_FILE"
    return 0
  fi
  # Fall back to first registered team from teams file
  if [ -f "$HIVEMIND_TEAMS" ] && [ -s "$HIVEMIND_TEAMS" ]; then
    head -1 "$HIVEMIND_TEAMS" | cut -d'|' -f1
    return 0
  fi
  # Fall back to first team found in roles registry
  if [ -f "$HIVEMIND_REGISTRY" ] && [ -s "$HIVEMIND_REGISTRY" ]; then
    head -1 "$HIVEMIND_REGISTRY" | cut -d':' -f1
    return 0
  fi
  # Ultimate fallback
  echo "cursorOrchestrator"
}

# ── Team Registry ────────────────────────────────────────────────────────────
# File-based list of known team sessions with descriptions.
# Format: session_name|description  (one per line)
# Location: ~/config/hivemind.teams.env

private.hiveMind.teams.ensure.dir() {
  local config_dir
  config_dir=$(dirname "$HIVEMIND_TEAMS")
  mkdir -p "$config_dir" 2>/dev/null
}

private.hiveMind.teams.complete() {
  # Shared completion helper: registered teams + running tmux sessions, deduplicated
  { [ -f "$HIVEMIND_TEAMS" ] && cut -d'|' -f1 "$HIVEMIND_TEAMS" 2>/dev/null; otmux sessions -F "#{session_name}" 2>/dev/null; } | sort -u
}

private.hiveMind.roles.complete() {
  # Shared completion helper: registered role names + pane titles from active team
  local session
  session=$(private.hiveMind.active.team 2>/dev/null)
  {
    # Source 1: Registry (canonical) — live discovery + file fallback
    if [ -n "$session" ]; then
      private.hiveMind.registry.list "$session" 2>/dev/null | cut -d'|' -f2
    else
      private.hiveMind.registry.list 2>/dev/null | cut -d'|' -f2
    fi

    # Source 2: Pane titles (for unregistered panes)
    if [ -n "$session" ]; then
      local pane title
      while IFS= read -r pane; do
        title=$(otmux pane.get "$pane" '#{pane_title}' 2>/dev/null)
        if [ -n "$title" ] && ! grep -q "|${title}$" "$HIVEMIND_REGISTRY" 2>/dev/null; then
          echo "$title"
        fi
      done < <(otmux panes -s -t "$session" -F "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null)
    fi
  } | sort -u
}

private.hiveMind.resolve.alias() {
  # Map legacy/alternate names to canonical registry keys.
  # The registry stores canonical names; this resolves aliases when direct lookup fails.
  case "$1" in
    agent-teacher|oosh-orchestrator) echo "orchestrator" ;;
    *) echo "$1" ;;
  esac
}

# Session ID detection delegated to claudeCode wrapper (DRY).
# Use: ./claudeCode process.find <pane>    — get Claude PID
#      ./claudeCode process.running <pane> — boolean check
#      ./claudeCode session.id <pane>      — get session UUID

private.hiveMind.pane.activity() {
  # Detect real Claude Code activity state from pane content.
  # Returns one of: active | idle | permission | unknown
  local target="$1"
  [ -z "$target" ] && echo "unknown" && return 1

  local content
  content=$(otmux pane.capture "$target" 5 2>/dev/null)

  # Empty or whitespace-only → unknown
  local trimmed
  trimmed=$(echo "$content" | tr -d '[:space:]')
  [ -z "$trimmed" ] && echo "unknown" && return 0

  # Permission prompt: Claude Code shows "Allow" and "Deny" options together
  if echo "$content" | grep -q 'Allow' && echo "$content" | grep -q 'Deny'; then
    echo "permission"
    return 0
  fi

  # Idle: last non-empty line is the input prompt character
  local last_line
  last_line=$(echo "$content" | sed '/^[[:space:]]*$/d' | tail -1)
  if echo "$last_line" | grep -qE '^[[:space:]]*(>|❯)[[:space:]]*$'; then
    echo "idle"
    return 0
  fi

  # Default: active (generating text, running tools, or indeterminate)
  echo "active"
  return 0
}

private.hiveMind.get.agents() {
  # Get all agent IDs from agentRoom
  agentRoom list.ids 2>/dev/null
}

private.hiveMind.get.agent.port() {
  local agent_id="$1"
  agentRoom list 2>/dev/null | grep "^$agent_id " | awk '{print $2}'
}

private.hiveMind.pane.for.agent() {
  local agent_id="$1"
  private.hiveMind.list.panes "#{pane_id} #{pane_title}" | grep "$agent_id" | awk '{print $1}' | head -1
}

private.hiveMind.create.agent.pane() {
  local agent_id="$1"
  local port="$2"
  local workdir="${3:-$(pwd)}"
  local window="${4:-agents}"

  # Select or create agents window
  if ! otmux windows -F "#{window_name}" 2>/dev/null | grep -q "^${window}$"; then
    otmux window.new "$window"
  else
    otmux window.select "$window"
  fi

  # Create new pane
  otmux split.h
  otmux tiled

  # Get the new pane
  local pane_id=$(otmux pane.get %)

  # Set pane title and export role env var
  private.hiveMind.pane.identify "$pane_id" "$agent_id"

  # Start Claude Code in the pane with agent role
  otmux send "$pane_id" "cd '$workdir' && claude --dangerously-skip-permissions" Enter

  echo "$pane_id"
}

# ─────────────────────────────────────────────────────────────────────────────
# PARAMETER COMPLETIONS (shared across all methods)
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.parameter.completion.agentId() {
  hiveMind.list
}

hiveMind.parameter.completion.pane() {
  tmux list-panes -a -F "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null
}

hiveMind.parameter.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.parameter.completion.workdir() {
  compgen -d "$1"
}

hiveMind.parameter.completion.name() {
  local session
  session=$(private.hiveMind.active.team)
  private.hiveMind.registry.list "$session" 2>/dev/null | cut -d'|' -f2
}

# ─────────────────────────────────────────────────────────────────────────────
# INITIALIZATION
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.init() # <?agents:orchestrator,coder,tester,reviewer> <?workdir:.> # initialize hivemind with real Claude agents
{
  local agents="${1:-orchestrator,coder,tester,reviewer}"
  local workdir="${2:-$(pwd)}"

  info.log "Initializing HiveMind with agents: $agents"

  # Ensure agentRoom is available
  if ! command -v agentRoom &>/dev/null && [ ! -f "$OOSH_DIR/agentRoom" ]; then
    error.log "agentRoom script not found. Ensure OOSH is properly set up."
    return 1
  fi

  # Start agentRoom backend
  info.log "Starting agentRoom backend..."
  agentRoom backend.start "$HIVEMIND_BASE_PORT"

  sleep 2

  # Check if in tmux or create session
  if [ -z "$TMUX" ]; then
    info.log "Creating tmux session: $HIVEMIND_SESSION"
    otmux new "$HIVEMIND_SESSION" -d -n "main"

    # Main pane for orchestrator
    private.hiveMind.pane.identify "${HIVEMIND_SESSION}:0.0" "orchestrator"
  fi

  # Start each agent
  IFS=',' read -ra agent_array <<< "$agents"
  local port=$((HIVEMIND_BASE_PORT + 1))

  for agent_id in "${agent_array[@]}"; do
    info.log "Starting agent: $agent_id on port $port"

    # Start agentRoom agent backend
    agentRoom agent.start "$agent_id" "$port" "$workdir" "$agent_id"

    # Create tmux pane for agent
    hiveMind.pane.create "$agent_id" "$workdir"

    ((port++))
    sleep 1
  done

  success.log "HiveMind initialized!"
  echo ""
  echo "Agents running:"
  agentRoom list
  echo ""
  echo "Tmux session: $HIVEMIND_SESSION"
  [ -z "$TMUX" ] && echo "Attach with: otmux attach $HIVEMIND_SESSION"
}

hiveMind.init.completion.agents() {
  echo "orchestrator,coder,tester,reviewer"
  echo "coder,tester"
  echo "orchestrator,coder,tester,reviewer,researcher"
  echo "oosh-expert,oosh-tester"
}

hiveMind.pane.create() # <agentId> <?workdir:.> # create tmux pane with Claude Code for agent
{
  local agent_id="$1"
  local workdir="${2:-$(pwd)}"

  if [ -z "$agent_id" ]; then
    error.log "Usage: hiveMind pane.create <agentId> <?workdir>"
    return 1
  fi

  # Check if pane already exists
  local existing_pane=$(private.hiveMind.pane.for.agent "$agent_id")
  if [ -n "$existing_pane" ]; then
    info.log "Pane for $agent_id already exists: $existing_pane"
    return 0
  fi

  info.log "Creating pane for $agent_id..."

  # Get agent port
  local port=$(private.hiveMind.get.agent.port "$agent_id")

  # Create pane in agents window
  if [ -n "$TMUX" ]; then
    # Check if agents window exists
    if ! otmux windows -F "#{window_name}" | grep -q "^agents$"; then
      otmux window.new "agents"
    else
      otmux window.select agents
    fi

    # Split and create new pane
    otmux split.h -t agents
    otmux tiled agents

    # Set title and export role env var
    local pane_id=$(otmux pane.get %)
    private.hiveMind.pane.identify "$pane_id" "$agent_id"

    # Get system prompt for role (use role-specific or generic)
    local system_prompt
    system_prompt=$(private.hiveMind.get.role.prompt "$agent_id") || system_prompt="You are the $agent_id agent. Focus on your specialized role."

    # Start Claude Code with role context
    otmux send "$pane_id" "cd '$workdir'" Enter
    otmux send "$pane_id" "echo '=== $agent_id Agent (port: $port) ==='" Enter
    otmux send "$pane_id" "claude --dangerously-skip-permissions -p '$system_prompt'" Enter

    success.log "Created pane for $agent_id"
  else
    warn.log "Not in tmux session. Run: hiveMind attach first"
    return 1
  fi
}

# ─────────────────────────────────────────────────────────────────────────────
# SESSION MANAGEMENT
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.attach() # # attach to hivemind tmux session
{
  if otmux has "$HIVEMIND_SESSION" 2>/dev/null; then
    otmux attach "$HIVEMIND_SESSION"
  else
    warn.log "HiveMind session not found. Run: hiveMind init"
    return 1
  fi
}

hiveMind.detach() # # detach from current session
{
  otmux detach
}

hiveMind.join() # <name> # rejoin an agent's Claude session by role name
{
  local name="$1"

  if [ -z "$name" ]; then
    error.log "Usage: hiveMind join <name>"
    echo "  Use 'hiveMind role.list' to see available roles"
    return 1
  fi

  # Resolve agent name to pane target
  local target
  target=$(hiveMind.resolve "$name")
  if [ $? -ne 0 ]; then
    return 1
  fi

  # Try to find session ID from a running Claude process first
  local session_id
  session_id=$(claudeCode session.id "$target" 2>/dev/null)

  if [ -n "$session_id" ]; then
    # Claude is still running — store the session ID and inform user
    private.hiveMind.session.store "$target" "$session_id"
    info.log "Claude is already running in $target (session: ${session_id:0:8}...)"
    echo "Session $session_id is active in pane $target"
    return 0
  fi

  # Process not running — look up stored session ID
  session_id=$(private.hiveMind.session.lookup "$target")

  if [ -z "$session_id" ]; then
    error.log "No session found for agent '$name'"
    echo "  The agent may not have been started yet, or session data was lost."
    echo "  Start fresh with: hiveMind agent.bootstrap $name"
    return 1
  fi

  info.log "Resuming session ${session_id:0:8}... for $name in pane $target"

  # Send resume command to the pane
  otmux send "$target" "claude --resume $session_id" Enter

  success.log "Sent resume command for $name (session: ${session_id:0:8}...)"
  return 0
}

hiveMind.kill() # # shutdown hivemind completely
{
  info.log "Shutting down HiveMind..."

  # Stop all agentRoom agents
  agentRoom stop.all 2>/dev/null

  # Kill tmux session
  if otmux has "$HIVEMIND_SESSION" 2>/dev/null; then
    otmux kill "$HIVEMIND_SESSION"
    success.log "HiveMind session killed"
  else
    info.log "No HiveMind tmux session found"
  fi
}

# ─────────────────────────────────────────────────────────────────────────────
# AGENT MANAGEMENT
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.spawn() # <agentId> <?workdir:.> # spawn a new agent with pane
{
  local agent_id="$1"
  local workdir="${2:-$(pwd)}"

  if [ -z "$agent_id" ]; then
    error.log "Usage: hiveMind spawn <agentId> <?workdir>"
    return 1
  fi

  # Find next free port
  local used_ports=$(agentRoom list.ports 2>/dev/null | sort -n | tail -1)
  local port=$((used_ports + 1))
  [ -z "$used_ports" ] && port=$((HIVEMIND_BASE_PORT + 1))

  info.log "Spawning agent $agent_id on port $port..."

  # Start agent backend
  agentRoom agent.start "$agent_id" "$port" "$workdir" "$agent_id"

  # Create pane
  hiveMind.pane.create "$agent_id" "$workdir"

  success.log "Agent $agent_id spawned"
}

hiveMind.spawn.completion.type() {
  echo "orchestrator"
  echo "coder"
  echo "tester"
  echo "reviewer"
  echo "oosh-expert"
  echo "oosh-tester"
  echo "scrum-master"
  echo "developer"
  echo "agent-trainer"
  echo "task-agent"
}

hiveMind.list() # # list all agents
{
  if command -v agentRoom &>/dev/null && agentRoom backend.status &>/dev/null 2>&1; then
    private.hiveMind.get.agents
  else
    # Fall back to tmux pane titles when agentRoom is unavailable
    local session="${HIVEMIND_SESSION:-$(private.hiveMind.active.team)}"
    if otmux has "$session" 2>/dev/null; then
      otmux panes -t "$session" -F "#{pane_title}" 2>/dev/null
    else
      echo "queen"
    fi
  fi
}

hiveMind.workers() # # list worker agents (non-orchestrator panes)
{
  if command -v agentRoom &>/dev/null && agentRoom backend.status &>/dev/null 2>&1; then
    agentRoom list.ids 2>/dev/null | grep -v "queen"
  else
    local session="${HIVEMIND_SESSION:-$(private.hiveMind.active.team)}"
    otmux panes -t "$session" -F "#{pane_title}" 2>/dev/null | grep -v -E "^(orchestrator|queen)$"
  fi
}

hiveMind.queen() # # show queen (orchestrator) agent ID
{
  if command -v agentRoom &>/dev/null && agentRoom backend.status &>/dev/null 2>&1; then
    agentRoom list.ids 2>/dev/null | grep "queen" | head -1
  else
    echo "queen"
  fi
}

hiveMind.status() # <?session> # show one-line hivemind overview
{
  local session="${1:-$(private.hiveMind.active.team)}"

  if otmux has "$session" 2>/dev/null; then
    local pane_count
    pane_count=$(private.hiveMind.pane.count "$session" -s)
    local role_count=0
    if [ -f "$HIVEMIND_REGISTRY" ]; then
      role_count=$(grep -c "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null || echo 0)
    fi
    echo "$session: $pane_count panes, $role_count registered agents"
  else
    echo "$session: not running"
  fi
}
hiveMind.status.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.focus() # <agentId> # focus on specific agent pane
{
  local agent_id="$1"

  if [ -z "$agent_id" ]; then
    error.log "Usage: hiveMind focus <agentId>"
    return 1
  fi

  local pane=$(private.hiveMind.pane.for.agent "$agent_id")

  if [ -n "$pane" ]; then
    otmux pane.select "$pane"
    success.log "Focused on $agent_id"
  else
    error.log "No pane found for agent: $agent_id"
    return 1
  fi
}

hiveMind.focus.completion.agentId() {
  hiveMind.list
}

hiveMind.resolve() # <name> <?session> # resolve agent name to pane target via role registry
{
  local name="$1"
  local session="${2:-$(private.hiveMind.active.team)}"

  if [ -z "$name" ]; then
    error.log "Usage: hiveMind resolve <name>"
    return 1
  fi

  local match
  match=$(private.hiveMind.registry.find "$name" "$session")

  # If no direct match, try canonical alias (e.g. agent-teacher → orchestrator)
  if [ -z "$match" ]; then
    local canonical
    canonical=$(private.hiveMind.resolve.alias "$name")
    if [ "$canonical" != "$name" ]; then
      match=$(private.hiveMind.registry.find "$canonical" "$session")
    fi
  fi

  if [ -n "$match" ]; then
    echo "$match"
    return 0
  else
    error.log "No agent matching '$name' in registry (session: $session)"
    return 1
  fi
}

hiveMind.resolve.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.send() # <name> <text...> # send literal text to agent by name (no Enter appended)
{
  local name="$1"
  shift

  if [ -z "$name" ] || [ -z "$*" ]; then
    error.log "Usage: hiveMind send <name> <text...>"
    return 1
  fi

  local target
  target=$(hiveMind.resolve "$name")
  if [ $? -ne 0 ]; then
    return 1
  fi

  # Detect trailing tmux key names (Enter, C-u, Tab, etc.)
  # These must be sent as keypresses, not literal text.
  # The -l flag is needed for text (preserves spaces) but breaks key names.
  local last="${@: -1}"
  local is_key=false
  [[ "$last" =~ ^(Enter|Escape|Tab|Space|BSpace|DC|IC|C-[a-z]|M-[a-z]|Up|Down|Left|Right|Home|End|PPage|NPage|F[0-9]+)$ ]] && is_key=true

  if $is_key && [ $# -gt 1 ]; then
    # Text + trailing key: send text literally, then key as keypress
    local text_args=("${@:1:$#-1}")
    otmux send "$target" -l "${text_args[*]}"
    sleep 0.05
    otmux send "$target" "$last"
  elif $is_key && [ $# -eq 1 ]; then
    # Single key only: send as keypress (no -l)
    otmux send "$target" "$last"
  else
    # Text only: send literally (preserving spaces)
    otmux send "$target" -l "$*"
  fi
  info.log "Sent to $name ($target): $*"
  return 0
}
hiveMind.send.enter() # <name> <message> # send message to agent by name (delegates to send.message)
{
  hiveMind.send.message "$@"
}
hiveMind.send.message() # <name> <message> # safe send: pre-check, clear blockers, send with verification
{
  local name="$1"
  shift
  local message="$*"

  if [ -z "$name" ] || [ -z "$message" ]; then
    error.log "Usage: hiveMind send.message <name> <message>"
    return 1
  fi

  local session
  session="$(private.hiveMind.active.team)"
  local target
  target=$(hiveMind.resolve "$name" "$session" 2>/dev/null)
  if [ -z "$target" ]; then
    error.log "Cannot resolve '$name' to a pane target"
    return 1
  fi

  # Step 1: Capture target pane — assess current state
  local capture
  capture=$("$OOSH_DIR/otmux" pane.capture "$target" 15 2>/dev/null)

  # Step 2: If blocker detected, try to clear it first
  local detect
  detect=$(private.hiveMind.sweep.detect "$target" 2>/dev/null)
  local status="${detect%%|*}"
  case "$status" in
    permission|tool-confirm|panel|overlay|autocomplete)
      info.log "Blocker detected on $name ($status) — clearing first"
      private.hiveMind.unblock.pane "$target" "$name" 1
      sleep 1
      ;;
  esac

  # Step 3: Clear input line
  "$OOSH_DIR/otmux" send "$target" C-u

  # Step 4: Send message with verification
  local result
  "$OOSH_DIR/otmux" send.verified "$target" "$message" 3
  result=$?

  if [ $result -eq 0 ]; then
    info.log "Message delivered to $name ($target)"
    create.result 0 "DELIVERED"
    return 0
  fi

  # Step 5+6: Verify failed — try sending Enter and re-verify
  warn.log "Initial send may have failed — retrying Enter"
  "$OOSH_DIR/otmux" send "$target" Enter
  sleep 2

  local after
  after=$("$OOSH_DIR/otmux" pane.capture "$target" 10 2>/dev/null)
  if echo "$after" | grep -qF "$message"; then
    info.log "Message visible on $name after Enter retry"
    create.result 0 "DELIVERED_RETRY"
    return 0
  fi

  # Pane changed = likely consumed
  warn.log "Message may have been consumed by $name — verify manually"
  create.result 0 "UNCERTAIN"
  return 0
}
# ─────────────────────────────────────────────────────────────────────────────
# TRANSPORT-INDEPENDENT MESSAGING
# ─────────────────────────────────────────────────────────────────────────────

private.hiveMind.channel.resolve() # <name> # resolve agent name to best available communication channel
{
  local name="$1"
  [ -z "$name" ] && return 1

  # Channel 1: tmux pane via registry
  local pane_target
  pane_target=$(hiveMind.resolve "$name" 2>/dev/null)
  if [ -n "$pane_target" ]; then
    echo "pane|${pane_target}"
    return 0
  fi

  # Channel 2: agentRoom API (if backend is running)
  if command -v agentRoom >/dev/null 2>&1; then
    local status
    status=$(agentRoom backend.status 2>/dev/null)
    if ! echo "$status" | grep -q "not running"; then
      echo "api|${name}"
      return 0
    fi
  fi

  # No channel available
  return 1
}

hiveMind.agent.send() # <name> <message> # send message to agent via best available channel
{
  local name="$1"
  shift
  local message="$*"

  if [ -z "$name" ] || [ -z "$message" ]; then
    error.log "Usage: hiveMind agent.send <name> <message>"
    return 1
  fi

  local channel
  channel=$(private.hiveMind.channel.resolve "$name")
  if [ $? -ne 0 ]; then
    error.log "No communication channel available for agent '$name'"
    return 1
  fi

  local transport="${channel%%|*}"
  local target="${channel#*|}"

  case "$transport" in
    pane)
      otmux send.enter "$target" "$message"
      ;;
    api)
      agentRoom chat "$target" "$message"
      ;;
    *)
      error.log "Unknown transport: $transport"
      return 1
      ;;
  esac

  info.log "Sent to $name via $transport ($target): $message"
  return 0
}
# ─────────────────────────────────────────────────────────────────────────────
# TASK MANAGEMENT
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.task() # <agentId> <description> # send task to specific agent
{
  local agent_id="$1"
  shift
  local description="$*"

  if [ -z "$agent_id" ] || [ -z "$description" ]; then
    error.log "Usage: hiveMind task <agentId> <description>"
    return 1
  fi

  info.log "Sending task to $agent_id: $description"

  # Send via tmux pane
  local pane=$(private.hiveMind.pane.for.agent "$agent_id")
  if [ -n "$pane" ]; then
    otmux send.enter "$pane" "$description"
  else
    # Fallback to API
    agentRoom chat "$agent_id" "$description"
  fi
}

hiveMind.delegate() # <name> <description> <?from:PO> # write task file and send file-read nudge to agent
{
  local name="$1"
  local description="$2"
  local from="${3:-PO}"

  if [ -z "$name" ] || [ -z "$description" ]; then
    error.log "Usage: hiveMind delegate <agent-name> <description> <?from>"
    return 1
  fi

  local session
  session="$(private.hiveMind.active.team)"

  # Resolve agent → pane
  local target
  target=$(hiveMind.resolve "$name" "$session" 2>/dev/null)
  if [ -z "$target" ]; then
    error.log "Cannot resolve '$name' to a pane target"
    return 1
  fi

  # Generate task file
  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  if [ -z "$workspace" ]; then
    error.log "Cannot determine workspace root"
    return 1
  fi

  local tasks_dir="${workspace}/session/tasks"
  mkdir -p "$tasks_dir" 2>/dev/null
  local timestamp
  timestamp=$(date -u "+%Y%m%dT%H%M%SZ")
  local task_file="${tasks_dir}/${timestamp}.task.md"

  {
    echo "# Task: $description"
    echo ""
    echo "**From**: $from"
    echo "**To**: $name"
    echo "**Priority**: NORMAL"
    echo "**Date**: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
    echo ""
    echo "## Description"
    echo ""
    echo "$description"
    echo ""
    echo "## Acceptance Criteria"
    echo ""
    echo "- [ ] Task completed as described"
  } > "$task_file"

  # Send file-read nudge (not inline text — avoids tmux garbling)
  local relative_path="session/tasks/${timestamp}.task.md"
  otmux send "$target" "Read ${relative_path}" Enter

  # Verify nudge landed
  sleep 1
  local verify
  verify=$("$OOSH_DIR/otmux" pane.capture "$target" 5 2>/dev/null)
  if echo "$verify" | grep -q "$relative_path"; then
    console.log "Delegated to $name: $task_file"
    create.result 0 "$task_file"
  else
    warn.log "Nudge may not have landed — verify manually"
    create.result 0 "$task_file"
  fi
  return $(result)
}
hiveMind.task.delegate() # <sshHost> <pane> <taskFile> <?message> # delegate task file to remote agent via scp + send
{
  local sshHost="$1"
  local pane="$2"
  local taskFile="$3"
  local message="$4"

  if [ -z "$sshHost" ] || [ -z "$pane" ] || [ -z "$taskFile" ]; then
    error.log "Usage: hiveMind task.delegate <sshHost> <pane> <taskFile> <?message>"
    return 1
  fi

  if [ ! -f "$taskFile" ]; then
    error.log "Task file not found: $taskFile"
    return 1
  fi

  local filename
  filename=$(basename "$taskFile")

  # Transfer task file to remote
  private.hiveMind.task.transfer "$sshHost" "$taskFile" "$filename"
  if [ $? -ne 0 ]; then
    return 1
  fi

  # Build default message if none provided
  if [ -z "$message" ]; then
    message="Read session/tasks/${filename} — new task"
  fi

  # Send message to agent on remote tmux
  "$OOSH_DIR/ossh" exec "$sshHost" "otmux send '$pane' '$message' Enter"

  # Verify nudge landed
  sleep 1
  local capture
  capture=$("$OOSH_DIR/ossh" exec "$sshHost" "otmux pane.capture '$pane' 5" 2>/dev/null)
  if echo "$capture" | grep -q "$filename"; then
    success.log "Delegated to $pane on $sshHost: $filename"
  else
    warn.log "Nudge may not have landed on $pane@$sshHost — verify manually"
  fi

  create.result 0 "$taskFile"
  return $(result)
}

private.hiveMind.task.transfer() # <sshHost> <taskFile> <filename> # scp task file to remote session/tasks/
{
  local sshHost="$1"
  local taskFile="$2"
  local filename="$3"

  local remoteWorkspace="${CLAUDE_PROJECT_DIR:-/Users/Shared/Workspaces/AI/Claude}"
  local remoteDir="${remoteWorkspace}/session/tasks"

  # Ensure remote tasks directory exists
  "$OOSH_DIR/ossh" exec "$sshHost" "mkdir -p '$remoteDir'" 2>/dev/null

  if ! scp "$taskFile" "${sshHost}:${remoteDir}/${filename}" 2>/dev/null; then
    error.log "scp failed — check SSH config for host '$sshHost'"
    return 1
  fi

  info.log "Transferred $filename to $sshHost:$remoteDir/"
  return 0
}

hiveMind.task.delegate.completion.sshHost() {
  ossh.parameter.completion.sshConfigHost 2>/dev/null
}
hiveMind.task.delegate.completion.pane() {
  private.claudeCode.complete.panes 2>/dev/null
}
hiveMind.task.delegate.completion.taskFile() {
  ls session/tasks/*.md 2>/dev/null
}

hiveMind.broadcast() # <message> # send message to all agents
{
  local message="$1"

  if [ -z "$message" ]; then
    error.log "Usage: hiveMind broadcast <message>"
    return 1
  fi

  info.log "Broadcasting: $message"

  for agent_id in $(hiveMind.list); do
    hiveMind.send.enter "$agent_id" "$message"
  done
}

# ─────────────────────────────────────────────────────────────────────────────
# DISCOVERY
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.discover() # # discover all agents as JSON
{
  agentRoom discover
}

hiveMind.panes() # # list all agent panes
{
  echo "HiveMind Agent Panes:"
  echo "─────────────────────────────────────────"
  private.hiveMind.list.panes "#{pane_id} #{pane_title} #{pane_current_command}" | \
    while read pane_id title cmd; do
      # Filter to only show agent panes
      if echo "$title" | grep -qE "orchestrator|coder|tester|reviewer|researcher|oosh-expert|oosh-tester|scrum-master|product-owner|developer|agent-trainer|task-agent"; then
        printf "%-12s %-20s %s\n" "$pane_id" "$title" "$cmd"
      fi
    done
  echo "─────────────────────────────────────────"
}

# ─────────────────────────────────────────────────────────────────────────────
# CLAUDE CODE INTEGRATION
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.claude() # <agentId> <?prompt> # interact with agent via Claude Code
{
  local agent_id="$1"
  shift
  local prompt="$*"

  if [ -z "$agent_id" ]; then
    error.log "Usage: hiveMind claude <agentId> <?prompt>"
    return 1
  fi

  local pane=$(private.hiveMind.pane.for.agent "$agent_id")

  if [ -z "$pane" ]; then
    error.log "No pane found for agent: $agent_id"
    return 1
  fi

  if [ -z "$prompt" ]; then
    # Focus on agent pane
    otmux pane.select "$pane"
  else
    # Send prompt to agent
    otmux send "$pane" "$prompt" Enter
  fi
}

hiveMind.process.lookup() # <pid> # resolve Claude PID to pane target, role, and session UUID
{
  local pid="$1"
  if [ -z "$pid" ]; then
    error.log "Usage: hiveMind process.lookup <pid>"
    return 1
  fi

  # 1. Verify PID exists and get TTY
  local tty
  tty=$(ps -p "$pid" -o tty= 2>/dev/null)
  tty="${tty// /}"
  if [ -z "$tty" ] || [ "$tty" = "??" ]; then
    error.log "PID $pid not found or has no TTY"
    return 1
  fi

  # 2. Find tmux pane matching this TTY
  local pane_target=""
  local tty_dev="/dev/$tty"
  while IFS=' ' read -r pane_tty pane_ref; do
    if [ "$pane_tty" = "$tty_dev" ]; then
      pane_target="$pane_ref"
      break
    fi
  done < <(private.hiveMind.list.panes tty)

  if [ -z "$pane_target" ]; then
    error.log "PID $pid (TTY $tty) not found in any tmux pane"
    return 1
  fi

  # 3. Extract session UUID from process args
  local sid
  sid=$(ps -p "$pid" -o args= 2>/dev/null | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1)

  # 4. Get role via live discovery
  local role
  role=$(private.hiveMind.live.discover "$pane_target" 2>/dev/null)

  # Output
  if [ -n "$role" ]; then
    echo "PID $pid → $pane_target ($role)"
  else
    echo "PID $pid → $pane_target"
  fi
  [ -n "$sid" ] && echo "Session: $sid"
  echo "TTY: $tty_dev"
  return 0
}

hiveMind.process.lookup.completion.pid() {
  ps -eo pid,args 2>/dev/null | grep -i 'claude' | grep -v grep | awk '{print $1}'
}

hiveMind.process.list() # <?session> # list all Claude processes with pane targets, roles, UUIDs
{
  local session="$1"

  # Header
  printf "%-8s %-34s %-20s %s\n" "PID" "PANE" "ROLE" "SESSION UUID"
  printf "%-8s %-34s %-20s %s\n" "---" "----" "----" "------------"

  local found=0
  while IFS='|' read -r pid tty paneTarget title rest; do
    [ -z "$pid" ] && continue

    # Filter by session if specified
    if [ -n "$session" ] && [[ "$paneTarget" != "${session}:"* ]]; then
      continue
    fi

    # Extract UUID from --resume args → session.id → session.probe
    local sid
    sid=$(echo "$rest" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1)
    if [ -z "$sid" ]; then
      sid=$("$OOSH_DIR/claudeCode" session.id "$paneTarget" 2>/dev/null)
    fi
    if [ -z "$sid" ]; then
      sid=$("$OOSH_DIR/claudeCode" session.probe "$paneTarget" 2>/dev/null)
    fi
    local sidShort="${sid:0:8}${sid:+...}"

    # Get role
    local role
    role=$(private.hiveMind.live.discover "$paneTarget" 2>/dev/null)

    printf "%-8s %-34s %-20s %s\n" "$pid" "$paneTarget" "${role:--}" "${sidShort:--}"
    found=$((found + 1))
  done < <(private.hiveMind.claude.processes)

  [ "$found" -eq 0 ] && echo "(no Claude processes found)"
  return 0
}

hiveMind.process.list.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.teams.save() # # snapshot all Claude processes for restore after restart
{
  local configPath="${CONFIG_PATH:-$HOME/config}"
  local outfile="${configPath}/hivemind.snapshot.$(date +%Y%m%dT%H%M%S).env"

  local count=0
  echo "# hiveMind snapshot $(date +%Y-%m-%dT%H:%M:%S)" > "$outfile"
  echo "# session|address|role|uuid|title" >> "$outfile"

  while IFS='|' read -r pid tty paneTarget title rest; do
    [ -z "$pid" ] && continue

    # Parse pane target → session + address
    local sess="${paneTarget%%:*}"
    local addr="${paneTarget#*:}"

    # UUID from --resume args → session.id → session.probe (last resort)
    local sid
    sid=$(echo "$rest" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1)
    if [ -z "$sid" ]; then
      sid=$("$OOSH_DIR/claudeCode" session.id "$paneTarget" 2>/dev/null)
    fi
    if [ -z "$sid" ]; then
      sid=$("$OOSH_DIR/claudeCode" session.probe "$paneTarget" 2>/dev/null)
    fi

    # Role from live discovery → registry → title fallback
    local role
    role=$(private.hiveMind.live.discover "$paneTarget" 2>/dev/null)
    if [ -z "$role" ]; then
      role=$(private.hiveMind.registry.get "$paneTarget" 2>/dev/null)
    fi
    if [ -z "$role" ] || [ "$role" = "unknown" ]; then
      role="${title}"
      role="${role#✳ }"; role="${role#⠐ }"; role="${role#⠂ }"; role="${role#✻ }"; role="${role#✢ }"; role="${role#✶ }"
      role="${role%%@*}"
      role=$(echo "$role" | tr -d '[:space:]' | head -c 30)
      [ -z "$role" ] && role="unknown"
    fi

    echo "${sess}|${addr}|${role}|${sid:-}|${title:-}" >> "$outfile"
    count=$((count + 1))
  done < <(private.hiveMind.claude.processes)

  # BUG-Q: Also include dead agents from registry+sessions.env
  local reg="$HIVEMIND_REGISTRY"
  local ses="$HIVEMIND_SESSIONS"
  local dead_count=0
  if [ -f "$reg" ]; then
    while IFS='|' read -r pane_target reg_role; do
      [ -z "$pane_target" ] || [ -z "$reg_role" ] && continue
      # Skip if already captured from live processes
      grep -q "|${reg_role}|" "$outfile" 2>/dev/null && continue
      local dead_sess="${pane_target%%:*}"
      local dead_addr="${pane_target#*:}"
      local dead_uuid=""
      [ -f "$ses" ] && dead_uuid=$(grep "^${pane_target}|" "$ses" 2>/dev/null | head -1 | cut -d'|' -f2)
      local dead_title="${reg_role}"
      echo "${dead_sess}|${dead_addr}|${reg_role}|${dead_uuid:-}|${dead_title} (dead)" >> "$outfile"
      dead_count=$((dead_count + 1))
    done < "$reg"
  fi

  echo "Saved $count live + $dead_count dead agents to $outfile"
  cat "$outfile"
  return 0
}

hiveMind.teams.restore() # <?snapshot_file> <?--fork> # restore teams from snapshot; --fork uses fork instead of join (cross-machine)
{
  local snapfile="" forkMode=""
  for arg in "$@"; do
    case "$arg" in
      --fork) forkMode="yes" ;;
      *) [ -z "$snapfile" ] && snapfile="$arg" ;;
    esac
  done
  if [ -z "$snapfile" ]; then
    snapfile=$(ls -t "${CONFIG_PATH:-$HOME/config}"/hivemind.snapshot.*.env 2>/dev/null | head -1)
  fi
  if [ -z "$snapfile" ] || [ ! -f "$snapfile" ]; then
    error.log "No snapshot found. Usage: hiveMind teams.restore <?file> <?--fork>"
    return 1
  fi

  echo "Restoring from: $snapfile${forkMode:+ (fork mode)}"
  echo ""

  # Ensure tmux server is running (BUG-Z1)
  local RESTORE_CLEANUP_SESSION=""
  if ! otmux sessions >/dev/null 2>&1; then
    otmux new __restore_init -d -x 200 -y 50 2>/dev/null
    RESTORE_CLEANUP_SESSION="__restore_init"
  fi

  local prev_sess=""
  while IFS='|' read -r sess addr role uuid title; do
    [[ "$sess" == "#"* ]] && continue
    [ -z "$sess" ] && continue

    # Create session if new
    if [ "$sess" != "$prev_sess" ]; then
      if ! otmux has "$sess" 2>/dev/null; then
        echo "Creating session: $sess"
        otmux new "$sess" -d -x 200 -y 50 2>/dev/null
      fi
      prev_sess="$sess"
    fi

    local pane_target="${sess}:${addr}"

    # Ensure pane exists (create window + split until target pane index exists)
    private.hiveMind.ensure.pane "$pane_target"

    # Set pane title
    if [ -n "$title" ]; then
      "$OOSH_DIR/otmux" pane.title "$pane_target" "$title" 2>/dev/null
    fi

    # Resume Claude if UUID available
    if [ -n "$uuid" ]; then
      local joinCmd="join"
      [ "$forkMode" = "yes" ] && joinCmd="fork"
      echo "  ${pane_target}: ${joinCmd}ing ${role:-unknown} (${uuid:0:8}...)"
      "$OOSH_DIR/otmux" send "$pane_target" "cd '${CLAUDE_PROJECT_DIR:-/Users/Shared/Workspaces/AI/Claude}'" Enter
      sleep 0.5
      "$OOSH_DIR/otmux" send "$pane_target" "claudeCode $joinCmd $uuid" Enter
      # Send boot prompt after resume
      if [ -n "$role" ] && [ "$role" != "unknown" ]; then
        sleep 5
        local boot_file="session/agents/${role}/boot.md"
        if [ -f "$HIVEMIND_AGENTS_DIR/../../$boot_file" ] 2>/dev/null; then
          "$OOSH_DIR/otmux" send "$pane_target" "Read $boot_file" Enter
        fi
      fi
    else
      echo "  ${pane_target}: ${role:-unknown} (no UUID — start manually)"
    fi

    # Update registry
    if [ -n "$role" ] && [ "$role" != "unknown" ]; then
      private.hiveMind.registry.set "$pane_target" "$role" 2>/dev/null
    fi

  done < "$snapfile"

  # Clean up init session if we created it
  if [ -n "$RESTORE_CLEANUP_SESSION" ]; then
    otmux kill "$RESTORE_CLEANUP_SESSION" 2>/dev/null
  fi

  echo ""
  echo "Restore complete. Check with: hiveMind team.status"
  return 0
}

hiveMind.teams.restore.completion.snapshot_file() {
  ls "${CONFIG_PATH:-$HOME/config}"/hivemind.snapshot.*.env 2>/dev/null
}

hiveMind.teams.migrate.completion.ossh() { ossh.parameter.completion.sshConfigHost 2>/dev/null; }

hiveMind.teams.migrate() # <ossh-host> <?snapshot_file> # migrate team to remote machine via ossh
{
  local host="$1"
  local snapfile="$2"

  if [ -z "$host" ]; then
    error.log "Usage: hiveMind teams.migrate <ossh-host> <?snapshot_file>"
    return 1
  fi

  # 1. Validate snapshot
  if [ -z "$snapfile" ]; then
    snapfile=$(ls -t "${CONFIG_PATH:-$HOME/config}"/hivemind.snapshot.*.env 2>/dev/null | head -1)
  fi
  if [ -z "$snapfile" ] || [ ! -f "$snapfile" ]; then
    echo "No snapshot found. Running teams.save first..."
    hiveMind.teams.save
    snapfile=$(ls -t "${CONFIG_PATH:-$HOME/config}"/hivemind.snapshot.*.env 2>/dev/null | head -1)
  fi
  echo "Using snapshot: $snapfile"

  # 2. Transfer config files
  echo ""
  echo "Transferring config to $host..."
  "$OOSH_DIR/ossh" push.dir "$host" ~/config

  # 3. Transfer JSONL session files
  echo ""
  echo "Transferring session JSONL files to $host..."
  local claudeProjectsDir="$HOME/.claude/projects"
  local transferCount=0
  while IFS='|' read -r sess addr role uuid title; do
    [[ "$sess" == "#"* ]] && continue
    [ -z "$uuid" ] && continue
    for dir in "$claudeProjectsDir"/*/; do
      if [ -f "${dir}${uuid}.jsonl" ]; then
        local remotePath="${dir}${uuid}.jsonl"
        # Ensure remote directory exists
        "$OOSH_DIR/ossh" exec "$host" "mkdir -p '$(dirname "$remotePath")'" 2>/dev/null
        scp "$remotePath" "${host}:${remotePath}" 2>/dev/null && transferCount=$((transferCount + 1))
        break
      fi
    done
  done < "$snapfile"
  echo "Transferred $transferCount JSONL files"

  # 4. Sync oosh code on remote
  echo ""
  echo "Syncing oosh on $host..."
  "$OOSH_DIR/ossh" exec "$host" "cd ~/oosh && git pull" 2>/dev/null || \
    "$OOSH_DIR/ossh" push.dir "$host" "$OOSH_DIR"

  # 4b. Check prerequisites on remote
  echo ""
  echo "Checking prerequisites on $host..."
  local missing=""
  missing=$("$OOSH_DIR/ossh" exec "$host" '
    errors=""
    command -v tmux >/dev/null 2>&1 || { test -x /opt/homebrew/bin/tmux && export PATH=/opt/homebrew/bin:$PATH; } || errors="${errors}tmux "
    command -v claude >/dev/null 2>&1 || test -x ~/.local/bin/claude || errors="${errors}claude "
    echo "$errors"
  ' 2>/dev/null)
  if [ -n "$missing" ]; then
    error.log "Missing on $host: $missing"
    return 1
  fi
  echo "Prerequisites OK"

  # 5. Model compatibility check — opus[1m] blocks ALL API calls on some subscriptions
  local badModel
  badModel=$("$OOSH_DIR/ossh" exec "$host" 'grep -o "opus\[1m\]" ~/.claude/settings.json 2>/dev/null' 2>/dev/null)
  if [ -n "$badModel" ]; then
    warn.log "Remote has opus[1m] model — fixing to opus (200k)"
    "$OOSH_DIR/ossh" exec "$host" "sed -i.bak 's/opus\[1m\]/opus/g' ~/.claude/settings.json" 2>/dev/null
  fi

  # 6. Ensure tmux server running + restore on remote (--fork for cross-machine)
  echo ""
  echo "Running teams.restore --fork on $host..."
  "$OOSH_DIR/ossh" exec "$host" "export PATH=/opt/homebrew/bin:\$PATH; hiveMind teams.restore --fork"

  # 7. Verify
  echo ""
  echo "Verifying on $host..."
  "$OOSH_DIR/ossh" exec "$host" "export PATH=/opt/homebrew/bin:\$PATH; hiveMind team.status" 2>/dev/null

  echo ""
  echo "Migration to $host complete."
  echo "Connect with: ossh login $host"
  return 0
}

hiveMind.teams.migrate.completion.ossh_host() {
  "$OOSH_DIR/ossh" config.get.completion 2>/dev/null
}

hiveMind.teams.migrate.completion.snapshot_file() {
  ls "${CONFIG_PATH:-$HOME/config}"/hivemind.snapshot.*.env 2>/dev/null
}

hiveMind.registry.set() # <pane> <role> # set registry entry for a pane
{
  local target="$1" role="$2"
  if [ -z "$target" ] || [ -z "$role" ]; then
    error.log "Usage: hiveMind registry.set <pane> <role>"
    return 1
  fi
  private.hiveMind.registry.set "$target" "$role"
  echo "Registry: $target → $role"
}

hiveMind.registry.remove() # <pane> # remove registry entry for a pane
{
  local target="$1"
  if [ -z "$target" ]; then
    error.log "Usage: hiveMind registry.remove <pane>"
    return 1
  fi
  local reg="$HIVEMIND_REGISTRY"
  if [ -f "$reg" ] && grep -q "^${target}|" "$reg" 2>/dev/null; then
    grep -v "^${target}|" "$reg" > "${reg}.tmp" && mv "${reg}.tmp" "$reg"
    echo "Removed $target from registry"
  else
    error.log "No registry entry for $target"
    return 1
  fi
}

hiveMind.registry.set.completion.pane() {
  private.hiveMind.list.panes addr
}

hiveMind.registry.set.completion.role() {
  hiveMind.role.list 2>/dev/null
}

hiveMind.registry.remove.completion.pane() {
  [ -f "$HIVEMIND_REGISTRY" ] && cut -d'|' -f1 "$HIVEMIND_REGISTRY" 2>/dev/null
}

hiveMind.registry.list() # <?session> # list registry entries (live discovery + file fallback)
{
  private.hiveMind.registry.list "$1"
}

hiveMind.registry.list.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.team.activate() # <session> # set active team (alias for team.switch)
{
  hiveMind.team.switch "$@"
}

hiveMind.team.activate.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.consistency.audit() # <?session> # cross-compare all identity sources and show mismatches
{
  local filter_session="$1"

  # Load registry file
  local reg="$HIVEMIND_REGISTRY"
  # Load sessions.env (role|uuid)
  local sess_file="$HIVEMIND_SESSIONS"

  echo -e "${BOLD_CYAN}Identity Consistency Audit${NORMAL}"
  echo -e "${GRAY}$(printf '%.0s═' {1..85})${NORMAL}"
  printf "${BOLD_WHITE}%-28s %-16s %-16s %-13s %-16s %s${NORMAL}\n" "PANE" "TITLE" "REGISTRY" "SESS.ENV" "LIVE UUID" "MATCH"
  echo -e "${GRAY}$(printf '%.0s─' {1..85})${NORMAL}"

  local consistent=0 inconsistent=0 noAgent=0
  local -A seenUuids

  # Build lookup of Claude processes by pane target
  local -A claudeProcs
  while IFS='|' read -r pid tty paneTarget title rest; do
    [ -z "$pid" ] && continue
    claudeProcs["$paneTarget"]="${pid}|${rest}"
  done < <(private.hiveMind.claude.processes)

  # Enumerate ALL panes (not just Claude-running ones)
  while IFS='|' read -r pane_target pane_title; do
    [ -z "$pane_target" ] && continue

    # Filter by session
    if [ -n "$filter_session" ] && [[ "$pane_target" != "${filter_session}:"* ]]; then
      continue
    fi

    local title="${pane_title}"
    local titleShort="${title:0:14}"
    [ ${#title} -gt 14 ] && titleShort="${titleShort}.."

    # Check if this pane has a Claude process
    local hasClaude=""
    local procRest=""
    if [ -n "${claudeProcs[$pane_target]:-}" ]; then
      hasClaude="yes"
      procRest="${claudeProcs[$pane_target]#*|}"
    fi

    # Registry role
    local regRole=""
    [ -f "$reg" ] && regRole=$(grep "^${pane_target}|" "$reg" 2>/dev/null | head -1 | cut -d'|' -f2)

    # Non-Claude pane with no registry entry — show as "no agent" (not an error)
    if [ -z "$hasClaude" ] && [ -z "$regRole" ]; then
      printf "%-28s %-16s ${GRAY}%-16s %-13s %-16s %s${NORMAL}\n" \
        "$pane_target" "$titleShort" "—" "—" "—" "no agent"
      noAgent=$((noAgent + 1))
      continue
    fi

    local regDisplay="${regRole:-${BOLD_YELLOW}MISSING${NORMAL}}"

    # Sessions.env UUID (look up by pane target)
    local sessUuid=""
    if [ -f "$sess_file" ]; then
      sessUuid=$(grep "^${pane_target}|" "$sess_file" 2>/dev/null | head -1 | cut -d'|' -f2)
    fi
    local sessShort="${sessUuid:0:8}"

    # Live UUID
    local liveUuid=""
    liveUuid=$(private.hiveMind.liveUuid "$pane_target" "$procRest")
    local liveShort="${liveUuid:0:8}"

    # Consistency checks
    local issues=()
    [ -z "$regRole" ] && issues+=("no registry")
    if [ -n "$regRole" ] && [ -n "$title" ]; then
      echo "$title" | grep -qi "$regRole" 2>/dev/null || issues+=("title≠reg")
    fi
    if [ -n "$sessUuid" ] && [ -n "$liveUuid" ] && [ "$sessUuid" != "$liveUuid" ]; then
      issues+=("UUID stale")
    fi
    if [ -n "$liveUuid" ]; then
      if [ -n "${seenUuids[$liveUuid]:-}" ]; then
        issues+=("dup UUID")
      fi
      seenUuids["$liveUuid"]="$pane_target"
    fi
    if [ -n "$regRole" ]; then
      [[ ${#regRole} -gt 30 ]] && issues+=("role>30")
      [[ "$regRole" == *" "* ]] && issues+=("role has space")
      private.hiveMind.role.isGeneric "$regRole" && issues+=("generic role")
    fi
    if [ -z "$hasClaude" ] && [ -n "$regRole" ]; then
      issues+=("no process")
    fi

    local match="${BOLD_GREEN}✓${NORMAL}"
    local issueStr=""
    if [ ${#issues[@]} -gt 0 ]; then
      match="${BOLD_RED}✗${NORMAL}"
      issueStr="${BOLD_RED}$(IFS=', '; echo "${issues[*]}")${NORMAL}"
      inconsistent=$((inconsistent + 1))
    else
      consistent=$((consistent + 1))
    fi

    printf "%-28s %-16s %-16b %-13s %-16s %b %b\n" \
      "$pane_target" "$titleShort" "$regDisplay" "${sessShort:--}" "${liveShort:--}" "$match" "$issueStr"

  done < <(private.hiveMind.list.panes "#{session_name}:#{window_index}.#{pane_index}|#{pane_title}" "${filter_session:--a}")

  echo -e "${GRAY}$(printf '%.0s─' {1..85})${NORMAL}"
  echo -e "Summary: ${BOLD_GREEN}$consistent consistent${NORMAL}, ${BOLD_RED}$inconsistent inconsistent${NORMAL}, ${GRAY}$noAgent no agent${NORMAL}"
  return 0
}

hiveMind.consistency.audit.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.consistency.fix() # <?session> # auto-repair identity mismatches from live truth
{
  local filter_session="$1"

  local reg="$HIVEMIND_REGISTRY"
  local sess_file="$HIVEMIND_SESSIONS"

  # Ensure files exist
  [ -f "$reg" ] || touch "$reg"
  [ -f "$sess_file" ] || touch "$sess_file"

  echo -e "${BOLD_CYAN}Fixing identity consistency...${NORMAL}"
  echo

  # --- 0. Clean up garbage entries in sessions.env ---
  # Clean garbage: UUIDs as keys, generic roles, old role-keyed entries (pre-schema-change), empty lines
  if [ -f "$sess_file" ]; then
    local garbageCount=0
    local gc
    # UUIDs used as keys
    gc=$(grep -cE '^[0-9a-f]{8}-' "$sess_file" 2>/dev/null) && garbageCount=$((garbageCount + gc))
    sed -i '' '/^[0-9a-f]\{8\}-/d' "$sess_file"
    # Generic "ClaudeCode" entries
    gc=$(grep -cE '^ClaudeCode\|' "$sess_file" 2>/dev/null) && garbageCount=$((garbageCount + gc))
    sed -i '' '/^ClaudeCode|/d' "$sess_file"
    # Legacy role-keyed entries (keys without ':' are old role names, not pane targets)
    gc=$(grep -cvE '^[^|]*:[0-9]' "$sess_file" 2>/dev/null) && garbageCount=$((garbageCount + gc))
    local paneOnly
    paneOnly=$(sed -n '/^[^|]*:[0-9]/p' "$sess_file")
    echo "$paneOnly" > "$sess_file"
    # Empty lines
    sed -i '' '/^$/d' "$sess_file"
    [ $garbageCount -gt 0 ] && echo -e "${BOLD_GREEN}Cleaned $garbageCount garbage/legacy entries from sessions.env${NORMAL}"
  fi

  local fixedReg=0 fixedSess=0 fixedTitle=0 skipped=0 dupResolved=0
  local -A seenUuids roleForUuid claudeProcs

  # Build lookup of Claude processes by pane target
  while IFS='|' read -r pid tty paneTarget title rest; do
    [ -z "$pid" ] && continue
    claudeProcs["$paneTarget"]="${pid}|${rest}"
  done < <(private.hiveMind.claude.processes)

  # Iterate ALL panes
  while IFS='|' read -r paneTarget title; do
    [ -z "$paneTarget" ] && continue

    if [ -n "$filter_session" ] && [[ "$paneTarget" != "${filter_session}:"* ]]; then
      continue
    fi

    # Check Claude process
    local hasClaude="" procRest=""
    if [ -n "${claudeProcs[$paneTarget]:-}" ]; then
      hasClaude="yes"
      procRest="${claudeProcs[$paneTarget]#*|}"
    fi

    # Non-Claude pane with no registry — skip
    local curReg=""
    [ -f "$reg" ] && curReg=$(grep "^${paneTarget}|" "$reg" 2>/dev/null | head -1 | cut -d'|' -f2)
    if [ -z "$hasClaude" ] && [ -z "$curReg" ]; then
      continue
    fi

    # --- 1. Live UUID (with probe fallback for forked sessions) ---
    local liveUuid=""
    liveUuid=$(private.hiveMind.liveUuid "$paneTarget" "$procRest" "probe")

    # --- 2. Resolve role (ground truth) ---
    local role="$curReg"
    if private.hiveMind.role.isGeneric "$role"; then
      role=$(private.hiveMind.role.fromTitle "$title" 2>/dev/null)
    fi
    if [ -z "$role" ]; then
      local discoverResult
      discoverResult=$(private.hiveMind.live.discover "$paneTarget" 2>/dev/null)
      if [ -n "$discoverResult" ]; then
        role="${discoverResult%%@*}"
      fi
    fi
    if [ -z "$role" ] || private.hiveMind.role.isGeneric "$role"; then
      printf "${BOLD_YELLOW}%-28s %-16s SKIPPED (can't determine role)${NORMAL}\n" "$paneTarget" "${title:0:14}"
      skipped=$((skipped + 1))
      continue
    fi

    local lineParts=()

    # --- 3a. Fix registry ---
    if [ -z "$curReg" ]; then
      private.hiveMind.registry.set "$paneTarget" "$role"
      lineParts+=("${BOLD_GREEN}registry.set ✓${NORMAL}")
      fixedReg=$((fixedReg + 1))
    elif [ "$curReg" != "$role" ]; then
      private.hiveMind.env.set "$reg" "$paneTarget" "$role"
      lineParts+=("${BOLD_GREEN}registry ${curReg} → ${role} ✓${NORMAL}")
      fixedReg=$((fixedReg + 1))
    else
      lineParts+=("${GREEN}registry ✓${NORMAL}")
    fi

    # --- 3b. Fix sessions.env UUID ---
    if [ -n "$liveUuid" ]; then
      if [ -n "${seenUuids[$liveUuid]:-}" ]; then
        local dupRole="${roleForUuid[$liveUuid]:-}"
        echo -e "${BOLD_RED}⚠ DUP UUID: $dupRole and $role share ${liveUuid:0:8}${NORMAL}"
        dupResolved=$((dupResolved + 1))
      fi
      # Purge stale entries sharing this UUID (only if pane has no live process)
      local dupPanes
      dupPanes=$(grep "|${liveUuid}$" "$sess_file" 2>/dev/null | cut -d'|' -f1 | grep -v "^${paneTarget}$")
      if [ -n "$dupPanes" ]; then
        while IFS= read -r stalePane; do
          [ -z "$stalePane" ] && continue
          if [ -n "${claudeProcs[$stalePane]:-}" ]; then
            continue  # Live process — shared session, not stale
          fi
          private.hiveMind.env.del "$sess_file" "$stalePane" "$liveUuid"
          lineParts+=("${BOLD_YELLOW}purged ${stalePane}|${liveUuid:0:8}${NORMAL}")
          dupResolved=$((dupResolved + 1))
        done <<< "$dupPanes"
      fi
      seenUuids["$liveUuid"]="$paneTarget"
      roleForUuid["$liveUuid"]="$role"

      if private.hiveMind.env.set "$sess_file" "$paneTarget" "$liveUuid"; then
        lineParts+=("${BOLD_GREEN}sessions.env → ${liveUuid:0:8} ✓${NORMAL}")
        fixedSess=$((fixedSess + 1))
      else
        lineParts+=("${GREEN}sessions.env ✓${NORMAL}")
      fi
    else
      lineParts+=("${BOLD_YELLOW}sessions.env — (no live UUID)${NORMAL}")
    fi

    # --- 3c. Fix pane title ---
    if [ -n "$hasClaude" ] && ! echo "$title" | grep -qi "$role" 2>/dev/null; then
      otmux pane.lock "$paneTarget" "$role"
      lineParts+=("${BOLD_GREEN}title → $role ✓${NORMAL}")
      fixedTitle=$((fixedTitle + 1))
    fi

    # --- 3d. Deregister dead panes ---
    if [ -z "$hasClaude" ] && [ -n "$curReg" ]; then
      private.hiveMind.env.del "$reg" "$paneTarget"
      lineParts+=("${BOLD_YELLOW}deregistered (no process)${NORMAL}")
      fixedReg=$((fixedReg + 1))
    fi

    printf "%-28s %-16s %b\n" "$paneTarget" "$role" "$(IFS='  '; echo "${lineParts[*]}")"

  done < <(private.hiveMind.list.panes "#{session_name}:#{window_index}.#{pane_index}|#{pane_title}" "${filter_session:--a}")

  echo
  echo -e "Fixed: ${BOLD_GREEN}$fixedReg${NORMAL} registry, ${BOLD_GREEN}$fixedSess${NORMAL} sessions, ${BOLD_GREEN}$fixedTitle${NORMAL} titles, ${BOLD_RED}$dupResolved${NORMAL} duplicates"
  [ $skipped -gt 0 ] && echo -e "${BOLD_YELLOW}Skipped: $skipped (unknown role)${NORMAL}"
  echo "Run hiveMind consistency.audit to verify."
  return 0
}

hiveMind.consistency.fix.completion.session() {
  private.hiveMind.teams.complete
}

# ─────────────────────────────────────────────────────────────────────────────
# OOSH SPECIALIST AGENTS
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.oosh.init() # <?session> # initialize OOSH expert and tester agents in existing session
{
  local session="${1:-$(private.hiveMind.active.team)}"
  
  info.log "Initializing OOSH specialist agents in session: $session"
  
  # Check if session exists
  if ! otmux has "$session" 2>/dev/null; then
    error.log "Session $session not found. Create it first with otmux new $session"
    return 1
  fi
  
  # List existing panes
  local pane_count=$(private.hiveMind.pane.count "$session")
  info.log "Session has $pane_count panes"
  
  success.log "OOSH agents ready in session: $session"
  echo ""
  echo "Use these methods to teach and interact:"
  echo "  hiveMind.teach <pane> <role>    # Teach agent its role"
  echo "  hiveMind.send.enter <name> <msg> # Send message to agent (with Enter)"
  echo "  hiveMind.send <name> <keys>     # Send raw keys to agent (no Enter)"
  echo "  hiveMind.task <agentId> <task>  # Assign task to agent"
}

hiveMind.team.setup.oosh() # <?session> # create 3-pane tmux session with oosh-orchestrator, expert, and tester agents
{
  local session="${1:-$(private.hiveMind.active.team)}"

  info.log "Setting up OOSH team in session: $session"

  # Check if session already exists
  if otmux has "$session" 2>/dev/null; then
    warn.log "Session '$session' already exists"
    echo "  Kill it first:  otmux kill $session"
    echo "  Or attach to it: otmux attach $session"
    return 1
  fi

  # ── Create session and pane layout ──────────────────────────────────────
  # ┌─────────────────────────────────────────┐
  # │ Pane 0 - ORCHESTRATOR (oosh-orchestrator)│
  # ├───────────────────────┬─────────────────┤
  # │ Pane 1 - EXPERT       │ Pane 2 - TESTER │
  # │ (oosh-expert)         │ (oosh-tester)   │
  # └───────────────────────┴─────────────────┘

  info.log "Creating tmux session: $session"
  otmux new "$session" -d

  # Name window 0
  otmux window.rename "${session}:0" "$HIVEMIND_WINDOW_NAME"

  # Split pane 0 vertically → pane 0 (upper) + pane 1 (lower)
  otmux split.v -t "${session}:0.0"

  # Split pane 1 horizontally → pane 1 (lower-left) + pane 2 (lower-right)
  otmux split.h -t "${session}:0.1"

  # Set pane titles and export role env vars
  private.hiveMind.pane.identify "${session}:0.0" "oosh-orchestrator"
  private.hiveMind.pane.identify "${session}:0.1" "oosh-expert"
  private.hiveMind.pane.identify "${session}:0.2" "oosh-tester"

  # ── Start Claude Code in each pane ─────────────────────────────────────
  info.log "Starting Claude Code in all panes..."

  otmux send.enter "${session}:0.0" "claude --dangerously-skip-permissions"
  sleep 2
  otmux send.enter "${session}:0.1" "claude --dangerously-skip-permissions"
  sleep 2
  otmux send.enter "${session}:0.2" "claude --dangerously-skip-permissions"

  # Wait for Claude Code to initialize
  info.log "Waiting for Claude Code agents to initialize..."
  sleep 8

  # ── Capture UUIDs for identity tracking ─────────────────────────────────
  info.log "Capturing session UUIDs..."
  local ses="$HIVEMIND_SESSIONS"
  local _panes=("${session}:0.0" "${session}:0.1" "${session}:0.2")
  for i in 0 1 2; do
    local _sid
    _sid=$(claudeCode session.probe "${_panes[$i]}" 2>/dev/null)
    if [ -n "$_sid" ]; then
      private.hiveMind.session.store "${_panes[$i]}" "$_sid"
    fi
  done

  # ── Send role prompts ──────────────────────────────────────────────────
  info.log "Sending role prompts to agents..."

  otmux send.enter "${session}:0.0" "$(private.hiveMind.get.role.prompt oosh-orchestrator)"
  sleep 1
  otmux send.enter "${session}:0.1" "$(private.hiveMind.get.role.prompt oosh-expert)"
  sleep 1
  otmux send.enter "${session}:0.2" "$(private.hiveMind.get.role.prompt oosh-tester)"

  # ── Done ───────────────────────────────────────────────────────────────
  success.log "OOSH team created in session: $session"
  echo ""
  echo "Pane layout:"
  echo "┌─────────────────────────────────────────┐"
  echo "│ Pane 0 - ORCHESTRATOR (oosh-orchestrator)│"
  echo "├───────────────────────┬─────────────────┤"
  echo "│ Pane 1 - EXPERT       │ Pane 2 - TESTER │"
  echo "│ (oosh-expert)         │ (oosh-tester)   │"
  echo "└───────────────────────┴─────────────────┘"
  echo ""
  echo "Attach with: otmux attach $session"
}

hiveMind.team.setup.oosh.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.teach() # <pane> <role> # teach an agent its specialized role via SKILL.md
{
  local pane="$1"
  local role="$2"

  if [ -z "$pane" ] || [ -z "$role" ]; then
    error.log "Usage: hiveMind teach <pane> <role>"
    echo "  Use 'hiveMind role.list' to see available roles"
    return 1
  fi

  # Get role prompt
  local prompt
  prompt=$(private.hiveMind.get.role.prompt "$role")
  if [ -z "$prompt" ]; then
    error.log "Unknown role: $role"
    echo "  Use 'hiveMind role.list' to see available roles"
    return 1
  fi

  info.log "Teaching $role to pane $pane..."

  # Check for SKILL.md in canonical location
  local skill_path="$HIVEMIND_AGENTS_DIR/$role/SKILL.md"

  local full_prompt="$prompt"
  if [ -f "$skill_path" ]; then
    full_prompt="$prompt Also read these key files to understand OOSH: CLAUDE.md, docs/oosh-architecture.md, and $skill_path"
  fi

  # Send the teaching prompt to the pane
  otmux send "$pane" "$full_prompt" Enter

  success.log "Sent role instructions to pane $pane"
  echo "The agent should now learn its role as $role"
}

hiveMind.teach.completion.role() {
  hiveMind.role.list 2>/dev/null || echo "oosh-expert"
}

hiveMind.roles() # [filter] # list available agent roles with descriptions (from SKILL.md)
{
  local filter="$1"
  local count=0

  if [ ! -d "$HIVEMIND_AGENTS_DIR" ]; then
    error.log "Agents directory not found: $HIVEMIND_AGENTS_DIR"
    return 1
  fi

  echo "Available HiveMind Roles:"
  echo "─────────────────────────"

  for role_dir in "$HIVEMIND_AGENTS_DIR"/*/; do
    [ -d "$role_dir" ] || continue
    local role_name=$(basename "$role_dir")
    local skill_file="$role_dir/SKILL.md"
    [ -f "$skill_file" ] || continue

    # Apply filter if given
    if [ -n "$filter" ] && [[ "$role_name" != *"$filter"* ]]; then
      continue
    fi

    # Extract description from SKILL.md YAML frontmatter
    local desc=""
    desc=$(grep '^description:' "$skill_file" | head -1 | sed 's/^description: *//; s/^"//; s/"$//')

    # Truncate long descriptions
    if [ ${#desc} -gt 60 ]; then
      desc="${desc:0:57}..."
    fi

    printf "  %-25s %s\n" "$role_name" "— $desc"
    count=$((count + 1))
  done

  echo "─────────────────────────"
  echo "$count roles found"
}

# ─────────────────────────────────────────────────────────────────────────────
# AGENT ROLE MANAGEMENT
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.role.list() # # list all agent roles from .claude/agents/
{
  if [ ! -d "$HIVEMIND_AGENTS_DIR" ]; then
    error.log "Agents directory not found: $HIVEMIND_AGENTS_DIR"
    return 1
  fi

  for role_dir in "$HIVEMIND_AGENTS_DIR"/*/; do
    [ -d "$role_dir" ] || continue
    local role_name=$(basename "$role_dir")
    if [ -f "$role_dir/SKILL.md" ]; then
      echo "$role_name"
    fi
  done
}

hiveMind.role.prompt() # <role> # output the teaching prompt for a role
{
  local role="$1"

  if [ -z "$role" ]; then
    error.log "Usage: hiveMind role.prompt <role>"
    return 1
  fi

  local prompt
  prompt=$(private.hiveMind.get.role.prompt "$role")
  if [ -z "$prompt" ]; then
    error.log "Unknown role: $role"
    return 1
  fi

  local skill_path="$HIVEMIND_AGENTS_DIR/$role/SKILL.md"
  if [ -f "$skill_path" ]; then
    echo "$prompt Also read these key files to understand OOSH: CLAUDE.md, docs/oosh-architecture.md, and $skill_path"
  else
    echo "$prompt"
  fi
}

hiveMind.role.prompt.completion.role() {
  hiveMind.role.list 2>/dev/null
}

hiveMind.role.teach() # <pane> <role> # teach a role to an existing pane (alias for teach)
{
  hiveMind.teach "$@"
}

hiveMind.role.teach.completion.role() {
  hiveMind.role.list 2>/dev/null
}

hiveMind.registry.fix() # # clean up invalid entries in roles registry (raw %NNN pane IDs)
{
  local registry="$HIVEMIND_REGISTRY"
  [ -f "$registry" ] || { error.log "No registry file"; return 1; }

  local tmpfile="${registry}.tmp"
  local fixed=0 dropped=0
  > "$tmpfile"

  while IFS='|' read -r pane role; do
    [ -z "$pane" ] || [ -z "$role" ] && continue
    # Skip entries with raw pane IDs (%NNN format)
    if echo "$pane" | grep -qE '^%[0-9]+$'; then
      local resolved
      resolved=$(otmux pane.get "$pane" '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null)
      if [ -n "$resolved" ] && echo "$resolved" | grep -qE '^[a-zA-Z0-9_-]+:[0-9]+\.[0-9]+$'; then
        echo "${resolved}|${role}" >> "$tmpfile"
        fixed=$((fixed + 1))
        info.log "Fixed: $pane → $resolved ($role)"
      else
        dropped=$((dropped + 1))
        warn.log "Dropped: $pane|$role (pane no longer exists)"
      fi
      continue
    fi
    echo "${pane}|${role}" >> "$tmpfile"
  done < "$registry"

  mv "$tmpfile" "$registry"
  console.log "Registry fix: $fixed resolved, $dropped dropped"
}
hiveMind.registry.fix.completion() { :; }

hiveMind.registry.refresh() # <?session> # probe all panes, update roles + sessions from /status ground truth
{
  local session="${1:-$(private.hiveMind.active.team)}"
  [ -z "$session" ] && session=$(private.hiveMind.current.session)

  local reg="$HIVEMIND_REGISTRY"
  local ses="$HIVEMIND_SESSIONS"
  local updated=0

  # Prune entries for panes that no longer exist in tmux
  if [ -f "$reg" ]; then
    local clean=""
    while IFS='|' read -r pt rl; do
      [ -z "$pt" ] && continue
      if otmux pane.get "$pt" % >/dev/null 2>&1; then
        clean="${clean}${pt}|${rl}\n"
      else
        info.log "  Pruning dead pane: $pt ($rl)"
      fi
    done < "$reg"
    printf '%b' "$clean" > "$reg"
  fi

  local pane_list
  pane_list=$(private.hiveMind.list.panes addr "$session")

  while IFS= read -r pane_target; do
    [ -z "$pane_target" ] && continue

    # Only probe panes with a Claude process
    claudeCode process.find "$pane_target" >/dev/null 2>&1 || continue

    # Probe: get ground-truth UUID from /status (bypasses stale env files)
    local sid
    sid=$(claudeCode session.probe "$pane_target" 2>/dev/null)
    [ -z "$sid" ] && continue

    # Get session name — only use /rename'd names (role@model format)
    local name
    name=$(claudeCode session.name "$sid" 2>/dev/null)

    # Remove stale entry for this pane regardless
    if [ -f "$reg" ]; then
      grep -v "^${pane_target}|" "$reg" > "${reg}.tmp" 2>/dev/null
      mv "${reg}.tmp" "$reg"
    fi

    # Only write back if we have a proper role@model name
    [ -z "$name" ] && continue
    [[ "$name" != *"@"* ]] && continue
    local role="${name%%@*}"

    # Validate role: reject garbage from firstPrompt leaking into session names
    [[ "$role" == *" "* ]] && { warn.log "  $pane_target: rejected role with spaces: $role"; continue; }
    [[ ${#role} -gt 30 ]] && { warn.log "  $pane_target: rejected role >30 chars: ${role:0:30}..."; continue; }
    [[ "$role" =~ ^(Read|You|I\ am|Write|Edit|Help|Search|Find|Look|Check|Run|Show) ]] && { warn.log "  $pane_target: rejected prompt-like role: $role"; continue; }

    # Write roles registry: pane → role
    echo "${pane_target}|${role}" >> "$reg"

    # Write sessions file: pane → uuid
    if [ -f "$ses" ]; then
      grep -v "^${pane_target}|" "$ses" > "${ses}.tmp" 2>/dev/null
      mv "${ses}.tmp" "$ses"
    fi
    echo "${pane_target}|${sid}" >> "$ses"

    updated=$((updated + 1))
    info.log "  $pane_target → $role [$sid]"
  done <<< "$pane_list"

  # Detect duplicate UUIDs across roles (potential data corruption)
  if [ -f "$ses" ]; then
    local dup_uuids
    dup_uuids=$(awk -F'|' '{print $2}' "$ses" 2>/dev/null | sort | uniq -d)
    if [ -n "$dup_uuids" ]; then
      while IFS= read -r dup_sid; do
        [ -z "$dup_sid" ] && continue
        local dupPanes
        dupPanes=$(grep "|${dup_sid}" "$ses" 2>/dev/null | cut -d'|' -f1 | tr '\n' ', ')
        warn.log "DUPLICATE UUID ${dup_sid:0:8}... shared by panes: ${dupPanes%, }"
      done <<< "$dup_uuids"
    fi
  fi

  success.log "Registry refreshed: $updated entries"
}
hiveMind.registry.refresh.completion.session() {
  private.hiveMind.teams.complete
}

# ─────────────────────────────────────────────────────────────────────────────
# AGENT LIFECYCLE
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.agent.bootstrap() # <role> <?session> <?pane> # full bootstrap: create pane, start bash, launch claude, teach role
{
  local role="$1"
  local session="${2:-$(private.hiveMind.active.team)}"
  local target_pane="$3"

  if [ -z "$role" ]; then
    error.log "Usage: hiveMind agent.bootstrap <role> <?session> <?pane>"
    echo "  Use 'hiveMind role.list' to see available roles"
    return 1
  fi

  # Verify role exists
  local prompt
  prompt=$(private.hiveMind.get.role.prompt "$role")
  if [ -z "$prompt" ]; then
    error.log "Unknown role: $role"
    return 1
  fi

  # Check session exists
  if ! otmux has "$session" 2>/dev/null; then
    error.log "Session $session not found"
    return 1
  fi

  info.log "Bootstrapping $role agent in session $session..."

  # Step 1: Create pane if not specified
  if [ -z "$target_pane" ]; then
    # Split from the last pane in window 0
    otmux split.h -t "${session}:0"
    otmux tiled "${session}:0"
    # Get the new pane index
    local pane_count=$(otmux panes -t "${session}:0" -F "#{pane_index}" | sort -n | tail -1)
    target_pane="${session}:0.${pane_count}"
    info.log "Created new pane: $target_pane"
  fi

  # Step 2: Set pane title and export role env var
  private.hiveMind.pane.identify "$target_pane" "$role"

  # Step 3: Start Claude Code
  info.log "Starting Claude Code in pane $target_pane..."
  "$OOSH_DIR/otmux" send.enter "$target_pane" "claudeCode new"

  # Step 4: Wait for startup
  info.log "Waiting for Claude Code to initialize..."
  sleep 8

  # Step 4b: Capture UUID and write to sessions file
  local new_sid
  new_sid=$(claudeCode session.probe "$target_pane" 2>/dev/null)
  if [ -n "$new_sid" ]; then
    private.hiveMind.session.store "$target_pane" "$new_sid"
    info.log "Captured session UUID for $role ($target_pane): ${new_sid:0:8}..."
  fi

  # Step 4c: Set session name via /rename so registry.refresh can identify this agent
  info.log "Setting session name to ${role}@opus..."
  otmux send.enter "$target_pane" "/rename ${role}@opus"
  sleep 2

  # Step 5: Teach role
  local full_prompt
  full_prompt=$(hiveMind.role.prompt "$role")
  otmux send.enter "$target_pane" "$full_prompt"

  # Step 6: Wait briefly for processing
  sleep 3

  success.log "Agent $role bootstrapped in pane $target_pane"
  echo "Verify with: hiveMind agent.verify $target_pane"
}

hiveMind.agent.bootstrap.completion.role() {
  hiveMind.role.list 2>/dev/null
}

hiveMind.agent.bootstrap.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.agent.verify() # <pane> # check if agent is alive and processing
{
  local pane="$1"

  if [ -z "$pane" ]; then
    error.log "Usage: hiveMind agent.verify <pane>"
    return 1
  fi

  info.log "Verifying agent in pane $pane..."

  local output
  output=$(otmux pane.capture "$pane" 15 2>/dev/null)

  if [ $? -ne 0 ]; then
    error.log "Cannot capture pane: $pane"
    return 1
  fi

  # Check for processing indicators
  if echo "$output" | grep -qiE "Composing|Musing|Thinking|Cascading|Incubating|Frosting|Reading.*files"; then
    success.log "Agent in $pane is PROCESSING"
    echo "$output" | tail -5
    return 0
  fi

  # Check for idle Claude prompt
  if echo "$output" | grep -qE "^>|❯"; then
    info.log "Agent in $pane is IDLE (ready for input)"
    return 0
  fi

  # Check for Claude Code running
  if echo "$output" | grep -qi "claude"; then
    info.log "Agent in $pane appears ACTIVE"
    echo "$output" | tail -5
    return 0
  fi

  warn.log "Agent in $pane status UNKNOWN"
  echo "$output" | tail -5
  return 1
}

hiveMind.agent.context.status() # <agent> <?session> # show context % for one agent by role name
{
  local agent="$1"
  local session="${2:-$(private.hiveMind.active.team)}"

  if [ -z "$agent" ]; then
    echo "Usage: hiveMind agent.context.status <agent-name> [session]"
    return 1
  fi

  # Find the agent in the registry
  local target=""
  while IFS='|' read -r t r; do
    [ -z "$t" ] || [ -z "$r" ] && continue
    case "$t" in "${session}:"*) ;; *) continue ;; esac
    if [ "$r" = "$agent" ]; then
      target="$t"
      break
    fi
  done < <(private.hiveMind.registry.list "$session")

  if [ -z "$target" ]; then
    echo "Agent '$agent' not found in $session"
    return 1
  fi

  local pane_short="${target#${session}:}"
  local self_pane
  self_pane=$("$OOSH_DIR/otmux" pane.get.target 2>/dev/null)

  # Self detection
  if [ "$target" = "$self_pane" ]; then
    printf "%-20s %-8s %-6s %-12s %s\n" "$agent" "$pane_short" "—" "—" "SELF (run from another pane)"
    return 0
  fi

  # Tron pane — never touch
  if [ "$pane_short" = "0.4" ]; then
    printf "%-20s %-8s %-6s %-12s %s\n" "$agent" "$pane_short" "—" "—" "TRON-SKIP"
    return 0
  fi

  # Capture pane
  local content
  content=$("$OOSH_DIR/otmux" pane.capture "$target" 20 2>/dev/null)
  if [ $? -ne 0 ] || [ -z "$content" ]; then
    printf "%-20s %-8s %-6s %-12s %s\n" "$agent" "$pane_short" "—" "—" "NO-PANE"
    return 1
  fi

  # Check if Claude is running
  if ! echo "$content" | grep -qE '❯|tool use|tokens|Composing|Musing|Thinking|Cascading|Incubating|Frosting|Kneading|Ideating|Seasoning|Noodling|Transmuting|Fluttering|Cerebrating|Orbiting|Running|Reading|Allow.*Deny|esc to interrupt'; then
    printf "%-20s %-8s %-6s %-12s %s\n" "$agent" "$pane_short" "—" "—" "NO-CLAUDE"
    return 1
  fi

  # Detect state
  local state="unknown"
  local bottom
  bottom=$(echo "$content" | tail -10)
  if echo "$bottom" | grep -qE '^[[:space:]]*❯|^[[:space:]]*>[[:space:]]*$'; then
    state="idle"
  elif echo "$content" | grep -q 'Allow' && echo "$content" | grep -q 'Deny'; then
    state="permission"
  elif echo "$content" | grep -qiE 'Composing|Musing|Thinking|Cascading|Incubating|Frosting|Kneading|Ideating|Seasoning|Noodling|Transmuting|Fluttering|Cerebrating|Orbiting|Running|Reading'; then
    state="active"
  fi

  # Only send /context to idle agents
  if [ "$state" != "idle" ]; then
    printf "%-20s %-8s %-6s %-12s %s\n" "$agent" "$pane_short" "—" "—" "BUSY:$state"
    return 0
  fi

  # Send /context, wait, capture result
  # Double-Enter: first accepts autocomplete dropdown, second submits
  "$OOSH_DIR/otmux" send.enter "$target" "/context"
  sleep 0.3
  "$OOSH_DIR/otmux" send "$target" Enter
  sleep 5

  local ctx_output
  # Full scrollback — use large line count for complete /context output
  ctx_output=$(otmux pane.capture "$target" 32767 2>/dev/null)

  # Parse token line
  local token_line pct tokens
  token_line=$(echo "$ctx_output" | sed 's/\x1b\[[0-9;]*m//g' | sed 's/\x1b\[[0-9;]*[A-Za-z]//g' | tr '\n' ' ' | grep -oE '[0-9]+k/[0-9]+k tokens \([0-9]+%\)' | tail -1)

  if [ -n "$token_line" ]; then
    pct=$(echo "$token_line" | grep -oE '\([0-9]+%\)' | grep -oE '[0-9]+')
    tokens=$(echo "$token_line" | grep -oE '[0-9]+k/[0-9]+k')
  else
    local clean fallback_line
    clean=$(echo "$ctx_output" | sed 's/\x1b\[[0-9;]*m//g' | sed 's/\x1b\[[0-9;]*[A-Za-z]//g')
    fallback_line=$(echo "$clean" | grep -iE 'token|context' | grep -E '[0-9]+%' | head -1)
    pct=$(echo "$fallback_line" | grep -oE '[0-9]+%' | grep -oE '[0-9]+' | head -1)
    if echo "$fallback_line" | grep -qi 'remaining'; then
      local pct_is_remaining="yes"
    fi
    tokens="—"
  fi

  if [ -z "$pct" ]; then
    printf "%-20s %-8s %-6s %-12s %s\n" "$agent" "$pane_short" "?" "parse-fail" "UNKNOWN"
    return 1
  fi

  local remaining
  if [ "${pct_is_remaining:-}" = "yes" ]; then
    remaining=$pct
  else
    remaining=$((100 - pct))
  fi

  local threshold="OK"
  if [ "$remaining" -lt 25 ]; then
    threshold="DANGER"
  elif [ "$remaining" -lt 35 ]; then
    threshold="CRITICAL"
  elif [ "$remaining" -lt 50 ]; then
    threshold="WARN"
  fi

  printf "%-20s %-8s %-6s %-12s %s\n" "$agent" "$pane_short" "${remaining}%" "$tokens" "$threshold"
}
hiveMind.agent.context.status.completion.agent() {
  private.hiveMind.roles.complete
}
hiveMind.agent.context.status.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.agent.restart.remote() # <role> <host> <?pane> # restart agent on remote machine by copying session JSONL
{
  local role="$1"
  local host="$2"
  local remotePane="$3"

  if [ -z "$role" ] || [ -z "$host" ]; then
    error.log "Usage: hiveMind agent.restart.remote <role> <host> <?pane>"
    return 1
  fi

  # 1. Resolve role → pane → UUID
  local localPane
  localPane=$(hiveMind.resolve "$role" 2>/dev/null)
  local uuid=""
  if [ -n "$localPane" ]; then
    uuid=$(private.hiveMind.session.lookup "$localPane")
    [ -z "$uuid" ] && uuid=$("$OOSH_DIR/claudeCode" session.id "$localPane" 2>/dev/null)
  fi
  if [ -z "$uuid" ]; then
    error.log "Could not resolve UUID for role '$role'"
    return 1
  fi
  info.log "UUID: $uuid"

  # 2. Find JSONL file
  local jsonlFile=""
  local claudeProjectsDir="$HOME/.claude/projects"
  for dir in "$claudeProjectsDir"/*/; do
    if [ -f "${dir}${uuid}.jsonl" ]; then
      jsonlFile="${dir}${uuid}.jsonl"
      break
    fi
  done
  if [ -z "$jsonlFile" ]; then
    error.log "JSONL not found for UUID $uuid"
    return 1
  fi
  info.log "JSONL: $jsonlFile"

  # 3. SCP to remote host (same path)
  info.log "Copying JSONL to $host..."
  if ! scp "$jsonlFile" "${host}:${jsonlFile}" 2>/dev/null; then
    error.log "scp failed — check SSH config for host '$host'"
    return 1
  fi
  success.log "JSONL copied to $host"

  # 4. Resolve remote pane (if not given, try local registry — remote may have same layout)
  if [ -z "$remotePane" ]; then
    remotePane=$(hiveMind.resolve "$role" 2>/dev/null)
    if [ -z "$remotePane" ]; then
      error.log "Could not resolve pane for '$role' — provide pane explicitly"
      return 1
    fi
    info.log "Remote pane (from local registry): $remotePane"
  fi

  # 5. Determine project directory
  # Use CLAUDE_PROJECT_DIR if set, otherwise derive from JSONL path hash
  local projectDir="${CLAUDE_PROJECT_DIR:-}"
  if [ -z "$projectDir" ]; then
    local projectHash
    projectHash=$(basename "$(dirname "$jsonlFile")")
    # Hash format: -Users-Shared-... → /Users/Shared/... (only works for paths without hyphens)
    projectDir=$(echo "$projectHash" | sed 's/^-/\//' | sed 's/-/\//g')
  fi
  info.log "Project dir: $projectDir"

  # 6. Send cd + fork to remote pane via ossh exec (must target REMOTE tmux, not local)
  "$OOSH_DIR/ossh" exec "$host" "otmux send '$remotePane' \"cd '$projectDir'\" Enter"
  sleep 0.5
  "$OOSH_DIR/ossh" exec "$host" "otmux send '$remotePane' \"claudeCode fork $uuid\" Enter"
  success.log "Sent fork command to $remotePane on $host"

  # 7. Wait and verify on remote
  sleep 5
  local capture
  capture=$("$OOSH_DIR/ossh" exec "$host" "otmux pane.capture '$remotePane' 10" 2>/dev/null)
  if echo "$capture" | grep -qE '❯|tool use|tokens|esc to interrupt|Composing|Thinking'; then
    success.log "Agent '$role' resumed on $host ($remotePane)"
  else
    warn.log "Could not confirm Claude UI on $remotePane — check manually"
  fi
}
hiveMind.agent.restart.remote.completion.role() {
  private.hiveMind.roles.complete
}
hiveMind.agent.restart.remote.completion.host() {
  grep '^Host ' ~/.ssh/config 2>/dev/null | awk '{print $2}'
}

hiveMind.team.context.status() # <?session> # show context % for all registered agents in a team
{
  local session="${1:-$(private.hiveMind.active.team)}"
  local self_pane
  self_pane=$("$OOSH_DIR/otmux" pane.get.target 2>/dev/null)

  echo "Agent Context Status — $session"
  echo "──────────────────────────────────────────"
  printf "%-20s %-8s %-6s %-12s %s\n" "AGENT" "PANE" "CTX%" "TOKENS" "STATUS"
  echo "──────────────────────────────────────────"

  local alerts=""
  local target role pane_title

  # Enumerate actual tmux panes with tab-separated fields for reliable parsing
  while IFS=$'\t' read -r target pane_title _rest; do
    [ -z "$target" ] && continue

    # Look up role from registry; fall back to pane title or "(unregistered)"
    role=$(private.hiveMind.registry.get "$target")
    [ -z "$role" ] && role="${pane_title:-(unregistered)}"

    local pane_short="${target#${session}:}"

    # Self detection (42 principle — cannot measure yourself)
    if [ "$target" = "$self_pane" ]; then
      printf "%-20s %-8s %-6s %-12s %s\n" "$role" "$pane_short" "—" "—" "SELF (run from another pane)"
      continue
    fi

    # Tron pane (0.4) — NEVER send anything. User's interface.
    if [ "$pane_short" = "0.4" ]; then
      printf "%-20s %-8s %-6s %-12s %s\n" "$role" "$pane_short" "—" "—" "TRON-SKIP"
      continue
    fi

    # Capture pane to detect state
    local content
    content=$("$OOSH_DIR/otmux" pane.capture "$target" 20 2>/dev/null)
    if [ $? -ne 0 ] || [ -z "$content" ]; then
      printf "%-20s %-8s %-6s %-12s %s\n" "$role" "$pane_short" "—" "—" "NO-PANE"
      continue
    fi

    # Check if Claude is running — look for Claude TUI indicators
    if ! echo "$content" | grep -qE '❯|tool use|tokens|Composing|Musing|Thinking|Cascading|Incubating|Frosting|Kneading|Ideating|Seasoning|Noodling|Transmuting|Fluttering|Cerebrating|Orbiting|Running|Reading|Allow.*Deny|esc to interrupt'; then
      printf "%-20s %-8s %-6s %-12s %s\n" "$role" "$pane_short" "—" "—" "NO-CLAUDE"
      continue
    fi

    # Detect state: idle = prompt visible, busy = anything else
    # TUI status bar sits BELOW the ❯ prompt, so scan last 10 lines not just last line
    local state="unknown"
    local bottom
    bottom=$(echo "$content" | tail -10)

    if echo "$bottom" | grep -qE '^[[:space:]]*❯|^[[:space:]]*>[[:space:]]*$'; then
      state="idle"
    elif echo "$content" | grep -q 'Allow' && echo "$content" | grep -q 'Deny'; then
      state="permission"
    elif echo "$content" | grep -qiE 'Composing|Musing|Thinking|Cascading|Incubating|Frosting|Kneading|Ideating|Seasoning|Noodling|Transmuting|Fluttering|Cerebrating|Orbiting|Running|Reading'; then
      state="active"
    fi

    # Only send /context to idle agents — never disrupt busy ones
    if [ "$state" != "idle" ]; then
      printf "%-20s %-8s %-6s %-12s %s\n" "$role" "$pane_short" "—" "—" "BUSY:$state"
      [ "$state" = "permission" ] && alerts="${alerts}  ${role}: blocked on permission\n"
      continue
    fi

    # Send /context, wait, capture result
    # Slash commands trigger TUI autocomplete dropdown. First Enter accepts
    # the autocomplete selection, second Enter submits. Double-Enter is safe.
    "$OOSH_DIR/otmux" send.enter "$target" "/context"
    sleep 0.3
    "$OOSH_DIR/otmux" send "$target" Enter
    sleep 5

    local ctx_output
    # Full scrollback capture — token line is at TOP of /context output,
    # ~400+ lines above bottom. Use large line count for full capture.
    ctx_output=$(otmux pane.capture "$target" 32767 2>/dev/null)

    # Parse token line: "claude-opus-4-6 · 79k/200k tokens (40%)"
    # Use tail -1 to get the MOST RECENT /context result in scrollback
    local token_line pct tokens
    # Join lines to handle narrow pane wrapping, strip ANSI, then match
    token_line=$(echo "$ctx_output" | sed 's/\x1b\[[0-9;]*m//g' | sed 's/\x1b\[[0-9;]*[A-Za-z]//g' | tr '\n' ' ' | grep -oE '[0-9]+k/[0-9]+k tokens \([0-9]+%\)' | tail -1)

    if [ -n "$token_line" ]; then
      pct=$(echo "$token_line" | grep -oE '\([0-9]+%\)' | grep -oE '[0-9]+')
      tokens=$(echo "$token_line" | grep -oE '[0-9]+k/[0-9]+k')
    else
      # Fallback: look for any percentage near "tokens" or "context"
      local clean fallback_line
      clean=$(echo "$ctx_output" | sed 's/\x1b\[[0-9;]*m//g' | sed 's/\x1b\[[0-9;]*[A-Za-z]//g')
      fallback_line=$(echo "$clean" | grep -iE 'token|context' | grep -E '[0-9]+%' | head -1)
      pct=$(echo "$fallback_line" | grep -oE '[0-9]+%' | grep -oE '[0-9]+' | head -1)
      # If "remaining" keyword found, pct IS the remaining — flag to skip inversion later
      if echo "$fallback_line" | grep -qi 'remaining'; then
        local pct_is_remaining="yes"
      fi
      tokens="—"
    fi

    if [ -z "$pct" ]; then
      printf "%-20s %-8s %-6s %-12s %s\n" "$role" "$pane_short" "?" "parse-fail" "UNKNOWN"
      alerts="${alerts}  ${role}: context parse failed\n"
      continue
    fi

    # Calculate remaining % (TUI shows usage%, we want remaining)
    # /context shows usage: "79k/200k tokens (40%)" means 40% used, 60% remaining
    # If fallback detected "remaining" keyword, pct is already the remaining value
    local remaining
    if [ "${pct_is_remaining:-}" = "yes" ]; then
      remaining=$pct
    else
      remaining=$((100 - pct))
    fi

    # Threshold assessment
    local threshold="OK"
    if [ "$remaining" -lt 25 ]; then
      threshold="DANGER"
      alerts="${alerts}  ${role}: ${remaining}% remaining — COMPACT NOW\n"
    elif [ "$remaining" -lt 35 ]; then
      threshold="CRITICAL"
      alerts="${alerts}  ${role}: ${remaining}% remaining — prepare compact\n"
    elif [ "$remaining" -lt 50 ]; then
      threshold="WARN"
    fi

    printf "%-20s %-8s %-6s %-12s %s\n" "$role" "$pane_short" "${remaining}%" "$tokens" "$threshold"
  done < <(tmux list-panes -t "$session" -s -F "#{session_name}:#{window_index}.#{pane_index}	#{pane_title}	#{pane_current_command}" 2>/dev/null)

  echo "──────────────────────────────────────────"

  if [ -n "$alerts" ]; then
    echo "Alerts:"
    printf '%b' "$alerts"
  else
    echo "No alerts."
  fi
}
hiveMind.team.context.status.completion.session() {
  private.hiveMind.teams.complete
}

# ─────────────────────────────────────────────────────────────────────────────
# TEAM SETUP
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.team.setup.full() # <?session> # bootstrap full team: Agent Teacher + Expert + Tester + ScrumMaster
{
  local session="${1:-$(private.hiveMind.active.team)}"

  info.log "Setting up full OOSH team in session: $session"

  # Check if session already exists
  if otmux has "$session" 2>/dev/null; then
    warn.log "Session '$session' already exists"
    echo "  Kill it first:  otmux kill $session"
    echo "  Or attach to it: otmux attach $session"
    return 1
  fi

  # ── Create session and pane layout ──────────────────────────────────────
  # ┌─────────────────────────────────────────┐
  # │ Pane 0.0 - AGENT TEACHER                │
  # ├───────────────────────┬─────────────────┤
  # │ Pane 0.1 - EXPERT     │ Pane 0.2 - TEST │
  # │ (oosh-expert)         │ (oosh-tester)   │
  # ├───────────────────────┴─────────────────┤
  # │ Pane 0.3 - SCRUMMASTER                  │
  # └─────────────────────────────────────────┘

  info.log "Creating tmux session: $session"
  otmux new "$session" -d

  # Name window 0
  otmux window.rename "${session}:0" "$HIVEMIND_WINDOW_NAME"

  # Split pane 0 vertically → pane 0 (upper) + pane 1 (lower)
  otmux split.v -t "${session}:0.0"

  # Split pane 1 horizontally → pane 1 (lower-left) + pane 2 (lower-right)
  otmux split.h -t "${session}:0.1"

  # Split pane 0 vertically again → add pane 3 below for ScrumMaster
  otmux split.v -t "${session}:0.0"

  # Set pane titles and export role env vars
  private.hiveMind.pane.identify "${session}:0.0" "orchestrator"
  private.hiveMind.pane.identify "${session}:0.1" "scrum-master"
  private.hiveMind.pane.identify "${session}:0.2" "oosh-expert"
  private.hiveMind.pane.identify "${session}:0.3" "oosh-tester"

  # ── Start Claude Code in each pane ─────────────────────────────────────
  info.log "Starting Claude Code in all panes..."

  otmux send.enter "${session}:0.0" "claude --dangerously-skip-permissions"
  sleep 2
  otmux send.enter "${session}:0.1" "claude --dangerously-skip-permissions"
  sleep 2
  otmux send.enter "${session}:0.2" "claude --dangerously-skip-permissions"
  sleep 2
  otmux send.enter "${session}:0.3" "claude --dangerously-skip-permissions"

  # Wait for Claude Code to initialize
  info.log "Waiting for Claude Code agents to initialize..."
  sleep 8

  # ── Capture UUIDs for identity tracking ─────────────────────────────────
  info.log "Capturing session UUIDs..."
  local ses="$HIVEMIND_SESSIONS"
  local _panes=("${session}:0.0" "${session}:0.1" "${session}:0.2" "${session}:0.3")
  for i in 0 1 2 3; do
    local _sid
    _sid=$(claudeCode session.probe "${_panes[$i]}" 2>/dev/null)
    if [ -n "$_sid" ]; then
      private.hiveMind.session.store "${_panes[$i]}" "$_sid"
    fi
  done

  # ── Send role prompts ──────────────────────────────────────────────────
  info.log "Sending role prompts to agents..."

  otmux send.enter "${session}:0.0" "$(private.hiveMind.get.role.prompt orchestrator)"
  sleep 1
  otmux send.enter "${session}:0.1" "$(private.hiveMind.get.role.prompt scrum-master)"
  sleep 1
  otmux send.enter "${session}:0.2" "$(private.hiveMind.get.role.prompt oosh-expert)"
  sleep 1
  otmux send.enter "${session}:0.3" "$(private.hiveMind.get.role.prompt oosh-tester)"

  # ── Done ───────────────────────────────────────────────────────────────
  success.log "Full OOSH team created in session: $session"
  echo ""
  echo "Pane layout:"
  echo "┌─────────────────────────────────────────┐"
  echo "│ Pane 0.0 - AGENT TEACHER                │"
  echo "├───────────────────────┬─────────────────┤"
  echo "│ Pane 0.2 - EXPERT     │ Pane 0.3 - TEST │"
  echo "│ (oosh-expert)         │ (oosh-tester)   │"
  echo "├───────────────────────┴─────────────────┤"
  echo "│ Pane 0.1 - SCRUMMASTER                  │"
  echo "└─────────────────────────────────────────┘"
  echo ""
  echo "Attach with: otmux attach $session"
}

hiveMind.team.setup.full.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.team.register() # <session> <?description> # register a team session in the team registry
{
  local session="$1"
  shift
  local description="${*:-}"

  if [ -z "$session" ]; then
    error.log "Usage: hiveMind team.register <session> <?description>"
    return 1
  fi

  private.hiveMind.teams.ensure.dir

  # Check if already registered
  if [ -f "$HIVEMIND_TEAMS" ] && grep -q "^${session}|" "$HIVEMIND_TEAMS" 2>/dev/null; then
    # Update description if provided
    if [ -n "$description" ]; then
      grep -v "^${session}|" "$HIVEMIND_TEAMS" > "${HIVEMIND_TEAMS}.tmp" 2>/dev/null
      mv "${HIVEMIND_TEAMS}.tmp" "$HIVEMIND_TEAMS"
      echo "${session}|${description}" >> "$HIVEMIND_TEAMS"
      info.log "Updated team: $session — $description"
    else
      info.log "Team $session already registered"
    fi
    return 0
  fi

  echo "${session}|${description}" >> "$HIVEMIND_TEAMS"
  success.log "Registered team: $session"

  # Auto-switch to first registered team if none active
  if [ ! -f "$HIVEMIND_ACTIVE_TEAM_FILE" ] || [ ! -s "$HIVEMIND_ACTIVE_TEAM_FILE" ]; then
    echo "$session" > "$HIVEMIND_ACTIVE_TEAM_FILE"
    info.log "Auto-switched active team to: $session"
  fi
}
hiveMind.team.register.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.team.remove() # <session> # unregister a team session
{
  local session="$1"

  if [ -z "$session" ]; then
    error.log "Usage: hiveMind team.remove <session>"
    return 1
  fi

  if [ ! -f "$HIVEMIND_TEAMS" ]; then
    error.log "No team registry found"
    return 1
  fi

  if ! grep -q "^${session}|" "$HIVEMIND_TEAMS" 2>/dev/null; then
    error.log "Team $session not registered"
    return 1
  fi

  grep -v "^${session}|" "$HIVEMIND_TEAMS" > "${HIVEMIND_TEAMS}.tmp" 2>/dev/null
  mv "${HIVEMIND_TEAMS}.tmp" "$HIVEMIND_TEAMS"
  success.log "Removed team: $session"

  # Clear active team if it was the removed one
  local active
  active=$(private.hiveMind.active.team)
  if [ "$active" = "$session" ]; then
    rm -f "$HIVEMIND_ACTIVE_TEAM_FILE"
    info.log "Active team cleared (was $session)"
  fi
}
hiveMind.team.remove.completion.session() {
  [ -f "$HIVEMIND_TEAMS" ] && cut -d'|' -f1 "$HIVEMIND_TEAMS" 2>/dev/null
}

hiveMind.team.switch() # <session> # set the active team context
{
  local session="$1"

  if [ -z "$session" ]; then
    error.log "Usage: hiveMind team.switch <session>"
    echo "  Registered teams:"
    hiveMind.team.list 2>/dev/null | sed 's/^/    /'
    return 1
  fi

  # Verify session exists in tmux or in team registry
  if ! otmux has "$session" 2>/dev/null; then
    if [ -f "$HIVEMIND_TEAMS" ] && grep -q "^${session}|" "$HIVEMIND_TEAMS" 2>/dev/null; then
      warn.log "Team $session is registered but tmux session is not running"
    else
      warn.log "Session $session not found in tmux or team registry"
    fi
  fi

  # Auto-register if not already in team registry
  if [ ! -f "$HIVEMIND_TEAMS" ] || ! grep -q "^${session}|" "$HIVEMIND_TEAMS" 2>/dev/null; then
    hiveMind.team.register "$session"
  fi

  private.hiveMind.teams.ensure.dir
  echo "$session" > "$HIVEMIND_ACTIVE_TEAM_FILE"
  success.log "Active team: $session"
}
hiveMind.team.switch.completion.session() {
  # Registered teams + running tmux sessions
  private.hiveMind.teams.complete
}

hiveMind.team.active() # # show the current active team
{
  local active
  active=$(private.hiveMind.active.team)
  echo "$active"
}

hiveMind.team.list() # # list all registered team sessions
{
  # Prefer team registry if it exists
  if [ -f "$HIVEMIND_TEAMS" ] && [ -s "$HIVEMIND_TEAMS" ]; then
    local active
    active=$(private.hiveMind.active.team)
    while IFS='|' read -r session description; do
      [ -z "$session" ] && continue
      local marker=" "
      [ "$session" = "$active" ] && marker="*"
      local running=""
      otmux has "$session" 2>/dev/null && running="running" || running="stopped"
      if [ -n "$description" ]; then
        printf " %s %-24s %-10s %s\n" "$marker" "$session" "($running)" "$description"
      else
        printf " %s %-24s %s\n" "$marker" "$session" "($running)"
      fi
    done < "$HIVEMIND_TEAMS"
    return 0
  fi

  # Fall back to deriving teams from role registry
  if [ -f "$HIVEMIND_REGISTRY" ]; then
    cut -d: -f1 "$HIVEMIND_REGISTRY" | sort -u
    return 0
  fi

  error.log "No team or role registry found"
  return 1
}

hiveMind.team.status() # <?session> # show team summary (no arg) or per-pane details (with session)
{
  local session="$1"

  # No argument: show one-line summary per team
  if [ -z "$session" ]; then
    local teams
    teams=$(hiveMind.team.list 2>/dev/null)
    [ -z "$teams" ] && { error.log "No teams registered"; return 1; }

    while read -r team; do
      if ! otmux has "$team" 2>/dev/null; then
        printf "%-24s (session not running)\n" "$team"
        continue
      fi
      local agent_count=0 active_count=0 idle_count=0 blocked_count=0
      while IFS='|' read -r target role; do
        [ -z "$target" ] || [ -z "$role" ] && continue
        case "$target" in "${team}:"*) ;; *) continue ;; esac
        agent_count=$((agent_count + 1))
        local detect_raw activity
        detect_raw=$(private.hiveMind.sweep.detect "$target" 2>/dev/null)
        activity="${detect_raw%%|*}"
        case "$activity" in
          idle|queued|unknown) idle_count=$((idle_count + 1)) ;;
          shell|shell-escaped|crash|subscription-limit) idle_count=$((idle_count + 1)) ;;
          permission|rate-limit|accept-edits|panel|overlay|autocomplete|tool-confirm|mcp-error|api-error|context-warning) blocked_count=$((blocked_count + 1)) ;;
          *) active_count=$((active_count + 1)) ;;
        esac
      done < "$HIVEMIND_REGISTRY"
      local summary="${active_count} active, ${idle_count} idle"
      [ "$blocked_count" -gt 0 ] && summary="${summary}, ${blocked_count} blocked"
      printf "%-24s %d agents  (%s)\n" "$team" "$agent_count" "$summary"
    done <<< "$teams"
    return 0
  fi

  # With argument: show detailed per-pane tree (existing behavior)
  if ! otmux has "$session" 2>/dev/null; then
    error.log "Session $session not found"
    return 1
  fi

  local pane_lines
  pane_lines=$(private.hiveMind.list.panes addr+cmd "$session")

  local total
  total=$(echo "$pane_lines" | wc -l | tr -d ' ')
  local count=0

  echo "$session"
  while IFS='|' read -r addr pane_cmd; do
    count=$((count + 1))

    local state="" detect_raw="" severity="" detail=""
    local is_claude=false
    case "$pane_cmd" in
      claude*|[0-9].[0-9]*)
        is_claude=true
        detect_raw=$(private.hiveMind.sweep.detect "${session}:${addr}" 2>/dev/null)
        IFS='|' read -r state _ severity detail <<< "$detect_raw"
        ;;
      bash|zsh)
        # Shell reported — but Claude Code may still be running on this TTY.
        # tmux pane_current_command can misreport idle Claude as 'bash'.
        if claudeCode process.running "${session}:${addr}"; then
          is_claude=true
          detect_raw=$(private.hiveMind.sweep.detect "${session}:${addr}" 2>/dev/null)
          IFS='|' read -r state _ severity detail <<< "$detect_raw"
        else
          state="shell"
          severity="info"
        fi
        ;;
      *) state="$pane_cmd"; severity="info" ;;
    esac

    local prefix="├──"
    [ "$count" -eq "$total" ] && prefix="└──"

    # Look up role from registry; fall back to pane address
    local role
    role=$(private.hiveMind.registry.get "${session}:${addr}")
    local label="${role:-pane $addr}"

    # Get Claude Code session ID — live detection with stored fallback
    local sid_label=""
    if [ "$is_claude" = "true" ]; then
      local sid
      sid=$(claudeCode session.id "${session}:${addr}")
      if [ -n "$sid" ]; then
        sid_label="  [${sid:0:8}]"
        private.hiveMind.session.store "${session}:${addr}" "$sid"
      else
        sid=$(private.hiveMind.session.lookup "${session}:${addr}")
        [ -n "$sid" ] && sid_label="  [${sid:0:8}*]"
      fi
    fi

    # Map raw states to 7-state vocabulary:
    #   active, idle, accept-edits, stuck-prompt, context-limit, compacting, offline
    local display_state="$state"
    case "$state" in
      active)                          display_state="active" ;;
      idle|queued)                     display_state="idle" ;;
      accept-edits)                    display_state="accept-edits" ;;
      permission|tool-confirm|panel|overlay|autocomplete)
                                       display_state="stuck-prompt" ;;
      context-warning)                 display_state="context-limit" ;;
      just-compacted)                  display_state="compacting" ;;
      shell|shell-escaped|crash)       display_state="offline" ;;
      rate-limit|api-error|mcp-error)  display_state="stuck-prompt" ;;
      subscription-limit)              display_state="offline" ;;
    esac

    local state_display="$display_state"
    case "$severity" in
      critical) state_display="$display_state !!" ;;
      blocker)  [ -n "$detail" ] && state_display="$display_state — $detail" ;;
      warning)  [ -n "$detail" ] && state_display="$display_state — $detail" ;;
    esac
    echo "$prefix $addr  $label ($state_display)${sid_label}"
  done <<< "$pane_lines"
}

hiveMind.team.list.completion.session() {
  private.hiveMind.teams.complete
}
hiveMind.team.status.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.team.sweep() # <session> <?interval> # structured one-line-per-pane status of all registered agents (optional sleep before sweep)
{
  local session="$1"
  local interval="$2"

  if [ -z "$session" ]; then
    error.log "Usage: hiveMind team.sweep <session> <?interval>"
    return 1
  fi

  # Sleep first if interval provided (avoids compound commands that trigger permission prompts)
  if [ -n "$interval" ] && [ "$interval" -gt 0 ] 2>/dev/null; then
    sleep "$interval"
  fi

  if [ ! -f "$HIVEMIND_REGISTRY" ]; then
    error.log "No role registry found at $HIVEMIND_REGISTRY"
    return 1
  fi

  if ! otmux has "$session" 2>/dev/null; then
    error.log "Session $session not found"
    return 1
  fi

  if ! grep -q "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null; then
    error.log "No agents registered for session $session"
    return 1
  fi

  # Identify own pane to skip self
  local own_pane=""
  [ -n "$TMUX_PANE" ] && own_pane="$TMUX_PANE"

  while IFS='|' read -r target role; do
    [ -z "$target" ] || [ -z "$role" ] && continue
    case "$target" in "${session}:"*) ;; *) continue ;; esac

    local addr="${target#*:}"

    # Skip self
    if [ -n "$own_pane" ]; then
      local pane_id
      pane_id=$(otmux pane.get "$target" % 2>/dev/null)
      [ "$pane_id" = "$own_pane" ] && continue
    fi

    # Capture last 15 lines
    local content
    content=$(otmux pane.capture "$target" 15 2>/dev/null)

    # Determine state and details using sweep.detect + verb enrichment
    local state="IDLE"
    local details=""

    # Check for empty/unreachable pane
    local trimmed
    trimmed=$(echo "$content" | tr -d '[:space:]')
    if [ -z "$trimmed" ]; then
      state="UNKNOWN"
      printf "%-5s %-22s %s\n" "$addr" "$role" "$state"
      continue
    fi

    # Use sweep.detect for structured detection (handles all blocker types)
    local detect_raw detect_status detect_severity detect_detail
    detect_raw=$(private.hiveMind.sweep.detect "$target" 2>/dev/null)
    IFS='|' read -r detect_status _ detect_severity detect_detail <<< "$detect_raw"

    case "$detect_status" in
      permission)
        state="PERMISSION"
        details=$(echo "$content" | grep -oE '"[^"]*"' | tail -1)
        ;;
      tool-confirm)
        state="TOOL_CONFIRM"
        details=$(echo "$content" | grep -oE '"[^"]*"' | tail -1)
        ;;
      rate-limit)
        state="RATE_LIMIT"
        [ -n "$detect_detail" ] && details="$detect_detail"
        ;;
      subscription-limit)
        state="SUB_LIMIT !!"
        ;;
      accept-edits)
        state="ACCEPT_EDITS"
        [ -n "$detect_detail" ] && [ "$detect_detail" != "0" ] && details="${detect_detail} pending"
        ;;
      context-warning)
        state="CONTEXT_LOW"
        [ -n "$detect_detail" ] && details="$detect_detail"
        ;;
      just-compacted)
        state="COMPACTED"
        ;;
      overlay)
        state="OVERLAY"
        ;;
      panel)
        state="PANEL"
        ;;
      autocomplete)
        state="AUTOCOMPLETE"
        ;;
      mcp-error)
        state="MCP_ERROR"
        ;;
      api-error)
        state="API_ERROR"
        [ -n "$detect_detail" ] && details="$detect_detail"
        ;;
      crash)
        state="CRASH !!"
        ;;
      shell-escaped)
        state="SHELL"
        ;;
      queued)
        state="INPUT"
        local last_line
        last_line=$(echo "$content" | sed '/^[[:space:]]*$/d' | tail -1)
        details=$(echo "$last_line" | sed -E 's/^[[:space:]]*(>|❯)[[:space:]]+//')
        [ ${#details} -gt 40 ] && details="${details:0:37}..."
        details="\"$details\""
        ;;
      idle)
        state="IDLE"
        ;;
      active)
        # Enrich with verb detection for active agents
        if echo "$content" | grep -qiE 'Composing|Musing|Thinking|Cascading|Incubating|Frosting|Misting|Brewing|Baking|Cooking|Simmering|Distilling|Weaving|Crafting|Processing|Reading|Writing|Editing|Searching|Running'; then
          state="ACTIVE"
          details=$(echo "$content" | grep -oiE 'Composing|Musing|Thinking|Cascading|Incubating|Frosting|Misting|Brewing|Baking|Cooking|Simmering|Distilling|Weaving|Crafting|Processing|Reading|Writing|Editing|Searching|Running' | tail -1)
          local elapsed
          elapsed=$(echo "$content" | grep -oE '\([0-9]+[ms]\)' | tail -1)
          [ -n "$elapsed" ] && details="$details $elapsed"
        elif echo "$content" | grep -qiE 'Baked|Brewed|Cooked|Simmered|Distilled|Woven|Crafted|Composed|Frosted|Incubated|Cascaded'; then
          state="COMPLETED"
        else
          state="ACTIVE"
        fi
        ;;
      *)
        state="${detect_status:-UNKNOWN}"
        ;;
    esac

    if [ -n "$details" ]; then
      printf "%-5s %-22s %-14s %s\n" "$addr" "$role" "$state" "$details"
    else
      printf "%-5s %-22s %s\n" "$addr" "$role" "$state"
    fi
  done < "$HIVEMIND_REGISTRY"
}
hiveMind.team.sweep.completion.session() {
  private.hiveMind.teams.complete
}
hiveMind.team.sweep.completion.interval() {
  echo "15"
  echo "30"
  echo "60"
  echo "120"
}

hiveMind.team.loop() # <session> <?interval:30> # continuous team.sweep at interval
{
  local session="$1"
  local interval="${2:-30}"

  if [ -z "$session" ]; then
    error.log "Usage: hiveMind team.loop <session> <?interval>"
    return 1
  fi

  console.log "Team loop: $session every ${interval}s (Ctrl+C to stop)"

  while true; do
    echo ""
    echo "── team.sweep @ $(date '+%H:%M:%S') ──────────────────────────────"
    hiveMind.team.sweep "$session"
    sleep "$interval"
  done
}
hiveMind.team.loop.completion.session() {
  private.hiveMind.teams.complete
}
hiveMind.team.loop.completion.interval() {
  echo "15"
  echo "30"
  echo "60"
}

hiveMind.monitor() # <?name-or-lines:5> <?session-or-lines> # capture pane output; name=specific agent, number=line count for all panes
{
  local first="${1:-5}"
  local second="$2"

  # If first arg is not a number, treat as agent name (monitor single agent)
  if [ -n "$first" ] && ! echo "$first" | grep -qE '^[0-9]+$'; then
    local name="$first"
    local lines="${second:-5}"
    local target
    target=$(hiveMind.resolve "$name")
    if [ $? -ne 0 ]; then
      return 1
    fi

    echo "───────────────────────────────────────────────────────────"
    echo " $name ($target) — last $lines lines"
    echo "───────────────────────────────────────────────────────────"
    otmux pane.capture "$target" "$lines"
    echo ""
    return 0
  fi

  # All-pane monitoring (existing behavior)
  local lines="$first"
  local session="${second:-${HIVEMIND_SESSION:-$(private.hiveMind.active.team)}}"

  if ! otmux has "$session" 2>/dev/null; then
    error.log "Session '$session' not found"
    return 1
  fi

  local own_pane=""
  if [ -n "$TMUX_PANE" ]; then
    own_pane="$TMUX_PANE"
  fi

  echo "═══════════════════════════════════════════════════════════"
  echo " hiveMind monitor — session: $session (last $lines lines)"
  echo "═══════════════════════════════════════════════════════════"

  otmux panes -t "$session" -F "#{pane_id}|#{pane_index}|#{pane_title}" 2>/dev/null | \
    while IFS='|' read -r pane_id pane_idx pane_title; do
      if [ -n "$own_pane" ] && [ "$pane_id" = "$own_pane" ]; then
        continue
      fi

      local label="${pane_title:-pane $pane_idx}"
      echo "───────────────────────────────────────────────────────────"
      echo " [$pane_idx] $label"
      echo "───────────────────────────────────────────────────────────"
      otmux pane.capture "${session}:0.${pane_idx}" "$lines"
      echo ""
    done

  info.log "Monitored $session"
  return 0
}

hiveMind.monitor.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.monitor.approve() # <pane> <?option:2> # approve a permission prompt in a pane
{
  local pane="$1"
  local option="${2:-2}"

  if [ -z "$pane" ]; then
    error.log "Usage: hiveMind monitor.approve <pane> <?option:2>"
    return 1
  fi

  info.log "Sending approval option $option to pane $pane..."
  otmux send.enter "$pane" "$option"
  success.log "Sent option $option to pane $pane"
}

hiveMind.monitor.approve.completion.option() {
  echo "2"
  echo "1"
  echo "3"
}

# ─────────────────────────────────────────────────────────────────────────────
# SWEEP & UNBLOCK
# ─────────────────────────────────────────────────────────────────────────────

private.hiveMind.sweep.detect() {
  # Enhanced blocker detection — returns status|action|severity[|detail]
  #
  # Statuses (18):
  #   active, idle, permission, accept-edits, context-warning, just-compacted,
  #   queued, rate-limit, subscription-limit, autocomplete, shell-escaped, shell,
  #   overlay, panel, mcp-error, api-error, tool-confirm, crash, unknown
  #
  # Actions: none, enter, escape, down-enter, wait
  #
  # Severity: critical, blocker, warning, info
  #   critical  — needs immediate human intervention (crash, subscription-limit)
  #   blocker   — auto-resolvable or needs approval (permission, accept-edits, rate-limit)
  #   warning   — may need attention soon (context-warning, just-compacted)
  #   info      — normal operational states (active, idle)
  #
  # Detail (optional 4th field): extra context (percentage, count, retry time)
  #
  # Backward-compatible: callers using ${result%%|*} for status still work.

  local target="$1"
  [ -z "$target" ] && echo "unknown|none|info" && return 1

  local content
  content=$(otmux pane.capture "$target" 20 2>/dev/null)

  local trimmed
  trimmed=$(echo "$content" | tr -d '[:space:]')
  [ -z "$trimmed" ] && echo "unknown|none|info" && return 0

  # ── Overlays and panels (dismiss with Escape) ──────────────────────────

  # Background tasks overlay
  if echo "$content" | grep -q 'Background tasks'; then
    echo "overlay|escape|blocker"
    return 0
  fi

  # Git diff/status panel — needs Escape to close
  # Note: 'files +N -N' also appears in the normal status bar — only match with Esc close
  if echo "$content" | grep -qE 'Esc close'; then
    echo "panel|escape|blocker"
    return 0
  fi
  if echo "$content" | grep -qE 'Uncommitted changes|git diff' && echo "$content" | grep -qE '^\s*[+-]{3}\s|^diff --git'; then
    echo "panel|escape|blocker"
    return 0
  fi

  # ── Error states (most urgent first) ───────────────────────────────────

  # Crash / exited: Claude Code process terminated unexpectedly
  if echo "$content" | grep -qiE 'claude.*exited|process.*terminated|segmentation fault|SIGKILL|SIGTERM|core dumped|fatal error'; then
    echo "crash|none|critical"
    return 0
  fi

  # API error: network/server errors from Anthropic API
  if echo "$content" | grep -qiE 'APIConnectionError|APIStatusError|ECONNREFUSED|ETIMEDOUT|502 Bad Gateway|503 Service|529 Overloaded|internal server error|network error|connection reset'; then
    local retry_detail
    retry_detail=$(echo "$content" | grep -oiE 'retry.*(in |after )[0-9]+\s*s(ec)?' | tail -1)
    echo "api-error|wait|blocker|${retry_detail:-transient}"
    return 0
  fi

  # MCP connection error: MCP server connection/reconnect issues
  if echo "$content" | grep -qiE 'MCP.*error|MCP.*disconnect|MCP.*reconnect|MCP.*timeout|MCP server.*not responding|failed to connect.*MCP'; then
    echo "mcp-error|wait|warning"
    return 0
  fi

  # ── Permission and confirmation prompts ────────────────────────────────

  # Permission prompt: "Allow" + "Deny" or "Do you want to proceed?" with Yes/No
  if echo "$content" | grep -q 'Allow' && echo "$content" | grep -q 'Deny'; then
    echo "permission|down-enter|blocker"
    return 0
  fi
  if echo "$content" | grep -q 'Do you want to proceed?' && echo "$content" | grep -qE '^\s*(❯\s*)?[0-9]+\.\s*(Yes|No)'; then
    echo "permission|enter|blocker"
    return 0
  fi

  # Tool-use confirmation: specific tool approval prompts
  if echo "$content" | grep -qE 'Run (this |the )?command|Execute.*\?|Create (this |the )?file|Write to|Delete.*\?'; then
    if echo "$content" | grep -qE '^\s*(❯\s*)?[0-9]+\.\s*(Yes|No|Allow|Deny)'; then
      echo "tool-confirm|enter|blocker"
      return 0
    fi
  fi

  # ── Rate and subscription limits ───────────────────────────────────────

  # Subscription limit: quota/subscription exhausted (distinct from rate-limit)
  if echo "$content" | grep -qiE 'subscription.*limit|daily.*limit.*reached|monthly.*limit|quota.*exhausted|billing.*limit|plan.*limit.*exceeded'; then
    echo "subscription-limit|none|critical"
    return 0
  fi

  # Rate limit: temporary throttling
  if echo "$content" | grep -qiE 'rate.limit|usage.limit|try again.*(in |later)|too many requests|throttl|request limit|capacity|overloaded.*try'; then
    local retry_time
    retry_time=$(echo "$content" | grep -oiE '(in |after )[0-9]+ ?(sec|min|second|minute)s?' | tail -1)
    echo "rate-limit|wait|blocker|${retry_time:-unknown}"
    return 0
  fi

  # ── Edit and acceptance prompts ────────────────────────────────────────

  # Accept edits prompt: ⏵⏵ accept edits on · N bash(es)
  if echo "$content" | grep -q '⏵⏵ accept'; then
    local edit_count
    edit_count=$(echo "$content" | grep -oE '[0-9]+ bash' | head -1 | grep -oE '[0-9]+')
    [ -z "$edit_count" ] && edit_count=0
    echo "accept-edits|enter|blocker|$edit_count"
    return 0
  fi

  # ── Context and compact states ─────────────────────────────────────────

  # Context warning: "Context left until auto-compact: N%"
  if echo "$content" | grep -qE 'auto-compact: [0-9]+%'; then
    local ctx_pct
    ctx_pct=$(echo "$content" | grep -oE 'auto-compact: [0-9]+%' | grep -oE '[0-9]+' | tail -1)
    echo "context-warning|none|warning|${ctx_pct:-unknown}%"
    return 0
  fi

  # Just compacted: "Compacted" in recent output
  if echo "$content" | grep -qiE 'Compacted|auto-compact.*completed'; then
    echo "just-compacted|none|warning"
    return 0
  fi

  # ── UI states ──────────────────────────────────────────────────────────

  # Autocomplete stuck: completion menu visible (e.g. /compact dropdown)
  if echo "$content" | grep -qE '^\s*/\w+.*─'; then
    echo "autocomplete|escape|blocker"
    return 0
  fi

  local last_line
  last_line=$(echo "$content" | sed '/^[[:space:]]*$/d' | tail -1)

  # Shell-escaped: bare $ prompt (agent left Claude Code)
  if echo "$last_line" | grep -qE '^[[:space:]]*\$[[:space:]]*$'; then
    echo "shell-escaped|none|critical"
    return 0
  fi

  # Idle: clean prompt (just ❯ or > with no text after)
  if echo "$last_line" | grep -qE '^[[:space:]]*(>|❯)[[:space:]]*$'; then
    echo "idle|none|info"
    return 0
  fi

  # Queued: text after prompt not submitted
  if echo "$last_line" | grep -qE '^[[:space:]]*(>|❯)[[:space:]]+.+'; then
    echo "queued|enter|blocker"
    return 0
  fi

  # Active (generating/running tools/indeterminate)
  echo "active|none|info"
  return 0
}

private.hiveMind.unblock.pane() {
  # Detect and resolve blocker on a single pane
  # Handles new sweep.detect format: status|action|severity[|detail]
  local target="$1"
  local label="$2"
  local attempt="${3:-1}"

  local result
  result=$(private.hiveMind.sweep.detect "$target")

  # Parse fields: status|action|severity|detail
  local status action severity detail
  IFS='|' read -r status action severity detail <<< "$result"

  # Skip if already healthy
  case "$status" in
    active|idle|unknown) return 0 ;;
  esac

  case "$status" in
    permission)
      # Select option 2 ("Yes, and don't ask again") — Down then Enter
      otmux send "$target" Down
      sleep 0.3
      otmux send "$target" Enter
      console.log "Unblocked $label ($status) → Down+Enter (option 2)"
      ;;
    tool-confirm)
      # Tool confirmation — just accept with Enter
      otmux send "$target" Enter
      console.log "Unblocked $label ($status) → Enter"
      ;;
    accept-edits)
      # Accept pending edits — Enter for each stacked edit + base
      local count=0
      [[ "$detail" =~ ^[0-9]+$ ]] && count="$detail"
      local i
      for ((i=0; i<=count; i++)); do
        otmux send "$target" Enter
        sleep 0.5
      done
      console.log "Unblocked $label ($status) → $((count+1))x Enter (accept edits)"
      ;;
    overlay|panel)
      # Dismiss overlay or git panel
      otmux send "$target" Escape
      console.log "Unblocked $label ($status) → Escape"
      ;;
    autocomplete)
      # Dismiss stuck autocomplete menu
      otmux send "$target" Escape
      console.log "Unblocked $label ($status) → Escape"
      ;;
    queued)
      # Submit queued text
      otmux send "$target" Enter
      console.log "Unblocked $label ($status) → Enter"
      ;;
    rate-limit|api-error)
      # Transient — just wait, auto-recovers
      console.log "Waiting $label ($status) — ${detail:-will auto-recover}"
      ;;
    subscription-limit|crash|shell-escaped)
      # Critical — cannot auto-resolve, log warning
      warn.log "CRITICAL $label ($status) — needs human intervention"
      ;;
    mcp-error)
      # MCP errors often self-recover
      console.log "Waiting $label ($status) — MCP reconnecting"
      ;;
    *)
      # Fallback: use action field
      case "$action" in
        enter)
          otmux send "$target" Enter
          console.log "Unblocked $label ($status) → Enter"
          ;;
        down-enter)
          otmux send "$target" Down
          sleep 0.3
          otmux send "$target" Enter
          console.log "Unblocked $label ($status) → Down+Enter"
          ;;
        escape)
          otmux send "$target" Escape
          sleep 0.1
          otmux send "$target" Enter
          console.log "Unblocked $label ($status) → Escape+Enter"
          ;;
        wait)
          console.log "Waiting $label ($status) — ${detail:-will auto-recover}"
          ;;
        none) ;;
      esac
      ;;
  esac

  # Verify: re-detect after action (skip for wait-only states)
  case "$status" in
    rate-limit|api-error|subscription-limit|crash|shell-escaped|mcp-error) return 0 ;;
  esac

  if [ "$attempt" -le 1 ]; then
    sleep 1
    local recheck
    recheck=$(private.hiveMind.sweep.detect "$target" 2>/dev/null)
    local new_status="${recheck%%|*}"
    case "$new_status" in
      active|idle|unknown)
        debug.log "Verified: $label cleared ($status → $new_status)"
        ;;
      *)
        warn.log "$label still blocked ($new_status) — retrying once"
        private.hiveMind.unblock.pane "$target" "$label" 2
        ;;
    esac
  fi
}

hiveMind.sweep() # <?session> <?interval> # show last lines of each agent pane (optional sleep before sweep)
{
  local session="${1:-$(private.hiveMind.active.team)}"
  local interval="$2"

  # Sleep first if interval provided (avoids compound commands that trigger permission prompts)
  if [ -n "$interval" ] && [ "$interval" -gt 0 ] 2>/dev/null; then
    sleep "$interval"
  fi

  if [ ! -f "$HIVEMIND_REGISTRY" ]; then
    error.log "No role registry found at $HIVEMIND_REGISTRY"
    return 1
  fi

  if ! otmux has "$session" 2>/dev/null; then
    error.log "Session $session not found"
    return 1
  fi

  if ! grep -q "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null; then
    error.log "No agents registered for session $session"
    return 1
  fi

  while IFS='|' read -r target role; do
    [ -z "$target" ] || [ -z "$role" ] && continue
    case "$target" in "${session}:"*) ;; *) continue ;; esac

    local addr="${target#*:}"
    echo "=== $role ($addr) ==="
    otmux pane.capture "$target" 8
    echo ""
  done < "$HIVEMIND_REGISTRY"
}
hiveMind.sweep.completion.session() {
  private.hiveMind.teams.complete
}
hiveMind.sweep.completion.interval() {
  echo "15"
  echo "30"
  echo "60"
  echo "120"
}

hiveMind.sweep.cycle() # <?session> <?interval:0> # run one sweep+unblock cycle with optional sleep
{
  local session="${1:-$(private.hiveMind.current.session)}"
  local interval="${2:-0}"

  [ "$interval" -gt 0 ] 2>/dev/null && sleep "$interval"

  hiveMind.sweep "$session"
  hiveMind.unblock all "$session"
}
hiveMind.sweep.cycle.completion() { :; }

hiveMind.auto.commit() # <?message> # auto-commit uncommitted changes if any exist
{
  local msg="${1:-Auto-commit: cycle checkpoint $(date '+%Y-%m-%d %H:%M')}"

  # Only add tracked files that changed (never untracked — avoids committing secrets)
  # Also add session/ files which are safe to commit
  local has_changes=false

  if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
    git add -u
    has_changes=true
  fi

  if [ "$has_changes" = "true" ]; then
    git commit -m "$msg

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
    git push 2>/dev/null &
    console.log "Auto-committed: $msg"
    return 0
  fi

  console.log "Nothing to commit"
  return 0
}
hiveMind.auto.commit.completion() { :; }

hiveMind.cycle.full() # <?session> # run full monitoring cycle: sweep + unblock + context check + auto-commit
{
  local session="${1:-$(private.hiveMind.current.session)}"

  # Step 1: Sweep and unblock
  console.log "Cycle: sweep $session"
  hiveMind.sweep "$session" 2>/dev/null
  hiveMind.unblock all "$session" 2>/dev/null

  # Step 2: Check context for all panes
  console.log "Cycle: context check"
  local panes
  panes=$(otmux panes -t "${session}:0" -F "#{pane_index}" 2>/dev/null)
  for pane in $panes; do
    local ctx
    ctx=$(claudeCode context.read "${session}:0.${pane}" 2>/dev/null)
    if [ -n "$ctx" ] && [ "$ctx" != "unknown" ]; then
      # Alert if below 25%
      local remaining
      remaining=$(echo "$ctx" | grep -oE '[0-9]+' | head -1)
      if [ -n "$remaining" ] && [ "$remaining" -lt 25 ] 2>/dev/null; then
        console.log "ALERT: ${session}:0.${pane} context at ${remaining}%"
      fi
    fi
  done

  # Step 3: Auto-commit if changes
  console.log "Cycle: auto-commit"
  hiveMind.auto.commit "Cycle checkpoint $(date '+%H:%M')" 2>/dev/null

  console.log "Cycle complete for $session"
}
hiveMind.cycle.full.completion() { :; }

hiveMind.dashboard() # <?session> # write consolidated team state to session/dashboard.md
{
  local session="${1:-$(private.hiveMind.active.team)}"
  local registry="$HIVEMIND_REGISTRY"
  local dashboard_file="$(git rev-parse --show-toplevel 2>/dev/null)/session/dashboard.md"

  local timestamp
  timestamp=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
  local local_time
  local_time=$(date '+%Y-%m-%d %H:%M %Z')

  # Git status
  local git_branch git_status git_commit
  git_branch=$(git -C "$OOSH_DIR" branch --show-current 2>/dev/null)
  git_commit=$(git -C "$OOSH_DIR" log -1 --oneline 2>/dev/null)
  if git -C "$OOSH_DIR" diff --quiet 2>/dev/null && git -C "$OOSH_DIR" diff --cached --quiet 2>/dev/null; then
    git_status="clean"
  else
    git_status="uncommitted changes"
  fi

  # Subscription velocity (from cached metrics)
  local vel_five="-" vel_seven="-"
  local latest_sub
  latest_sub=$(ls -t "${CONFIG_PATH:-$HOME/config}/metrics"/subscription.*.scenario.env 2>/dev/null | head -1)
  if [ -n "$latest_sub" ] && [ -f "$latest_sub" ]; then
    source "$latest_sub"
    vel_five="${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}%"
    vel_seven="${SUBSCRIPTION_SEVEN_DAY_UTIL:-0}%"
  fi

  # Start dashboard
  {
    echo "# Team Dashboard"
    echo ""
    echo "**Updated**: $timestamp ($local_time)"
    echo "**Session**: $session"
    echo ""
    echo "## Git Status"
    echo ""
    echo "| Field | Value |"
    echo "|-------|-------|"
    echo "| Branch | \`$git_branch\` |"
    echo "| Status | $git_status |"
    echo "| Last commit | \`$git_commit\` |"
    echo ""
    echo "## Subscription"
    echo ""
    echo "| Metric | Value |"
    echo "|--------|-------|"
    echo "| 5-hour usage | $vel_five |"
    echo "| 7-day usage | $vel_seven |"
    echo ""
    echo "## Team Status"
    echo ""
    echo "| Agent | Pane | Context | State | Activity |"
    echo "|-------|------|---------|-------|----------|"
  } > "$dashboard_file"

  # Iterate all agents
  if [ -f "$registry" ]; then
    while IFS='|' read -r target role; do
      [ -z "$role" ] && continue
      [[ "$target" != "$session:"* ]] && continue

      local pane="${target##*.}"
      local context_pct state activity

      # Get context
      context_pct=$(claudeCode context.read "$target" 2>/dev/null)
      [ -z "$context_pct" ] && context_pct="-"
      [ "$context_pct" != "-" ] && context_pct="${context_pct}%"

      # Get state via sweep detect
      local detect_result
      detect_result=$(private.hiveMind.sweep.detect "$target" 2>/dev/null)
      state="${detect_result%%|*}"
      [ -z "$state" ] && state="unknown"

      # Get activity from pane capture
      local content
      content=$(otmux pane.capture "$target" 5 2>/dev/null | tail -3)
      activity=$(echo "$content" | grep -oE '(Reading|Writing|Thinking|Running|Editing|Searching|waiting|idle)' | tail -1)
      [ -z "$activity" ] && activity="-"

      echo "| $role | 0.$pane | $context_pct | $state | $activity |" >> "$dashboard_file"
    done < "$registry"
  fi

  # Recent commits
  {
    echo ""
    echo "## Recent Commits"
    echo ""
    echo "\`\`\`"
    git -C "$OOSH_DIR" log --oneline -5 2>/dev/null
    echo "\`\`\`"
    echo ""
    echo "## Recovery"
    echo ""
    echo "After \`/compact\`, read:"
    echo "1. This file (\`session/dashboard.md\`)"
    echo "2. Your role's context file (\`session/agents/<role>.context.md\`)"
    echo "3. Your SKILL.md (\`.claude/agents/<role>/SKILL.md\`)"
    echo ""
    echo "---"
    echo "*Generated by \`hiveMind dashboard\`*"
  } >> "$dashboard_file"

  console.log "Dashboard updated: $dashboard_file"
  create.result 0 "$dashboard_file"
  return $(result)
}
hiveMind.dashboard.completion() { :; }

hiveMind.unblock() # <name> <?session> # detect and resolve stuck prompt; name=agent name or 'all'
{
  local name="$1"
  if [ -z "$name" ]; then
    error.log "Usage: hiveMind unblock <name|all>"
    return 1
  fi

  local session="${2:-$(private.hiveMind.active.team)}"

  if [ "$name" = "all" ]; then
    while IFS='|' read -r target role; do
      [ -z "$target" ] || [ -z "$role" ] && continue
      case "$target" in "${session}:"*) ;; *) continue ;; esac
      private.hiveMind.unblock.pane "$target" "$role"
    done < "$HIVEMIND_REGISTRY"
    return 0
  fi

  # Single agent
  local target
  target=$(hiveMind.resolve "$name")
  [ $? -ne 0 ] && return 1
  private.hiveMind.unblock.pane "$target" "$name"
}
hiveMind.unblock.completion.name() {
  hiveMind.parameter.completion.name
  echo "all"
}
hiveMind.unblock.completion.session() {
  private.hiveMind.teams.complete
}

hiveMind.monitor.cycle() # <?session> # full monitoring cycle: context health, velocity, detect blockers, unblock, log
{
  local session="${1:-$(private.hiveMind.current.session)}"

  # List all panes across all windows (-s = session-wide)
  local pane_lines
  pane_lines=$(private.hiveMind.list.panes addr+cmd "$session")
  [ -z "$pane_lines" ] && { error.log "No panes found for $session"; return 1; }

  local checked=0 blocked=0 low_context=0

  # Workspace for burn-log (computed once outside loop)
  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  local logfile=""
  if [ -n "$workspace" ]; then
    logfile="${workspace}/session/context-burn-log.md"
    if [ ! -f "$logfile" ]; then
      echo "| Time | Pane | % | State | Velocity | TTC |" > "$logfile"
      echo "|------|------|---|-------|----------|-----|" >> "$logfile"
    fi
  fi

  while IFS='|' read -r addr pane_cmd; do
    local target="${session}:${addr}"
    local is_claude=false

    case "$pane_cmd" in
      claude*|[0-9].[0-9]*)
        is_claude=true ;;
      bash|zsh)
        "$OOSH_DIR/claudeCode" process.running "$target" && is_claude=true ;;
    esac

    [ "$is_claude" = "false" ] && continue
    checked=$((checked + 1))

    # Step 1: Context health check
    local pct
    pct=$("$OOSH_DIR/claudeCode" context.read "$target" 2>/dev/null)
    [ -z "$pct" ] && pct="unknown"

    # Step 2: Velocity (tokens/hr, time-to-compact)
    local velocity tokens_hr ttc
    velocity=$("$OOSH_DIR/claudeCode" context.velocity "$target" 2>/dev/null)
    tokens_hr=$(echo "$velocity" | grep -oE '^[0-9]+ tokens/hr' || echo "-")
    ttc=$(echo "$velocity" | grep -oE '~[0-9]+min' || echo "-")

    # Step 3: Alert if context low (<25%)
    if [ "$pct" != "unknown" ] && [ "$pct" -le 25 ] 2>/dev/null; then
      low_context=$((low_context + 1))
      warn.log "$target: ${pct}% context — triggering alert"
      "$OOSH_DIR/claudeCode" context.alert "$target" 25
    fi

    # Step 4: Detect blockers and unblock
    local detect_raw status
    detect_raw=$(private.hiveMind.sweep.detect "$target" 2>/dev/null)
    status="${detect_raw%%|*}"
    case "$status" in
      active|idle|unknown) ;; # healthy — skip
      *) blocked=$((blocked + 1))
         private.hiveMind.unblock.pane "$target" "${addr}" ;;
    esac

    # Step 5: Log to burn-log
    if [ -n "$logfile" ]; then
      local ts
      ts=$(date '+%H:%M')
      echo "| $ts | $addr | ${pct}% | $status | $tokens_hr | $ttc |" >> "$logfile"
    fi

  done <<< "$pane_lines"

  info.log "Monitor cycle: $checked checked, $blocked blocked, $low_context low-context"
  create.result 0 "$checked"
  return $(result)
}
hiveMind.monitor.cycle.completion() { :; }

hiveMind.peer.compact() # <target> <?session> # trigger seamless compact for a peer agent
{
  local name="$1"
  local session="${2:-$(private.hiveMind.active.team)}"

  if [ -z "$name" ]; then
    error.log "Usage: hiveMind peer.compact <target> <?session>"
    return 1
  fi

  # Resolve name to pane target
  local target
  if [[ "$name" == *":"* ]] || [[ "$name" == *"."* ]]; then
    target="$name"
    [[ "$target" != *":"* ]] && target="${session}:${target}"
  else
    target=$(hiveMind.resolve "$name" "$session" 2>/dev/null)
  fi

  if [ -z "$target" ]; then
    error.log "Cannot resolve '$name' to a pane target"
    return 1
  fi

  local role
  role=$(private.hiveMind.registry.get "$target" 2>/dev/null)
  [ -z "$role" ] && role="$name"

  info.log "Compacting peer: $role at $target"

  # Step 1: Capture peer state (30 lines)
  local pane_content
  pane_content=$("$OOSH_DIR/otmux" pane.capture "$target" 30 2>/dev/null)
  if [ -z "$pane_content" ]; then
    warn.log "Could not capture $target — pane may be empty"
  fi

  # Step 2: Check context % before compact
  local pct
  pct=$("$OOSH_DIR/claudeCode" context.read "$target" 2>/dev/null)
  info.log "$role context: ${pct:-unknown}%"

  # Step 3: Kill rogue resume hook processes
  local addr="${target#*:}"
  local pid_file="/tmp/resume-${addr}.pid"
  if [ -f "$pid_file" ]; then
    local old_pid
    old_pid=$(cat "$pid_file" 2>/dev/null)
    if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
      info.log "Killing rogue hook process $old_pid"
      kill "$old_pid" 2>/dev/null
    fi
    rm -f "$pid_file" 2>/dev/null
  fi

  # Step 4: Clear input line
  "$OOSH_DIR/otmux" send "$target" C-u

  # Step 5: Send /compact
  "$OOSH_DIR/otmux" send "$target" "/compact" Enter
  sleep 2
  "$OOSH_DIR/otmux" send "$target" "" Enter

  # Step 6: Wait for hook to process
  info.log "Waiting for compact to complete..."
  local waited=0
  while [ "$waited" -lt 30 ]; do
    sleep 5
    waited=$((waited + 5))
    local check
    check=$("$OOSH_DIR/otmux" pane.capture "$target" 5 2>/dev/null)
    if echo "$check" | grep -qE 'Compacted|auto-compact|resumed'; then
      info.log "Compact confirmed after ${waited}s"
      break
    fi
  done

  # Step 7: Verify recovery
  local verify
  verify=$("$OOSH_DIR/otmux" pane.capture "$target" 10 2>/dev/null)
  if echo "$verify" | grep -qE 'Compacted|❯'; then
    console.log "peer.compact: $role at $target — OK"
    create.result 0 "compacted"
  else
    warn.log "peer.compact: $role — compact may not have completed"
    create.result 1 "unverified"
  fi
  return $(result)
}
hiveMind.peer.compact.completion.target() {
  hiveMind.resolve.completion.name 2>/dev/null
}
hiveMind.peer.compact.completion.session() {
  otmux sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.handoff() # <target> <?session> # full generational handoff: detect low context, compact, verify recovery
{
  local name="$1"
  local session="${2:-$(private.hiveMind.active.team)}"

  if [ -z "$name" ]; then
    error.log "Usage: hiveMind handoff <target> <?session>"
    return 1
  fi

  # Resolve name to pane
  local target
  if [[ "$name" == *":"* ]] || [[ "$name" == *"."* ]]; then
    target="$name"
    [[ "$target" != *":"* ]] && target="${session}:${target}"
  else
    target=$(hiveMind.resolve "$name" "$session" 2>/dev/null)
  fi

  if [ -z "$target" ]; then
    error.log "Cannot resolve '$name' to a pane target"
    return 1
  fi

  local role
  role=$(private.hiveMind.registry.get "$target" 2>/dev/null)
  [ -z "$role" ] && role="$name"

  # ── Phase 1: Detect ──────────────────────────────────────────────────
  local pct
  pct=$("$OOSH_DIR/claudeCode" context.read "$target" 2>/dev/null)
  info.log "Handoff check: $role at $target — context: ${pct:-unknown}%"

  # If context is healthy (>15%), nothing to do
  if [ -n "$pct" ] && [ "$pct" -gt 15 ] 2>/dev/null; then
    console.log "handoff: $role has ${pct}% context — no handoff needed"
    create.result 0 "healthy"
    return 0
  fi

  # Alert agent to save state
  info.log "Context low (${pct:-?}%) — alerting $role to save state"
  "$OOSH_DIR/otmux" send "$target" C-u
  "$OOSH_DIR/otmux" send "$target" "Save your context and run /compact NOW. You are at ${pct:-low}%." Enter

  # Wait for agent to save context file
  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  local context_file="${workspace}/session/agents/${role}/context.md"
  local waited=0
  local context_saved=false
  while [ "$waited" -lt 15 ]; do
    sleep 3
    waited=$((waited + 3))
    # Check if context file was updated recently (within last 30s)
    if [ -f "$context_file" ]; then
      local age
      age=$(( $(date +%s) - $(stat -f %m "$context_file" 2>/dev/null || echo 0) ))
      if [ "$age" -lt 30 ]; then
        context_saved=true
        info.log "Context file saved (${age}s ago)"
        break
      fi
    fi
  done

  if [ "$context_saved" = false ]; then
    warn.log "Context file not updated — proceeding with compact anyway"
  fi

  # ── Phase 2: Trigger ─────────────────────────────────────────────────
  # Check if agent already ran /compact (may have self-compacted from our message)
  local check
  check=$("$OOSH_DIR/otmux" pane.capture "$target" 10 2>/dev/null)
  if echo "$check" | grep -qE 'Compacted|auto-compact'; then
    info.log "Agent already compacted — skipping trigger"
  else
    info.log "Triggering compact via peer.compact"
    hiveMind.peer.compact "$target" "$session"
  fi

  # ── Phase 3: Verify ──────────────────────────────────────────────────
  sleep 3
  info.log "Verifying recovery..."
  "$OOSH_DIR/claudeCode" recover "$target"
  local recover_result=$?

  if [ $recover_result -eq 0 ]; then
    console.log "handoff: $role — complete (detect → compact → recover)"
    create.result 0 "handoff_complete"
  else
    warn.log "handoff: $role — recovery uncertain, may need manual intervention"
    create.result 1 "recovery_uncertain"
  fi
  return $(result)
}
hiveMind.handoff.completion.target() {
  hiveMind.resolve.completion.name 2>/dev/null
}
hiveMind.handoff.completion.session() {
  otmux sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.cold.recover() # <?session> # cold-start recovery: reconcile registry with live infrastructure, nudge agents
{
  local session="${1:-$(private.hiveMind.current.session)}"

  if [ -z "$session" ]; then
    error.log "No tmux session found — are you inside tmux?"
    return 1
  fi

  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"

  info.log "Cold-start recovery for session: $session"

  # Step 1-2: Discover live infrastructure
  local live_panes
  live_panes=$(private.hiveMind.list.panes "#{session_name}:#{window_index}.#{pane_index}|#{pane_current_command}|#{pane_title}" "$session")

  if [ -z "$live_panes" ]; then
    error.log "No panes found in session $session"
    return 1
  fi

  local pane_count
  pane_count=$(echo "$live_panes" | wc -l | tr -d ' ')
  info.log "Found $pane_count live panes"

  # Step 3-5: Reconcile registry with reality
  local reg="$HIVEMIND_REGISTRY"
  local stale_count=0
  local live_count=0
  local recovered_count=0

  # Remove registry entries for panes that no longer exist
  if [ -f "$reg" ]; then
    local old_entries
    old_entries=$(grep "^${session}:" "$reg" 2>/dev/null)
    while IFS= read -r entry; do
      [ -z "$entry" ] && continue
      local reg_pane="${entry%%|*}"
      if ! echo "$live_panes" | grep -q "^${reg_pane}|"; then
        info.log "Stale registry entry: $entry — removing"
        grep -v "^${reg_pane}|" "$reg" > "${reg}.tmp" 2>/dev/null
        mv "${reg}.tmp" "$reg"
        stale_count=$((stale_count + 1))
      fi
    done <<< "$old_entries"
  fi

  # Walk live panes, identify agents
  while IFS='|' read -r pane_addr cmd title; do
    [ -z "$pane_addr" ] && continue

    local role
    role=$(private.hiveMind.registry.get "$pane_addr" 2>/dev/null)

    # Check if pane is running Claude Code (node = claude process)
    local is_claude=false
    case "$cmd" in
      node) is_claude=true ;;
    esac

    if [ "$is_claude" = true ]; then
      live_count=$((live_count + 1))

      if [ -z "$role" ]; then
        # Unregistered Claude pane — try to identify from title or capture
        local capture
        capture=$("$OOSH_DIR/otmux" pane.capture "$pane_addr" 10 2>/dev/null)
        # Check if it mentions a role name
        local guessed_role=""
        for candidate in orchestrator oosh-expert oosh-tester scrum-master agent-teacher product-owner developer; do
          if echo "$capture" | grep -qi "$candidate"; then
            guessed_role="$candidate"
            break
          fi
        done
        if [ -n "$guessed_role" ]; then
          info.log "Identified $pane_addr as $guessed_role (from pane content)"
          private.hiveMind.registry.set "$pane_addr" "$guessed_role"
          role="$guessed_role"
        else
          warn.log "Unidentified Claude pane at $pane_addr — register manually"
        fi
      fi

      # Step 6-10: Nudge agents that need recovery
      if [ -n "$role" ] && [ -n "$workspace" ]; then
        local boot_file="${workspace}/session/boot/${role}.md"
        local context_file="${workspace}/session/agents/${role}/context.md"

        # Check if agent looks stuck or just booted
        local state
        state=$(private.hiveMind.sweep.detect "$pane_addr" 2>/dev/null)
        local status="${state%%|*}"

        case "$status" in
          active|idle)
            info.log "$role at $pane_addr: $status — no recovery needed"
            ;;
          *)
            info.log "$role at $pane_addr: $status — sending recovery nudge"
            if [ -f "$boot_file" ]; then
              "$OOSH_DIR/otmux" send "$pane_addr" C-u
              "$OOSH_DIR/otmux" send "$pane_addr" "Read session/boot/${role}.md" Enter
            elif [ -f "$context_file" ]; then
              "$OOSH_DIR/otmux" send "$pane_addr" C-u
              "$OOSH_DIR/otmux" send "$pane_addr" "Read session/agents/${role}/context.md" Enter
            fi
            recovered_count=$((recovered_count + 1))
            ;;
        esac
      fi
    fi
  done <<< "$live_panes"

  console.log "cold.recover: session=$session panes=$pane_count claude=$live_count stale=$stale_count recovered=$recovered_count"
  create.result 0 "panes=$pane_count claude=$live_count stale=$stale_count recovered=$recovered_count"
  return 0
}
hiveMind.cold.recover.completion.session() {
  otmux sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.train() # <role> <?session> # train agent: verify reading list, send training task, monitor, verify completion
{
  local role="$1"
  local session="${2:-$(private.hiveMind.active.team)}"

  if [ -z "$role" ]; then
    error.log "Usage: hiveMind train <role> <?session>"
    return 1
  fi

  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  if [ -z "$workspace" ]; then
    error.log "Cannot determine workspace root"
    return 1
  fi

  # Resolve role to pane
  local target
  target=$(hiveMind.resolve "$role" "$session" 2>/dev/null)
  if [ -z "$target" ]; then
    error.log "Cannot resolve '$role' — is the agent bootstrapped?"
    return 1
  fi

  info.log "Training $role at $target"

  # Step 1: Verify SKILL.md has a Reading List
  local agents_dir
  agents_dir=$(private.hiveMind.find.agents.dir 2>/dev/null)
  local skill_file="${agents_dir}/${role}/SKILL.md"
  if [ -z "$agents_dir" ] || [ ! -f "$skill_file" ]; then
    error.log "No SKILL.md found for role $role (tried $skill_file)"
    return 1
  fi

  if ! grep -q "## Reading List" "$skill_file" 2>/dev/null; then
    warn.log "SKILL.md for $role has no Reading List section — agent may not know what to read"
  else
    info.log "SKILL.md has Reading List section"
  fi

  # Step 2: Write training task file
  local tasks_dir="${workspace}/session/tasks"
  mkdir -p "$tasks_dir" 2>/dev/null
  local task_file="${tasks_dir}/${role}-training.md"

  local rel_skill
  rel_skill=$(echo "$skill_file" | sed "s|^${workspace}/||")

  {
    echo "# Training: $role"
    echo ""
    echo "**From**: agent-trainer"
    echo "**To**: $role"
    echo "**Priority**: HIGH"
    echo "**Date**: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
    echo ""
    echo "## Instructions"
    echo ""
    echo "1. Read your SKILL.md: \`${rel_skill}\`"
    echo "2. Follow the Reading List section — read each file in order"
    echo "3. After reading all files, write your context to \`session/agents/${role}/context.md\`"
    echo "4. Check \`session/tasks/\` for assigned work"
    echo "5. Report training complete with files-read count"
  } > "$task_file"

  # Send task to agent
  "$OOSH_DIR/otmux" send "$target" C-u
  "$OOSH_DIR/otmux" send "$target" "Read session/tasks/${role}-training.md" Enter

  # Step 3: Monitor progress — wait up to 60s for file reads
  info.log "Monitoring training progress..."
  local waited=0
  local trained=false
  while [ "$waited" -lt 60 ]; do
    sleep 10
    waited=$((waited + 10))

    local capture
    capture=$("$OOSH_DIR/otmux" pane.capture "$target" 15 2>/dev/null)

    # Check for signs of completion
    if echo "$capture" | grep -qE 'files read|training complete|context.*written|TaskList|session/tasks'; then
      trained=true
      info.log "Training activity detected after ${waited}s"
      break
    fi

    # Check if agent is actively reading (tool use activity)
    local state
    state=$(private.hiveMind.sweep.detect "$target" 2>/dev/null)
    local status="${state%%|*}"
    if [ "$status" = "active" ]; then
      debug.log "Agent active at ${waited}s — still training"
    fi
  done

  # Step 4: Verify completion
  local context_file="${workspace}/session/agents/${role}/context.md"
  if [ -f "$context_file" ]; then
    local age
    age=$(( $(date +%s) - $(stat -f %m "$context_file" 2>/dev/null || echo 0) ))
    if [ "$age" -lt 120 ]; then
      info.log "Context file written (${age}s ago)"
      trained=true
    fi
  fi

  # Step 5: Report result
  if [ "$trained" = true ]; then
    console.log "train: $role at $target — trained and ready for work"
    create.result 0 "trained"
  else
    warn.log "train: $role at $target — training may still be in progress (waited 60s)"
    create.result 0 "in_progress"
  fi
  return 0
}
hiveMind.train.completion.role() {
  hiveMind.role.list 2>/dev/null
}
hiveMind.train.completion.session() {
  otmux sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.improvement() # <?action:next|done|list> <?number> # CMM improvement workflow: find next, mark done, list status
{
  local action="${1:-next}"
  local number="$2"

  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  if [ -z "$workspace" ]; then
    error.log "Cannot determine workspace root"
    return 1
  fi

  local imp_file="${workspace}/session/cmm.improvement.md"
  local pipeline_file="${workspace}/session/knowledge-base/cmm-pipeline.md"

  # Ensure improvement file exists
  if [ ! -f "$imp_file" ]; then
    if [ "$action" = "next" ] || [ "$action" = "list" ]; then
      warn.log "No improvement file at $imp_file"
      create.result 1 "no_file"
      return 1
    fi
  fi

  case "$action" in

    next)
      # Step 1: Find top unchecked improvement
      # Format: lines with "- [ ]" are unchecked, "- [x]" are done
      local next_item
      next_item=$(grep -n '\- \[ \]' "$imp_file" 2>/dev/null | head -1)
      if [ -z "$next_item" ]; then
        console.log "improvement: all items checked — pipeline empty"
        create.result 0 "all_done"
        return 0
      fi

      local line_num="${next_item%%:*}"
      local item_text="${next_item#*:}"
      # Strip leading whitespace and checkbox
      item_text=$(echo "$item_text" | sed 's/^[[:space:]]*- \[ \] //')

      info.log "Next improvement (line $line_num): $item_text"

      # Step 2: Extract KPIs — read lines after the item until next item or section
      local kpis=""
      local kpi_line=$((line_num + 1))
      local total_lines
      total_lines=$(wc -l < "$imp_file" | tr -d ' ')
      while [ "$kpi_line" -le "$total_lines" ]; do
        local line
        line=$(sed -n "${kpi_line}p" "$imp_file")
        # Stop at next item, blank line after KPIs, or section header
        case "$line" in
          "- ["*|"## "*|"# "*) break ;;
          "") break ;;
          *) kpis="${kpis}${line}"$'\n' ;;
        esac
        kpi_line=$((kpi_line + 1))
      done

      if [ -n "$kpis" ]; then
        echo "KPIs:"
        echo "$kpis"
      fi

      console.log "improvement next: $item_text"
      create.result 0 "$item_text"
      ;;

    done)
      # Steps 5-8: Mark improvement as done, commit
      if [ -z "$number" ]; then
        # Default: mark first unchecked item
        local first_unchecked
        first_unchecked=$(grep -n '\- \[ \]' "$imp_file" 2>/dev/null | head -1)
        if [ -z "$first_unchecked" ]; then
          warn.log "No unchecked items to mark done"
          return 1
        fi
        number="${first_unchecked%%:*}"
      fi

      # Mark the checkbox at line $number
      if sed -n "${number}p" "$imp_file" | grep -q '\- \[ \]'; then
        sed -i '' "${number}s/- \[ \]/- [x]/" "$imp_file"
        info.log "Marked line $number as done"
      else
        warn.log "Line $number is not an unchecked item"
        return 1
      fi

      # Update pipeline status if it exists
      if [ -f "$pipeline_file" ]; then
        info.log "Remember to update $pipeline_file status"
      fi

      # Step 8: Commit
      (cd "$workspace" && git add -f session/cmm.improvement.md session/knowledge-base/cmm-pipeline.md 2>/dev/null && \
       git commit -m "CMM improvement done (line $number)" 2>/dev/null)
      local commit_result=$?
      if [ $commit_result -eq 0 ]; then
        console.log "improvement done: line $number — committed"
      else
        console.log "improvement done: line $number — marked (nothing to commit)"
      fi
      create.result 0 "done"
      ;;

    list)
      # Show status summary
      local total checked unchecked
      total=$(grep -c '\- \[' "$imp_file" 2>/dev/null || echo 0)
      checked=$(grep -c '\- \[x\]' "$imp_file" 2>/dev/null || echo 0)
      unchecked=$(grep -c '\- \[ \]' "$imp_file" 2>/dev/null || echo 0)
      echo "CMM Improvements: $checked/$total done, $unchecked remaining"
      grep '\- \[' "$imp_file" 2>/dev/null
      create.result 0 "$checked/$total"
      ;;

    *)
      error.log "Usage: hiveMind improvement [next|done|list] <?line-number>"
      return 1
      ;;
  esac
  return 0
}
hiveMind.improvement.completion.action() {
  echo "next done list"
}

hiveMind.metrics.log() # <?session> # log sweep metrics per agent + flag context/subscription thresholds
{
  local session="${1:-$(private.hiveMind.active.team)}"

  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  if [ -z "$workspace" ]; then
    error.log "Cannot determine workspace root"
    return 1
  fi

  local logfile="${workspace}/session/metrics/sweep-log.md"
  mkdir -p "$(dirname "$logfile")" 2>/dev/null

  # Initialize log with header if new
  if [ ! -f "$logfile" ]; then
    echo "| Time | Agent | Pane | Context% | State | Velocity | TTC |" > "$logfile"
    echo "|------|-------|------|----------|-------|----------|-----|" >> "$logfile"
  fi

  local pane_lines
  pane_lines=$(private.hiveMind.list.panes addr+cmd "$session")
  [ -z "$pane_lines" ] && { error.log "No panes for $session"; return 1; }

  local ts logged=0 alerts=""
  ts=$(date -u '+%H:%M')

  while IFS='|' read -r addr cmd; do
    local target="${session}:${addr}"

    # Only log Claude panes
    local is_claude=false
    case "$cmd" in
      node) is_claude=true ;;
    esac
    [ "$is_claude" = false ] && continue

    local role
    role=$(private.hiveMind.registry.get "$target" 2>/dev/null)
    [ -z "$role" ] && role="$addr"

    # Context %
    local pct
    pct=$("$OOSH_DIR/claudeCode" context.read "$target" 2>/dev/null)
    [ -z "$pct" ] && pct="?"

    # State
    local detect status
    detect=$(private.hiveMind.sweep.detect "$target" 2>/dev/null)
    status="${detect%%|*}"

    # Velocity
    local velocity tokens_hr ttc
    velocity=$("$OOSH_DIR/claudeCode" context.velocity "$target" 2>/dev/null)
    tokens_hr=$(echo "$velocity" | grep -oE '^[0-9]+ tokens/hr' || echo "-")
    ttc=$(echo "$velocity" | grep -oE '~[0-9]+min' || echo "-")

    # Log row
    echo "| $ts | $role | $addr | ${pct}% | $status | $tokens_hr | $ttc |" >> "$logfile"
    logged=$((logged + 1))

    # Flag: context > 80% used (i.e. <20% remaining)
    if [ "$pct" != "?" ] && [ "$pct" -le 20 ] 2>/dev/null; then
      alerts="${alerts}CONTEXT: $role at ${pct}% — save state now\n"
      "$OOSH_DIR/claudeCode" context.alert "$target" 20
    fi

  done <<< "$pane_lines"

  # Flag: subscription threshold
  local sub_pct
  sub_pct=$("$OOSH_DIR/scrumMaster" subscription 2>/dev/null | grep -oE '[0-9]+%' | head -1 | tr -d '%')
  if [ -n "$sub_pct" ] && [ "$sub_pct" -ge 80 ] 2>/dev/null; then
    alerts="${alerts}SUBSCRIPTION: ${sub_pct}% — throttle team\n"
  fi

  if [ -n "$alerts" ]; then
    warn.log "Metric alerts:\n$alerts"
  fi

  console.log "metrics.log: $logged agents logged to sweep-log.md"
  create.result 0 "$logged"
  return 0
}
hiveMind.metrics.log.completion.session() {
  otmux sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.metrics.summary() # <?lines:20> # summarize recent sweep-log: trends, burn rates, projections
{
  local lines="${1:-20}"

  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  if [ -z "$workspace" ]; then
    error.log "Cannot determine workspace root"
    return 1
  fi

  local logfile="${workspace}/session/metrics/sweep-log.md"
  if [ ! -f "$logfile" ]; then
    warn.log "No sweep-log.md — run hiveMind metrics.log first"
    return 1
  fi

  local total_rows
  total_rows=$(grep -c '^|' "$logfile" | tr -d ' ')
  total_rows=$((total_rows - 2))  # subtract header rows

  echo "=== Metrics Summary (last $lines entries of $total_rows total) ==="
  echo ""

  # Show recent entries
  tail -"$lines" "$logfile"
  echo ""

  # Count agents at risk (context <=20%)
  local at_risk
  at_risk=$(tail -"$lines" "$logfile" | grep -oE '[0-9]+%' | while read pct; do
    p="${pct%\%}"
    [ "$p" -le 20 ] 2>/dev/null && echo "$p"
  done | wc -l | tr -d ' ')

  # Count blocked agents
  local blocked
  blocked=$(tail -"$lines" "$logfile" | grep -cE 'permission|stuck|panel|overlay' || echo 0)

  echo "At risk (<=20% context): $at_risk"
  echo "Blocked states: $blocked"
  echo "Total rows in log: $total_rows"

  create.result 0 "entries=$total_rows at_risk=$at_risk blocked=$blocked"
  return 0
}

hiveMind.plan.create() # <slug> # link a plan from ~/.claude/plans/ to session/plans/ with timestamp, symlink back, git commit
{
  local slug="$1"
  local plans_dir="$HOME/.claude/plans"
  local source_file="$plans_dir/${slug}.md"

  if [ -z "$slug" ]; then
    error.log "Usage: hiveMind plan.create <slug>"
    return 1
  fi

  if [ ! -f "$source_file" ]; then
    error.log "Plan not found: $source_file"
    return 1
  fi

  if [ -L "$source_file" ]; then
    error.log "Already linked (symlink): $source_file"
    return 1
  fi

  # Find session/plans/ dir relative to git workspace
  local workspace
  workspace=$(git rev-parse --show-toplevel 2>/dev/null)
  if [ -z "$workspace" ]; then
    error.log "Not in a git repository"
    return 1
  fi

  local session_plans="$workspace/session/plans"
  mkdir -p "$session_plans"

  # Generate timestamped filename
  local timestamp
  timestamp=$(date -u '+%Y%m%dT%H%M%SZ')
  local target_name="${timestamp}.${slug}.plan.md"
  local target_path="$session_plans/$target_name"

  # Copy to session/plans/
  cp "$source_file" "$target_path"

  # Replace original with symlink
  rm "$source_file"
  ln -s "$target_path" "$source_file"

  # Git add + commit
  (cd "$workspace" && git add "session/plans/$target_name" && \
   git commit -m "Plan linked: $target_name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>")

  success.log "Plan linked: session/plans/$target_name"
  RESULT="session/plans/$target_name"
}
hiveMind.plan.create.completion.slug() {
  # List non-symlink .md files in ~/.claude/plans/ (plans not yet linked)
  local plans_dir="$HOME/.claude/plans"
  [ -d "$plans_dir" ] || return
  local f
  for f in "$plans_dir"/*.md; do
    [ -f "$f" ] && [ ! -L "$f" ] && basename "$f" .md
  done
}

hiveMind.fix.path() # # verify OOSH PATH works, scan SKILL.md files for stale export patterns
{
  local issues=0

  # Step 1: Verify OOSH on PATH
  local otmux_path
  otmux_path=$(which otmux 2>/dev/null)
  if [ -n "$otmux_path" ]; then
    info.log "otmux on PATH: $otmux_path"
  else
    error.log "otmux NOT on PATH — check ~/.bashrc"
    issues=$((issues + 1))
  fi

  local hivemind_path
  hivemind_path=$(which hiveMind 2>/dev/null)
  if [ -n "$hivemind_path" ]; then
    info.log "hiveMind on PATH: $hivemind_path"
  else
    error.log "hiveMind NOT on PATH — check ~/.bashrc"
    issues=$((issues + 1))
  fi

  # Step 2: Verify direct command works (no export prefix needed)
  local test_result
  test_result=$(otmux pane.capture "$(otmux pane.get.target)" 1 2>/dev/null)
  if [ $? -eq 0 ]; then
    info.log "Direct otmux command: OK"
  else
    warn.log "Direct otmux command failed — PATH may not be inherited by subshells"
    issues=$((issues + 1))
  fi

  # Step 3: Scan SKILL.md files for stale PATH patterns
  local agents_dir
  agents_dir=$(private.hiveMind.find.agents.dir 2>/dev/null)
  if [ -n "$agents_dir" ]; then
    local stale_files
    stale_files=$(grep -rlE 'export PATH.*oosh|OOSH PATH Setup|MANDATORY.*run FIRST|cd.*/oosh.*&&' "$agents_dir" 2>/dev/null)
    if [ -n "$stale_files" ]; then
      warn.log "Stale PATH patterns found in:"
      echo "$stale_files"
      issues=$((issues + 1))
    else
      info.log "No stale PATH patterns in SKILL.md files"
    fi
  fi

  # Step 4: Check for compound-command workarounds in settings
  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  if [ -n "$workspace" ]; then
    local settings_file="${workspace}/.claude/settings.json"
    if [ -f "$settings_file" ]; then
      if grep -qE 'sleep.*&&.*cd|export PATH' "$settings_file" 2>/dev/null; then
        warn.log "Stale compound-command patterns in settings.json"
        issues=$((issues + 1))
      else
        info.log "settings.json: no stale patterns"
      fi
    fi
  fi

  if [ "$issues" -eq 0 ]; then
    console.log "fix.path: all checks passed — PATH healthy"
    create.result 0 "healthy"
  else
    warn.log "fix.path: $issues issue(s) found"
    create.result 1 "$issues issues"
  fi
  return "$issues"
}

hiveMind.sweep.loop() # <?seconds:30> # continuous sweep + unblock all sessions at interval
{
  local interval="${1:-30}"

  console.log "Starting sweep loop every ${interval}s across all sessions (Ctrl+C to stop)"

  while true; do
    echo ""
    echo "── sweep @ $(date '+%H:%M:%S') ──────────────────────────────────"
    local teams
    teams=$(hiveMind.team.list 2>/dev/null)
    if [ -n "$teams" ]; then
      while read -r session; do
        hiveMind.sweep "$session"
        hiveMind.unblock all "$session"
      done <<< "$teams"
    fi
    sleep "$interval"
  done
}
hiveMind.sweep.loop.completion.seconds() {
  echo "15"
  echo "30"
  echo "60"
  echo "120"
}
hiveMind.sweep.loop.completion.session() {
  private.hiveMind.teams.complete
}

# ─────────────────────────────────────────────────────────────────────────────
# WATCHDOG — external sweep/unblock loop (runs outside Claude Code)
# ─────────────────────────────────────────────────────────────────────────────

: ${HIVEMIND_WATCHDOG_PID_FILE:=/tmp/hivemind.watchdog.pid}
: ${HIVEMIND_WATCHDOG_LOG:=/tmp/hivemind.watchdog.log}
: ${HIVEMIND_WATCHDOG_HEARTBEAT:=/tmp/hivemind.watchdog.heartbeat}

hiveMind.watchdog() # <?seconds:30> # start external sweep+unblock loop in a new tmux pane
{
  local interval="${1:-30}"

  # Check if already running
  if hiveMind.watchdog.status > /dev/null 2>&1; then
    console.log "Watchdog already running (PID $(cat "$HIVEMIND_WATCHDOG_PID_FILE"))"
    return 0
  fi

  local session
  session=$(private.hiveMind.current.session)
  [ -z "$session" ] && { error.log "Not inside a tmux session"; return 1; }

  # Spawn a new pane running plain bash (no Claude Code = no permission prompts)
  otmux split.v -t "${session}:0" -l 5 \
    "echo \$\$ > '$HIVEMIND_WATCHDOG_PID_FILE' && while true; do touch '$HIVEMIND_WATCHDOG_HEARTBEAT'; echo \"── watchdog @ \$(date '+%H:%M:%S') ──\" >> '$HIVEMIND_WATCHDOG_LOG'; for team in \$(hiveMind team.list 2>/dev/null); do hiveMind unblock all \"\$team\" >> '$HIVEMIND_WATCHDOG_LOG' 2>&1; done; sleep $interval; done"

  # Lock the pane title
  sleep 0.5
  local watchdog_pane
  watchdog_pane=$(otmux panes -t "${session}:0" -F "#{pane_index}" | tail -1)
  otmux pane.lock "${session}:0.${watchdog_pane}" "watchdog" 2>/dev/null

  console.log "Watchdog started in ${session}:0.${watchdog_pane} (every ${interval}s)"
  console.log "Log: $HIVEMIND_WATCHDOG_LOG"
}
hiveMind.watchdog.completion() { :; }

hiveMind.watchdog.stop() # # stop the external watchdog loop
{
  if [ ! -f "$HIVEMIND_WATCHDOG_PID_FILE" ]; then
    error.log "No watchdog PID file found"
    return 1
  fi

  local pid
  pid=$(cat "$HIVEMIND_WATCHDOG_PID_FILE")
  if kill -0 "$pid" 2>/dev/null; then
    kill "$pid" 2>/dev/null
    rm -f "$HIVEMIND_WATCHDOG_PID_FILE" "$HIVEMIND_WATCHDOG_HEARTBEAT"
    console.log "Watchdog stopped (PID $pid)"
  else
    rm -f "$HIVEMIND_WATCHDOG_PID_FILE" "$HIVEMIND_WATCHDOG_HEARTBEAT"
    console.log "Watchdog was not running (stale PID file cleaned)"
  fi
}
hiveMind.watchdog.stop.completion() { :; }

hiveMind.watchdog.status() # # check if watchdog is running
{
  if [ ! -f "$HIVEMIND_WATCHDOG_PID_FILE" ]; then
    echo "not running"
    return 1
  fi

  local pid
  pid=$(cat "$HIVEMIND_WATCHDOG_PID_FILE")
  if kill -0 "$pid" 2>/dev/null; then
    echo "running (PID $pid)"
    return 0
  else
    echo "not running (stale PID file)"
    rm -f "$HIVEMIND_WATCHDOG_PID_FILE"
    return 1
  fi
}
hiveMind.watchdog.status.completion() { :; }

hiveMind.watchdog.supervisor() # <?interval:30> # check watchdog health and restart if dead/stale
{
  local watchdog_interval="${1:-30}"
  local max_stale=$((watchdog_interval * 3))  # 3x interval = stale

  # Check if watchdog is running (PID alive)
  if ! hiveMind.watchdog.status > /dev/null 2>&1; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') SUPERVISOR: Watchdog dead, restarting..." >> "$HIVEMIND_WATCHDOG_LOG"
    hiveMind.watchdog "$watchdog_interval"
    return $?
  fi

  # Check heartbeat freshness (detect stuck watchdog)
  if [ -f "$HIVEMIND_WATCHDOG_HEARTBEAT" ]; then
    local last_beat now age
    last_beat=$(stat -f %m "$HIVEMIND_WATCHDOG_HEARTBEAT" 2>/dev/null)  # macOS stat
    now=$(date +%s)
    age=$(( now - last_beat ))
    if [ "$age" -gt "$max_stale" ]; then
      echo "$(date '+%Y-%m-%d %H:%M:%S') SUPERVISOR: Watchdog stale (${age}s > ${max_stale}s), restarting..." >> "$HIVEMIND_WATCHDOG_LOG"
      hiveMind.watchdog.stop
      hiveMind.watchdog "$watchdog_interval"
      return $?
    fi
  fi

  console.log "Watchdog healthy"
  return 0
}
hiveMind.watchdog.supervisor.completion() { :; }

# ─────────────────────────────────────────────────────────────────────────────
# USAGE
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.usage()
{
  local this=${0##*/}
  echo "You started"
  echo "$0

  hiveMind - Multi-agent orchestrator for oosh
  Manages REAL Claude Code agents in tmux panes

  Usage:
  $this command     Description
  ─────────────────────────────────────────────────────
  INITIALIZATION:
      init              initialize hivemind with agents
      attach            attach to hivemind session
      detach            detach from session
      join              rejoin agent's Claude session by role name
      kill              shutdown hivemind completely

  AGENTS:
      spawn             spawn a new agent with pane
      list              list all agents
      status            one-line session overview
      focus             focus on agent pane
      resolve           resolve agent name to pane target
      panes             list all agent panes

  ROLES:
      roles             show all role descriptions
      role.list         list roles from .claude/agents/
      role.prompt       output teaching prompt for role
      role.teach        teach role to existing pane

  AGENT LIFECYCLE:
      agent.bootstrap   full bootstrap: pane + claude + teach
      agent.verify      check if agent is alive

  TEAM:
      team.register     register a team session
      team.remove       unregister a team session
      team.switch       set the active team context
      team.active       show the current active team
      team.list         list registered teams (with status)
      team.setup.oosh   create 3-pane oosh team
      team.setup.full   create 4-pane full team
      team.status       show team members as tree
      team.sweep        structured one-line-per-pane status
      team.loop         continuous team.sweep at interval
      pane.titles       set border labels for all agents
      teach             teach agent a role

  MONITORING:
      monitor           capture pane output (by name or all)
      monitor.approve   approve permission prompt in pane
      sweep <s> <?sec>  batch-capture all panes (optional sleep before)
      unblock <name>    detect and resolve stuck prompts (or 'all')
      sweep.loop <sec>  continuous sweep + unblock at interval

  MESSAGING:
      agent.send        send message via best available channel (transport-independent)
      send              send raw keys to agent by name (no Enter)
      send.enter        send message to agent by name (with Enter)
      task              send task to specific agent
      broadcast         broadcast message to all agents

  DISCOVERY:
      discover          discover agents as JSON

  CLAUDE CODE:
      claude            interact with agent via Claude Code
      pane.create       create tmux pane for agent
      process.lookup    resolve Claude PID to pane, role, UUID
      process.list      list all Claude processes with pane mappings
      teams.save        snapshot all agents for restore after restart
      teams.restore     restore teams from snapshot file
      teams.migrate     migrate team to remote machine via ossh
      registry.set      set registry entry for a pane
      registry.remove   remove registry entry for a pane
      registry.list     list registry entries (live discovery + file)
      registry.fix      clean up invalid entries in registry
      team.activate     set active team (alias for team.switch)
      consistency.audit cross-compare all identity sources, show mismatches
      consistency.fix  auto-repair identity mismatches from live truth
  ─────────────────────────────────────────────────────

  Examples:
    $this team.register projectTeam 'OOSH dev team'  # register a team
    $this team.register claudeWoda 'Story team'      # register another
    $this team.switch projectTeam    # set active team context
    $this team.active                # show active team
    $this team.list                  # list all teams with status
    $this team.status                # tree view of active team agents
    $this team.status projectTeam    # tree view of specific team
    $this resolve expert             # find expert's pane in active team
    $this agent.send expert 'fix bug' # send to expert (active team)
    $this send.enter expert 'fix bug' # send message by name (with Enter)
    $this monitor expert             # capture 5 lines from expert
    $this role.list                  # list available roles
    $this agent.bootstrap oosh-expert # bootstrap an expert
    $this join product-owner         # rejoin PO's Claude session
    $this team.setup.full            # create full team
  "
}

hiveMind.help() # # show usage information
{
  hiveMind.usage
}

hiveMind.start()
{
  #echo "sourcing init"
  source this

  # Resolve HIVEMIND_AGENTS_DIR by searching upward for .claude/agents with SKILL.md files
  if [ -z "$HIVEMIND_AGENTS_DIR" ]; then
    HIVEMIND_AGENTS_DIR=$(private.hiveMind.find.agents.dir 2>/dev/null) || true
  fi

  if [ -z "$1" ]; then
    hiveMind.status
    return 0
  fi

  this.start "$@"
}

hiveMind.start "$@"
