#!/usr/bin/env bash
# kit adopt — hands the kit OVER (handover) to an EXISTING project, brownfield-safe.
# Later becomes the `kit adopt` subcommand. Handover philosophy: don't break the project · don't lose decisions made ·
# don't leave the kit passive (100% hybrid). Kit agents are namespaced with -csk -> no clash with project agents.
#
# >>> STAGE 1: detection + smart suggestion only. CHANGES NOTHING (read-only). <<<
# Later stages: open git branch -> mutation (settings merge · DISCIPLINE.md · coexist) -> install proof
# -> HANDOVER.md / ADR. The SUGGESTION produced here for each decision is applied in the next stage via review/override.
#
# Usage: at the target project root (same directory as claude-starter/):  bash adopt.sh
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
SRC="$HERE/claude-starter"
[ -d "$SRC" ] || { echo "ERROR: claude-starter/ not found (must be in the same directory as adopt.sh)."; exit 1; }

# --- flags (B5 — where a refresh lands) ---  --here: the current branch · --new-branch: a fresh review branch.
# Left empty, Stage 2 picks a smart default (first adopt -> new; update + untracked .claude -> here; update +
# tracked -> ask). Unknown flags are ignored here (start.sh owns --backend/--dotnet/… ; adopt auto-detects shape).
BRANCH_MODE=""; ASSUME_YES=0
for _a in "$@"; do case "$_a" in
  --here)       BRANCH_MODE=here ;;
  --new-branch) BRANCH_MODE=new  ;;
  --yes|-y)     ASSUME_YES=1     ;;   # assume "yes" at every gate — for agent-driven / CI updates (no TTY to prompt)
esac; done

# --- color: only on an interactive TTY (same guard as start.sh) ---
if [ -t 1 ] && [ "${TERM:-dumb}" != "dumb" ] && [ -z "${NO_COLOR:-}" ]; then
  R=$'\033[0m'; B=$'\033[1m'; D=$'\033[2m'; CY=$'\033[36m'; GR=$'\033[32m'; YE=$'\033[33m'; MG=$'\033[35m'
else R=''; B=''; D=''; CY=''; GR=''; YE=''; MG=''; fi
h1()  { printf '\n%s%s%s%s\n' "$B" "$CY" "$1" "$R"; }
sub() { printf '%s%s%s\n' "$D" "$1" "$R"; }
row() { printf '  %s%-20s%s %s\n' "$B" "$1" "$R" "$2"; }
warn(){ printf '  %s!%s %s%s%s\n' "$YE" "$R" "$YE" "$1" "$R"; }
# smart suggestion line:  number+decision · SUGGESTED(green) · rationale(dim)
prop(){ printf '  %s%-18s%s %s%-24s%s %s%s%s\n' "$B" "$1" "$R" "$GR" "$2" "$R" "$D" "$3" "$R"; }
# --yes ALWAYS wins — check it BEFORE the TTY test. A `read` on a TTY blocks on human input, so if we tested
# `-t 0` first, an agent/CI run that DID pass --yes but happens to inherit a TTY (Claude Code on Windows runs
# under a pty) would hang at the prompt, ignoring --yes. Only when --yes is absent do we prompt (TTY) or decline
# cleanly (no TTY — a bare `read` would otherwise block forever on an open-but-empty stdin).
ask_yes(){ local a
  if [ "${ASSUME_YES:-0}" = 1 ]; then printf '%s yes %s(--yes)%s\n' "$1" "$D" "$R"; a=yes
  elif [ -t 0 ]; then printf '%s [yes/no]: ' "$1"; read -r a || a=""
  else printf '%s no %s(non-interactive — pass --yes to apply)%s\n' "$1" "$D" "$R"; a=no; fi
  case "$a" in [yY]|[yY][eE][sS]|[eE]|[eE][vV][eE][tT]) return 0;; *) return 1;; esac; }
# never-overwrite copy: does NOT overwrite an EXISTING target file (project file is preserved), skips+counts.
# Result globals: ret_add / ret_skip; conflicts are added to SKIP_LIST. Do NOT call in a subshell (globals are lost).
SKIP_LIST=""
# $4 = space-separated names to SKIP entirely, matched against the first path component of each source file
# ('frontend-expert-csk.md' for agents/, 'a11y' for skills/). We skip rather than copy-then-delete: a project
# may own a directory of the same name, and a refresh must never remove the project's own files.
# Per-FILE process spawns are what make this hurt on Windows: Git Bash pays 62-135 ms per process where Linux pays
# ~1.7ms, so `dirname`+`mkdir`+`cp` for each of ~100 payload files is minutes, not milliseconds. Measured on a
# user's machine: a refresh took 6m43s wall with 66s of it in the kernel — the fork signature, not file I/O.
#   - the refresh case (force=1, nothing excluded) is ONE `cp -R`, not one `cp` per file;
#   - the selective case keeps per-file decisions but drops `dirname` for parameter expansion, and only calls
#     `mkdir` when the directory actually changes (find walks a directory at a time, so that is nearly always).
copy_noclobber(){ local src="$1" dst="$2" force="${3:-0}" exclraw="${4:-}" excl=" ${4:-} " rel top f d lastd=""; ret_add=0; ret_skip=0; [ -d "$src" ] || return; mkdir -p "$dst"
  if [ "$force" = 1 ] && [ -z "$exclraw" ]; then
    ret_add="$(find "$src" -type f 2>/dev/null | wc -l | tr -d ' ')"
    cp -R "$src/." "$dst/" 2>/dev/null
    return
  fi
  while IFS= read -r f; do rel="${f#"$src"/}"; top="${rel%%/*}"
    case "$excl" in *" $top "*) continue ;; esac
    if [ -e "$dst/$rel" ] && [ "$force" != 1 ]; then ret_skip=$((ret_skip+1)); SKIP_LIST="$SKIP_LIST $dst/$rel"
    else
      case "$rel" in */*) d="$dst/${rel%/*}" ;; *) d="$dst" ;; esac
      [ "$d" = "$lastd" ] || { mkdir -p "$d"; lastd="$d"; }
      cp "$f" "$dst/$rel"; ret_add=$((ret_add+1))
    fi
  done < <(find "$src" -type f 2>/dev/null); }

# --- CLAUDE.md split (shared contract with start.sh; keep the two in lockstep) ---
IMPORT_LINE='@.claude/DISCIPLINE.md'
# Sentinel matched ANCHORED to line start, so prose that merely names the token is never mistaken for the split
# point. Abort loudly if it is gone: a silent miss ships the ENTIRE template as "discipline" — which is exactly
# what the previous '<PROJE ADI>' marker did once the payload was translated to English.
kit_require_sentinel() { grep -qE '^<!-- KIT:DISCIPLINE-END' "$1" || { echo "ERROR: the '<!-- KIT:DISCIPLINE-END' sentinel line is missing from $1 — refusing to guess the discipline/project split."; exit 1; }; }
kit_discipline_of()    { awk '/^<!-- KIT:DISCIPLINE-END/{exit} {print}' "$1"; }
kit_project_of()       { awk 'f{print} /^<!-- KIT:DISCIPLINE-END/{f=1}' "$1"; }
# Anchored: the import must BE the line, not merely be mentioned in prose (the discipline text names the path).
kit_has_import()       { grep -qE '^[[:space:]]*@\.claude/DISCIPLINE\.md[[:space:]]*$' "$1" 2>/dev/null; }
# A pre-1.1 install pasted the whole discipline inline into CLAUDE.md; adding the @import would load it twice.
# Both markers are required: a project that happens to write its own "Four working principles" heading is NOT a
# legacy kit install, and treating it as one would leave it without the discipline forever.
kit_claude_md_is_legacy() {
  grep -q '^## Four working principles' "$1" 2>/dev/null && grep -qE '^### 4\.[45] ' "$1" 2>/dev/null
}
# Where does the project section of a legacy CLAUDE.md begin? Line 1 is the discipline's own '# CLAUDE.md — Working
# rules' heading, so look from line 2 on. Fallback: the first '# ' heading after the §4.5 block (covers a renamed
# heading). Capture the output — `awk … && return` would return on awk's exit status even when it printed nothing.
kit_legacy_boundary() {
  local n
  n="$(awk 'NR>1 && /^# CLAUDE\.md/{print NR; exit}' "$1" 2>/dev/null)"
  [ -n "$n" ] || n="$(awk '/^### 4\.5 /{f=1;next} f && /^# /{print NR; exit}' "$1" 2>/dev/null)"
  printf '%s' "$n"
}
# A project installed before kit.conf existed carries its backend pattern only in what is on disk. Read it
# back, so a refresh does not graft devarch-module onto a repo that deliberately runs without it.
kit_infer_shape() {
  # A .NET stack is marked by the devarch-module skill being installed; the generic stack prunes it. (The
  # backend agent itself is pattern-neutral now, so its text is no longer a reliable stack signal.)
  if [ -d .claude/skills/devarch-module ]; then KIT_STACK=dotnet; else KIT_STACK=generic; fi
  KIT_INSTALLER="${KIT_INSTALLER:-pre-kit.conf}"
  INFERRED=1
}
# tr -d '\r' on both readers: a CRLF file (Windows checkout, or kit.conf reopened in Notepad) would otherwise
# glue '\r' to every value — 'generic\r' matches no branch, and the refresh would pick the wrong pattern.
kit_conf_get()         { [ -f .claude/kit.conf ] && sed -n "s/^$1=//p" .claude/kit.conf | head -1 | tr -d '\r'; }

# Turn a project agent into a DRAFT project skill (prints the SKILL.md to stdout). On takeover the kit's -csk
# agent owns routing (who/when); this carries the OLD agent's domain (its "how") into an active skill the kit
# agent can apply, so nothing is lost from the working setup. It keeps the agent's description (domain keywords)
# and body, guarantees a Trigger-phrases line for discovery, and marks it as a carried-over draft to refine.
kit_agent_to_skill() {   # $1 = agent .md file, $2 = base name
  local f="$1" b="$2" desc trig body
  desc="$(awk '/^---[ \t]*$/{c++; next} c==1 && /^description:/{sub(/^description:[ \t]*\|?[ \t]*/,""); if($0!="")print; exit}' "$f")"
  [ -n "$desc" ] || desc="Project-specific $b knowledge carried over when the kit took over the role."
  trig="$(awk '/[Tt]rigger phrases:/{sub(/.*[Tt]rigger phrases:[ \t]*/,""); print; exit}' "$f")"
  [ -n "$trig" ] || trig="\"$b\""
  body="$(awk 'c>=2{print} /^---[ \t]*$/{c++}' "$f")"
  printf '%s\n' '---' "name: ${b}-local"
  printf 'description: |\n  %s\n' "$desc"
  printf '  Carried over from the project'"'"'s own %s agent when %s-csk took over the role — trim to the domain worth keeping.\n' "$b" "$b"
  printf '  Trigger phrases: %s\n---\n\n' "$trig"
  printf '# %s — project knowledge (carried over on kit adoption)\n\n' "$b"
  printf '> Draft generated from your original `%s` agent; the kit'"'"'s `%s-csk` applies this skill. Refine it to the domain "how" worth keeping.\n\n' "$b" "$b"
  printf '%s\n' "$body"
}

h1 "kit adopt · Stage 1 — DETECTION (read-only; nothing changes)"
sub "Reads the existing project, produces a smart suggestion for the 7 handover decisions. Approval + mutation in the next stage."

# ========================= [1] ENVIRONMENT =========================
h1 "[1] Environment"
# git context — in a worktree/submodule .git is a FILE (do NOT use [ -d .git ]; red-team hole #6)
IS_GIT=0; GITTOP=""; GITKIND="no git — 'git init' required"
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  IS_GIT=1; GITTOP="$(git rev-parse --show-toplevel 2>/dev/null || echo .)"
  if [ -f "$GITTOP/.git" ]; then GITKIND="worktree/submodule (.git file)"; else GITKIND="normal repo"; fi
fi
row "git" "$GITKIND"

# existing hook system (decision #5 — single-hooksPath clash with husky/lefthook)
HOOKSYS="none"
CURHP="$(git config --get core.hooksPath 2>/dev/null || true)"
case "$CURHP" in
  "") : ;;
  .claude/hooks|.claude/git-shim) HOOKSYS="kit (already armed)"; CURHP="" ;;   # kit's OWN path — not a foreign chain (re-adopt must not shim itself)
  *) HOOKSYS="core.hooksPath=$CURHP" ;;
esac
[ -d .husky ] && HOOKSYS="husky (.husky/)"
{ [ -f lefthook.yml ] || [ -f .lefthook.yml ]; } && HOOKSYS="lefthook"
[ -f .pre-commit-config.yaml ] && HOOKSYS="pre-commit framework"
row "git hook system" "$HOOKSYS"

# stack hint (context). Look PAST the root: a .NET solution commonly lives in ./backend, ./src, ./server — the
# old root-only `ls ./*.sln` reported "unknown" for exactly those layouts and the install silently fell back to
# generic (dropping the DevArch pattern skill). Search a few levels deep, skipping build/vendor dirs.
STACK="unknown"; IS_DOTNET=0; IS_DEVARCH=0
# PRUNE, don't filter. The old form walked bin/obj/node_modules in full and threw the results away afterwards —
# on a real .NET repo that is tens of thousands of directory entries, and on Windows every one of them is a
# Defender-scanned syscall. Pruning stops the descent instead of discarding its output.
PRUNE_DIRS=' ( -name bin -o -name obj -o -name node_modules -o -name .git -o -name .vs -o -name packages ) -prune -o '
# shellcheck disable=SC2086
DOTNET_HIT="$(find . -maxdepth 3 $PRUNE_DIRS \( -name '*.sln*' -o -name '*.csproj' \) -print 2>/dev/null | head -1)"
if [ -n "$DOTNET_HIT" ]; then
  STACK=".NET"; IS_DOTNET=1
  # DevArchitecture signature: the canonical Business/Handlers CQRS layout, or a solution literally named DevArchitecture.
  # shellcheck disable=SC2086
  { find . -maxdepth 4 $PRUNE_DIRS -type d -path '*/Business/Handlers' -print 2>/dev/null | grep -q . \
    || find . -maxdepth 3 $PRUNE_DIRS -iname 'devarchitecture.sln*' -print 2>/dev/null | grep -q .; } && IS_DEVARCH=1
elif [ -f package.json ]; then STACK="Node/JS"
elif [ -f go.mod ]; then STACK="Go"
elif [ -f pyproject.toml ] || [ -f requirements.txt ]; then STACK="Python"; fi
row "stack hint" "$STACK$([ "$IS_DEVARCH" = 1 ] && echo " · DevArchitecture layout detected")"

# ================= [2] EXISTING AGENTIC SETUP =================
h1 "[2] Existing agentic setup (accumulated work to inherit)"
HAS_CLAUDE=0; [ -d .claude ] && HAS_CLAUDE=1
# count only the PROJECT's own agents/skills — exclude the kit's -csk agents and kit skills left by a prior adopt
N_PAGENTS=0; [ -d .claude/agents ] && N_PAGENTS="$(find .claude/agents -name '*.md' ! -name '*-csk.md' 2>/dev/null | wc -l | tr -d ' ')"
N_PSKILLS=0
if [ -d .claude/skills ]; then
  # `basename $(dirname …)` per skill is two spawns × every installed skill, for a number printed once. Parameter
  # expansion does the same slicing with none.
  while IFS= read -r f; do d="${f%/SKILL.md}"; d="${d##*/}"; [ -d "$SRC/skills/$d" ] || N_PSKILLS=$((N_PSKILLS+1)); done < <(find .claude/skills -name 'SKILL.md' 2>/dev/null)
fi
# Same-domain agent overlap: a PROJECT agent whose base name matches a kit -csk agent (e.g. backend-expert vs
# backend-expert-csk). The two describe the same job, so the router has to pick between them — plain coexist
# leaves that ambiguous and the project's older agent tends to win, which defeats installing the kit. Collect
# the overlaps so the handover can RESOLVE them, not merely note them.
COLLIDE=""
if [ -d .claude/agents ]; then
  for kf in "$SRC"/agents/*-csk.md; do b="$(basename "$kf" -csk.md)"; [ -f ".claude/agents/$b.md" ] && COLLIDE="$COLLIDE $b"; done
fi
COLLIDE="${COLLIDE# }"; N_COLLIDE=0; [ -n "$COLLIDE" ] && N_COLLIDE="$(printf '%s\n' $COLLIDE | wc -l | tr -d ' ')"
HAS_MD=0; [ -f CLAUDE.md ] && HAS_MD=1
HAS_SETTINGS=0; [ -f .claude/settings.json ] && HAS_SETTINGS=1
# already-adopted fingerprint: did a PRIOR adopt/kit install run here? -> REFRESH semantics, not a fresh handover
KIT_PRESENT=0; KIT_VER=""
{ [ -f .claude/DISCIPLINE.md ] || [ -d .claude/git-shim ] || ls .claude/agents/*-csk.md >/dev/null 2>&1 || [ -f .claude/VERSION ]; } && KIT_PRESENT=1
[ -f .claude/VERSION ] && KIT_VER="$(head -1 .claude/VERSION 2>/dev/null)"
# Backend pattern of the existing install. A .NET install must not have its DevArch expert swapped for the
# generic one, and a Node repo must not be handed devarch-module. LEGACY_PROFILE is a pre-2.0 leftover: back
# then the component set varied, so it recorded which parts were pruned. It no longer selects anything — it
# only tells the migration notice below that this project was installed under the old, narrower shape.
LEGACY_PROFILE="$(kit_conf_get profile)"; KIT_STACK="$(kit_conf_get stack)"; KIT_INSTALLER="$(kit_conf_get installer)"
# No kit.conf means the project predates it (v1.0.x). Recover the pattern from the files themselves, or the
# refresh would swap a .NET project's expert for the generic one.
INFERRED=0
{ [ "$KIT_PRESENT" = 1 ] && [ -z "$KIT_STACK" ]; } && kit_infer_shape
row ".claude/" "$([ "$HAS_CLAUDE" = 1 ] && echo "present — $N_PAGENTS project agents · $N_PSKILLS project skills" || echo "none")"
row "CLAUDE.md" "$([ "$HAS_MD" = 1 ] && echo "present" || echo "none")"
row "settings.json" "$([ "$HAS_SETTINGS" = 1 ] && echo "present" || echo "none")"
[ "$KIT_PRESENT" = 1 ] && row "kit status" "${YE}already adopted${KIT_VER:+ (v$KIT_VER)} — this run REFRESHES kit files, project untouched${R}"
[ -n "$KIT_STACK" ] && row "$([ "$INFERRED" = 1 ] && echo 'inferred pattern' || echo 'recorded pattern')" \
  "stack=${KIT_STACK}$([ "$INFERRED" = 1 ] && echo " ${YE}(no kit.conf — read back from the installed files)${R}" || echo " · via ${KIT_INSTALLER:-?}") — the refresh keeps it"
[ -n "$LEGACY_PROFILE" ] && row "pre-2.0 profile" \
  "${YE}profile=${LEGACY_PROFILE}${R} ${D}— profile pruning was removed in 2.0; this refresh completes the install${R}"

# tracked in git? (decision #4 — share/hide)
TRACKED=0
if [ "$IS_GIT" = 1 ]; then
  git ls-files --error-unmatch CLAUDE.md >/dev/null 2>&1 && TRACKED=1
  { [ "$HAS_CLAUDE" = 1 ] && [ -n "$(git ls-files .claude 2>/dev/null | head -1)" ]; } && TRACKED=1
fi
row ".claude/CLAUDE.md in git" "$([ "$TRACKED" = 1 ] && echo "YES — shared with the team" || echo "no/untracked")"

# Supply-chain scan (advisory, read-only): the project's OWN (non-csk) skills/agents may have been pulled from an
# untrusted source. Scan them for red flags (curl|bash, prompt-injection directives, credential exfil) before the
# kit starts coexisting with them. Heuristic; it NEVER blocks — it surfaces, the user judges.
if { [ "$N_PAGENTS" != 0 ] || [ "$N_PSKILLS" != 0 ]; } && [ -f "$SRC/eval/scan-skill.sh" ]; then
  SCANOUT="$(bash "$SRC/eval/scan-skill.sh" .claude 2>/dev/null)"
  if printf '%s' "$SCANOUT" | grep -qE 'DANGER|REVIEW'; then
    warn "supply-chain scan flagged existing project skills/agents (advisory — review before trusting them):"
    printf '%s\n' "$SCANOUT" | grep -E 'DANGER|REVIEW' | sed 's/^/    /'
    sub "    full report after install: bash .claude/eval/scan-skill.sh .claude  (heuristic; a security skill can score low by design)"
  else
    row "supply-chain scan" "existing project skills/agents look clean (no red flags)"
  fi
fi

# co-author/sign-off convention (decision #3)
COAUTHOR=0
[ "$IS_GIT" = 1 ] && git log -80 --format='%b' 2>/dev/null | grep -qiE 'Co-Authored[-]By|Signed-off-by' && COAUTHOR=1  # [-] : not a contiguous literal in source (trace hook)

# off-repo hint (decision #7 — decisions may live in chat/on the web)
OFFREPO=0; { [ "$HAS_CLAUDE" = 0 ] && [ "$HAS_MD" = 0 ]; } && OFFREPO=1

# ===================== [3] SMART SUGGESTION ======================
h1 "[3] 7 handover decisions — SMART SUGGESTION"
sub "format:  decision  ->  SUGGESTED  ->  rationale   (you can review and override all of them in the next stage)"
if [ "$N_COLLIDE" != 0 ]; then
  prop "1 Role overlap" "kit takes over" "$N_COLLIDE project agent(s) cover the SAME job as a kit agent ($COLLIDE) — routing is ambiguous; kit wins, yours preserved"
elif [ "$N_PAGENTS" != 0 ]; then
  prop "1 Role clash" "keep (coexist)" "$N_PAGENTS project agents, none overlap a kit role; thanks to -csk they live side by side"
else
  prop "1 Role clash" "none" "no custom agents found in the project"
fi
prop "2 Precedence" "project wins (fixed)" "on conflict the project's rules always win; the kit fills gaps (not overridable)"
if [ "$COAUTHOR" = 1 ]; then
  prop "3 Trace gate" "loosen (.trace-allowlist)" "co-author/sign-off present in git log — may be a convention"
else
  prop "3 Trace gate" "keep" "no co-author/sign-off convention seen"
fi
if [ "$TRACKED" = 1 ]; then
  prop "4 Share/hide" "share" ".claude/CLAUDE.md is tracked — keep sharing with the team"
else
  prop "4 Share/hide" "share" "untracked; kit files are shared by default — pick hide to keep them local"
fi
if [ "$HOOKSYS" = "none" ]; then
  prop "5 Git hooks" "install directly" "no existing hook system"
else
  prop "5 Git hooks" "SHIM (bridge)" "existing $HOOKSYS present — let both run"
fi
prop "6 Brownfield DoD" "baseline+regression" "existing code debt unknown; absolute 0/0/0/0 is risky"
if [ "$OFFREPO" = 1 ]; then
  warn "7 Off-repo: no local .claude/CLAUDE.md — decisions may live in chat/on the web; there is context I CANNOT SEE."
  prop "  -> suggestion" "you transfer" "in the mutation stage 'paste if any' is asked; goes into HANDOVER.md"
else
  prop "7 Off-repo" "local + ask" "some decisions are in files; still may be in-chat (asked during the stage)"
fi

# ============ COMPILE DECISIONS + OVERRIDE (Stage B) ============
DEC1="$([ "$N_PAGENTS" != 0 ] && echo keep || echo none)"
DEC2="project"   # precedence is FIXED to project-wins (not overridable — reflected in the @import comment)
DEC3="$([ "$COAUTHOR" = 1 ] && echo loosen || echo keep)"
DEC4="$([ "$TRACKED" = 1 ] && echo share || echo kit-default)"
DEC6="baseline"
DEC7="$([ "$OFFREPO" = 1 ] && echo transfer || echo local)"
# normalize display defaults to a real, offered token
[ "$DEC1" = none ] && DEC1=keep
[ "$DEC4" = kit-default ] && DEC4=share
if [ -t 0 ]; then
  h1 "Review the decisions"
  # ask_dec: echoes the chosen value to STDOUT; ALL prompts/errors go to STDERR so $(...) captures only the value
  ask_dec(){ local label="$1" a="$2" b="$3" cur="$4" v fa fb; fa="${a:0:1}"; fb="${b:0:1}"
    while :; do
      printf '  %s%s%s [%s/%s] (current: %s%s%s, ENTER=keep): ' "$B" "$label" "$R" "$a" "$b" "$B" "$cur" "$R" >&2
      read -r v || v=""
      case "$v" in
        "")         echo "$cur"; return ;;
        "$a"|"$fa") echo "$a";   return ;;
        "$b"|"$fb") echo "$b";   return ;;
        *) printf '     %s! type "%s" or "%s" (or ENTER to keep "%s")%s\n' "$YE" "$a" "$b" "$cur" "$R" >&2 ;;
      esac
    done; }
  if ask_yes "Accept all smart suggestions?"; then
    sub "All smart suggestions accepted."
  else
    sub "Reviewing each decision. ENTER keeps the current value. (#1 overlap and #2/#5 are handled separately below.)"
    DEC3="$(ask_dec '#3 Trace gate'     loosen   keep     "$DEC3")"
    DEC4="$(ask_dec '#4 Share/hide'     share    hide     "$DEC4")"
    DEC6="$(ask_dec '#6 Brownfield DoD' baseline absolute "$DEC6")"
    DEC7="$(ask_dec '#7 Off-repo'       transfer skip     "$DEC7")"
  fi
  echo "  Final: #3=$DEC3 #4=$DEC4 #6=$DEC6 #7=$DEC7  (#2 project-wins, #5 SHIM — fixed)"
else
  sub "(non-interactive: smart defaults accepted)"
fi

# --- Backend stack (fresh adopt only) --------------------------------------------------------------------
# A REFRESH keeps the recorded stack (kit.conf / inferred). A FRESH adopt used to fall back to generic whenever
# the root had no .sln — wrong for a solution under ./backend. Decide it from the deeper detection, and confirm
# when a TTY is present. Setting KIT_STACK here feeds the existing prune/kit.conf logic below.
if [ -z "${KIT_STACK:-}" ] && [ "$KIT_PRESENT" != 1 ]; then
  if [ "$IS_DOTNET" = 1 ]; then
    KIT_STACK=dotnet
    if [ -t 0 ]; then
      h1 "Backend stack"
      sub "Detected a .NET project$([ "$IS_DEVARCH" = 1 ] && echo ' with a DevArchitecture (Business/Handlers CQRS) layout')."
      ask_yes "Install the .NET/DevArchitecture backend pattern (devarch-module)? (no = stack-agnostic generic)" || KIT_STACK=generic
    fi
  else
    KIT_STACK=generic
  fi
  echo "  backend stack -> ${KIT_STACK}$([ "$IS_DEVARCH" = 1 ] && [ "$KIT_STACK" = dotnet ] && echo ' (DevArchitecture)')"
fi

# --- Refresh: a recorded stack is a decision, and a sniff does not get to overrule it -----------------------
# A REFRESH trusts the recorded stack over a sniff, so a sniff miss cannot flip a dotnet install to generic.
# The reverse needed the same protection and did not have it. A recorded 'generic' on a project that LOOKS like
# DevArchitecture (a Business/Handlers tree OR a DevArchitecture.sln — either alone) may be a stale record — or it may be
# exactly what the user chose, on a .NET project that simply is not DevArchitecture. The sniff cannot tell those
# apart, and this one fired on a repo whose layout is WebAPI/Business/DataAccess with no DevArchitecture in it.
#
# The old test was `[ ! -t 0 ] || ask_yes …`, i.e. NO TTY MEANT YES. Every agent-driven or CI update runs without
# a tty, so the branch whose own comment said "never flip silently" was the silent one. Measured: a .NET-shaped
# repo installed with --generic, updated with `adopt.sh --yes`, came out stack=dotnet with devarch-module
# installed and backend-expert-csk rewritten to the .NET variant — which then hands the agent a pattern the
# project does not use. The user's report was "I installed this as generic, why does it think it is .NET".
#
# So: correcting a recorded choice needs a PERSON, or an explicit flag from whoever automated it. Not-asking is
# not consent, and the fail-safe direction is to keep what is written down. The mismatch is still reported
# loudly every run, with the one command that corrects it on purpose.
if [ "$KIT_PRESENT" = 1 ] && [ "$KIT_STACK" = generic ] && [ "$IS_DEVARCH" = 1 ]; then
  h1 "Recorded backend stack looks wrong"
  warn "kit.conf records stack=generic, but this project has a DevArchitecture layout (a Business/Handlers tree, or a DevArchitecture.sln)."
  sub "Left as-is, the refresh keeps pruning devarch-module and holds the generic backend agent."
  if [ "${CSK_CORRECT_STACK:-0}" = 1 ]; then
    KIT_STACK=dotnet; echo "  stack corrected -> dotnet (CSK_CORRECT_STACK=1)"
  elif [ -t 0 ] && [ "${ASSUME_YES:-0}" != 1 ] && ask_yes "Correct it to dotnet? (install the DevArchitecture pattern skill + the .NET backend agent)"; then
    KIT_STACK=dotnet; echo "  stack corrected -> dotnet (DevArchitecture)"
  else
    echo "  kept stack=generic — a recorded choice is not overruled where nobody can be asked."
    echo "  If the record IS stale, correct it deliberately:  CSK_CORRECT_STACK=1 bash adopt.sh --yes"
  fi
fi

# --- Role overlap (#1): resolve same-domain agent collisions ---------------------------------------------
# When a project agent and a kit -csk agent cover the same job, "coexist" leaves routing ambiguous. Offer to
# resolve it. takeover = kit wins (your agent preserved, moved out of the routing pool); keepmine = your agent
# wins (the kit's overlapping -csk is not installed); coexist = keep both (documented). Non-interactive -> takeover
# (you ran adopt to get the kit's agents). The chosen mode is APPLIED on the handover branch in Stage 2.
COLLIDE_MODE=coexist
if [ "$N_COLLIDE" != 0 ]; then
  h1 "Role overlap — project & kit both cover: $COLLIDE"
  sub "Two agents for one job = the router picks one, usually your older agent — so the kit's would sit idle."
  sub "  takeover  kit's -csk agents win; each old agent's domain is imported to a draft skill (skills/<name>-local), original backed up"
  sub "  keepmine  your agents win; the kit's overlapping -csk agents are not installed"
  sub "  coexist   keep both (routing stays ambiguous; only documented in HANDOVER)"
  COLLIDE_MODE=takeover
  if [ -t 0 ] && [ "${ASSUME_YES:-0}" != 1 ]; then   # --yes keeps the documented non-interactive default (takeover)
    while :; do
      printf '  %sowner%s [takeover/keepmine/coexist] (ENTER=takeover): ' "$B" "$R"
      read -r _v || _v=""
      case "$_v" in ""|t|takeover) COLLIDE_MODE=takeover; break ;; k|keepmine) COLLIDE_MODE=keepmine; break ;; c|coexist) COLLIDE_MODE=coexist; break ;;
        *) printf '     %s! type takeover, keepmine or coexist%s\n' "$YE" "$R" >&2 ;; esac
    done
  fi
  echo "  overlap -> $COLLIDE_MODE"
fi
# #1 display/HANDOVER value reflects what actually happens.
case "$COLLIDE_MODE" in
  takeover) DEC1=takeover ;; keepmine) DEC1=keepmine ;;
  *) DEC1="$([ "$N_PAGENTS" != 0 ] && echo keep || echo none)" ;;
esac

# A non-interactive REFRESH of an existing kit install is low-risk — it rewrites only kit-owned files and the change
# is staged/reversible — so it applies by default; this is what lets /update-csk self-heal without any flag. A first
# adopt (KIT_PRESENT=0, a larger brownfield change) still requires an explicit --yes when there is no TTY to ask.
if [ ! -t 0 ] && [ "$KIT_PRESENT" = 1 ]; then ASSUME_YES=1; fi

# ================= [STAGE 2] HANDOVER BRANCH + COEXIST =================
h1 "Stage 2 — apply the kit (coexist)"
if [ "$IS_GIT" != 1 ]; then
  warn "no git repo — cannot apply safely. First:  git init && git add -A && git commit -m init  (then run again)."
  exit 0
fi

# --- Branch decision (B5): where does this land? A flag wins; otherwise a smart default. ---
# A new branch isolates a big first change so the main line stays clean until you review. But on a routine UPDATE
# of a project whose .claude/ is gitignored, a forced new branch is empty and pointless — the refresh lands on disk
# with no tracked diff to review, so a branch is pure noise on top of your working branch.
BASE="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)"
DEC_BR="$BRANCH_MODE"
if [ -z "$DEC_BR" ]; then
  if   [ "$KIT_PRESENT" != 1 ]; then DEC_BR=new     # first adopt: isolate the change, keep the main line clean
  elif [ "$TRACKED" != 1 ];     then DEC_BR=here    # update + untracked .claude: no tracked diff -> a branch is noise
  elif [ -t 0 ]; then                               # update + tracked: a real diff exists -> prefer new, but ask
    ask_yes "Apply on a NEW review branch? (no = apply on the current branch '$BASE')" && DEC_BR=new || DEC_BR=here
  else DEC_BR=new; fi                               # non-interactive + tracked: the safe default is a new branch
fi

if [ "$DEC_BR" = here ]; then WHERE="the current branch '$BASE'"; else WHERE="a new review branch (off '$BASE')"; fi
# Missing tools named before the mutation prompt, not after. It matters more here than on a fresh install: the
# settings MERGE is the step that needs jq/python, and without them an update replaces settings.json (backup
# kept) instead of merging — so a project's own hooks are dropped. Better to say that while it is still a
# choice. Report-only; never blocks.
[ -f "$SRC/eval/preflight.sh" ] && bash "$SRC/eval/preflight.sh"
if ! ask_yes "Apply the kit onto $WHERE now? (mutation; staged-not-committed, reversible with git)"; then
  h1 "Stopped"; sub "Stayed at Stage 1 — NOTHING CHANGED (read-only)."; exit 0
fi

if [ "$DEC_BR" = here ]; then
  BR="$BASE"                                          # $BR is referenced downstream; on 'here' it IS the current branch
  echo "  applying on the current branch: ${B}$BASE${R}  (no separate branch; staged, HEAD untouched until you commit)"
  BR_HANDOVER_LINE="Applied on the current branch: $BASE (the change set is STAGED-not-committed — review in 'git status' / your editor, then commit; HEAD untouched until you do)."
  GEN_WHERE="current branch $BASE"
  ADR_BR_STATUS="accepted (applied on current branch: $BASE — staged, not committed)"
  ONBRANCH_LINE="You are on your current branch $BASE with everything STAGED but NOT committed."
  ACCEPT_LINE="accept:   git commit -m 'adopt agentic kit'"
  DISCARD_LINE="discard:  git reset --hard HEAD   (un-stages everything; nothing was committed)"
else
  case "$BASE" in kit-adopt-*) warn "HEAD is a prior adopt branch ($BASE) — the review diff will be vs it, not your main line. Consider 'git checkout <main>' first." ;; esac
  TS="$(date +%Y%m%d-%H%M%S)"; BR="kit-adopt-$TS"
  # Second-resolution timestamp: two adopts within one second would collide and the second checkout would fail.
  # Bump a counter until the name is free (also covers a re-run after a discarded attempt left the branch behind).
  n=2; while git rev-parse --verify -q "refs/heads/$BR" >/dev/null 2>&1; do BR="kit-adopt-$TS-$n"; n=$((n+1)); done
  git checkout -b "$BR" >/dev/null 2>&1 || { echo "ERROR: could not open branch '$BR'."; exit 1; }
  echo "  handover branch: ${B}$BR${R}  (${BASE} stays clean)"
  BR_HANDOVER_LINE="Handover branch: $BR  ($BASE untouched; the change set is STAGED-not-committed — review in your editor / 'git status', then commit)."
  GEN_WHERE="branch $BR"
  ADR_BR_STATUS="accepted (handover branch: $BR)"
  ONBRANCH_LINE="You are on branch $BR with everything STAGED but NOT committed."
  ACCEPT_LINE="accept:   git commit -m 'adopt agentic kit'   then:  git checkout $BASE && git merge $BR"
  DISCARD_LINE="discard:  git reset --hard $BASE && git checkout $BASE && git branch -D $BR"
fi

mkdir -p .claude
# Every adopt installs the full payload; the only thing that varies is the .NET pattern skill below.
EXCL_A=""; EXCL_S=""
# Pre-2.0 migration: that install pruned by profile, so components are MISSING and this refresh restores them.
# The list is derived from a before/after disk diff rather than from a profile→pruned map, because the map
# (profiles.conf) no longer exists — and a diff also reports components added by the version bump itself,
# which is the honest thing to show: it names what actually appeared, not what a table predicts.
MIGRATE_MISSING=""
if [ -n "$LEGACY_PROFILE" ]; then
  for f in "$SRC"/agents/*.md;  do [ -e "$f" ] && [ ! -e ".claude/agents/$(basename "$f")" ] && MIGRATE_MISSING="$MIGRATE_MISSING agents/$(basename "$f")"; done
  for d in "$SRC"/skills/*/;    do [ -d "$d" ] && [ ! -d ".claude/skills/$(basename "$d")" ] && MIGRATE_MISSING="$MIGRATE_MISSING skills/$(basename "$d")"; done
fi
# The .NET pattern skill ships only for a dotnet backend. Prune it for generic on a FRESH adopt too (not only a
# recorded refresh) — otherwise a generic project silently carries a DevArch pattern skill it never uses.
[ "$KIT_STACK" = "generic" ] && EXCL_S="$EXCL_S devarch-module"
# #1 keepmine: your overlapping agents own those roles, so the kit's matching -csk agents are NOT installed.
[ "$COLLIDE_MODE" = keepmine ] && for b in $COLLIDE; do EXCL_A="$EXCL_A $b-csk.md"; done
# kit-owned trees: FORCE-refresh on a re-adopt (KIT_PRESENT) so kit updates land; never-overwrite on a fresh adopt
copy_noclobber "$SRC/agents"   .claude/agents   "$KIT_PRESENT" "$EXCL_A"; A_ADD=$ret_add; A_SKIP=$ret_skip
copy_noclobber "$SRC/skills"   .claude/skills   "$KIT_PRESENT" "$EXCL_S"; S_ADD=$ret_add; S_SKIP=$ret_skip
# EXCL_S keeps devarch-module from being COPIED, which is not the same as removing one already on disk. A
# refresh that records stack=generic while the skill sits installed leaves the project in a state the kit
# itself treats as impossible: kit_infer_shape reads the stack back OUT of that very directory when kit.conf
# is missing, route-hint scores it and recommends it, and it shows in the session's skill list. Measured on a
# real repo whose recorded stack is generic — the skill survived the refresh and a turn opened with
# "Use the `devarch-module` skill for this task", i.e. the wrong routing arrived by a second path after the
# backend agent had already been put right. `start.sh --generic` has always deleted it; a refresh now agrees.
if [ "$KIT_STACK" = "generic" ] && [ -d .claude/skills/devarch-module ]; then
  rm -rf .claude/skills/devarch-module 2>/dev/null && echo "  devarch-module removed (the recorded stack is generic)"
fi
copy_noclobber "$SRC/commands" .claude/commands "$KIT_PRESENT"; C_ADD=$ret_add; C_SKIP=$ret_skip
copy_noclobber "$SRC/hooks"    .claude/hooks    "$KIT_PRESENT"; H_ADD=$ret_add; H_SKIP=$ret_skip
copy_noclobber "$SRC/eval"     .claude/eval     "$KIT_PRESENT"; E_ADD=$ret_add; E_SKIP=$ret_skip
# Report the migration by what LANDED, not by what was missing: devarch-module is on the missing list of every
# generic project and must not be announced as restored when EXCL_S kept it out.
if [ -n "$MIGRATE_MISSING" ]; then
  MIGRATED=""
  for c in $MIGRATE_MISSING; do [ -e ".claude/$c" ] && MIGRATED="$MIGRATED $c"; done
  if [ -n "$MIGRATED" ]; then
    warn "pre-2.0 install (profile=$LEGACY_PROFILE): profile pruning was removed — completing the install"
    for c in $MIGRATED; do echo "      ${GR}+${R} $c"; done
  else
    echo "  pre-2.0 install (profile=$LEGACY_PROFILE): nothing was missing — the full set was already present"
  fi
fi
# #1 takeover: the kit's -csk owns the role, and the OLD agent's domain is IMPORTED into an active project skill
# (skills/<base>-local) that the kit agent applies — so nothing is lost from the working setup. The agent is then
# removed from the routing pool (Claude Code discovers .claude/agents/*.md, not subdirs) so routing is no longer
# ambiguous, and the raw original is kept under .claude/superseded/agents/ as a backup. The skill is a DRAFT to refine.
N_TAKEN=0
if [ "$COLLIDE_MODE" = takeover ] && [ -n "$COLLIDE" ]; then
  mkdir -p .claude/superseded/agents
  for b in $COLLIDE; do
    af=".claude/agents/$b.md"; [ -f "$af" ] || continue
    if [ -e ".claude/skills/$b-local/SKILL.md" ]; then       # idempotent: a prior takeover already imported it
      echo "  overlap: $b -> skill '$b-local' already present (kept); original re-backed up"
    else
      mkdir -p ".claude/skills/$b-local"
      kit_agent_to_skill "$af" "$b" > ".claude/skills/$b-local/SKILL.md"
      echo "  overlap: $b -> imported to skill '$b-local' (draft); kit's $b-csk owns routing"
    fi
    cp "$af" ".claude/superseded/agents/$b.md"; rm -f "$af"
    N_TAKEN=$((N_TAKEN+1))
  done
fi
# #1b takeover reference sweep — the rename above orphaned every project reference to the taken-over agents
# ($COLLIDE): "→ backend-expert" in CLAUDE.md, the "detail: docs/AGENTS.md" orchestration doc, etc. This is the ONE
# moment the kit knows the exact old→new map, so it COMPLETES the migration instead of leaving the user to chase
# dangling names. It rewrites each bare old name to its -csk id across CLAUDE.md's reference chain (its @imports +
# docs/…md paths), boundary-safe: `backend-expert` → `backend-expert-csk`, but `backend-expert-csk`/`-local` and
# `backend-expertise` are left intact (no double-suffix). Unreferenced design/audit docs and code comments are NOT
# touched (precise, no false positives). The edit lands on the adopt review branch — visible in the diff, revertible.
if [ "$N_TAKEN" -gt 0 ] && [ -f CLAUDE.md ]; then
  SWEEP="CLAUDE.md"
  for r in $(grep -oE '@?[A-Za-z0-9_./-]+\.md' CLAUDE.md 2>/dev/null | sed 's/^@//' | sort -u); do
    [ -f "$r" ] && [ "$r" != "CLAUDE.md" ] && SWEEP="$SWEEP $r"
  done
  SWEPT=0
  for b in $COLLIDE; do
    for f in $SWEEP; do
      i=0
      while grep -qE "(^|[^A-Za-z-])$b([^A-Za-z-]|$)" "$f" 2>/dev/null && [ "$i" -lt 5 ]; do
        sed -E "s/(^|[^A-Za-z-])$b([^A-Za-z-]|\$)/\1$b-csk\2/g" "$f" > "$f.kit-sweep" && mv "$f.kit-sweep" "$f"
        i=$((i+1)); SWEPT=$((SWEPT+1))
      done
      [ "$i" -gt 0 ] && echo "  ref-sweep: $b → $b-csk in $f"
    done
  done
  [ "$SWEPT" -gt 0 ] && h1 "Reference sweep: rewrote taken-over agent names to their -csk id across CLAUDE.md + referenced docs" \
                     || echo "  ref-sweep: no stale references in CLAUDE.md's chain"
fi
# Stack-compatible backend: non-.NET projects get the generic backend-expert-csk. A RECORDED stack always beats
# repo sniffing — a refresh of a 'dotnet' install must keep the DevArch-bound agent even when the .sln lives in
# ./backend and the sniffer reports "unknown". And never clobber a preserved project file.
WANT_GENERIC=0
if [ -n "$KIT_STACK" ]; then [ "$KIT_STACK" = "generic" ] && WANT_GENERIC=1
elif [ "$STACK" != ".NET" ]; then WANT_GENERIC=1; fi
if [ "$WANT_GENERIC" = 1 ] && [ -f "$SRC/agents-optional/backend-expert-generic.md" ] && [ -e .claude/agents/backend-expert-csk.md ]; then
  case " $SKIP_LIST " in
    *" .claude/agents/backend-expert-csk.md "*) warn "backend-expert-csk.md pre-existed (preserved) — generic variant NOT applied" ;;
    *) cp "$SRC/agents-optional/backend-expert-generic.md" .claude/agents/backend-expert-csk.md; echo "  backend-expert-csk -> generic variant (${KIT_STACK:-$STACK})" ;;
  esac
elif [ "$KIT_STACK" = "dotnet" ] && [ -e .claude/agents/backend-expert-csk.md ]; then
  echo "  backend-expert-csk kept on the .NET/DevArchitecture variant (recorded stack: dotnet)"
fi
chmod +x .claude/hooks/*.sh .claude/hooks/pre-commit .claude/hooks/commit-msg 2>/dev/null || true
[ -f "$HERE/VERSION" ] && cp "$HERE/VERSION" .claude/VERSION 2>/dev/null || true   # first-class marker so a future adopt detects a REFRESH
# Stale kit files: names the kit USED to ship and no longer does. `copy_noclobber` only ever adds, so a component
# removed or renamed upstream lives on in the project forever — and that is not cosmetic, because for all three
# kinds the NAME IS THE INVOCATION. A command's filename is what the / picker lists: after 1.11.0 renamed the
# commands, an un-pruned `review.md` sits beside `review-csk.md` and both show up. A SKILL's directory name is
# what the router scores — `route-hint.sh` ranks `.claude/skills/*/SKILL.md` by the trigger phrases inside, so a
# renamed skill left on disk competes with its own replacement for every prompt. Measured on this machine: a
# 2.6.0 install upgraded in place kept `skills/vps-deploy` beside the new `skills/deploy`, both with live
# triggers, and the installer said nothing — it had counted the leftover as one of the PROJECT's own skills.
# That silence is the bug; skills were simply outside the scan below.
#
# They are REPORTED, never deleted: this installer deliberately preserves a pre-existing project file that
# happens to sit under a kit name, so a name in the old manifest is not proof the file is ours. Deleting on that
# assumption would destroy the user's own work; naming it costs them one command.
if [ -f .claude/kit-manifest.txt ]; then
  STALE=""
  while IFS= read -r entry; do
    case "$entry" in commands/*|agents/*|skills/*) ;; *) continue ;; esac
    [ -e "$SRC/${entry#*/}" ] && continue                       # still shipped under the same folder? keep
    [ -e "$SRC/$entry" ] && continue
    [ -e ".claude/$entry" ] && STALE="$STALE $entry"
  done < .claude/kit-manifest.txt
  if [ -n "$STALE" ]; then
    echo
    echo "  ⚠️  installed by an older kit and no longer shipped:$STALE"
    echo "     The name is the invocation: a leftover COMMAND still lists in the / picker (/review twice), and a"
    echo "     leftover SKILL still matches prompts, so it competes with whatever replaced it."
    echo "     Nothing is deleted for you — one of these may be a file you customised. To drop them all:"
    printf '       rm -r'; for e in $STALE; do printf ' .claude/%s' "$e"; done; echo
  fi
fi

# Install manifest — the names the KIT ships (see start.sh for the full rationale). Generated from the PAYLOAD,
# never from disk: this installer deliberately PRESERVES a pre-existing project file under a kit name, and a
# disk-read manifest would brand that project file "kit-owned". Consumers: the doctor readiness check and the
# skill trust gate.
{ for d in "$SRC"/skills/*/;     do [ -d "$d" ] && echo "skills/$(basename "$d")"; done
  for f in "$SRC"/agents/*.md;   do [ -e "$f" ] && echo "agents/$(basename "$f")"; done
  for f in "$SRC"/commands/*.md; do [ -e "$f" ] && echo "commands/$(basename "$f")"; done
} > .claude/kit-manifest.txt 2>/dev/null || true

# Record (or re-record) the backend pattern so the NEXT update keeps it. Rewritten WITHOUT the pre-2.0
# 'profile=' key: dropping it is what retires the migration notice, so a second refresh stays quiet.
{ echo "# Written by the kit installer. The updater reads this to keep the project's backend pattern."
  echo "stack=${KIT_STACK:-$([ "$WANT_GENERIC" = 1 ] && echo generic || echo dotnet)}"
  echo "installer=${KIT_INSTALLER:-adopt.sh}"
  echo "version=$( [ -f "$HERE/VERSION" ] && head -1 "$HERE/VERSION" || echo unknown )"
} > .claude/kit.conf

h1 "Coexist summary"
row "kit agents (-csk)" "+$A_ADD added$([ "$A_SKIP" != 0 ] && echo " · $A_SKIP skipped")"
row "skills"            "+$S_ADD$([ "$S_SKIP" != 0 ] && echo " · $S_SKIP skipped")"
row "commands"           "+$C_ADD$([ "$C_SKIP" != 0 ] && echo " · $C_SKIP skipped")"
row "hooks"           "+$H_ADD$([ "$H_SKIP" != 0 ] && echo " · $H_SKIP skipped")"
row "eval"               "+$E_ADD"
row "project agents"     "$N_PAGENTS$([ "${N_TAKEN:-0}" != 0 ] && echo " ($N_TAKEN imported to skills/<name>-local drafts; originals backed up in superseded/)") — the rest UNTOUCHED"
case "$COLLIDE_MODE" in
  keepmine) [ "$N_COLLIDE" != 0 ] && row "overlap" "keepmine — your agents own: $COLLIDE (kit's -csk for these NOT installed)" ;;
  coexist)  [ "$N_COLLIDE" != 0 ] && warn "overlap: $COLLIDE — BOTH kept; routing between your agent and the kit's -csk stays ambiguous" ;;
esac
[ -n "$SKIP_LIST" ] && { warn "conflicting files (the project's was PRESERVED, the kit's skipped):"; for s in $SKIP_LIST; do printf '     %s- %s%s\n' "$D" "$s" "$R"; done; }

# ============ [STAGE 3] DISCIPLINE ACTIVE + SETTINGS MERGE ============
h1 "Stage 3 — activate the kit discipline (without touching the project CLAUDE.md) + settings merge"

# 3a) DISCIPLINE.md: install the discipline half of the payload CLAUDE.md as a separate, FLAT file —
#     everything above the sentinel line. Contains NO @import (leaf) -> no 4-hop trap.
if [ -f "$SRC/CLAUDE.md" ]; then
  kit_require_sentinel "$SRC/CLAUDE.md"
  kit_discipline_of "$SRC/CLAUDE.md" > .claude/DISCIPLINE.md
  echo "  DISCIPLINE.md written (kit discipline only; the project template stays out of it)"
fi

# 3b) single-line @import into the project CLAUDE.md (if present DON'T touch content, only prepend; if absent create).
if [ -f CLAUDE.md ]; then
  if kit_has_import CLAUDE.md; then echo "  CLAUDE.md: @import already present (idempotent)"
  elif kit_claude_md_is_legacy CLAUDE.md; then
    # Pre-1.1: the whole discipline sits inline. Blindly prepending the @import would load it TWICE, and leaving
    # it alone means discipline updates never reach this project. Offer the exact swap, with a backup.
    warn "CLAUDE.md carries the discipline INLINE (pre-1.1 layout) — discipline updates cannot reach it."
    BND="$(kit_legacy_boundary CLAUDE.md | head -1)"
    if [ -n "$BND" ] && [ "$BND" -gt 1 ] 2>/dev/null \
       && head -n "$((BND-1))" CLAUDE.md | grep -q '^## Four working principles' \
       && head -n "$((BND-1))" CLAUDE.md | grep -q '^### 4\.5 '; then
      printf '     %sthe inline block is lines 1-%s; your project section starts at line %s%s\n' "$D" "$((BND-1))" "$BND" "$R"
      if ask_yes "  Replace that inline block with the single @import line? (a backup is written; this branch is reviewable)"; then
        BK=".claude/CLAUDE.md.pre-kit-$TS"
        cp CLAUDE.md "$BK"
        { printf '<!-- kit discipline · on conflict the project rules BELOW win -->\n%s\n\n' "$IMPORT_LINE"
          tail -n +"$BND" CLAUDE.md; } > CLAUDE.md.kit-tmp && mv CLAUDE.md.kit-tmp CLAUDE.md
        echo "  CLAUDE.md migrated -> @import + your project section (backup: $BK)"
      else
        printf '     %sSkipped. Discipline updates will NOT reach this project until you migrate.%s\n' "$D" "$R"
      fi
    else
      printf '     %sProject heading not found — migrate by hand: delete everything above it, leave only:  %s%s\n' "$D" "$IMPORT_LINE" "$R"
    fi
  else
    { printf '<!-- kit discipline · on conflict the project rules BELOW win -->\n%s\n\n' "$IMPORT_LINE"; cat CLAUDE.md; } > CLAUDE.md.kit-tmp && mv CLAUDE.md.kit-tmp CLAUDE.md
    echo "  CLAUDE.md: single-line @import prepended (project content untouched)"
  fi
else
  printf '%s\n\n# CLAUDE.md — <PROJECT NAME>\n\n## Project\n<One sentence: what it does, for whom.>\n' "$IMPORT_LINE" > CLAUDE.md
  echo "  CLAUDE.md was missing -> @import + project template created"
fi

# 3c) settings.json HOOK-AWARE merge: the kit OWNS its hooks (any command referencing .claude/hooks/), so on update
# kit hook entries are REFRESHED (new events + current timeouts land; stale kit entries drop) while the project's OWN
# custom hooks and permissions are PRESERVED. Non-hook keys deep-merge (project scalar wins, arrays concat+dedup).
# Blind concat would leave a stale duplicate (e.g. an old timeout-10 context-usage hook that then times out).
KSET="$SRC/settings.json"; PSET=".claude/settings.json"
JQ_MERGE='
def ddedup: reduce .[] as $x ([]; if any(.[]; .==$x) then . else .+[$x] end);
def dm(a;b): reduce (b|keys_unsorted[]) as $k (a;
  if (.[$k]|type)=="object" and (b[$k]|type)=="object" then .[$k]=dm(.[$k];b[$k])
  elif (.[$k]|type)=="array" and (b[$k]|type)=="array" then .[$k]=((.[$k]+b[$k])|ddedup)
  else .[$k]=b[$k] end);
def is_kit: ((.hooks // []) | map((((.command // "") + " " + ((.args // []) | join(" "))) | contains(".claude/hooks/"))) | any);   # command AND args: tolerates either wiring shape
def merge_hooks(kh;ph):
  (((kh|keys_unsorted)+(ph|keys_unsorted))|unique) as $e
  | reduce $e[] as $k ({}; .[$k]=((kh[$k] // [])+((ph[$k] // [])|map(select(is_kit|not)))));
(dm($k[0]; $p[0])) | .hooks=merge_hooks(($k[0].hooks // {}); ($p[0].hooks // {}))'
if [ ! -f "$PSET" ]; then
  [ -f "$KSET" ] && { cp "$KSET" "$PSET"; echo "  settings.json: was missing in the project -> the kit's was installed"; }
# Probed by RUNNING — the python3 arm below already does exactly this, with a comment about the Store
# redirector. Selecting on `command -v` alone made a broken jq abort the merge and wire NOTHING, while the
# run still ended in OK + PROOF and HANDOVER recorded "kit hooks REFRESHED". Measured: 10 hook entries with
# a working jq, 10 via the python3 arm with jq absent, 0 with a jq that resolves and fails.
elif command -v jq >/dev/null 2>&1 && printf '{}' | jq -e . >/dev/null 2>&1; then
  if ! jq -e . "$PSET" >/dev/null 2>&1; then
    warn "settings.json: existing file is INVALID JSON -> merge ABORT (no silent overwrite). Fix it by hand first."
  else
    MERGED="$(jq -n --slurpfile p "$PSET" --slurpfile k "$KSET" "$JQ_MERGE" 2>/dev/null || true)"
    if [ -n "$MERGED" ] && printf '%s' "$MERGED" | jq -e . >/dev/null 2>&1; then
      printf '%s\n' "$MERGED" > "$PSET"; echo "  settings.json: hook-aware MERGE via jq (kit hooks refreshed - custom hooks/permissions PRESERVED)"
    else
      warn "settings.json: jq merge failed -> project setting PRESERVED (not overwritten)."
    fi
  fi
elif PYBIN=""; for _pc in python3 python py; do
       # Pick the first that RUNS, not the first that EXISTS. Windows puts a Microsoft Store redirector stub
       # named python3 (and python) on PATH by default; `command -v` stops there and never reaches `py`, the
       # Windows Python Launcher, which on a machine with real Python installed is the one that works. The old
       # order therefore selected the stub on exactly the machines this branch exists for, and the merge below
       # fell through to "merge failed -> project setting PRESERVED" with no hint why. Args are always passed:
       # an argless run of that stub opens the Microsoft Store instead of failing.
       if command -v "$_pc" >/dev/null 2>&1 && printf '{}' | "$_pc" -c 'import sys,json;json.load(sys.stdin)' >/dev/null 2>&1; then
         PYBIN="$(command -v "$_pc")"; break
       fi
     done; [ -n "$PYBIN" ]; then
  if "$PYBIN" - "$KSET" "$PSET" "$PSET.tmp" 2>/dev/null <<'PYEOF' && [ -s "$PSET.tmp" ]; then
import json,sys
kit=json.load(open(sys.argv[1])); proj=json.load(open(sys.argv[2]))
def ddedup(a):
  out=[]
  for x in a:
    if x not in out: out.append(x)
  return out
def dm(a,b):
  r=dict(a)
  for k,v in b.items():
    if isinstance(r.get(k),dict) and isinstance(v,dict): r[k]=dm(r[k],v)
    elif isinstance(r.get(k),list) and isinstance(v,list): r[k]=ddedup(r[k]+v)
    else: r[k]=v
  return r
def is_kit(e): return any(".claude/hooks/" in (h.get("command") or "") for h in (e.get("hooks") or []))
def merge_hooks(kh,ph):
  evs=list(dict.fromkeys(list(kh)+list(ph))); o={}
  for e in evs: o[e]=list(kh.get(e,[]))+[x for x in ph.get(e,[]) if not is_kit(x)]
  return o
m=dm(kit,proj); m["hooks"]=merge_hooks(kit.get("hooks",{}),proj.get("hooks",{}))
open(sys.argv[3],"w").write(json.dumps(m,indent=2)+"\n")
PYEOF
    mv "$PSET.tmp" "$PSET"; echo "  settings.json: hook-aware MERGE via ${PYBIN##*/} (kit hooks refreshed - custom hooks/permissions PRESERVED)"
  else
    rm -f "$PSET.tmp"; warn "settings.json: ${PYBIN##*/} merge failed -> project setting PRESERVED (not overwritten)."
  fi
else
  # No jq AND no python3 (common on Windows Git-Bash) — we can't parse JSON to merge. But if the project's
  # settings.json carries ONLY kit-owned hooks (every "command" points at .claude/hooks/ — no hook the user added),
  # it is safe to REPLACE it wholesale with the kit's current settings: stale hooks/timeouts refresh and new events
  # (SessionStart) wire up, with zero dependencies. A timestamped backup is kept so nothing is lost (re-add any
  # custom permissions from it). If a FOREIGN hook is present we do NOT guess — leave the file, drop a kit reference.
  if grep '"command"' "$PSET" | grep -qv '\.claude/hooks/'; then
    cp "$KSET" "$PSET.kit" 2>/dev/null || true
    warn "settings.json: no jq/python3 and a non-kit hook is present -> cannot merge safely. Kit reference at .claude/settings.json.kit -> reconcile by hand."
  else
    BAK="$PSET.bak-$(date +%Y%m%d-%H%M%S)"; cp "$PSET" "$BAK" 2>/dev/null || true
    cp "$KSET" "$PSET"
    echo "  settings.json: no jq/python3 -> kit-only settings REPLACED with the current kit's (backup: $BAK — re-add any custom permissions from it)"
  fi
fi

# ============ [STAGE 4] GIT-HOOK ARMING (SHIM) + PROOF ============
h1 "Stage 4 — arm the git gates (SHIM via husky) + PROOF"

# 4a) location of the existing hook chain (the shim calls this too)
ORIG_HOOKS=""
if [ -n "$CURHP" ]; then ORIG_HOOKS="$CURHP"
elif [ -d .husky ]; then ORIG_HOOKS=".husky"
elif [ -x .git/hooks/pre-commit ] || [ -x .git/hooks/commit-msg ]; then ORIG_HOOKS=".git/hooks"; fi
# never shim the kit onto its OWN hooks (re-adopt) — the shim would exec itself and recurse on every commit
case "$ORIG_HOOKS" in .claude/hooks|.claude/git-shim) ORIG_HOOKS="" ;; esac

if [ -z "$ORIG_HOOKS" ]; then
  git config core.hooksPath .claude/hooks
  echo "  core.hooksPath -> .claude/hooks (no existing hook chain)"
else
  mkdir -p .claude/git-shim
  for hk in pre-commit commit-msg; do
    cat > ".claude/git-shim/$hk" <<SHIM
#!/usr/bin/env bash
# kit git-shim: runs the kit hook + the existing project chain IN ORDER (if one fails git stops).
set -e
H="\$(basename "\$0")"
ROOT="\$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
K="\$ROOT/.claude/hooks/\$H"; [ -x "\$K" ] && "\$K" "\$@"
P="\$ROOT/$ORIG_HOOKS/\$H"
if   [ -x "\$P" ]; then "\$P" "\$@"
elif [ -f "\$P" ]; then bash "\$P" "\$@"; fi
exit 0
SHIM
    chmod +x ".claude/git-shim/$hk"
  done
  git config core.hooksPath .claude/git-shim
  echo "  SHIM installed -> core.hooksPath=.claude/git-shim (kit + $ORIG_HOOKS run together)"
fi
[ "$GITKIND" = "worktree/submodule (.git file)" ] && warn "worktree/submodule: core.hooksPath may also affect the main checkout (git design)."

# 4b) PROOF — is the kit actually working? (not a claim)
h1 "Stage 4b — PROOF"
PROOF_OK=1; HP="$(git config --get core.hooksPath 2>/dev/null || echo .claude/hooks)"
# 1) trace-scan git hook: a staged AI trace MUST be BLOCKED (NO real commit; run the hook directly)
# move any allowlist aside so PROOF measures the SCANNER itself, not the project's own exemptions (else a loosened repo fails the proof)
[ -f .trace-allowlist.txt ] && mv .trace-allowlist.txt .trace-allowlist.txt.proofbak 2>/dev/null
printf 'Co-Authored%s: Test <x@y.z>\n' '-By' > .kit-proof.txt   # not contiguous in source (so the trace hook doesn't block itself); full at runtime
# `-f`, and the staging is CHECKED. Without both, a project whose .gitignore happens to cover this probe file
# (`*.txt`, `.kit-*`, anything broad) stages nothing, the scanner reads an empty diff, the hook exits 0 — and
# the proof then accuses a perfectly working gate of letting an AI trace through. Reproduced here: identical
# repos, the only difference a .gitignore line for the probe, and PROOF-1 flipped from BLOCKED to FAILED.
# A proof that can fail for a reason unrelated to what it proves is worse than no proof: it sends the reader
# hunting a gate that is fine. (The allowlist is already handled above by moving it aside.)
if ! git add -f .kit-proof.txt >/dev/null 2>&1 || [ -z "$(git diff --cached --name-only -- .kit-proof.txt 2>/dev/null)" ]; then
  echo "  ~  PROOF-1: skipped — could not stage the probe file (nothing was measured)"
elif bash "$HP/pre-commit" >/tmp/kitproof.$$ 2>&1; then
  warn "PROOF-1 FAILED: the trace scan LET THROUGH the AI trace"; PROOF_OK=0
elif grep -qiE 'TRACE-SCANNER|Commit stopped|forbidden' /tmp/kitproof.$$; then
  echo "  OK · PROOF-1: staged AI trace BLOCKED by the trace scan"
else echo "  ~  PROOF-1: hook blocked ($(head -1 /tmp/kitproof.$$ 2>/dev/null))"; fi
git reset -q .kit-proof.txt 2>/dev/null; rm -f .kit-proof.txt /tmp/kitproof.$$
[ -f .trace-allowlist.txt.proofbak ] && mv .trace-allowlist.txt.proofbak .trace-allowlist.txt 2>/dev/null
# 2) guard-bash git-approval gate: keyless 'git commit' -> block
if printf '{"tool_name":"Bash","tool_input":{"command":"git commit -m x"}}' | bash .claude/hooks/guard-bash.sh >/dev/null 2>&1; then
  warn "PROOF-2 FAILED: guard-bash LET THROUGH the keyless commit"; PROOF_OK=0
else echo "  OK · PROOF-2: guard-bash BLOCKED the keyless 'git commit' (holds in auto/bypass too)"; fi
# 3) can the kit agents + discipline be loaded
NCCK="$(ls .claude/agents/*-csk.md 2>/dev/null | wc -l | tr -d ' ')"
if [ "${NCCK:-0}" -ge 1 ]; then echo "  OK · PROOF-3: $NCCK kit agents (-csk) installed + discoverable"; else warn "PROOF-3: no kit agent"; PROOF_OK=0; fi
if [ -s .claude/DISCIPLINE.md ] && grep -qF '@.claude/DISCIPLINE.md' CLAUDE.md; then echo "  OK · PROOF-4: DISCIPLINE.md loaded + @import-ed from CLAUDE.md"; else warn "PROOF-4: discipline not linked"; PROOF_OK=0; fi
# PROOF-5: a takeover renamed the project's agents to `-csk`, but CLAUDE.md — or an orchestration doc it points to
# (e.g. "detail: docs/AGENTS.md") — may still name the OLD bare agent, which now matches no installed agent, so
# delegation to it silently fails. We follow CLAUDE.md's reference chain (its @imports + docs/…md paths) so the
# pointed-to docs are checked too; the migration cannot safely rewrite hand-authored prose, so we TELL, precisely.
if [ -f CLAUDE.md ] && ls .claude/agents/*-csk.md >/dev/null 2>&1; then
  PULL_AGENTS=" commit-agent-csk session-manager-csk "   # invoked explicitly, not auto-delegated
  SCAN="CLAUDE.md"
  for r in $(grep -oE '@?[A-Za-z0-9_./-]+\.md' CLAUDE.md 2>/dev/null | sed 's/^@//' | sort -u); do
    [ -f "$r" ] && [ "$r" != "CLAUDE.md" ] && SCAN="$SCAN $r"
  done
  # This was agent × document with `sed|head|tr` per agent and `grep|cut|tr|sed` per PAIR — 12 agents × 7 docs ×
  # 4 spawns is ~340 processes to discover, almost always, that nothing is stale. doctor.sh carried the identical
  # loop and was converted to two awk passes in 2.0.1; adopt.sh kept it, which is why an update still crawled on
  # Git Bash. Same conversion, same output (agent order, then file order, comma-joined line numbers).
  STALE=""; STALE_PULL=""
  CSK_AGENT_BASES="$(awk '
    FNR==1 { files[++nf]=FILENAME }
    !got[FILENAME] && /^name:[[:space:]]*/ {
      n=$0; sub(/^name:[[:space:]]*/,"",n); gsub(/[^a-zA-Z0-9-]/,"",n)
      if (n != "") { nm[FILENAME]=n; got[FILENAME]=1 }
    }
    END {
      for (i=1;i<=nf;i++) {
        f=files[i]; n=(f in nm) ? nm[f] : ""
        if (n=="") { n=f; sub(/\.md$/,"",n); sub(/.*\//,"",n) }
        if (n ~ /-csk$/) { b=n; sub(/-csk$/,"",b); print b "\t" n }
      }
    }' .claude/agents/*-csk.md 2>/dev/null)"
  export CSK_AGENT_BASES
  while IFS="$(printf '\t')" read -r base aname f lines; do
    [ -n "$base" ] || continue
    entry="
     ↳ \"$base\" → \"$aname\"  ($f line(s): $lines)"
    case "$PULL_AGENTS" in *" $aname "*) STALE_PULL="$STALE_PULL$entry" ;; *) STALE="$STALE$entry" ;; esac
  done <<EOF
$(awk '
  BEGIN {
    n = split(ENVIRON["CSK_AGENT_BASES"], rows, "\n"); k=0
    for (i=1;i<=n;i++) { if (rows[i]=="") continue; split(rows[i], a, "\t"); k++; base[k]=a[1]; full[k]=a[2] }
    nb=k
  }
  FNR==1 { order[++nf]=FILENAME }
  {
    for (i=1;i<=nb;i++)
      if ($0 ~ ("(^|[^a-zA-Z-])" base[i] "([^a-zA-Z-]|\$)"))
        hit[i, FILENAME] = (hit[i, FILENAME]=="" ? FNR : hit[i, FILENAME] "," FNR)
  }
  END {
    for (i=1;i<=nb;i++)
      for (j=1;j<=nf;j++)
        if ((i, order[j]) in hit) print base[i] "\t" full[i] "\t" order[j] "\t" hit[i, order[j]]
  }' $SCAN 2>/dev/null)
EOF
  [ -n "$STALE" ] && warn "PROOF-5: CLAUDE.md (or a doc it references) names auto-delegated agent(s) by an old bare id — rename each to its -csk id, else delegation to them silently fails:$STALE"
  [ -n "$STALE_PULL" ] && warn "PROOF-5: CLAUDE.md (or a referenced doc) names pull-only agent(s) by an old bare id (still work; rename for consistency):$STALE_PULL"
fi
[ "$PROOF_OK" = 1 ] && h1 "PROOF: kit 100% ACTIVE — gates armed, agents + discipline loaded" || warn "PROOF: some gates could not be verified (see above)"

# ============ [STAGE B] APPLY THE DECISIONS ============
h1 "Stage B — apply the decisions"
# #3 loosen trace gate -> repo-root .trace-allowlist.txt (co-author/sign-off exempt from the trace scan)
if [ "$DEC3" = loosen ]; then
  { [ -f .trace-allowlist.txt ] && cat .trace-allowlist.txt; printf 'Co-Authored%s\n' '-By'; } | sort -u > .trace-allowlist.txt.t && mv .trace-allowlist.txt.t .trace-allowlist.txt
  echo "  #3 loosen -> .trace-allowlist.txt (co-author trailer exempt)"
else echo "  #3 keep -> full trace scan"; fi
# #4 share/hide — the payload is ALWAYS committed to the review branch (so the diff is real + rollback stays clean);
# 'hide' becomes a post-merge follow-up in HANDOVER. (Gitignoring .claude BEFORE the commit would drop it from the
# review diff and leave it untracked after a rollback -> 'project untouched' would be a lie.)
HIDE_NOTE=""
if [ "$DEC4" = hide ]; then
  HIDE_NOTE="Keep the kit local after merging:  git rm -r --cached .claude CLAUDE.md  &&  printf '.claude/\nCLAUDE.md\n' >> .gitignore  &&  git commit -m 'kit: keep local'"
  echo "  #4 hide -> recorded; .claude stays TRACKED on the branch (rollback-safe). Post-merge steps in HANDOVER."
else echo "  #4 share -> .claude tracked + shared with the team"; fi
# #1 merge: document (NO automatic risky merge — red-team; merging is a human-approved follow-up)
case "$DEC1" in
  takeover) MERGE_NOTE="takeover: the kit's -csk agents own the overlapping roles ($COLLIDE); each old agent's domain was imported to a draft skill (skills/<name>-local) the kit agent applies, and the original backed up under superseded/agents/ — refine the drafts" ;;
  keepmine) MERGE_NOTE="keepmine: your agents own the overlapping roles ($COLLIDE); the kit's matching -csk agents were not installed" ;;
  *)        MERGE_NOTE="keep: project + kit agents side by side (no overlaps, or overlaps left to coexist)" ;;
esac
# #7 off-repo transfer: paste from the user (interactive; skipped on non-TTY and under --yes)
OFFREPO_TEXT=""
if [ "$DEC7" = transfer ] && [ -t 0 ] && [ "${ASSUME_YES:-0}" != 1 ]; then
  h1 "#7 off-repo decisions — paste them here"
  sub "Write the decisions made in chat/on the web but NOT in the repo. When done, an EMPTY line (Enter)."
  while IFS= read -r line; do [ -z "$line" ] && break; OFFREPO_TEXT="$OFFREPO_TEXT
- $line"; done
  [ -n "$OFFREPO_TEXT" ] && echo "  #7 -> $(printf '%s' "$OFFREPO_TEXT" | grep -c .) lines will go into HANDOVER" || echo "  #7 -> empty"
fi

# ============ [STAGE 5] HANDOVER.md + ADR (decisions persist) ============
h1 "Stage 5 — HANDOVER.md + ADR (handover persists; decisions are not lost)"
mkdir -p docs docs/adr
DATE_H="$(date +%Y-%m-%d)"
# compute the decision values first (avoid inner-quote/command-sub tangle in the heredoc)
case "$DEC1" in takeover) D1='takeover (kit -csk owns overlaps; your agents imported to <name>-local skills, originals in superseded/)';; keepmine) D1='keepmine (your agents own overlaps)';; none) D1='none';; *) D1='keep (coexist)';; esac
D2='project wins'   # precedence is fixed (DEC2 not overridable) — no false 'kit wins' record
D3="$([ "$DEC3" = loosen ] && echo 'loosen (.trace-allowlist written)' || echo 'keep (full)')"
D4="$([ "$DEC4" = hide ] && echo 'hide (gitignore)' || echo 'keep sharing')"
D5="$([ -n "$ORIG_HOOKS" ] && echo "SHIM ($ORIG_HOOKS)" || echo 'direct')"
D6="$([ "$DEC6" = absolute ] && echo 'absolute 0/0/0/0' || echo 'baseline+regression')"
case "$DEC7" in transfer) D7='transferred (below)';; skip) D7='knowingly missing';; *) D7='local + ask';; esac
HOOKDESC="$([ -n "$ORIG_HOOKS" ] && echo "SHIM (kit + $ORIG_HOOKS together)" || echo '.claude/hooks direct')"
if [ -n "${OFFREPO_TEXT:-}" ]; then OFFSEC="$OFFREPO_TEXT"
elif [ "$OFFREPO" = 1 ]; then OFFSEC="> WARNING: no local .claude/CLAUDE.md -> decisions may also be in chat/on the web; the tool COULD NOT SEE them.
<!-- Write off-repo decisions here; move the important ones under docs/adr/. -->"
else OFFSEC="<!-- Write potentially in-chat decisions here; move the important ones under docs/adr/. -->"; fi

# 5a) HANDOVER.md — mechanical fact (verifiable) + human section (NO LLM signature)
cat > docs/HANDOVER.md <<HAND
# Handover Note (HANDOVER) — $DATE_H

> This document records what the tool did MECHANICALLY (verifiable) + marks the HUMAN
> sections you need to fill in. The tool does NOT SIGN off anything as "done".

## What was handed over (mechanical)
- Kit agents: $NCCK (-csk namespace; no clash with project agents).
- Project agents: $N_PAGENTS — UNTOUCHED, in place + active (recursive discovery).
- Discipline: .claude/DISCIPLINE.md + @import into the project CLAUDE.md (content untouched).
- settings.json: hook-aware merge (kit hooks REFRESHED to current — new events + timeouts land; your own custom hooks/permissions PRESERVED).
- Git gates: $HOOKDESC.
- Overlapping roles: $MERGE_NOTE.
- $BR_HANDOVER_LINE

## Decisions made (smart suggestion; review/override in Stage B)
| # | Decision | Value |
|---|---|---|
| 1 | Role clash | $D1 |
| 2 | Precedence | $D2 (axis-by-axis) |
| 3 | Trace gate | $D3 |
| 4 | Share/hide | $D4 |
| 5 | Git hook | $D5 |
| 6 | Brownfield DoD | $D6 |
| 7 | Off-repo | $D7 |

## CONFIRM (the tool cannot verify — you check)
- [ ] Are the inherited project rules/agents UP TO DATE? (stale rule = regression)
- [ ] Overlapping roles (project + kit same job): which one to use / merge?
- [ ] Has the staged change set been reviewed (editor's Changes panel / git status) before committing?
${HIDE_NOTE:+- [ ] HIDE chosen — after merge run:  $HIDE_NOTE}

## Off-repo / in-chat decisions
$OFFSEC

---
Generated: kit adopt · $DATE_H · $GEN_WHERE  (apart from this line there is NO tool SIGNATURE)
HAND
echo "  docs/HANDOVER.md written"

# 5b) ADR-0001 — the handover itself is a persistent decision (never-overwrite)
ADR1="docs/adr/0001-agentic-kit-adoption.md"
if [ ! -e "$ADR1" ]; then
  cat > "$ADR1" <<ADR
# ADR-0001: The Agentic Kit was handed over to this project

- Date: $DATE_H
- Status: $ADR_BR_STATUS

## Context
The existing project was equipped for agentic work with the standard kit under a "team-to-team handover" logic.
Goal: don't break the project, don't lose decisions made, don't leave the kit passive (hybrid).

## Decision
- Kit agents were installed under the -csk namespace; project agents preserved side by side, untouched.
- Kit discipline active via .claude/DISCIPLINE.md + @import; the project CLAUDE.md untouched.
- On rule conflicts the PROJECT wins (axis-by-axis).
- Git gates: $HOOKDESC.
- Every change is on a reviewable git branch; rollback = git.

## Consequence
From now on decisions are written as ADRs under docs/adr/, NOT in chat (persistence).
Inherited stale rules are subject to "confirm"; not authoritative until verified with code.
ADR
  echo "  $ADR1 written (persistent handover decision)"
else
  echo "  $ADR1 already exists — untouched (never-overwrite)"
fi

git add .claude CLAUDE.md docs >/dev/null 2>&1
[ -e .gitignore ] && git add .gitignore >/dev/null 2>&1
[ -e .trace-allowlist.txt ] && git add .trace-allowlist.txt >/dev/null 2>&1
# NO auto-commit: the change set stays STAGED-but-uncommitted on branch $BR, so every added/changed file shows up
# in your editor's Source Control / Changes panel for review. HEAD is untouched until you commit yourself.

h1 "Review in your editor — nothing committed yet"
row "staged" "$(git diff --cached --stat 2>/dev/null | tail -1 || echo '(none)')"
sub "$ONBRANCH_LINE"
sub "see it:   open the Source Control / Changes panel (every added + changed file is listed)  ·  or: git status"
sub "$ACCEPT_LINE"
sub "$DISCARD_LINE"
warn "If Claude Code is running in this project, run /compact (or /clear) — CLAUDE.md and the discipline reload"
printf '     %son /compact and /clear in the same process, so a session opened before this run stops quoting the old rules (no restart needed).%s\n' "$D" "$R"
