#!/usr/bin/env bash
# PreToolUse / Agent — ask when an agent is dispatched without an explicit `model`.
#
# The kickoff prompt (docs/handoffs/chicorytv-issue-queue.md) says to route by capability: cheap/fast
# for bounded recon, mid tier for a mechanical slice against a documented contract, orchestrator tier
# for judgment-heavy work. That rule lived only in prose, and on 2026-07-25 an orchestrator dispatched
# two implementers with `model` omitted — both silently inherited the Opus orchestrator tier. Nothing
# in the session report revealed it; the operator had to ask.
#
# WHY a hook: omitting `model` is the SILENT path. Every other constraint in that kickoff has a hook,
# a CI job or a script behind it, and those were all followed in the same session — the one rule with
# no forcing function was the one that got defaulted. A check that runs beats a rule you must remember
# (the same reasoning as pretooluse-bom-guard.sh).
#
# SCOPE — gate EVERY dispatch that names no model, not just implementer-looking ones. The first cut
# tried to be clever: it fired only when the prompt text matched implementer signals (`git commit`,
# `worktree`, `fixes #`…). Review of that version (#583) confirmed the heuristic both over- and
# under-fired — a read-only recon brief mentioning "worktree" nagged, while "author the change and
# open a PR", "land this on the branch" and "make the changes and commit them" all sailed through
# silently, i.e. it missed the exact case it existed to catch. Prompt prose is not a reliable signal
# for authority, and a gate with an unreliable catch rate is worse than an honest one.
#
# Two further reasons the broad form is correct here:
#   - The HARD CONSTRAINT itself says "every dispatched agent". A narrower hook contradicted the rule
#     it was built to enforce.
#   - Routing matters MOST for the cheap cases. The old exemption list ("read-only, so routing barely
#     matters") had it backwards: bounded recon is precisely what should be explicitly routed DOWN to
#     a fast tier, and that review also showed the premise was false — Explore, Plan and
#     claude-code-guide all carry Bash, so none of them provably "cannot commit".
#
# The prompt costs nothing to avoid: name a tier and this never fires. That is the habit being built.
#
# Exempt: `fork` only — a fork ALWAYS inherits the parent model and the tool IGNORES a `model`
# override, so asking would demand something unachievable.
#
# "ask", never "deny": routing is a judgment call with no derivable right answer, unlike the
# merge-consent gate (H6/H10) which derives a verifiable state. This gate exists to make an invisible
# default visible, not to impose a tier.
#
# Fail-open by design: any parse trouble -> allow (exit 0, no output).
set -uo pipefail

input=$(cat)

tool=$(printf '%s' "$input" | jq -r '.tool_name // ""' 2>/dev/null || true)
[ "$tool" = "Agent" ] || exit 0

# An explicit choice was made — nothing to surface. This is the path to prefer.
model=$(printf '%s' "$input" | jq -r '.tool_input.model // ""' 2>/dev/null || true)
[ -z "$model" ] || exit 0

subagent=$(printf '%s' "$input" | jq -r '.tool_input.subagent_type // ""' 2>/dev/null || true)

# A fork's model is fixed to the parent's by the tool; a prompt here could not be acted on.
[ "$subagent" = "fork" ] && exit 0

label="${subagent:-general-purpose}"
reason="Dispatching an agent (subagent_type: ${label}) with no explicit \`model\`.

It will silently inherit this session's model — which may be right, but it is a default, not a choice.
Name the tier (and say so in the dispatch message), per the kickoff routing rule
\`process.per-agent-model-routing\`:

  - bounded recon / inventory / log triage         -> cheapest fast tier (haiku)
  - mechanical slice against a documented contract -> mid tier (sonnet)
  - judgment-heavy: design, compiler/parser, security,
    migrations, review arbitration                 -> orchestrator tier (opus)

Independent review should also prefer a DIFFERENT model family than the implementer — a cold
same-family review is worth less than a cross-family one.

Pass \`model\` on the Agent call and this never fires. Approve as-is only if inheriting the
orchestrator tier is the deliberate call."

jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"ask",permissionDecisionReason:$r}}'
exit 0
