#!/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>"

# PDCA counters are stored in $CONFIG_PATH/stateMachines/<machine>.pdca.env
# to survive across subprocess calls when state.check re-sources this script

# ============================================================================
# Measurement configuration (CMM4)
# ============================================================================
: ${SCRUMMASTER_METRICS_DIR:=$(git rev-parse --show-toplevel 2>/dev/null)/session/metrics}
: ${SCRUMMASTER_CAPTURE_LINES:=30}

### new.method

# Helper: Load PDCA counters from file
private.pdca.config.load() {
  local machine=${1:-PDCA}
  local configFile="$CONFIG_PATH/stateMachines/${machine}.pdca.env"
  if [ -f "$configFile" ]; then
    source "$configFile"
  else
    PDCA_MAX_ITERATIONS=5
    PDCA_ERROR_COUNT=0
    PDCA_ITERATION=0
  fi
}

# Helper: Save PDCA counters to file
private.pdca.config.save() {
  local machine=${1:-PDCA}
  local configFile="$CONFIG_PATH/stateMachines/${machine}.pdca.env"
  {
    echo "PDCA_MAX_ITERATIONS=$PDCA_MAX_ITERATIONS"
    echo "PDCA_ERROR_COUNT=$PDCA_ERROR_COUNT"
    echo "PDCA_ITERATION=$PDCA_ITERATION"
  } > "$configFile"
}

scrumMaster.pdca.start() # <?machineName:PDCA> <?errorCount:0> # creates and starts a new PDCA cycle state machine
{
  local machineName=${1:-PDCA}
  shift 2>/dev/null
  local errorCount=${1:-0}

  source state

  # Initialize PDCA counters and save to file
  PDCA_MAX_ITERATIONS=5
  PDCA_ERROR_COUNT=$errorCount
  PDCA_ITERATION=0
  private.pdca.config.save $machineName

  # Delete existing machine if present
  if state.machine.exists $machineName 2>/dev/null; then
    state.machine.delete $machineName 2>/dev/null
  fi

  console.log "Creating PDCA state machine: $machineName"

  # Create PDCA state machine using proper state tool pattern (like oo does)
  # Structure after state.add:
  #   [11] = planning (P)
  #   [12] = doing (D)
  #   [13] = checking (C)
  #   [14] = acting (A)
  #   [15] = finished    # Terminal: Check passed
  #   [16] = error.max.iterations  # Error: too many iterations
  # Note: Acting loops back to Checking via private.check.acting returning 13

  state.machine.create $machineName scrumMaster
  state.add planning               silent
  state.add doing                  silent
  state.add checking               silent
  state.add acting                 silent
  state.add finished               silent
  state.add error.max.iterations   silent
  state.add 99                     silent  # Transition to finished - closes the state sequence

  # Start the machine (validates private.check.* exist)
  state.machine.start scrumMaster

  # Move to first PDCA state (planning)
  state.next

  success.log "PDCA cycle started: $machineName (errors=$errorCount, max_iterations=$PDCA_MAX_ITERATIONS)"
  create.result 0 "PLANNING"
}

scrumMaster.pdca.state() # <?machineName:PDCA> # shows the current PDCA state (PLANNING, DOING, CHECKING, ACTING, FINISHED)
{
  local machineName=${1:-PDCA}

  source state

  if ! state machine.exists $machineName 2>/dev/null; then
    error.log "PDCA machine '$machineName' does not exist. Run: scrumMaster pdca.start"
    create.result 1 "NOT_STARTED"
    return 1
  fi

  local currentState=$(state name $machineName)
  local upperState=$(echo "$currentState" | tr '[:lower:]' '[:upper:]' | tr '.' '_')

  echo "$upperState"
  create.result 0 "$upperState"
}

scrumMaster.pdca.next() # <?machineName:PDCA> # advances to the next PDCA state with validation
{
  local machineName=${1:-PDCA}

  source state

  if ! state machine.exists $machineName 2>/dev/null; then
    error.log "PDCA machine '$machineName' does not exist"
    return 1
  fi

  # Select the machine first, then call state.next without script arg
  # state.check will use PDCA_CUSTOM_SCRIPT=scrumMaster from the env file
  state of $machineName
  state next

  local currentState=$(state name $machineName)
  create.result 0 "$currentState"
}

scrumMaster.pdca.errors() # <?machineName:PDCA> <?count> # gets or sets the error count for Check phase
{
  local machineName=${1:-PDCA}
  shift 2>/dev/null
  local count=$1

  if [ -n "$count" ]; then
    export PDCA_ERROR_COUNT=$count
    console.log "Set PDCA_ERROR_COUNT=$count"
  fi

  echo $PDCA_ERROR_COUNT
  create.result 0 "$PDCA_ERROR_COUNT"
}

scrumMaster.pdca.iteration() # <?machineName:PDCA> # shows the current C→A iteration count
{
  echo $PDCA_ITERATION
  create.result 0 "$PDCA_ITERATION"
}

scrumMaster.pdca.reset() # <?machineName:PDCA> # resets the PDCA state machine
{
  local machineName=${1:-PDCA}

  source state

  if state machine.exists $machineName 2>/dev/null; then
    state machine.delete $machineName
  fi

  export PDCA_ERROR_COUNT=0
  export PDCA_ITERATION=0

  success.log "PDCA machine '$machineName' reset"
  create.result 0 "RESET"
}

scrumMaster.pdca.run() # <?machineName:PDCA> <?errorCount:0> # runs complete PDCA cycle until finished or max iterations
{
  local machineName=${1:-PDCA}
  shift 2>/dev/null
  local errorCount=${1:-0}

  scrumMaster.pdca.start $machineName $errorCount

  local maxSteps=20  # Safety limit
  local step=0

  while [ $step -lt $maxSteps ]; do
    local currentState=$(scrumMaster.pdca.state $machineName)
    console.log "Step $step: $currentState"

    if [ "$currentState" = "FINISHED" ] || [ "$currentState" = "ERROR_MAX_ITERATIONS" ]; then
      break
    fi

    scrumMaster.pdca.next $machineName
    ((step++))
  done

  local finalState=$(scrumMaster.pdca.state $machineName)
  create.result 0 "$finalState"
}

# ============================================================================
# private.check.* implementations for PDCA state transitions
# ============================================================================

private.check.planning() # <script> <stageTo> <stateFound> # P: Plan phase - always succeeds
{
  local script=$1; shift
  local stageTo=$1; shift
  local stateFound=$1; shift

  console.log "PLAN: Defining objectives and success criteria"
  create.result 0 "planning complete"
  return $(result)
}

private.check.doing() # <script> <stageTo> <stateFound> # D: Do phase - execute the plan
{
  local script=$1; shift
  local stageTo=$1; shift
  local stateFound=$1; shift

  console.log "DO: Executing the plan"
  create.result 0 "doing complete"
  return $(result)
}

private.check.checking() # <script> <stageTo> <stateFound> # C: Check phase - verify results, may loop to Act
{
  local script=$1; shift
  local stageTo=$1; shift
  local stateFound=$1; shift

  # Load counters from file (survives re-sourcing)
  private.pdca.config.load PDCA

  console.log "CHECK: Iteration $PDCA_ITERATION, Errors=$PDCA_ERROR_COUNT"

  if [ "$PDCA_ERROR_COUNT" -gt 0 ]; then
    console.log "CHECK: Found $PDCA_ERROR_COUNT errors - transitioning to Act"
    create.result 0 14  # → [14]=acting
    return $(result)
  fi

  console.log "CHECK: No errors - success!"
  create.result 0 15  # → [15]=finished
  return $(result)
}

private.check.acting() # <script> <stageTo> <stateFound> # A: Act phase - fix issues, then back to Check
{
  local script=$1; shift
  local stageTo=$1; shift
  local stateFound=$1; shift

  # Load counters from file
  private.pdca.config.load PDCA

  # Increment iteration on each act→check loop
  ((PDCA_ITERATION++))
  info.log "ACT: iteration $PDCA_ITERATION of $PDCA_MAX_ITERATIONS, errors=$PDCA_ERROR_COUNT"

  # Guard against infinite loops
  if [ "$PDCA_ITERATION" -ge "$PDCA_MAX_ITERATIONS" ]; then
    private.pdca.config.save PDCA
    warn.log "ACT: Max iterations ($PDCA_MAX_ITERATIONS) reached!"
    create.result 0 16  # → [16]=error.max.iterations
    return $(result)
  fi

  if [ "$PDCA_ERROR_COUNT" -gt 0 ]; then
    ((PDCA_ERROR_COUNT--))
    private.pdca.config.save PDCA
    console.log "ACT: Fixed 1 error, $PDCA_ERROR_COUNT remaining - looping back to Check"
    create.result 0 13  # → [13]=checking
    return $(result)
  fi

  # No more errors - proceed to finished
  private.pdca.config.save PDCA
  console.log "ACT: All errors fixed - proceeding to Finished"
  create.result 0 15  # → [15]=finished
  return $(result)
}

private.check.finished() # <script> <stageTo> <stateFound> # Terminal state - checks if really done or needs more C→A loops
{
  local script=$1; shift
  local stageTo=$1; shift
  local stateFound=$1; shift

  # Load counters from file
  private.pdca.config.load PDCA

  # Check if we came from acting and still have errors - loop back to checking
  if [ "$PDCA_ERROR_COUNT" -gt 0 ]; then
    ((PDCA_ITERATION++))
    private.pdca.config.save PDCA

    # Guard against infinite loops
    if [ "$PDCA_ITERATION" -ge "$PDCA_MAX_ITERATIONS" ]; then
      warn.log "FINISHED: Max iterations ($PDCA_MAX_ITERATIONS) reached!"
      create.result 0 16  # → [16]=error.max.iterations
      return $(result)
    fi

    console.log "FINISHED: Still have $PDCA_ERROR_COUNT errors - back to checking"
    create.result 0 13  # → [13]=checking (loop back)
    return $(result)
  fi

  success.log "PDCA cycle completed successfully!"
  create.result 0 "finished"
  return $(result)
}

private.check.error.max.iterations() # <script> <stageTo> <stateFound> # Error state - max iterations exceeded
{
  local script=$1; shift
  local stageTo=$1; shift
  local stateFound=$1; shift

  error.log "PDCA cycle failed: Max iterations exceeded"
  create.result 0 "error.max.iterations"
  return $(result)
}

private.check.next.custom.state() # <script> <stageTo> <stateFound> # Placeholder state - pass through
{
  local script=$1; shift
  local stageTo=$1; shift
  local stateFound=$1; shift

  create.result 0 "next.custom.state"
  return $(result)
}

# ============================================================================
# Measurement — private parsing methods (CMM4)
# ============================================================================

private.scrumMaster.parse.normalize.k() # <value> # convert "4.6k" to 4600, pass through plain numbers
{
  local val="$1"
  if echo "$val" | grep -q 'k$'; then
    local num="${val%k}"
    echo "$num" | awk '{printf "%d", $1 * 1000}'
  else
    echo "$val"
  fi
}

private.scrumMaster.parse.tokens() # <pane_content> # extract token counts, sets METRIC_TOKENS_UP/DOWN/UP_RAW/DOWN_RAW
{
  local content="$1"

  METRIC_TOKENS_UP_RAW=""
  METRIC_TOKENS_DOWN_RAW=""
  METRIC_TOKENS_UP=0
  METRIC_TOKENS_DOWN=0

  # Pattern: ↑ 20.3k tokens  or  ↑ 590 tokens
  local up_raw
  up_raw=$(echo "$content" | grep -o '↑ [0-9][0-9.]*k\{0,1\} tokens' | tail -1 | sed 's/↑ //;s/ tokens//')
  if [ -n "$up_raw" ]; then
    METRIC_TOKENS_UP_RAW="$up_raw"
    METRIC_TOKENS_UP=$(private.scrumMaster.parse.normalize.k "$up_raw")
  fi

  # Pattern: ↓ 7.9k tokens  or  ↓ 590 tokens
  local down_raw
  down_raw=$(echo "$content" | grep -o '↓ [0-9][0-9.]*k\{0,1\} tokens' | tail -1 | sed 's/↓ //;s/ tokens//')
  if [ -n "$down_raw" ]; then
    METRIC_TOKENS_DOWN_RAW="$down_raw"
    METRIC_TOKENS_DOWN=$(private.scrumMaster.parse.normalize.k "$down_raw")
  fi
}

private.scrumMaster.parse.timing() # <pane_content> # extract timing info, sets METRIC_THINK_TIME_S/WALL_TIME/WALL_TIME_S/TOOL_USES
{
  local content="$1"

  METRIC_THINK_TIME_S=0
  METRIC_WALL_TIME=""
  METRIC_WALL_TIME_S=0
  METRIC_TOOL_USES=0

  # Pattern: "thought for 33s" or "Thought for 33s"
  local think
  think=$(echo "$content" | grep -oE '[Tt]hought for [0-9]+s' | tail -1 | grep -oE '[0-9]+')
  [ -n "$think" ] && METRIC_THINK_TIME_S="$think"

  # Pattern: "(4m 9s" or "(1m 16s" — wall time in parentheses
  local wall
  wall=$(echo "$content" | grep -oE '\([0-9]+m [0-9]+s' | tail -1 | sed 's/(//')
  if [ -n "$wall" ]; then
    METRIC_WALL_TIME="$wall"
    local mins secs
    mins=$(echo "$wall" | grep -oE '^[0-9]+')
    secs=$(echo "$wall" | grep -oE '[0-9]+s$' | tr -d 's')
    METRIC_WALL_TIME_S=$(( mins * 60 + secs ))
  fi

  # Pattern: "14 tool uses" or "1 tool use"
  local tools
  tools=$(echo "$content" | grep -oE '[0-9]+ tool uses?' | tail -1 | grep -oE '[0-9]+')
  [ -n "$tools" ] && METRIC_TOOL_USES="$tools"
}

private.scrumMaster.parse.state() # <pane_content> # detect activity state, sets METRIC_ACTIVITY/METRIC_STATE
{
  local content="$1"

  METRIC_ACTIVITY="none"
  METRIC_STATE="unknown"

  # Check for completed verbs (past-tense spinner text)
  if echo "$content" | grep -qiE '(Brewed|Churned|Cooked|Crisped|Baked|Distilled|Rendered|Forged|Sauteed) '; then
    METRIC_ACTIVITY=$(echo "$content" | grep -oiE '(Brewed|Churned|Cooked|Crisped|Baked|Distilled|Rendered|Forged|Sauteed)' | tail -1)
    METRIC_STATE="completed"
    return 0
  fi

  # Check for active verbs (present-tense spinner text)
  if echo "$content" | grep -qiE '(Composing|Musing|Thinking|Cascading|Incubating|Frosting|Kneading|Ideating|Seasoning|Noodling|Transmuting|Fluttering|Cerebrating|Orbiting|Running|Reading)'; then
    METRIC_ACTIVITY=$(echo "$content" | grep -oiE '(Composing|Musing|Thinking|Cascading|Incubating|Frosting|Kneading|Ideating|Seasoning|Noodling|Transmuting|Fluttering|Cerebrating|Orbiting|Running|Reading)' | tail -1)
    METRIC_STATE="active"
    return 0
  fi

  # Check for permission prompt
  if echo "$content" | grep -q 'Allow' && echo "$content" | grep -q 'Deny'; then
    METRIC_ACTIVITY="permission-prompt"
    METRIC_STATE="permission"
    return 0
  fi

  # Check for idle prompt (> or ❯)
  local last_line
  last_line=$(echo "$content" | sed '/^[[:space:]]*$/d' | tail -1)
  if echo "$last_line" | grep -qE '^[[:space:]]*>[[:space:]]*$' || echo "$last_line" | grep -q '^[[:space:]]*❯[[:space:]]*$'; then
    METRIC_ACTIVITY="idle-prompt"
    METRIC_STATE="idle"
    return 0
  fi

  return 0
}

private.scrumMaster.resolve.pane() # <agent_name> <?session> # resolve agent name to pane target
{
  local name="$1"
  local session="${2:-$(cat "$HOME/config/hivemind.active.team" 2>/dev/null || echo projectTeam)}"

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

  local target
  target=$("$OOSH_DIR/hiveMind" resolve "$name" "$session" 2>/dev/null)
  if [ -n "$target" ]; then
    echo "$target"
    return 0
  fi

  error.log "Cannot resolve agent '$name' to pane target"
  return 1
}

private.scrumMaster.metrics.save() # <agent_name> <target> # persist METRIC_ variables to session/metrics/
{
  local agent="$1"
  local target="$2"
  local metrics_dir="${SCRUMMASTER_METRICS_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)/session/metrics}"

  [ ! -d "$metrics_dir" ] && mkdir -p "$metrics_dir"

  local timestamp
  timestamp=$(date -u "+%Y%m%dT%H%M%SZ")
  local filename="${agent}.${timestamp}.scenario.env"

  {
    echo "METRIC_TARGET=\"${target}\""
    echo "METRIC_TIMESTAMP=\"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\""
    echo "METRIC_TOKENS_UP=\"${METRIC_TOKENS_UP:-0}\""
    echo "METRIC_TOKENS_DOWN=\"${METRIC_TOKENS_DOWN:-0}\""
    echo "METRIC_TOKENS_UP_RAW=\"${METRIC_TOKENS_UP_RAW:-}\""
    echo "METRIC_TOKENS_DOWN_RAW=\"${METRIC_TOKENS_DOWN_RAW:-}\""
    echo "METRIC_WALL_TIME=\"${METRIC_WALL_TIME:-}\""
    echo "METRIC_WALL_TIME_S=\"${METRIC_WALL_TIME_S:-0}\""
    echo "METRIC_THINK_TIME_S=\"${METRIC_THINK_TIME_S:-0}\""
    echo "METRIC_TOOL_USES=\"${METRIC_TOOL_USES:-0}\""
    echo "METRIC_ACTIVITY=\"${METRIC_ACTIVITY:-none}\""
    echo "METRIC_STATE=\"${METRIC_STATE:-unknown}\""
  } > "$metrics_dir/$filename"

  info.log "Metrics saved: $metrics_dir/$filename"
  echo "$metrics_dir/$filename"
}

# ============================================================================
# Measurement — public API (CMM4)
# ============================================================================

scrumMaster.pane.capture() # <agent_name> <?session> # capture and parse metrics for one agent pane
{
  local agent="$1"
  local session="${2:-$(cat "$HOME/config/hivemind.active.team" 2>/dev/null || echo projectTeam)}"

  if [ -z "$agent" ]; then
    error.log "Usage: scrumMaster pane.capture <agent_name>"
    create.result 1 "missing agent name"
    return $(result)
  fi

  local target
  target=$(private.scrumMaster.resolve.pane "$agent" "$session")
  if [ $? -ne 0 ]; then
    create.result 1 "cannot resolve $agent"
    return $(result)
  fi

  local content
  content=$("$OOSH_DIR/otmux" pane.capture "$target" "${SCRUMMASTER_CAPTURE_LINES:-30}")

  if [ -z "$content" ]; then
    warn.log "Empty pane content for $agent ($target)"
    create.result 1 "empty pane"
    return $(result)
  fi

  private.scrumMaster.parse.tokens "$content"
  private.scrumMaster.parse.timing "$content"
  private.scrumMaster.parse.state "$content"

  local saved_file
  saved_file=$(private.scrumMaster.metrics.save "$agent" "$target")

  echo "$agent ($target):"
  echo "  tokens: up=${METRIC_TOKENS_UP:-0} down=${METRIC_TOKENS_DOWN:-0}"
  echo "  timing: wall=${METRIC_WALL_TIME:-n/a} think=${METRIC_THINK_TIME_S:-0}s"
  echo "  tools:  ${METRIC_TOOL_USES:-0}"
  echo "  state:  ${METRIC_STATE:-unknown} (${METRIC_ACTIVITY:-none})"

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

scrumMaster.pane.capture.completion.agent_name() # # agent names from hiveMind registry
{
  local reg="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
  [ -f "$reg" ] && cut -d'|' -f2 "$reg" 2>/dev/null
}

scrumMaster.pane.capture.completion.session() # # tmux session names
{
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

scrumMaster.team.capture() # <?session> # capture metrics for all agents in a session
{
  local session="${1:-$(cat "$HOME/config/hivemind.active.team" 2>/dev/null || echo projectTeam)}"
  local registry="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"

  if [ ! -f "$registry" ]; then
    error.log "No hiveMind registry found"
    create.result 1 "no registry"
    return $(result)
  fi

  echo "═══════════════════════════════════════════════"
  echo " Team Metrics — session: $session"
  echo "═══════════════════════════════════════════════"

  local count=0
  while IFS='|' read -r target role; do
    [ -z "$role" ] && continue
    echo "───────────────────────────────────────────────"
    scrumMaster.pane.capture "$role" "$session"
    count=$((count + 1))
  done < <(grep "^${session}:" "$registry" 2>/dev/null)

  echo "═══════════════════════════════════════════════"
  echo " Measured $count agents"
  echo "═══════════════════════════════════════════════"

  create.result 0 "$count agents measured"
  return $(result)
}

scrumMaster.team.capture.completion.session() # # tmux session names
{
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

private.scrumMaster.sweep.isStable() # <?session> # 0 if safe to reconcile (no in-flight mutation), 1 otherwise
{
  # SC-D.2 stability gate. Conservative: any RECENT lifecycle/mutation process
  # → skip reconcile this cycle. Reconcile is a safety net, not a hot path —
  # one missed cycle is harmless; fighting a mid-flight mutation is destructive.
  #
  # CRITICAL: filter by RECENCY (etime < 60s). Long-running parent shells like
  # `bash claudeCode fork ...` persist as agent wrappers — every running Claude
  # agent looks like a "fork in progress" forever. Recency filter restricts to
  # actual in-flight ops which finish within seconds.
  #
  # Three gate patterns (combined into one awk pass for performance):
  #   1. claudeCode short-lived mutations: teams.save | teams.restore | session.probe
  #      (NOT fork/join/new — those are agent wrappers that persist)
  #   2. hiveMind agent lifecycle: bootstrap | respawn | restart | spawn | rename
  #   3. tronMonitor lifecycle: setup | reset | add | sync | remove | prune
  #   4. hiveMind consistency.* — anti-recursion (don't reconcile during reconcile)
  local session="$1"  # reserved for future per-team gating; gates currently global
  : "${session:=}"

  local matched
  matched=$(ps -eo etime,args 2>/dev/null | awk '
    function et2s(e,   a) {
      if (split(e, a, "-") == 2) return 86400 * a[1] + 3600*substr(a[2],1,2) + 60*substr(a[2],4,2) + substr(a[2],7,2)
      if (split(e, a, ":") == 3) return 3600*a[1] + 60*a[2] + a[3]
      if (split(e, a, ":") == 2) return 60*a[1] + a[2]
      return e+0
    }
    NR == 1 { next }                                            # skip header
    {
      sec = et2s($1)
      if (sec >= 60) next                                       # too old — likely a wrapper, not in-flight
      line = $0
      if (line ~ /claudeCode (teams\.(save|restore)|session\.probe)/) { print "claudeCode"; exit }
      if (line ~ /hiveMind agent\.(bootstrap|respawn|restart|spawn|rename)/) { print "agent-lifecycle"; exit }
      if (line ~ /tronMonitor (setup|reset|add|sync|remove|prune)/) { print "tronMonitor"; exit }
      if (line ~ /hiveMind consistency\.(fix|reconcile|audit)/) { print "consistency"; exit }
    }
  ')
  if [ -n "$matched" ]; then
    debug.log "reconcile gate: recent $matched mutation in flight — skip"
    return 1
  fi
  return 0
}

scrumMaster.cycle() # <?session> <?interval:0> # one measure+sweep+unblock+reconcile cycle with optional sleep
{
  local session="${1:-$(tmux display-message -p '#{session_name}' 2>/dev/null)}"
  local interval="${2:-0}"

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

  scrumMaster.team.capture "$session"
  scrumMaster.subscription 2>/dev/null
  scrumMaster.dashboard "$session"
  "$OOSH_DIR/hiveMind" pane.sweep "$session"
  "$OOSH_DIR/hiveMind" agent.unblock all "$session"

  # SC-D.2 — close the safety-net loop: SM sweeps → detects drift → reconcile fixes.
  # Reconcile in apply mode after sweep+unblock have settled, gated by stability
  # check so we never mutate against in-flight lifecycle ops. Output is a single
  # line per the consistency.reconcile contract.
  if private.scrumMaster.sweep.isStable "$session"; then
    "$OOSH_DIR/hiveMind" consistency.reconcile "$session" apply
  else
    info.log "scrumMaster.cycle: reconcile skipped (mutation in flight)"
  fi

  # D5 — stale read-only client sweep. Catches zombie tronMon attaches (idle
  # >=30min, any size) that would otherwise crush window-size=largest on the
  # next render. Broad gate at this layer; tronMonitor.sync uses tighter gate.
  "$OOSH_DIR/otmux" client.cleanup.stale 30 0 read-only >/dev/null 2>&1
}
scrumMaster.cycle.completion() { :; }

scrumMaster.dashboard() # <?session> # generate team health dashboard to session/dashboard.md
{
  local session="${1:-$(tmux display-message -p '#{session_name}' 2>/dev/null)}"
  local registry="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"

  # Workspace root via git (reliable, avoids symlink issues)
  local workspace
  workspace="$(git rev-parse --show-toplevel 2>/dev/null)"
  [ -z "$workspace" ] && { error.log "Cannot determine workspace root"; return 1; }
  local dashboard_file="${workspace}/session/dashboard.md"
  mkdir -p "${workspace}/session" 2>/dev/null

  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 "$workspace" branch --show-current 2>/dev/null)
  git_commit=$(git -C "$workspace" log -1 --oneline 2>/dev/null)
  if git -C "$workspace" diff --quiet 2>/dev/null && git -C "$workspace" diff --cached --quiet 2>/dev/null; then
    git_status="clean"
  else
    git_status="uncommitted changes"
  fi

  # Subscription (from cached metrics if available)
  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

  # Task summary
  local tasks_dir="${workspace}/session/tasks"
  local task_total=0 task_today=0
  if [ -d "$tasks_dir" ]; then
    task_total=$(ls -1 "$tasks_dir"/*.task.md 2>/dev/null | wc -l | tr -d ' ')
    local today
    today=$(date '+%Y%m%d')
    task_today=$(ls -1 "$tasks_dir"/${today}*.task.md 2>/dev/null | wc -l | tr -d ' ')
  fi

  # Write 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 "## Tasks"
    echo ""
    echo "| Metric | Value |"
    echo "|--------|-------|"
    echo "| Total task files | $task_total |"
    echo "| Today's tasks | $task_today |"
    echo ""
    echo "## Team Status"
    echo ""
    echo "| Agent | Pane | Context | Velocity | State |"
    echo "|-------|------|---------|----------|-------|"
  } > "$dashboard_file"

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

      local addr="${target#*:}"
      local context_pct velocity state

      # Per-pane context %
      context_pct=$("$OOSH_DIR/claudeCode" context.read "$target" 2>/dev/null)
      [ -z "$context_pct" ] && context_pct="-"
      [ "$context_pct" != "-" ] && [ "$context_pct" != "no-claude" ] && [ "$context_pct" != "unknown" ] && context_pct="${context_pct}%"

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

      # Activity state via pane capture + parse
      local pane_content
      pane_content=$("$OOSH_DIR/otmux" pane.capture "$target" 15 2>/dev/null)
      private.scrumMaster.parse.state "$pane_content"
      state="${METRIC_STATE:-unknown}"

      echo "| $role | $addr | $context_pct | $velocity_short | $state |" >> "$dashboard_file"
    done < "$registry"
  fi

  # Recent commits
  {
    echo ""
    echo "## Recent Commits"
    echo ""
    echo "\`\`\`"
    git -C "$workspace" 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 \`scrumMaster dashboard\`*"
  } >> "$dashboard_file"

  console.log "Dashboard updated: $dashboard_file"
  create.result 0 "$dashboard_file"
  return $(result)
}
scrumMaster.dashboard.completion() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

scrumMaster.subscription.json() # # get active subscription block data as JSON via ccusage
{
  local json
  json=$(npx ccusage blocks --active --json 2>/dev/null)
  if [ -z "$json" ] || [ "$json" = "{}" ]; then
    error.log "ccusage returned no data"
    return 1
  fi
  echo "$json"
}
scrumMaster.subscription.json.completion() { :; }

# DEPRECATED — BROKEN (requires rate-limit-cache.json which doesn't exist on Linux)
# Use: scrumMaster subscription.api (OAuth API, works everywhere)
private.scrumMaster.subscription() # # show subscription status with alert thresholds
{
  # --- Primary source: rate-limit-cache.json (written by Claude TUI, reflects real API headers) ---
  local cache_file="$HOME/.claude/rate-limit-cache.json"
  local have_cache=false
  local cache_pct_5h=0 cache_pct_7d=0 cache_reset_5h=0 cache_reset_7d=0 cache_ts=0

  if [ -f "$cache_file" ]; then
    cache_pct_5h=$(jq -r '.session5h // 0' "$cache_file" 2>/dev/null)
    cache_pct_7d=$(jq -r '.weekly7d // 0' "$cache_file" 2>/dev/null)
    cache_reset_5h=$(jq -r '.reset5h // 0' "$cache_file" 2>/dev/null)
    cache_reset_7d=$(jq -r '.reset7d // 0' "$cache_file" 2>/dev/null)
    cache_ts=$(jq -r '.timestamp // 0' "$cache_file" 2>/dev/null)
    [ "$cache_reset_5h" != "0" ] && [ "$cache_reset_5h" != "null" ] && have_cache=true
  fi

  # Check cache freshness (stale if >10 min old)
  local cache_age_s=9999
  if $have_cache; then
    local now_ms=$(($(date +%s) * 1000))
    cache_age_s=$(( (now_ms - cache_ts) / 1000 ))
  fi

  # --- Secondary source: ccusage (for token counts, costs, models) ---
  local json total_tokens=0 cost_usd=0 burn_rate=0 models=""
  json=$(scrumMaster.subscription.json 2>/dev/null)
  if [ -n "$json" ] && [ "$json" != "{}" ]; then
    total_tokens=$(echo "$json" | jq -r '.blocks[0].totalTokens // 0' 2>/dev/null)
    cost_usd=$(echo "$json" | jq -r '.blocks[0].costUSD // 0' 2>/dev/null)
    burn_rate=$(echo "$json" | jq -r '.blocks[0].burnRate.tokensPerMinute // 0' 2>/dev/null)
    models=$(echo "$json" | jq -r '.blocks[0].models | join(", ")' 2>/dev/null)
  fi

  if ! $have_cache && [ -z "$json" ]; then
    error.log "No data sources available (neither rate-limit-cache.json nor ccusage)"
    return 1
  fi

  # --- Compute times from rate-limit-cache (ground truth) ---
  local now_epoch reset_epoch remaining_min pct_used block_start_epoch
  local reset_local reset_utc start_local start_utc
  local is_active="true"
  now_epoch=$(date +%s)

  if $have_cache; then
    reset_epoch="$cache_reset_5h"
    remaining_min=$(( (reset_epoch - now_epoch) / 60 ))
    [ "$remaining_min" -lt 0 ] && remaining_min=0 && is_active="false"
    pct_used=$(echo "$cache_pct_5h" | awk '{printf "%d", $1 * 100}')
    block_start_epoch=$(( reset_epoch - (5 * 3600) ))
    reset_local=$(date -r "$reset_epoch" "+%H:%M %Z")
    reset_utc=$(date -r "$reset_epoch" -u "+%H:%M UTC")
    start_local=$(date -r "$block_start_epoch" "+%H:%M %Z")
    start_utc=$(date -r "$block_start_epoch" -u "+%H:%M UTC")
  else
    # Fallback: derive from ccusage (less accurate)
    local cc_end cc_start cc_remaining
    cc_end=$(echo "$json" | jq -r '.blocks[0].endTime // empty' 2>/dev/null)
    cc_start=$(echo "$json" | jq -r '.blocks[0].startTime // empty' 2>/dev/null)
    cc_remaining=$(echo "$json" | jq -r '.blocks[0].projection.remainingMinutes // 0' 2>/dev/null)
    remaining_min="${cc_remaining}"
    pct_used=$(echo "$json" | jq -r '
      .blocks[0] | if .projection.totalTokens > 0
      then ((.totalTokens / .projection.totalTokens) * 100 | floor)
      else 0 end' 2>/dev/null)
    reset_utc=$(echo "$cc_end" | grep -oE 'T[0-9]{2}:[0-9]{2}' | tr -d T)
    reset_utc="${reset_utc} UTC (ccusage — may be inaccurate)"
    start_utc=$(echo "$cc_start" | grep -oE 'T[0-9]{2}:[0-9]{2}' | tr -d T)
    start_utc="${start_utc} UTC"
    reset_local="$reset_utc"
    start_local="$start_utc"
  fi

  # --- Alert level based on real percentage and remaining time ---
  local alert="OK"
  if [ "$remaining_min" -le 0 ] 2>/dev/null; then
    alert="EXHAUSTED (block ended)"
  elif [ "$remaining_min" -le 5 ] 2>/dev/null; then
    alert="CRITICAL (${remaining_min} min left, ${pct_used}% used)"
  elif [ "$remaining_min" -le 15 ] 2>/dev/null; then
    alert="WARNING (${remaining_min} min left, ${pct_used}% used)"
  elif [ "$pct_used" -ge 90 ] 2>/dev/null; then
    alert="WARNING (${pct_used}% used, ${remaining_min} min left)"
  fi

  # Weekly quota check
  local weekly_pct=0
  if $have_cache; then
    weekly_pct=$(echo "$cache_pct_7d" | awk '{printf "%d", $1 * 100}')
    if [ "$weekly_pct" -ge 90 ] 2>/dev/null; then
      alert="CRITICAL-WEEKLY (${weekly_pct}% of weekly quota)"
    fi
  fi

  # Status label
  local status_label="INACTIVE"
  [ "$is_active" = "true" ] && status_label="ACTIVE"
  local source_label="rate-limit-cache"
  $have_cache || source_label="ccusage (fallback)"
  local stale_warning=""
  [ "$cache_age_s" -gt 600 ] && stale_warning=" (stale: ${cache_age_s}s ago)"

  echo "Subscription Status:"
  echo "  Block: ${start_local} — ${reset_local} ($status_label)"
  echo "  Reset: ${reset_utc} / ${reset_local}"
  echo "  Used: ${pct_used}% of 5h block / ${remaining_min} min remaining"
  $have_cache && echo "  Weekly: ${weekly_pct}% of 7d quota (resets $(date -r "$cache_reset_7d" "+%a %H:%M %Z"))"
  echo "  Tokens: ${total_tokens} / burn ${burn_rate%.*} tok/min"
  local cost_fmt
  cost_fmt=$(echo "$cost_usd" | awk '{printf "%.2f", $1}')
  echo "  Cost: \$${cost_fmt}"
  [ -n "$models" ] && echo "  Models: ${models}"
  echo "  Alert: ${alert}"
  echo "  Source: ${source_label}${stale_warning}"

  # Save to metrics for dashboard pickup
  local metrics_dir="${SCRUMMASTER_METRICS_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)/session/metrics}"
  [ ! -d "$metrics_dir" ] && mkdir -p "$metrics_dir"
  local ts_label
  ts_label=$(date -u "+%Y%m%dT%H%M%SZ")
  {
    echo "export SUBSCRIPTION_ACTIVE=\"$is_active\""
    echo "export SUBSCRIPTION_BLOCK_START=\"$start_utc\""
    echo "export SUBSCRIPTION_BLOCK_END=\"$reset_utc\""
    echo "export SUBSCRIPTION_TOKENS=\"$total_tokens\""
    echo "export SUBSCRIPTION_COST_USD=\"$cost_usd\""
    echo "export SUBSCRIPTION_BURN_RATE=\"${burn_rate%.*}\""
    echo "export SUBSCRIPTION_REMAINING_MIN=\"$remaining_min\""
    echo "export SUBSCRIPTION_PCT_USED=\"$pct_used\""
    echo "export SUBSCRIPTION_WEEKLY_PCT=\"$weekly_pct\""
    echo "export SUBSCRIPTION_ALERT=\"$alert\""
    echo "export SUBSCRIPTION_SOURCE=\"$source_label\""
    $have_cache && echo "export SUBSCRIPTION_RESET_EPOCH=\"$cache_reset_5h\""
    $have_cache && echo "export SUBSCRIPTION_RESET_LOCAL=\"$reset_local\""
  } > "$metrics_dir/subscription.${ts_label}.scenario.env"

  # Also save to config metrics for dashboard
  local config_metrics="${CONFIG_PATH:-$HOME/config}/metrics"
  mkdir -p "$config_metrics" 2>/dev/null
  cp "$metrics_dir/subscription.${ts_label}.scenario.env" "$config_metrics/subscription.${ts_label}.scenario.env"

  create.result 0 "$alert"
  return $(result)
}
scrumMaster.subscription.completion() { :; }

scrumMaster.velocity() # # show current velocity zone with actionable directive
{
  # Read rate-limit-cache.json (same source as subscription)
  local cache_file="$HOME/.claude/rate-limit-cache.json"
  if [ ! -f "$cache_file" ]; then
    error.log "No rate-limit-cache.json — run scrumMaster subscription first"
    create.result 1 "NO-DATA"
    return 1
  fi

  local pct_5h pct_7d reset_5h cache_ts
  pct_5h=$(jq -r '.session5h // 0' "$cache_file" 2>/dev/null)
  pct_7d=$(jq -r '.weekly7d // 0' "$cache_file" 2>/dev/null)
  reset_5h=$(jq -r '.reset5h // 0' "$cache_file" 2>/dev/null)
  cache_ts=$(jq -r '.timestamp // 0' "$cache_file" 2>/dev/null)

  if [ "$reset_5h" = "0" ] || [ "$reset_5h" = "null" ]; then
    error.log "rate-limit-cache.json has no reset time"
    create.result 1 "NO-DATA"
    return 1
  fi

  # Compute remaining time
  local now_epoch remaining_min pct_used weekly_pct
  now_epoch=$(date +%s)
  remaining_min=$(( (reset_5h - now_epoch) / 60 ))
  pct_used=$(echo "$pct_5h" | awk '{printf "%d", $1 * 100}')
  weekly_pct=$(echo "$pct_7d" | awk '{printf "%d", $1 * 100}')

  # Check staleness (warn if >10 min old)
  local now_ms=$((now_epoch * 1000))
  local cache_age_s=$(( (now_ms - cache_ts) / 1000 ))
  local stale=""
  [ "$cache_age_s" -gt 600 ] && stale=" (cache ${cache_age_s}s old — may be stale)"

  # Determine velocity zone
  local zone directive
  if [ "$remaining_min" -le 0 ]; then
    zone="EXHAUSTED"
    directive="Standing down. Next block in ~5-7 min. Run scrumMaster subscription to confirm."
  elif [ "$remaining_min" -le 5 ]; then
    zone="COMPACT"
    directive="Compact in hierarchy order: SM -> orchestrator -> workers."
  elif [ "$remaining_min" -le 15 ]; then
    zone="SAVE"
    directive="Trigger context saves. Prepare for compacts."
  elif [ "$remaining_min" -le 30 ]; then
    zone="WIND-DOWN"
    directive="Agents commit current work NOW. No new assignments."
  elif [ "$remaining_min" -le 60 ]; then
    zone="STEADY"
    directive="No new large tasks. Current work continues."
  else
    zone="FULL"
    directive="Assign freely. No restrictions."
  fi

  # Weekly override
  if [ "$weekly_pct" -ge 90 ] 2>/dev/null; then
    if [ "$zone" = "FULL" ] || [ "$zone" = "STEADY" ]; then
      zone="WIND-DOWN"
      directive="Weekly quota ${weekly_pct}% — conserve. Agents commit current work."
    fi
  fi

  local reset_local
  reset_local=$(date -r "$reset_5h" "+%H:%M %Z")

  echo "Velocity: $zone (${pct_used}% used, ${remaining_min} min remaining)${stale}"
  echo "  -> $directive"
  [ "$weekly_pct" -ge 80 ] 2>/dev/null && echo "  Weekly: ${weekly_pct}% (reset $(date -r "$(jq -r '.reset7d' "$cache_file")" "+%a %H:%M %Z"))"
  [ "$zone" != "EXHAUSTED" ] && echo "  Block resets: $reset_local"

  create.result 0 "$zone"
  return 0
}
scrumMaster.velocity.completion() { :; }

scrumMaster.context.read() # <agent_name> <?session> # show context health + token consumption for an agent
{
  local agent="$1"
  local session="${2:-$(cat "$HOME/config/hivemind.active.team" 2>/dev/null || echo projectTeam)}"

  if [ -z "$agent" ]; then
    error.log "Usage: scrumMaster context.read <agent_name>"
    create.result 1 "missing agent name"
    return $(result)
  fi

  local target
  target=$(private.scrumMaster.resolve.pane "$agent" "$session")
  if [ $? -ne 0 ]; then
    create.result 1 "cannot resolve $agent"
    return $(result)
  fi

  local content
  content=$("$OOSH_DIR/otmux" pane.capture "$target" "${SCRUMMASTER_CAPTURE_LINES:-50}")

  # Token consumption
  private.scrumMaster.parse.tokens "$content"
  local total=$(( ${METRIC_TOKENS_UP:-0} + ${METRIC_TOKENS_DOWN:-0} ))

  # Context health (peer observation via TUI status bar)
  local context_pct
  context_pct=$("$OOSH_DIR/claudeCode" context.read "$target" 2>/dev/null)
  [ -z "$context_pct" ] && context_pct="above-threshold"

  echo "$agent context:"
  if [ "$context_pct" = "above-threshold" ]; then
    echo "  context left:  healthy (above threshold)"
  else
    echo "  context left:  ${context_pct}%"
  fi
  echo "  input (down):  ${METRIC_TOKENS_DOWN:-0} tokens (${METRIC_TOKENS_DOWN_RAW:-0})"
  echo "  output (up):   ${METRIC_TOKENS_UP:-0} tokens (${METRIC_TOKENS_UP_RAW:-0})"
  echo "  total:         $total tokens"

  # Burn rate: compare with previous reading
  local metrics_dir="${SCRUMMASTER_METRICS_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)/session/metrics}"
  local prev_file
  prev_file=$(ls -1t "$metrics_dir"/context.${agent}.*.env 2>/dev/null | head -1)
  if [ -n "$prev_file" ] && [ "$context_pct" != "above-threshold" ]; then
    local prev_pct
    prev_pct=$(grep '^METRIC_CONTEXT_PCT=' "$prev_file" 2>/dev/null | cut -d'"' -f2)
    if [ -n "$prev_pct" ] && [ "$prev_pct" != "above-threshold" ]; then
      local delta=$(( prev_pct - context_pct ))
      if [ "$delta" -gt 0 ] 2>/dev/null; then
        echo "  burn rate:     ${delta}% since last reading"
      fi
    fi
  fi

  # Persist reading
  [ ! -d "$metrics_dir" ] && mkdir -p "$metrics_dir"
  local timestamp
  timestamp=$(date -u "+%Y%m%dT%H%M%SZ")
  {
    echo "METRIC_TYPE=\"context\""
    echo "METRIC_TARGET=\"${target}\""
    echo "METRIC_TIMESTAMP=\"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\""
    echo "METRIC_CONTEXT_PCT=\"${context_pct}\""
    echo "METRIC_TOKENS_UP=\"${METRIC_TOKENS_UP:-0}\""
    echo "METRIC_TOKENS_DOWN=\"${METRIC_TOKENS_DOWN:-0}\""
    echo "METRIC_TOKENS_TOTAL=\"${total}\""
  } > "$metrics_dir/context.${agent}.${timestamp}.scenario.env"

  # Alert if low
  if [ "$context_pct" != "above-threshold" ] && [ "$context_pct" -le 20 ] 2>/dev/null; then
    warn.log "$agent context CRITICAL: ${context_pct}%"
  fi

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

scrumMaster.context.read.completion.agent_name() # # agent names from hiveMind registry
{
  scrumMaster.pane.capture.completion.agent_name
}

scrumMaster.context.read.completion.session() # # tmux session names
{
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

scrumMaster.speed.check() # <agent_name> <?session> # show token rate for an agent
{
  local agent="$1"
  local session="${2:-$(cat "$HOME/config/hivemind.active.team" 2>/dev/null || echo projectTeam)}"

  if [ -z "$agent" ]; then
    error.log "Usage: scrumMaster speed.check <agent_name>"
    create.result 1 "missing agent name"
    return $(result)
  fi

  local target
  target=$(private.scrumMaster.resolve.pane "$agent" "$session")
  if [ $? -ne 0 ]; then
    create.result 1 "cannot resolve $agent"
    return $(result)
  fi

  local content
  content=$("$OOSH_DIR/otmux" pane.capture "$target" "${SCRUMMASTER_CAPTURE_LINES:-30}")

  private.scrumMaster.parse.tokens "$content"
  private.scrumMaster.parse.timing "$content"

  local rate="n/a"
  if [ "${METRIC_WALL_TIME_S:-0}" -gt 0 ] && [ "${METRIC_TOKENS_UP:-0}" -gt 0 ]; then
    rate=$(echo "${METRIC_TOKENS_UP} ${METRIC_WALL_TIME_S}" | awk '{printf "%.1f", $1 / $2}')
  fi

  echo "$agent token rate:"
  echo "  output tokens: ${METRIC_TOKENS_UP:-0}"
  echo "  wall time:     ${METRIC_WALL_TIME:-n/a} (${METRIC_WALL_TIME_S:-0}s)"
  echo "  rate:          ${rate} tokens/sec"

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

scrumMaster.speed.check.completion.agent_name() # # agent names from hiveMind registry
{
  scrumMaster.pane.capture.completion.agent_name
}

scrumMaster.speed.check.completion.session() # # tmux session names
{
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

scrumMaster.subscription.scan() # <?session> # show overall subscription usage across all agents
{
  local session="${1:-$(cat "$HOME/config/hivemind.active.team" 2>/dev/null || echo projectTeam)}"
  local registry="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"

  if [ ! -f "$registry" ]; then
    error.log "No hiveMind registry found"
    create.result 1 "no registry"
    return $(result)
  fi

  local total_up=0
  local total_down=0
  local agent_count=0

  while IFS='|' read -r target role; do
    [ -z "$role" ] && continue

    local content
    content=$("$OOSH_DIR/otmux" pane.capture "$target" "${SCRUMMASTER_CAPTURE_LINES:-30}" 2>/dev/null)
    [ -z "$content" ] && continue

    private.scrumMaster.parse.tokens "$content"

    total_up=$((total_up + ${METRIC_TOKENS_UP:-0}))
    total_down=$((total_down + ${METRIC_TOKENS_DOWN:-0}))
    agent_count=$((agent_count + 1))
  done < <(grep "^${session}:" "$registry" 2>/dev/null)

  local total=$((total_up + total_down))

  echo "Subscription Usage — session: $session"
  echo "═══════════════════════════════════════════════"
  echo "  agents measured: $agent_count"
  echo "  total input:     $total_down tokens"
  echo "  total output:    $total_up tokens"
  echo "  combined:        $total tokens"
  echo "═══════════════════════════════════════════════"

  # Save subscription-level metric
  local metrics_dir="${SCRUMMASTER_METRICS_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)/session/metrics}"
  [ ! -d "$metrics_dir" ] && mkdir -p "$metrics_dir"
  local timestamp
  timestamp=$(date -u "+%Y%m%dT%H%M%SZ")
  {
    echo "METRIC_TYPE=\"subscription\""
    echo "METRIC_SESSION=\"$session\""
    echo "METRIC_TIMESTAMP=\"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\""
    echo "METRIC_AGENT_COUNT=\"$agent_count\""
    echo "METRIC_TOKENS_UP_TOTAL=\"$total_up\""
    echo "METRIC_TOKENS_DOWN_TOTAL=\"$total_down\""
    echo "METRIC_TOKENS_COMBINED=\"$total\""
  } > "$metrics_dir/subscription.${timestamp}.scenario.env"

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

scrumMaster.subscription.scan.completion.session() # # tmux session names
{
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

# ============================================================================
# Measurement — OAuth API subscription metrics (CMM4, Task 29)
# ============================================================================
# The methods above (pane.capture, team.capture, etc.) scrape pane output for
# per-agent activity metrics (tokens visible in TUI, timing, activity state).
# The method below calls the Anthropic OAuth usage API for real subscription
# utilization percentages — the actual rate-limit data.

private.scrumMaster.subscription.auth() # # extract OAuth access token (credentials file or macOS Keychain)
{
  local raw_creds=""

  # Source 1: Direct credentials file (works on Linux and macOS)
  local creds_file="$HOME/.claude/.credentials.json"
  if [ -f "$creds_file" ]; then
    raw_creds=$(cat "$creds_file" 2>/dev/null)
  fi

  # Source 2: macOS Keychain fallback
  if [ -z "$raw_creds" ] && command -v security >/dev/null 2>&1; then
    raw_creds=$(security find-generic-password -s 'Claude Code-credentials' -w 2>/dev/null)
  fi

  if [ -z "$raw_creds" ]; then
    error.log "No Claude Code credentials found (checked $creds_file and macOS Keychain)"
    return 1
  fi

  local token expires_at
  if command -v python3 >/dev/null 2>&1; then
    token=$(echo "$raw_creds" | python3 -c "import sys,json; print(json.load(sys.stdin)['claudeAiOauth']['accessToken'])" 2>/dev/null)
    expires_at=$(echo "$raw_creds" | python3 -c "import sys,json; print(json.load(sys.stdin)['claudeAiOauth'].get('expiresAt',''))" 2>/dev/null)
  elif command -v jq >/dev/null 2>&1; then
    token=$(echo "$raw_creds" | jq -r '.claudeAiOauth.accessToken' 2>/dev/null)
    expires_at=$(echo "$raw_creds" | jq -r '.claudeAiOauth.expiresAt // ""' 2>/dev/null)
  else
    error.log "Neither python3 nor jq available for JSON parsing"
    return 1
  fi

  if [ -z "$token" ] || [ "$token" = "null" ]; then
    error.log "Failed to extract OAuth token from credentials"
    return 1
  fi

  # Check token expiry (expiresAt is epoch milliseconds)
  if [ -n "$expires_at" ] && command -v python3 >/dev/null 2>&1; then
    local expired
    expired=$(python3 -c "import time; print('yes' if time.time() > ${expires_at}/1000 else 'no')" 2>/dev/null)
    if [ "$expired" = "yes" ]; then
      error.log "OAuth token expired — Claude Code will refresh it on next interaction"
      return 1
    fi
  fi

  echo "$token"
  return 0
}

private.scrumMaster.subscription.parse() # <json_response> # parse OAuth usage API response, sets SUBSCRIPTION_* variables
{
  local json="$1"
  [ -z "$json" ] && return 1

  SUBSCRIPTION_FIVE_HOUR_UTIL=""
  SUBSCRIPTION_FIVE_HOUR_RESETS=""
  SUBSCRIPTION_SEVEN_DAY_UTIL=""
  SUBSCRIPTION_SEVEN_DAY_RESETS=""
  SUBSCRIPTION_OPUS_UTIL=""
  SUBSCRIPTION_OPUS_RESETS=""
  SUBSCRIPTION_SONNET_UTIL=""
  SUBSCRIPTION_SONNET_RESETS=""
  SUBSCRIPTION_EXTRA_ENABLED="false"

  local parsed
  if command -v python3 >/dev/null 2>&1; then
    parsed=$(echo "$json" | python3 -c "
import sys, json
try:
    d = json.load(sys.stdin)
    fh = d.get('five_hour') or {}
    sd = d.get('seven_day') or {}
    op = d.get('seven_day_opus') or {}
    sn = d.get('seven_day_sonnet') or {}
    ex = d.get('extra_usage') or {}
    print(fh.get('utilization', '') if fh.get('utilization') is not None else '')
    print(fh.get('resets_at', ''))
    print(sd.get('utilization', '') if sd.get('utilization') is not None else '')
    print(sd.get('resets_at', ''))
    print(op.get('utilization', '') if isinstance(op, dict) and op.get('utilization') is not None else '')
    print(op.get('resets_at', '') if isinstance(op, dict) else '')
    print(sn.get('utilization', '') if isinstance(sn, dict) and sn.get('utilization') is not None else '')
    print(sn.get('resets_at', '') if isinstance(sn, dict) else '')
    print(str(ex.get('is_enabled', False)).lower())
except Exception:
    for _ in range(9): print('')
" 2>/dev/null)
  elif command -v jq >/dev/null 2>&1; then
    parsed=$(echo "$json" | jq -r '
      (.five_hour.utilization // ""),
      (.five_hour.resets_at // ""),
      (.seven_day.utilization // ""),
      (.seven_day.resets_at // ""),
      (if .seven_day_opus then (.seven_day_opus.utilization // "") else "" end),
      (if .seven_day_opus then (.seven_day_opus.resets_at // "") else "" end),
      (if .seven_day_sonnet then (.seven_day_sonnet.utilization // "") else "" end),
      (if .seven_day_sonnet then (.seven_day_sonnet.resets_at // "") else "" end),
      (.extra_usage.is_enabled // false)
    ' 2>/dev/null)
  else
    error.log "Neither python3 nor jq available for JSON parsing"
    return 1
  fi

  [ -z "$parsed" ] && return 1

  local i=0
  while IFS= read -r line; do
    case $i in
      0) SUBSCRIPTION_FIVE_HOUR_UTIL="$line" ;;
      1) SUBSCRIPTION_FIVE_HOUR_RESETS="$line" ;;
      2) SUBSCRIPTION_SEVEN_DAY_UTIL="$line" ;;
      3) SUBSCRIPTION_SEVEN_DAY_RESETS="$line" ;;
      4) SUBSCRIPTION_OPUS_UTIL="$line" ;;
      5) SUBSCRIPTION_OPUS_RESETS="$line" ;;
      6) SUBSCRIPTION_SONNET_UTIL="$line" ;;
      7) SUBSCRIPTION_SONNET_RESETS="$line" ;;
      8) SUBSCRIPTION_EXTRA_ENABLED="$line" ;;
    esac
    i=$((i + 1))
  done <<< "$parsed"

  return 0
}

# DEPRECATED — replaced by scrumMaster.subscription.* family
private.scrumMaster.subscription.api.legacy() # # old monolithic subscription API method
{
  # Step 1: Authenticate via macOS Keychain
  local token
  token=$(private.scrumMaster.subscription.auth)
  if [ $? -ne 0 ]; then
    create.result 1 "auth failed"
    return $(result)
  fi

  # Step 2: Call the OAuth usage API
  local response
  response=$(curl -s -f \
    -H "Authorization: Bearer $token" \
    -H "anthropic-beta: oauth-2025-04-20" \
    -H "Accept: application/json" \
    "https://api.anthropic.com/api/oauth/usage" 2>/dev/null)

  if [ $? -ne 0 ] || [ -z "$response" ]; then
    error.log "OAuth usage API call failed"
    create.result 1 "api call failed"
    return $(result)
  fi

  # Step 3: Parse JSON response
  private.scrumMaster.subscription.parse "$response"
  if [ $? -ne 0 ]; then
    error.log "Failed to parse API response"
    create.result 1 "parse failed"
    return $(result)
  fi

  # Step 4: Display results
  echo "Subscription Utilization (OAuth API)"
  echo "═══════════════════════════════════════════════"
  echo "  5-hour window:   ${SUBSCRIPTION_FIVE_HOUR_UTIL:-n/a}%"
  [ -n "$SUBSCRIPTION_FIVE_HOUR_RESETS" ] && \
    echo "    resets at:     $SUBSCRIPTION_FIVE_HOUR_RESETS"
  echo "  7-day window:    ${SUBSCRIPTION_SEVEN_DAY_UTIL:-n/a}%"
  [ -n "$SUBSCRIPTION_SEVEN_DAY_RESETS" ] && \
    echo "    resets at:     $SUBSCRIPTION_SEVEN_DAY_RESETS"
  [ -n "$SUBSCRIPTION_OPUS_UTIL" ] && \
    echo "  7-day opus:      ${SUBSCRIPTION_OPUS_UTIL}%"
  [ -n "$SUBSCRIPTION_SONNET_UTIL" ] && \
    echo "  7-day sonnet:    ${SUBSCRIPTION_SONNET_UTIL}%"
  echo "  extra usage:     ${SUBSCRIPTION_EXTRA_ENABLED}"
  echo "═══════════════════════════════════════════════"

  # Step 5: Alert on high utilization
  local five_hour_int
  five_hour_int=$(echo "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}" | awk '{printf "%d", $1}')

  if [ "$five_hour_int" -ge 90 ] 2>/dev/null; then
    error.log "CRITICAL: 5-hour utilization at ${SUBSCRIPTION_FIVE_HOUR_UTIL}% — throttle all agents!"
  elif [ "$five_hour_int" -ge 80 ] 2>/dev/null; then
    console.log "WARNING: 5-hour utilization at ${SUBSCRIPTION_FIVE_HOUR_UTIL}% — reduce activity"
  fi

  local seven_day_int
  seven_day_int=$(echo "${SUBSCRIPTION_SEVEN_DAY_UTIL:-0}" | awk '{printf "%d", $1}')

  if [ "$seven_day_int" -ge 90 ] 2>/dev/null; then
    error.log "CRITICAL: 7-day utilization at ${SUBSCRIPTION_SEVEN_DAY_UTIL}% — stop non-essential work!"
  elif [ "$seven_day_int" -ge 80 ] 2>/dev/null; then
    console.log "WARNING: 7-day utilization at ${SUBSCRIPTION_SEVEN_DAY_UTIL}% — reduce activity"
  fi

  # Step 6: Persist to ~/config/metrics/
  local metrics_dir="${CONFIG_PATH:-$HOME/config}/metrics"
  [ ! -d "$metrics_dir" ] && mkdir -p "$metrics_dir"
  local timestamp
  timestamp=$(date -u "+%Y%m%dT%H%M%SZ")
  {
    echo "METRIC_TYPE=\"subscription.api\""
    echo "METRIC_TIMESTAMP=\"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\""
    echo "SUBSCRIPTION_FIVE_HOUR_UTIL=\"${SUBSCRIPTION_FIVE_HOUR_UTIL:-}\""
    echo "SUBSCRIPTION_FIVE_HOUR_RESETS=\"${SUBSCRIPTION_FIVE_HOUR_RESETS:-}\""
    echo "SUBSCRIPTION_SEVEN_DAY_UTIL=\"${SUBSCRIPTION_SEVEN_DAY_UTIL:-}\""
    echo "SUBSCRIPTION_SEVEN_DAY_RESETS=\"${SUBSCRIPTION_SEVEN_DAY_RESETS:-}\""
    echo "SUBSCRIPTION_OPUS_UTIL=\"${SUBSCRIPTION_OPUS_UTIL:-}\""
    echo "SUBSCRIPTION_SONNET_UTIL=\"${SUBSCRIPTION_SONNET_UTIL:-}\""
    echo "SUBSCRIPTION_EXTRA_ENABLED=\"${SUBSCRIPTION_EXTRA_ENABLED:-false}\""
  } > "$metrics_dir/subscription.${timestamp}.scenario.env"
  info.log "Metrics saved: $metrics_dir/subscription.${timestamp}.scenario.env"

  create.result 0 "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}"
  return $(result)
}

scrumMaster.velocity.check() # # velocity snapshot — subscription usage + task rate + burn assessment
{
  # Get latest subscription data from stored metrics
  local metrics_dir="${CONFIG_PATH:-$HOME/config}/metrics"
  local latest_sub
  latest_sub=$(ls -t "$metrics_dir"/subscription.*.scenario.env 2>/dev/null | head -1)

  local five_hour_pct=0 seven_day_pct=0 data_source="commits_only"
  if [ -n "$latest_sub" ]; then
    source "$latest_sub"
    five_hour_pct=$(echo "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}" | awk '{printf "%d", $1}')
    seven_day_pct=$(echo "${SUBSCRIPTION_SEVEN_DAY_UTIL:-0}" | awk '{printf "%d", $1}')
    [ "$five_hour_pct" -gt 0 ] || [ "$seven_day_pct" -gt 0 ] && data_source="api+commits"
  fi

  # Count today's completed tasks (commits on dev.claude today)
  local tasks_today=0
  if [ -d "$OOSH_DIR/.git" ]; then
    tasks_today=$(git -C "$OOSH_DIR" log --oneline --since="midnight" 2>/dev/null | wc -l | tr -d ' ')
  fi

  # Calculate burn rate via velocity.target
  local burn_rate
  burn_rate=$(scrumMaster.velocity.target)

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

  # Token efficiency estimate based on tasks per 5-hour window
  local tokens_per_task="low"
  if [ "$tasks_today" -gt 0 ] && [ "$five_hour_pct" -gt 0 ]; then
    local ratio=$(( five_hour_pct / tasks_today ))
    if [ "$ratio" -gt 15 ]; then
      tokens_per_task="high"
    elif [ "$ratio" -gt 8 ]; then
      tokens_per_task="medium"
    fi
  fi

  # When no API data, burn rate is unknown — don't mislead
  [ "$data_source" = "commits_only" ] && burn_rate="no_api_data"

  # Output env-style
  echo "VELOCITY_FIVE_HOUR_PCT=$five_hour_pct"
  echo "VELOCITY_SEVEN_DAY_PCT=$seven_day_pct"
  echo "VELOCITY_TASKS_TODAY=$tasks_today"
  echo "VELOCITY_TOKENS_PER_TASK_EST=$tokens_per_task"
  echo "VELOCITY_BURN_RATE=$burn_rate"
  echo "VELOCITY_DATA_SOURCE=$data_source"
  echo "VELOCITY_TIMESTAMP=$timestamp"

  # Persist
  local vel_dir="$(git rev-parse --show-toplevel 2>/dev/null)/session/metrics"
  [ ! -d "$vel_dir" ] && mkdir -p "$vel_dir"
  local ts_file
  ts_file=$(date -u "+%Y%m%dT%H%M%SZ")
  {
    echo "VELOCITY_FIVE_HOUR_PCT=$five_hour_pct"
    echo "VELOCITY_SEVEN_DAY_PCT=$seven_day_pct"
    echo "VELOCITY_TASKS_TODAY=$tasks_today"
    echo "VELOCITY_TOKENS_PER_TASK_EST=$tokens_per_task"
    echo "VELOCITY_BURN_RATE=$burn_rate"
    echo "VELOCITY_DATA_SOURCE=$data_source"
    echo "VELOCITY_TIMESTAMP=$timestamp"
  } > "$vel_dir/velocity.${ts_file}.scenario.env"
  info.log "Velocity saved: $vel_dir/velocity.${ts_file}.scenario.env"
}

scrumMaster.velocity.check.completion() { :; }

# DEPRECATED — use scrumMaster.velocity.target
private.scrumMaster.velocity.target() # # calculate burn rate — too_fast, on_target, or too_slow
{
  # Use seven_day as primary metric (more stable than five_hour which resets)
  local metrics_dir="${CONFIG_PATH:-$HOME/config}/metrics"
  local latest_sub
  latest_sub=$(ls -t "$metrics_dir"/subscription.*.scenario.env 2>/dev/null | head -1)

  local seven_day_pct=0
  if [ -n "$latest_sub" ]; then
    source "$latest_sub"
    seven_day_pct=$(echo "${SUBSCRIPTION_SEVEN_DAY_UTIL:-0}" | awk '{printf "%d", $1}')
  fi

  # Ideal: 7-day limit spread over 7 days = ~14.3% per day
  # Actual: seven_day_pct / days_elapsed_this_week
  local day_of_week
  day_of_week=$(date +%u)  # 1=Mon, 7=Sun
  [ "$day_of_week" -eq 0 ] && day_of_week=7

  # Expected usage at this point in the week
  local expected_pct=$(( (day_of_week * 100) / 7 ))

  # Velocity ratio: actual / expected (scaled by 100 for integer math)
  if [ "$expected_pct" -gt 0 ]; then
    local ratio_x100=$(( (seven_day_pct * 100) / expected_pct ))
    if [ "$ratio_x100" -gt 120 ]; then
      echo "too_fast"
    elif [ "$ratio_x100" -lt 80 ]; then
      echo "too_slow"
    else
      echo "on_target"
    fi
  else
    echo "on_target"
  fi
}
# completion stub kept for backward compat
scrumMaster.velocity.target.completion() { :; }

# DEPRECATED — use scrumMaster.evaluate
private.scrumMaster.health.evaluate() # # single health check — subscription + velocity + verdict
{
  # Step 1: Read cached subscription data (avoid API call per evaluation)
  local metrics_dir="${CONFIG_PATH:-$HOME/config}/metrics"
  local latest_sub
  latest_sub=$(ls -t "$metrics_dir"/subscription.*.scenario.env 2>/dev/null | head -1)

  local five_hour_pct=0 seven_day_pct=0
  if [ -n "$latest_sub" ]; then
    source "$latest_sub"
    five_hour_pct=$(echo "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}" | awk '{printf "%d", $1}')
    seven_day_pct=$(echo "${SUBSCRIPTION_SEVEN_DAY_UTIL:-0}" | awk '{printf "%d", $1}')
  fi

  # Step 2: Get burn rate
  local burn_rate
  burn_rate=$(scrumMaster.velocity.target)

  # Step 3: Evaluate thresholds — most severe first
  local eval_result="on_target"
  local alert="none"

  if [ "$five_hour_pct" -ge 90 ] || [ "$seven_day_pct" -ge 90 ]; then
    eval_result="stand_down_90"
    alert="STAND_DOWN — utilization >= 90%"
  elif [ "$five_hour_pct" -ge 80 ] || [ "$seven_day_pct" -ge 80 ]; then
    eval_result="quota_80"
    alert="QUOTA_80 — utilization >= 80%, reduce activity"
  elif [ "$burn_rate" = "too_fast" ]; then
    eval_result="throttle"
    alert="THROTTLE — burn rate too high"
  elif [ "$burn_rate" = "too_slow" ]; then
    eval_result="increase"
    alert="INCREASE — burn rate too low, capacity available"
  fi

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

  # Step 4: Output env-style
  echo "EVALUATE_RESULT=$eval_result"
  echo "EVALUATE_FIVE_HOUR=$five_hour_pct"
  echo "EVALUATE_SEVEN_DAY=$seven_day_pct"
  echo "EVALUATE_BURN_RATE=$burn_rate"
  echo "EVALUATE_ALERT=$alert"
  echo "EVALUATE_TIMESTAMP=$timestamp"

  # Step 5: Append to alerts log if not on_target
  if [ "$eval_result" != "on_target" ]; then
    local alerts_log="$(git rev-parse --show-toplevel 2>/dev/null)/session/metrics/alerts.log"
    [ ! -d "$(dirname "$alerts_log")" ] && mkdir -p "$(dirname "$alerts_log")"
    echo "$timestamp $eval_result $alert" >> "$alerts_log"
  fi
}
scrumMaster.health.evaluate.completion() { :; }

# ============================================================================
# CMM4 PDCA Health Check — full cycle (Task 40.5)
# ============================================================================
# Single command for SM: refresh API → velocity → evaluate → alert orchestrator
# Run every 30 minutes as part of the feedback loop.

# DEPRECATED — use scrumMaster.health
private.scrumMaster.health.check() # # full PDCA health check — refresh, evaluate, alert
{
  # Step 1: PLAN — refresh subscription data from API
  local api_ok="yes"
  private.scrumMaster.subscription.api.call > /dev/null 2>&1 || api_ok="no"

  # Step 2: DO — snapshot velocity
  scrumMaster.velocity.check > /dev/null 2>&1

  # Step 3: CHECK — evaluate thresholds
  local eval_output
  eval_output=$(scrumMaster.health.evaluate)

  # Parse evaluation result
  local eval_result="" eval_alert=""
  eval_result=$(echo "$eval_output" | grep '^EVALUATE_RESULT=' | cut -d= -f2)
  eval_alert=$(echo "$eval_output" | grep '^EVALUATE_ALERT=' | cut -d= -f2-)

  # Step 4: ACT — send alert to orchestrator if deviation detected
  if [ "$eval_result" != "on_target" ] && [ "$eval_alert" != "none" ]; then
    "$OOSH_DIR/hiveMind" agent.send orchestrator "ALERT: $eval_alert" 2>/dev/null || \
      warn.log "Could not send alert to orchestrator — hiveMind unavailable"
    console.log "HEALTH: $eval_alert"
  else
    console.log "HEALTH: on_target — no action needed"
  fi

  # Output for caller (SM can parse or ignore)
  echo "$eval_output"
  echo "HEALTH_API_REFRESH=$api_ok"
  echo "HEALTH_ACTION=$([ "$eval_result" != "on_target" ] && echo "alerted" || echo "none")"
}
scrumMaster.health.check.completion() { :; }

# ============================================================================
# Structured KPI Logging — per-cycle metrics (Task from PO)
# ============================================================================
# Logs: (1) tokens/hour per agent, (2) max tokens per model, (3) velocity,
#       (4) time until compact prediction, (5) CMM4 velocity/wait per agent

scrumMaster.metrics.collect() # <?session> # log structured KPIs for all agents
{
  local session="${1:-$(cat "$HOME/config/hivemind.active.team" 2>/dev/null || echo projectTeam)}"
  local registry="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
  local metrics_dir="${SCRUMMASTER_METRICS_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)/session/metrics}"
  local kpi_log="$metrics_dir/kpi.log"

  [ ! -d "$metrics_dir" ] && mkdir -p "$metrics_dir"

  local timestamp
  timestamp=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
  local epoch
  epoch=$(date +%s)

  # Model max tokens (Claude limits)
  local CLAUDE_OPUS_MAX=200000
  local CLAUDE_SONNET_MAX=200000
  local CLAUDE_HAIKU_MAX=200000

  # Get subscription velocity (cached from API)
  local vel_five=0 vel_seven=0 vel_burn="unknown"
  local latest_sub="${CONFIG_PATH:-$HOME/config}/metrics"
  latest_sub=$(ls -t "$latest_sub"/subscription.*.scenario.env 2>/dev/null | head -1)
  if [ -n "$latest_sub" ]; then
    source "$latest_sub"
    vel_five=$(echo "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}" | awk '{printf "%d", $1}')
    vel_seven=$(echo "${SUBSCRIPTION_SEVEN_DAY_UTIL:-0}" | awk '{printf "%d", $1}')
  fi
  vel_burn=$(scrumMaster.velocity.target 2>/dev/null)

  # Start KPI entry
  {
    echo "───────────────────────────────────────────────"
    echo "CYCLE_TIMESTAMP=$timestamp"
    echo "SESSION=$session"
    echo ""
    echo "# Subscription Velocity"
    echo "VELOCITY_FIVE_HOUR_PCT=$vel_five"
    echo "VELOCITY_SEVEN_DAY_PCT=$vel_seven"
    echo "VELOCITY_BURN_RATE=$vel_burn"
    echo "MODEL_OPUS_MAX_TOKENS=$CLAUDE_OPUS_MAX"
    echo "MODEL_SONNET_MAX_TOKENS=$CLAUDE_SONNET_MAX"
    echo ""
    echo "# Per-Agent KPIs"
  } >> "$kpi_log"

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

      local content
      content=$("$OOSH_DIR/otmux" pane.capture "$target" "${SCRUMMASTER_CAPTURE_LINES:-30}" 2>/dev/null)
      [ -z "$content" ] && continue

      # Parse metrics
      private.scrumMaster.parse.tokens "$content"
      private.scrumMaster.parse.timing "$content"
      private.scrumMaster.parse.state "$content"

      # Tokens per hour calculation
      local tokens_total=$(( ${METRIC_TOKENS_UP:-0} + ${METRIC_TOKENS_DOWN:-0} ))
      local wall_secs=${METRIC_WALL_TIME_S:-0}
      local tokens_per_hour=0
      if [ "$wall_secs" -gt 0 ]; then
        tokens_per_hour=$(echo "$tokens_total $wall_secs" | awk '{printf "%d", ($1 / $2) * 3600}')
      fi

      # Context percentage (time until compact prediction)
      local context_pct
      context_pct=$("$OOSH_DIR/claudeCode" context.read "$target" 2>/dev/null)
      [ -z "$context_pct" ] && context_pct="above-threshold"

      # Time until compact prediction (rough estimate)
      # Assume ~1% context per 30s of active work
      local compact_eta="n/a"
      if [ "$context_pct" != "above-threshold" ] && [ "$context_pct" -gt 0 ] 2>/dev/null; then
        if [ "${METRIC_STATE:-unknown}" = "active" ] && [ "$tokens_per_hour" -gt 0 ]; then
          # Estimate: remaining context / burn rate
          local mins_remaining=$(( context_pct * 2 ))  # ~2 mins per percent at active rate
          compact_eta="${mins_remaining}m"
        elif [ "$context_pct" -le 20 ]; then
          compact_eta="CRITICAL"
        fi
      fi

      # CMM4 velocity/wait state
      local cmm4_state="idle"
      case "${METRIC_STATE:-unknown}" in
        active)     cmm4_state="working" ;;
        completed)  cmm4_state="ready" ;;
        permission) cmm4_state="blocked" ;;
        idle)       cmm4_state="waiting" ;;
      esac

      {
        echo ""
        echo "[$role]"
        echo "  target=$target"
        echo "  tokens_up=${METRIC_TOKENS_UP:-0}"
        echo "  tokens_down=${METRIC_TOKENS_DOWN:-0}"
        echo "  tokens_per_hour=$tokens_per_hour"
        echo "  wall_time=${METRIC_WALL_TIME:-n/a}"
        echo "  think_time=${METRIC_THINK_TIME_S:-0}s"
        echo "  context_pct=$context_pct"
        echo "  compact_eta=$compact_eta"
        echo "  cmm4_state=$cmm4_state"
        echo "  activity=${METRIC_ACTIVITY:-none}"
      } >> "$kpi_log"

    done < "$registry"
  fi

  {
    echo ""
    echo "═══════════════════════════════════════════════"
    echo ""
  } >> "$kpi_log"

  # Also output summary to console
  echo "KPIs logged: $kpi_log"
  echo "  velocity: ${vel_five}% 5h, ${vel_seven}% 7d, burn=$vel_burn"

  create.result 0 "$kpi_log"
  return $(result)
}
scrumMaster.metrics.collect.completion() { :; }

# ─────────────────────────────────────────────────────────────────────────────
# SUBSCRIPTION METHOD FAMILY — object.verb naming
# Answers management questions about subscription usage
# ─────────────────────────────────────────────────────────────────────────────

private.scrumMaster.subscription.cacheFile() { # # canonical path for subscription cache env
  echo "${CONFIG_PATH:-$HOME/config}/scrumMaster.subscription.env"
}

private.scrumMaster.subscription.api.call() # # fetch subscription data from OAuth API; on rate_limit/failure keeps existing cache marked stale
{
  local cacheFile
  cacheFile=$(private.scrumMaster.subscription.cacheFile)

  # Auth
  local token
  token=$(private.scrumMaster.subscription.auth)
  if [ $? -ne 0 ]; then
    # No token — if we have any cache, load and mark stale; otherwise fail.
    if [ -f "$cacheFile" ]; then
      source "$cacheFile" 2>/dev/null
      export SUBSCRIPTION_STALE=1
      return 0
    fi
    return 1
  fi

  # API call — split status from body so we can detect 429 / 5xx
  local tmpBody
  tmpBody=$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/scrumMaster.sub.$$.body")
  local httpCode
  httpCode=$(curl -s -o "$tmpBody" -w "%{http_code}" \
    -H "Authorization: Bearer $token" \
    -H "anthropic-beta: oauth-2025-04-20" \
    -H "Accept: application/json" \
    "https://api.anthropic.com/api/oauth/usage" 2>/dev/null)
  local curlRc=$?
  local response
  response=$(cat "$tmpBody" 2>/dev/null)
  rm -f "$tmpBody" 2>/dev/null

  # Transient failure path — serve cache if available, mark stale
  if [ "$curlRc" -ne 0 ] || [ -z "$response" ] || [ "$httpCode" != "200" ]; then
    local reason="network"
    [ "$httpCode" = "429" ] && reason="rate_limit"
    [ "$httpCode" -ge 500 ] 2>/dev/null && reason="server_${httpCode}"
    [ "$httpCode" -ge 400 ] 2>/dev/null && [ "$httpCode" -lt 500 ] && reason="client_${httpCode}"

    if [ -f "$cacheFile" ]; then
      source "$cacheFile" 2>/dev/null
      export SUBSCRIPTION_STALE=1
      export SUBSCRIPTION_STALE_REASON="$reason"
      warn.log "OAuth usage API failed ($reason) — using cached data (age: $(scrumMaster.subscription.cache.age)s)"
      return 0
    fi
    error.log "OAuth usage API failed ($reason) — no cache available"
    return 1
  fi

  # Parse into SUBSCRIPTION_* variables
  private.scrumMaster.subscription.parse "$response"
  if [ $? -ne 0 ]; then
    # Parse failure — same fallback as API failure
    if [ -f "$cacheFile" ]; then
      source "$cacheFile" 2>/dev/null
      export SUBSCRIPTION_STALE=1
      export SUBSCRIPTION_STALE_REASON="parse_error"
      warn.log "OAuth response parse failed — using cached data"
      return 0
    fi
    return 1
  fi

  # Fresh data — clear stale flags, write cache
  unset SUBSCRIPTION_STALE SUBSCRIPTION_STALE_REASON
  {
    echo "SUBSCRIPTION_TIMESTAMP=\"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\""
    echo "SUBSCRIPTION_TIMESTAMP_EPOCH=\"$(date +%s)\""
    echo "SUBSCRIPTION_FIVE_HOUR_UTIL=\"${SUBSCRIPTION_FIVE_HOUR_UTIL:-}\""
    echo "SUBSCRIPTION_FIVE_HOUR_RESETS=\"${SUBSCRIPTION_FIVE_HOUR_RESETS:-}\""
    echo "SUBSCRIPTION_SEVEN_DAY_UTIL=\"${SUBSCRIPTION_SEVEN_DAY_UTIL:-}\""
    echo "SUBSCRIPTION_SEVEN_DAY_RESETS=\"${SUBSCRIPTION_SEVEN_DAY_RESETS:-}\""
    echo "SUBSCRIPTION_OPUS_UTIL=\"${SUBSCRIPTION_OPUS_UTIL:-}\""
    echo "SUBSCRIPTION_SONNET_UTIL=\"${SUBSCRIPTION_SONNET_UTIL:-}\""
    echo "SUBSCRIPTION_EXTRA_ENABLED=\"${SUBSCRIPTION_EXTRA_ENABLED:-false}\""
  } > "$cacheFile"
  return 0
}

scrumMaster.subscription.cache.age() # # seconds since last fresh API read (or "no-cache" / "unknown")
{
  local cacheFile
  cacheFile=$(private.scrumMaster.subscription.cacheFile)
  [ ! -f "$cacheFile" ] && { echo "no-cache"; return 1; }

  # Prefer epoch field (added 2026-04-24); fall back to parsing ISO timestamp
  local ts_epoch
  ts_epoch=$(grep -E '^SUBSCRIPTION_TIMESTAMP_EPOCH=' "$cacheFile" 2>/dev/null | cut -d'"' -f2)
  if [ -z "$ts_epoch" ]; then
    local ts_iso
    ts_iso=$(grep -E '^SUBSCRIPTION_TIMESTAMP=' "$cacheFile" 2>/dev/null | cut -d'"' -f2)
    [ -z "$ts_iso" ] && { echo "unknown"; return 1; }
    # Convert ISO → epoch (BSD + GNU date compatible)
    ts_epoch=$(date -j -f '%Y-%m-%dT%H:%M:%SZ' "$ts_iso" '+%s' 2>/dev/null \
            || date -d "$ts_iso" '+%s' 2>/dev/null)
    [ -z "$ts_epoch" ] && { echo "unknown"; return 1; }
  fi

  local now
  now=$(date +%s)
  echo $((now - ts_epoch))
  return 0
}
scrumMaster.subscription.cache.age.completion() { :; }

private.scrumMaster.subscription.resets.in() # # calculate human-readable time until reset
{
  local resets_at="$1"
  [ -z "$resets_at" ] && echo "unknown" && return
  python3 -c "
from datetime import datetime, timezone
try:
    r = datetime.fromisoformat('$resets_at')
    if r.tzinfo is None: r = r.replace(tzinfo=timezone.utc)
    d = r - datetime.now(timezone.utc)
    s = int(d.total_seconds())
    if s <= 0: print('now'); exit()
    h, m = s // 3600, (s % 3600) // 60
    print(f'{h}h {m:02d}m' if h > 0 else f'{m}m')
except: print('unknown')
" 2>/dev/null
}

private.scrumMaster.subscription.verdict() # # return safe/caution/critical based on 5h util
{
  local util="$1"
  local int_util
  int_util=$(echo "$util" | awk '{printf "%d", $1}' 2>/dev/null)
  [ -z "$int_util" ] && echo "unknown" && return
  if [ "$int_util" -ge 90 ]; then echo "CRITICAL"
  elif [ "$int_util" -ge 75 ]; then echo "CAUTION"
  else echo "safe"
  fi
}

scrumMaster.subscription() # # one-liner subscription status — "are we OK?"
{
  private.scrumMaster.subscription.api.call > /dev/null 2>&1
  if [ $? -ne 0 ]; then
    # Try cached data
    local cacheFile="${CONFIG_PATH:-$HOME/config}/scrumMaster.subscription.env"
    if [ -f "$cacheFile" ]; then
      source "$cacheFile"
    else
      echo "subscription: unavailable (API failed, no cache)"
      return 1
    fi
  fi

  local resets_in
  resets_in=$(private.scrumMaster.subscription.resets.in "$SUBSCRIPTION_FIVE_HOUR_RESETS")
  local verdict
  verdict=$(private.scrumMaster.subscription.verdict "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}")

  local staleTag=""
  if [ "${SUBSCRIPTION_STALE:-0}" = "1" ]; then
    local age
    age=$(scrumMaster.subscription.cache.age 2>/dev/null)
    staleTag=" (cached ${age}s, ${SUBSCRIPTION_STALE_REASON:-stale})"
  fi

  echo "${SUBSCRIPTION_FIVE_HOUR_UTIL:-?}% 5h | ${SUBSCRIPTION_SEVEN_DAY_UTIL:-?}% 7d | resets in ${resets_in} | ${verdict}${staleTag}"
}
scrumMaster.subscription.completion() { :; }

scrumMaster.subscription.status() # # detailed subscription status view
{
  private.scrumMaster.subscription.api.call > /dev/null 2>&1
  if [ $? -ne 0 ]; then
    local cacheFile="${CONFIG_PATH:-$HOME/config}/scrumMaster.subscription.env"
    [ -f "$cacheFile" ] && source "$cacheFile" || { error.log "No subscription data available"; return 1; }
  fi

  local resets5h resets7d
  resets5h=$(private.scrumMaster.subscription.resets.in "$SUBSCRIPTION_FIVE_HOUR_RESETS")
  resets7d=$(private.scrumMaster.subscription.resets.in "$SUBSCRIPTION_SEVEN_DAY_RESETS")
  local verdict
  verdict=$(private.scrumMaster.subscription.verdict "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}")

  echo "Subscription Status"
  echo "═══════════════════════════════════════════════"
  echo "  5-hour usage:    ${SUBSCRIPTION_FIVE_HOUR_UTIL:-?}%"
  echo "  resets in:       ${resets5h}"
  echo "  7-day usage:     ${SUBSCRIPTION_SEVEN_DAY_UTIL:-?}%"
  echo "  resets in:       ${resets7d}"
  [ -n "$SUBSCRIPTION_OPUS_UTIL" ] && [ "$SUBSCRIPTION_OPUS_UTIL" != "" ] && \
    echo "  7-day opus:      ${SUBSCRIPTION_OPUS_UTIL}%"
  [ -n "$SUBSCRIPTION_SONNET_UTIL" ] && [ "$SUBSCRIPTION_SONNET_UTIL" != "0.0" ] && \
    echo "  7-day sonnet:    ${SUBSCRIPTION_SONNET_UTIL}%"
  echo "  extra usage:     ${SUBSCRIPTION_EXTRA_ENABLED:-false}"
  echo "  verdict:         ${verdict}"
  echo "═══════════════════════════════════════════════"

  # Alert on high utilization
  case "$verdict" in
    CRITICAL) error.log "CRITICAL: 5-hour at ${SUBSCRIPTION_FIVE_HOUR_UTIL}% — throttle all agents!" ;;
    CAUTION)  console.log "WARNING: 5-hour at ${SUBSCRIPTION_FIVE_HOUR_UTIL}% — reduce activity" ;;
  esac
}
scrumMaster.subscription.status.completion() { :; }

scrumMaster.subscription.check() # <?agents:1> # check if headroom exists for N more agents
{
  local agentCount="${1:-1}"

  private.scrumMaster.subscription.api.call > /dev/null 2>&1
  if [ $? -ne 0 ]; then
    local cacheFile="${CONFIG_PATH:-$HOME/config}/scrumMaster.subscription.env"
    [ -f "$cacheFile" ] && source "$cacheFile" || { echo "NO — cannot check (API unavailable)"; return 1; }
  fi

  local remaining
  remaining=$(echo "100 - ${SUBSCRIPTION_FIVE_HOUR_UTIL:-100}" | bc 2>/dev/null || echo "0")
  local resets_in
  resets_in=$(private.scrumMaster.subscription.resets.in "$SUBSCRIPTION_FIVE_HOUR_RESETS")

  local int_remaining
  int_remaining=$(echo "$remaining" | awk '{printf "%d", $1}')

  if [ "$int_remaining" -gt 20 ]; then
    echo "YES — ${remaining}% remaining, resets in ${resets_in}"
  elif [ "$int_remaining" -gt 10 ]; then
    echo "CAUTION — only ${remaining}% remaining, resets in ${resets_in}"
  else
    echo "NO — ${remaining}% remaining, would likely exhaust before reset (${resets_in})"
  fi
}
scrumMaster.subscription.check.completion.agents() {
  echo "1 2 3 4"
}

# ─────────────────────────────────────────────────────────────────────────────
# SHORTCUTS — public names delegating to private implementations
# ─────────────────────────────────────────────────────────────────────────────

scrumMaster.health.check() # # full PDCA health check — refresh, evaluate, alert
{
  private.scrumMaster.health.check "$@"
}
scrumMaster.health.check.completion() { :; }

scrumMaster.health.evaluate() # # quick health check with verdict
{
  private.scrumMaster.health.evaluate "$@"
}
scrumMaster.health.evaluate.completion() { :; }

scrumMaster.velocity.target() # # calculate burn rate target
{
  private.scrumMaster.velocity.target "$@"
}
scrumMaster.velocity.target.completion() { :; }

# ─────────────────────────────────────────────────────────────────────────────
# F1 — Velocity time-series logging + burn-rate alerts (CMM4)
# ─────────────────────────────────────────────────────────────────────────────
# Log format: append-only env-file, one sample per line:
#   <epoch>|<utcTimestamp>|<fiveHourPct>|<sevenDayPct>|<remainingMin>
# Stored at:  ~/config/metrics/velocity.log.env
#
# scrumMaster.velocity.log        — capture current sample + compute 10-min burn
# scrumMaster.velocity.alert      — log then warn if 10-min burn > 15%
# scrumMaster.velocity.history    — show recent samples (diagnostic)
# scrumMaster.velocity.rate       — compute burn rate over arbitrary window

: ${SCRUMMASTER_VELOCITY_LOG:=${CONFIG_PATH:-$HOME/config}/metrics/velocity.log.env}
: ${SCRUMMASTER_VELOCITY_ALERT_THRESHOLD:=15}  # % per 10-min that triggers alert
: ${SCRUMMASTER_VELOCITY_WINDOW_MIN:=10}       # minutes per burn-rate window

private.scrumMaster.velocity.ensure.log() {
  local logdir="$(dirname "$SCRUMMASTER_VELOCITY_LOG")"
  [ -d "$logdir" ] || mkdir -p "$logdir"
  [ -f "$SCRUMMASTER_VELOCITY_LOG" ] || touch "$SCRUMMASTER_VELOCITY_LOG"
}

private.scrumMaster.velocity.capture.sample() # # refresh SUBSCRIPTION_* via live API (or cache)
{
  # Prefer live API call; fall back to cache if API fails (consistent with the
  # other scrumMaster.subscription.* wrappers).
  if ! private.scrumMaster.subscription.api.call >/dev/null 2>&1; then
    local cacheFile="${CONFIG_PATH:-$HOME/config}/scrumMaster.subscription.env"
    if [ -f "$cacheFile" ]; then
      source "$cacheFile"
    else
      return 1
    fi
  fi
  return 0
}

scrumMaster.velocity.log() # # capture current 5h% + compute 10-min burn rate, persist sample
{
  private.scrumMaster.velocity.ensure.log

  # Step 1 — capture live sample
  if ! private.scrumMaster.velocity.capture.sample; then
    error.log "velocity.log: could not fetch subscription data (API + cache both empty)"
    create.result 1 "no data"
    return 1
  fi

  local fiveHour sevenDay resetsAt now_epoch now_iso remain_min
  fiveHour=$(echo "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}" | awk '{printf "%d", $1}')
  sevenDay=$(echo "${SUBSCRIPTION_SEVEN_DAY_UTIL:-0}" | awk '{printf "%d", $1}')
  resetsAt="${SUBSCRIPTION_FIVE_HOUR_RESETS:-}"
  now_epoch=$(date +%s)
  now_iso=$(date -u '+%Y-%m-%dT%H:%M:%SZ')

  # Compute remaining minutes until 5h window reset (best-effort)
  remain_min=0
  if [ -n "$resetsAt" ]; then
    remain_min=$(python3 -c "
from datetime import datetime, timezone
try:
    r = datetime.fromisoformat('$resetsAt')
    if r.tzinfo is None: r = r.replace(tzinfo=timezone.utc)
    d = r - datetime.now(timezone.utc)
    print(max(0, int(d.total_seconds() // 60)))
except: print(0)
" 2>/dev/null)
  fi

  # Step 2 — append sample (one line per capture)
  echo "${now_epoch}|${now_iso}|${fiveHour}|${sevenDay}|${remain_min}" >> "$SCRUMMASTER_VELOCITY_LOG"

  # Step 3 — compute burn rate over the 10-min window
  local burn
  burn=$(scrumMaster.velocity.rate "$SCRUMMASTER_VELOCITY_WINDOW_MIN")

  # Output summary
  echo "sample: ${fiveHour}% 5h / ${sevenDay}% 7d / ${remain_min}min left"
  if [ -n "$burn" ] && [ "$burn" != "-" ]; then
    echo "burn  : ${burn}% per ${SCRUMMASTER_VELOCITY_WINDOW_MIN}-min window"
  else
    echo "burn  : insufficient history (need ≥ 2 samples in window)"
  fi

  create.result 0 "$fiveHour"
}
scrumMaster.velocity.log.completion() { :; }

scrumMaster.velocity.rate() # <?windowMin:10> # compute 5h% burn rate over a time window (empty if < 2 samples)
{
  local windowMin="${1:-$SCRUMMASTER_VELOCITY_WINDOW_MIN}"
  private.scrumMaster.velocity.ensure.log

  local now_epoch
  now_epoch=$(date +%s)
  local window_start=$(( now_epoch - windowMin * 60 ))

  # Find samples whose epoch ≥ window_start — earliest and latest
  local earliest_pct="" earliest_epoch="" latest_pct="" latest_epoch=""
  while IFS='|' read -r e iso five seven rem; do
    [ -z "$e" ] && continue
    [[ "$e" != [0-9]* ]] && continue
    [ "$e" -lt "$window_start" ] && continue
    if [ -z "$earliest_pct" ]; then
      earliest_pct="$five"
      earliest_epoch="$e"
    fi
    latest_pct="$five"
    latest_epoch="$e"
  done < "$SCRUMMASTER_VELOCITY_LOG"

  if [ -z "$earliest_pct" ] || [ -z "$latest_pct" ] || [ "$earliest_epoch" = "$latest_epoch" ]; then
    echo "-"
    return 1
  fi

  # Burn = (latest_pct - earliest_pct) normalized to per-windowMin rate.
  # Since the window is already windowMin long (roughly), the delta is the rate.
  local delta=$(( latest_pct - earliest_pct ))
  echo "$delta"
  return 0
}
scrumMaster.velocity.rate.completion.windowMin() { echo "5"; echo "10"; echo "30"; echo "60"; }

scrumMaster.velocity.alert() # <?threshold:15> <?windowMin:10> # warn if burn rate exceeds threshold
{
  local threshold="${1:-$SCRUMMASTER_VELOCITY_ALERT_THRESHOLD}"
  local windowMin="${2:-$SCRUMMASTER_VELOCITY_WINDOW_MIN}"

  # Log a fresh sample first (alert without log is just a one-shot check)
  scrumMaster.velocity.log >/dev/null 2>&1

  local burn
  burn=$(scrumMaster.velocity.rate "$windowMin")

  if [ "$burn" = "-" ] || [ -z "$burn" ]; then
    info.log "velocity.alert: insufficient history (need ≥ 2 samples in ${windowMin}-min window)"
    create.result 0 "no-data"
    return 0
  fi

  if [ "$burn" -ge "$threshold" ] 2>/dev/null; then
    local now_five
    now_five=$(echo "${SUBSCRIPTION_FIVE_HOUR_UTIL:-0}" | awk '{printf "%d", $1}')
    warn.log "velocity BURN RATE HIGH: ${burn}% per ${windowMin}min (threshold ${threshold}%, current ${now_five}% 5h)"
    create.result 1 "$burn"
    return 1
  fi

  info.log "velocity ok: ${burn}%/${windowMin}min (threshold ${threshold}%)"
  create.result 0 "$burn"
  return 0
}
scrumMaster.velocity.alert.completion.threshold() { echo "10"; echo "15"; echo "20"; echo "25"; }
scrumMaster.velocity.alert.completion.windowMin() { scrumMaster.velocity.rate.completion.windowMin; }

scrumMaster.velocity.history() # <?lines:20> # show recent velocity samples for diagnostics
{
  local lines="${1:-20}"
  private.scrumMaster.velocity.ensure.log
  if [ ! -s "$SCRUMMASTER_VELOCITY_LOG" ]; then
    info.log "velocity log empty: $SCRUMMASTER_VELOCITY_LOG"
    return 0
  fi
  printf "%-22s %-6s %-6s %-6s\n" "TIMESTAMP" "5h%" "7d%" "MIN"
  tail -n "$lines" "$SCRUMMASTER_VELOCITY_LOG" | while IFS='|' read -r e iso five seven rem; do
    [ -z "$e" ] && continue
    printf "%-22s %-6s %-6s %-6s\n" "$iso" "$five" "$seven" "$rem"
  done
}
scrumMaster.velocity.history.completion.lines() { echo "10"; echo "20"; echo "50"; echo "200"; }

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

  scrumMaster - PDCA State Machine Manager + CMM4 Measurement

  PDCA (CMM3):
  P → D → C → A → C → A → C → A ... → finished
  Loop C→A until Check passes or max iterations.

  MEASUREMENT (CMM4):
  Extract metrics from agent panes — tokens, timing, activity state.
  Persist to session/metrics/ for trend analysis.

  Usage:
  $this: command   Parameter and Description"
  this.help
  echo "

  PDCA Examples:
    $this pdca.start              # Start new PDCA cycle
    $this pdca.start MYPDCA 3     # Start with 3 errors to fix
    $this pdca.state              # Show current state
    $this pdca.next               # Advance to next state
    $this pdca.errors             # Show error count
    $this pdca.errors MYPDCA 5    # Set 5 errors
    $this pdca.run                # Run complete cycle
    $this pdca.reset              # Reset machine

  Measurement Examples (agent activity — pane scraping):
    $this pane.capture oosh-expert       # Capture metrics for one agent
    $this team.capture                   # Capture metrics for all agents
    $this context.read oosh-expert       # Read context health
    $this speed.check oosh-expert        # Check token rate
    $this subscription.capture           # Pane-scraped token totals

  Subscription API (real utilization — OAuth):
    $this subscription.api               # Real utilization % from Anthropic API

  CMM4 Feedback Loop (PDCA health check):
    $this health.check                   # Full cycle: API refresh + velocity + evaluate + alert
    $this velocity.check                 # Velocity snapshot (burn rate + tasks)
    $this velocity.target                # Burn rate classification
    $this health.evaluate                # Threshold evaluation with alert logging
  "
}

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

  # if [ -z "$1" ]; then
  #   status.discover "$@"
  #   return 0
  # fi

  this.start "$@"
}

scrumMaster.start "$@"

