#!/usr/bin/env bash
#
# agent-discovery-info.sh — BALDART SessionStart hook (informational).
#
# Runs at session start (and on /clear, /compact, URL resume). Compares the
# set of BALDART agents shipped under .framework/framework/.claude/agents/
# against the per-name symlinks the consumer should have under
# .claude/agents/, and — when one or more are missing — injects an
# `additionalContext` warning so the model can see, at the very top of the
# session, which agent names will be unavailable.
#
# Why injection instead of blocking: SessionStart hooks have no decision
# control (per Claude Code docs), they can only inject context. The
# blocking counterpart is `agent-discovery-gate.js` (PreToolUse on the
# Agent tool). These two hooks complement each other:
#   - This hook (C′) raises early awareness so the model never tries to
#     call a missing agent in the first place.
#   - The gate (B′) is the hard wall when a call slips through anyway.
#
# Known limitation: there's a Claude Code bug (#10373) where SessionStart
# hook output is not injected on new conversations under certain
# conditions. When that happens, the defense falls back to B′ + the
# AGENTS.md MUST rule. We accept this — the hook is a best-effort
# augmentation, not the primary line of defense.
#
# Anti-prompt-injection note: per Claude Code docs, SessionStart context
# is most reliable when phrased as factual statements ("The following
# agents are missing: …") rather than imperative system instructions.
# We follow that convention below.
#
# Exit: always 0 (Stop/SessionStart hooks should never bubble errors out
# to the user; failure isolation is a matter of hygiene).

set -u
exec 2>/dev/null  # silence stderr; we are not user-visible

# --- read envelope --------------------------------------------------------

INPUT=""
if [ ! -t 0 ]; then
  INPUT="$(cat)"
fi

get_field() {
  local field="$1"
  if command -v jq >/dev/null 2>&1; then
    printf '%s' "$INPUT" | jq -r ".$field // empty" 2>/dev/null
  else
    printf '%s' "$INPUT" | sed -n "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -1
  fi
}

CWD_FROM_ENV="$(get_field cwd)"
CWD="${CWD_FROM_ENV:-$PWD}"

cd "$CWD" 2>/dev/null || exit 0

# --- locate the framework agent directory --------------------------------

FRAMEWORK_AGENTS_DIR=".framework/framework/.claude/agents"
CONSUMER_AGENTS_DIR=".claude/agents"

[ -d "$FRAMEWORK_AGENTS_DIR" ] || exit 0  # consumer not installed — silent.

# --- compute missing + degraded sets --------------------------------------
#
# MISSING — file does not exist (or symlink is broken). agent-discovery-gate
#           will hard-deny calls to these names.
# DEGRADED — file exists as a *regular file* with the baldart-generated marker.
#           Pre-v3.27.0 layout produced these, but Claude Code upstream bug
#           #20931 skips regular files at session-start discovery, so the
#           `subagent_type` value is silently unavailable to the orchestrator.
#           The fix is `npx baldart update` (migrates them to a symlink into
#           .baldart/generated/<kind>s/).

MISSING=""
DEGRADED=""
while IFS= read -r f; do
  [ -z "$f" ] && continue
  name="$(basename "$f" .md)"
  [ "$name" = "REGISTRY" ] && continue
  target="$CONSUMER_AGENTS_DIR/${name}.md"
  # `test -e` returns false for broken symlinks too — exactly what we want.
  if [ ! -e "$target" ]; then
    if [ -z "$MISSING" ]; then MISSING="$name"; else MISSING="$MISSING, $name"; fi
    continue
  fi
  # File exists. If it's a symlink → CC discovery sees it, healthy.
  # If it's a regular file with the baldart-generated marker → degraded
  # state (legacy overlay layout before v3.27.0).
  if [ ! -L "$target" ] && [ -f "$target" ]; then
    if head -c 4096 "$target" 2>/dev/null | grep -q "<!-- baldart-generated:" ; then
      if [ -z "$DEGRADED" ]; then DEGRADED="$name"; else DEGRADED="$DEGRADED, $name"; fi
    fi
  fi
done < <(find "$FRAMEWORK_AGENTS_DIR" -maxdepth 1 -type f -name '*.md' 2>/dev/null | sort)

# Nothing to report — exit silently. No noise when everything is healthy.
[ -z "$MISSING" ] && [ -z "$DEGRADED" ] && exit 0

# --- emit additionalContext ----------------------------------------------

# Build the JSON string with awk-based escaping (no jq required).
escape_json() {
  printf '%s' "$1" | awk 'BEGIN{ORS=""} {gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); gsub(/\r/,""); gsub(/\n/,"\\n"); print}'
}

MSG=""
if [ -n "$MISSING" ]; then
  MSG="BALDART expects the following sub-agents to be reachable via the Agent tool in this session, but their backing files are missing from .claude/agents/: ${MISSING}. Calling Agent with one of these subagent_type values will be denied by the agent-discovery-gate PreToolUse hook. Run \`npx baldart doctor\` to repair, then restart this session."
fi
if [ -n "$DEGRADED" ]; then
  DEG_MSG="BALDART detected overlay-merged agents stored in the legacy pre-v3.27.0 layout (regular files in .claude/agents/ with the baldart-generated marker): ${DEGRADED}. Claude Code upstream bug #20931 silently skips regular files at session-start discovery, so calling Agent with one of these subagent_type values will fail with InputValidationError even though the file is on disk. Run \`npx baldart update\` to migrate to the v3.27.0+ symlink-indirection layout (the file is moved to .baldart/generated/<kind>s/<name>.md and .claude/agents/<name>.md becomes a symlink to it), then restart this session."
  if [ -n "$MSG" ]; then MSG="${MSG} | ${DEG_MSG}"; else MSG="$DEG_MSG"; fi
fi

cat <<JSON
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "$(escape_json "$MSG")"
  }
}
JSON

exit 0
