#!/usr/bin/env bash
# ship-init.sh — generate a project's FROZEN ship wrapper (<root>/.claude/scripts/ship.sh).
# audience: AI coding agents first.
#
# WHAT IT EMITS: a thin DATA wrapper that pins this project's frozen land facts and delegates ALL
# logic to the shared brain (finish-branch.sh `land`/`drift`). The wrapper carries ZERO orchestration
# — a brain change updates every project at once. This generator is the ONLY thing that writes that
# wrapper, so the wrapper shape lives in one place (here), not hand-copied per project.
#
# TWO-PHASE, FAIL-CLOSED on the one judgment call (land mode):
#   Phase A (no --mode)  → SNIFF objective facts + mode EVIDENCE, print a JSON proposal, write
#                          NOTHING, exit 10. The agent reads the evidence and decides the mode.
#   Phase B (--mode M)   → validate + emit the wrapper + register the local exclude + a smoke test.
# Never silently picks merge-to-main (the catastrophic direction: it pushes main; pr only fails
# closed). merge-to-main REQUIRES a deploy anchor (sniffed or --anchor); absent → refuse (exit 3).
#
# Usage:
#   ship-init.sh <project_root>                         # Phase A: propose (exit 10), writes nothing
#   ship-init.sh <project_root> --mode pr|merge-to-main \
#                [--base B] [--testcmd T] [--anchor KIND:VAL] [--depcmd D] \
#                [--delivery none|local-script [--delivery-cmd C]] [--force]   # Phase B: emit
# Delivery is a DECLARED kind, never an opaque command — merge-to-main requires --delivery
# explicitly ('none' is a real declared value, not omission); pr mode's delivery is implicit ('pr').
set -uo pipefail

# Live install path. $HOME keeps one source working on every seat; on the owner laptop
# this resolves to /home/user/.claude/workflows/lib/finish-branch.sh.
BRAIN="${FINISH_BRANCH_BRAIN:-$HOME/.claude/workflows/lib/finish-branch.sh}"

die() { echo "ship-init: $1" >&2; exit "${2:-3}"; }
jstr() { local s=${1//\\/\\\\}; s=${s//\"/\\\"}; s=${s//$'\n'/ }; printf '%s' "$s"; }

ROOT="" MODE="" BASE="" TESTCMD="" ANCHOR="" DEPCMD="" FORCE=0 DEPCMD_SET=0
DEPLOYCMD="" E2ECMD="" PROMOTE="" DELIVERY="" DELIVERYCMD="" OUTNAME="ship.sh"
[[ $# -ge 1 ]] || die "usage: ship-init.sh <project_root> [--mode pr|merge-to-main|deploy-verify] [--base B] [--testcmd T] [--anchor KIND:VAL] [--depcmd D] [--delivery none|local-script [--delivery-cmd C]] [--deploycmd C --e2ecmd C --promote pr|merge-to-main] [--out <filename>] [--force]"
ROOT=$1; shift
while [[ $# -gt 0 ]]; do case "$1" in
  --mode) MODE=$2; shift 2;; --base) BASE=$2; shift 2;; --testcmd) TESTCMD=$2; shift 2;;
  --anchor) ANCHOR=$2; shift 2;; --depcmd) DEPCMD=$2; DEPCMD_SET=1; shift 2;; --force) FORCE=1; shift;;
  --deploycmd) DEPLOYCMD=$2; shift 2;; --e2ecmd) E2ECMD=$2; shift 2;; --promote) PROMOTE=$2; shift 2;;
  --delivery) DELIVERY=$2; shift 2;; --delivery-cmd) DELIVERYCMD=$2; shift 2;;
  --out) OUTNAME=$2; shift 2;;
  *) die "unknown arg: $1";; esac; done

ROOT=$(cd "$ROOT" 2>/dev/null && pwd) || die "project root does not exist: $ROOT"
[[ -e "$ROOT/.git" ]] || die "not a git repository: $ROOT"
[[ -f "$BRAIN" ]] || die "shared brain missing: $BRAIN"

# ── sniff objective facts ─────────────────────────────────────────────────────────────────────
# BASE: origin's default branch, else first of main/master that exists locally.
sniff_base() {
  local b; b=$(git -C "$ROOT" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')
  if [[ -z "$b" ]]; then local c; for c in main master; do git -C "$ROOT" show-ref -q --verify "refs/heads/$c" && { b=$c; break; }; done; fi
  printf '%s' "$b"
}
# package manager from lockfile / packageManager field.
sniff_pm() {
  local pj="$ROOT/package.json"
  if [[ -f "$ROOT/pnpm-lock.yaml" ]] || { [[ -f "$pj" ]] && grep -q '"packageManager"[[:space:]]*:[[:space:]]*"pnpm' "$pj"; }; then printf pnpm
  elif [[ -f "$ROOT/yarn.lock" ]]; then printf yarn
  elif [[ -f "$ROOT/package-lock.json" ]]; then printf npm
  fi
}
depcmd_for() { case "$1" in
  pnpm) printf 'pnpm install --prefer-offline --silent';;
  yarn) printf 'yarn install --silent';;
  npm)  printf 'npm install --no-audit --no-fund';;
  esac; }
has_script() { [[ -f "$ROOT/package.json" ]] && python3 -c "import json,sys;d=json.load(open('$ROOT/package.json'));sys.exit(0 if '$1' in (d.get('scripts') or {}) else 1)" 2>/dev/null; }
runner_for() { case "$1" in yarn) printf 'yarn';; *) printf '%s run' "$1";; esac; }
# TESTCMD proposal: prefer a `gate` script; else typecheck&&test when both exist; else either alone.
# (Monorepo subdir cmds need --testcmd.) Typecheck must not be omitted when the repo declares it —
# a green test suite is not a substitute for tsc.
sniff_testcmd() {
  local pm=$1 r; r=$(runner_for "$pm")
  if has_script gate; then printf '%s gate' "$r"
  elif has_script typecheck && has_script test; then printf '%s typecheck && %s test' "$r" "$r"
  elif has_script test; then printf '%s test' "$r"
  elif has_script typecheck; then printf '%s typecheck' "$r"
  fi
}
# Deploy-on-push workflow (merge-to-main evidence + the deploy anchor). Echoes relpath or empty.
sniff_deploy_wf() {
  local f base=$1
  shopt -s nullglob
  for f in "$ROOT"/.github/workflows/*.yml "$ROOT"/.github/workflows/*.yaml; do
    [[ -f "$f" ]] || continue
    if grep -qiE 'deploy|cloudflare|pages|vercel|netlify|wrangler' "$f" && grep -qE '^[[:space:]]*push:' "$f"; then
      printf '.github/workflows/%s' "$(basename "$f")"; shopt -u nullglob; return
    fi
  done
  shopt -u nullglob
}
# Preview-deploy capability (deploy-verify evidence). Echoes an evidence token or empty:
#   script:<name>  (a deploy:preview / preview npm script) | config:<relpath> (wrangler/vercel/netlify)
sniff_preview_capable() {
  has_script deploy:preview && { printf 'script:deploy:preview'; return; }
  has_script preview && { printf 'script:preview'; return; }
  shopt -s nullglob; local f
  for f in "$ROOT"/wrangler.* "$ROOT"/apps/*/wrangler.* "$ROOT"/vercel.json "$ROOT"/netlify.toml; do
    [[ -f "$f" ]] && { printf 'config:%s' "${f#"$ROOT"/}"; shopt -u nullglob; return; }
  done
  shopt -u nullglob
}
# e2e capability (deploy-verify evidence). Echoes evidence token or empty:
#   script:test:e2e | config:<relpath> (playwright config / e2e dir)
sniff_e2e_capable() {
  has_script test:e2e && { printf 'script:test:e2e'; return; }
  shopt -s nullglob; local f
  for f in "$ROOT"/playwright.config.* "$ROOT"/apps/*/playwright.config.* "$ROOT"/e2e "$ROOT"/tests/e2e; do
    [[ -e "$f" ]] && { printf 'config:%s' "${f#"$ROOT"/}"; shopt -u nullglob; return; }
  done
  shopt -u nullglob
}
# Proposed deploy command from the capability token (empty when config-only → agent supplies one
# that writes the reachable URL to .ship-preview-url, the deploy-preview contract).
propose_deploycmd() { local r=$1 cap=$2; case "$cap" in
  script:deploy:preview) printf '%s deploy:preview' "$r";; script:preview) printf '%s preview' "$r";; *) printf '';; esac; }
propose_e2ecmd() { local r=$1 cap=$2; case "$cap" in
  script:test:e2e) printf '%s test:e2e' "$r";; config:playwright*) printf 'npx playwright test';; *) printf '';; esac; }

# Branch protection on base (pr evidence). Echoes yes|no|unknown.
sniff_protection() {
  local base=$1 slug
  command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1 || { printf unknown; return; }
  slug=$(git -C "$ROOT" remote get-url origin 2>/dev/null | sed -E 's#^.*github.com[:/]##; s#\.git$##')
  [[ -n "$slug" ]] || { printf unknown; return; }
  local out
  if out=$(gh api "repos/$slug/branches/$base/protection" 2>&1); then printf yes
  # GitHub returns 404 body "Branch not protected" ONLY when the branch genuinely has no protection;
  # 403/anything-else = token can't read it. Assert 'no' only on the canonical 404 — never conflate
  # not-protected with no-scope, which would falsely nudge the proposal toward merge-to-main.
  elif printf '%s' "$out" | grep -qi "Branch not protected"; then printf no
  else printf unknown; fi
}
# .claude tracked (committed) vs ignored — decides exclude mechanism.
claude_tracked() { git -C "$ROOT" check-ignore -q ".claude/scripts/$OUTNAME" 2>/dev/null && printf no || printf yes; }

[[ -n "$BASE" ]] || BASE=$(sniff_base)
[[ -n "$BASE" ]] || die "could not determine base branch — pass --base"
PM=$(sniff_pm)
[[ $DEPCMD_SET -eq 1 ]] || DEPCMD=$(depcmd_for "$PM")
[[ -n "$TESTCMD" ]] || TESTCMD=$(sniff_testcmd "$PM")
DEPLOY_WF=$(sniff_deploy_wf "$BASE")
PROTECTED=$(sniff_protection "$BASE")
CLAUDE_TRACKED=$(claude_tracked)
REMOTE_URL=$(git -C "$ROOT" remote get-url origin 2>/dev/null)
RUNNER=$(runner_for "$PM")
PREVIEW_CAP=$(sniff_preview_capable)
E2E_CAP=$(sniff_e2e_capable)

# ── Phase A: propose (no --mode) ──────────────────────────────────────────────────────────────
if [[ -z "$MODE" ]]; then
  # Bias pr (safe direction + CLAUDE.md default). Only lean merge-to-main on POSITIVE deploy-on-push
  # evidence AND no branch protection — never silently; the agent confirms by re-running with --mode.
  base_proposed="pr"
  if [[ -n "$DEPLOY_WF" && "$PROTECTED" != "yes" ]]; then base_proposed="merge-to-main"; fi
  # A project that can deploy a preview AND has an e2e suite proposes deploy-verify — verify the live
  # preview before promoting (the promote sub-mode defaults to the pr/merge-to-main bias above).
  local_proposed="$base_proposed"; PROMOTE="$base_proposed"
  if [[ -n "$PREVIEW_CAP" && -n "$E2E_CAP" ]]; then local_proposed="deploy-verify"; fi
  [[ -n "$DEPLOYCMD" ]] || DEPLOYCMD=$(propose_deploycmd "$RUNNER" "$PREVIEW_CAP")
  [[ -n "$E2ECMD" ]]    || E2ECMD=$(propose_e2ecmd "$RUNNER" "$E2E_CAP")
  ev=()
  [[ -n "$DEPLOY_WF" ]] && ev+=("\"deploy-on-push workflow found: $DEPLOY_WF (→ merge-to-main signal)\"") || ev+=("\"no deploy-on-push workflow found (→ pr signal)\"")
  case "$PROTECTED" in
    yes)     ev+=("\"base '$BASE' branch-protection: yes (protected → pr signal)\"");;
    no)      ev+=("\"base '$BASE' branch-protection: no (confirmed unprotected → merge-to-main allowed)\"");;
    *)       ev+=("\"base '$BASE' branch-protection: unknown (token can't read it — NOT evidence either way; confirm before merge-to-main)\"");;
  esac
  [[ -n "$PREVIEW_CAP" ]] && ev+=("\"preview-deploy capability: $PREVIEW_CAP (→ deploy-verify signal)\"") || ev+=("\"no preview-deploy capability found (→ no deploy-verify)\"")
  [[ -n "$E2E_CAP" ]] && ev+=("\"e2e capability: $E2E_CAP (→ deploy-verify signal)\"") || ev+=("\"no e2e capability found (→ no deploy-verify)\"")
  [[ "$local_proposed" == "deploy-verify" ]] && ev+=("\"deploy-verify proposed: deploys a preview, runs e2e against the LIVE url, then promotes via '$PROMOTE'; the deploy command MUST write the reachable url to .ship-preview-url\"")
  evj=$(IFS=,; echo "${ev[*]}")
  # anchor: pr→remote; merge-to-main→deploy-on-push wf; deploy-verify→the preview-deploy mechanism.
  anchor_cand="remote:$REMOTE_URL"
  case "$local_proposed" in
    merge-to-main) anchor_cand="deploy:${DEPLOY_WF:-<none-found>}";;
    deploy-verify) case "$PREVIEW_CAP" in config:*) anchor_cand="deploy:${PREVIEW_CAP#config:}";; *) anchor_cand="deploy:package.json";; esac;;
  esac
  nextmsg="re-run with --mode pr|merge-to-main (and --testcmd if the proposed one is wrong, e.g. a monorepo subdir)"
  [[ "$local_proposed" == "deploy-verify" ]] && nextmsg="re-run with --mode deploy-verify --deploycmd <cmd-that-writes-url-to-.ship-preview-url> --e2ecmd <cmd> --promote pr|merge-to-main (proposed cmds may be empty/partial — wire the url-emitting deploy)"
  printf '{"phase":"propose","proposed_mode":"%s","base":"%s","remote":"%s","package_manager":"%s","depcmd":"%s","testcmd":"%s","deploy_workflow":"%s","base_protected":"%s","preview_capability":"%s","e2e_capability":"%s","proposed_deploycmd":"%s","proposed_e2ecmd":"%s","proposed_promote":"%s","claude_tracked":%s,"anchor_candidate":"%s","evidence":[%s],"next":"%s"}\n' \
    "$local_proposed" "$(jstr "$BASE")" "$(jstr "$REMOTE_URL")" "${PM:-none}" "$(jstr "$DEPCMD")" "$(jstr "$TESTCMD")" "${DEPLOY_WF:-}" "$PROTECTED" \
    "$(jstr "$PREVIEW_CAP")" "$(jstr "$E2E_CAP")" "$(jstr "$DEPLOYCMD")" "$(jstr "$E2ECMD")" "$(jstr "$PROMOTE")" \
    "$([[ $CLAUDE_TRACKED == yes ]] && echo true || echo false)" \
    "$(jstr "$anchor_cand")" \
    "$evj" "$(jstr "$nextmsg")"
  exit 10
fi

# ── Phase B: validate + emit ──────────────────────────────────────────────────────────────────
[[ "$MODE" == "pr" || "$MODE" == "merge-to-main" || "$MODE" == "deploy-verify" ]] || die "invalid --mode '$MODE' (pr|merge-to-main|deploy-verify)"
[[ -n "$TESTCMD" ]] || die "no test command sniffed — pass --testcmd"

# deploy-verify needs the deploy/e2e/promote facts up front (fail-closed — never freeze a half spec).
if [[ "$MODE" == "deploy-verify" ]]; then
  [[ -n "$DEPLOYCMD" ]] || die "deploy-verify needs --deploycmd (a command that deploys a preview AND writes the reachable url to .ship-preview-url)"
  [[ -n "$E2ECMD" ]]    || die "deploy-verify needs --e2ecmd (an e2e command; PREVIEW_URL is exported into it)"
  [[ "$PROMOTE" == "pr" || "$PROMOTE" == "merge-to-main" ]] || die "deploy-verify needs --promote pr|merge-to-main (how to land AFTER e2e passes)"
fi

# Delivery kind: DATA, not an opaque shell string — the project declares WHAT happens after landing,
# never a blank/omitted fact. pr mode's delivery is the PR itself (implicit, no flag needed); refuse
# a --delivery that contradicts it. merge-to-main REQUIRES an explicit kind: 'none' is a first-class
# declared value distinguishing "no deployment" from "not yet configured" — omission refuses, never
# defaults. push-triggered/pr-as-a-standalone-flag are recognized vocabulary but have no runtime
# implementation yet (the poller lands in a later slice) — fail closed rather than enroll a project
# whose delivery Overdeck cannot actually carry out.
if [[ "$MODE" == "pr" ]]; then
  [[ -z "$DELIVERY" || "$DELIVERY" == "pr" ]] || die "pr mode's delivery kind is implicitly 'pr' (opens a PR) — --delivery '$DELIVERY' contradicts --mode pr"
  DELIVERY="pr"
elif [[ "$MODE" == "merge-to-main" ]]; then
  case "$DELIVERY" in
    none) ;;
    local-script) [[ -n "$DELIVERYCMD" ]] || die "--delivery local-script needs --delivery-cmd <command>";;
    push-triggered|pr) die "--delivery $DELIVERY is recognized vocabulary but not yet implemented — enroll with --delivery none|local-script until it lands";;
    "") die "merge-to-main needs an explicit --delivery none|local-script — 'none' must be declared, never omitted";;
    *) die "unknown --delivery '$DELIVERY' (none|local-script)";;
  esac
fi

# Resolve the drift anchor for the chosen mode (fail-closed).
if [[ -z "$ANCHOR" ]]; then
  if [[ "$MODE" == "pr" ]]; then
    [[ -n "$REMOTE_URL" ]] || die "pr mode needs an origin remote (the PR destination) — none found"
    ANCHOR="remote:$REMOTE_URL"
  elif [[ "$MODE" == "deploy-verify" ]]; then
    case "$PREVIEW_CAP" in
      config:*) ANCHOR="deploy:${PREVIEW_CAP#config:}";;
      *) [[ -f "$ROOT/package.json" ]] || die "deploy-verify anchor: no preview-deploy config sniffed and no package.json — pass --anchor deploy:<relpath>"; ANCHOR="deploy:package.json";;
    esac
  else
    if [[ -n "$DEPLOY_WF" ]]; then
      ANCHOR="deploy:$DEPLOY_WF"
    elif [[ -n "$REMOTE_URL" ]]; then
      ANCHOR="remote:$REMOTE_URL"
    else
      die "merge-to-main needs either a deploy-on-push workflow or an origin remote — neither found; pass --anchor remote:<url> or --anchor deploy:<relpath>"
    fi
  fi
fi
# Validate the anchor resolves NOW (so we never freeze a dead fact).
bash "$BRAIN" drift --root "$ROOT" --base "$BASE" --mode "$MODE" --anchor "$ANCHOR" >/dev/null 2>&1 \
  || die "anchor/base/mode do not validate against the live repo (drift) — fix the facts before freezing"

OUT="$ROOT/.claude/scripts/$OUTNAME"
[[ -f "$OUT" && $FORCE -eq 0 ]] && die "$OUT already exists — re-sniff is intentional; pass --force to overwrite" 3
mkdir -p "$ROOT/.claude/scripts"

# shq: emit a value as a SINGLE-quoted shell literal so it bakes verbatim into the generated wrapper.
# A double-quoted bake re-expands any $/`/\ at wrapper-load under `set -u` — a $-laden testcmd (e.g. a
# loop var $t) becomes "unbound variable" and EVERY ship.sh subcommand (land/drift) crashes. Single-
# quoting freezes the value; embedded ' is closed/escaped/reopened ('\'').
shq() { local s=${1//\'/\'\\\'\'}; printf "'%s'" "$s"; }

# deploy-verify carries three extra frozen facts + passes them to `land`. Other modes emit neither
# (keeps the pr/merge-to-main wrapper minimal). DV_FACTS = extra frozen lines; DV_FLAGS = extra land args.
DV_FACTS="" DV_FLAGS=""
if [[ "$MODE" == "deploy-verify" ]]; then
  DV_FACTS=$(printf 'DEPLOYCMD=%s\nE2ECMD=%s\nPROMOTE=%s\n' "$(shq "$DEPLOYCMD")" "$(shq "$E2ECMD")" "$(shq "$PROMOTE")")
  DV_FLAGS=' --deploycmd "$DEPLOYCMD" --e2ecmd "$E2ECMD" --promote "$PROMOTE"'
fi

# delivery kind (merge-to-main only — pr mode's delivery is implicit and never passed to `land`;
# deploy-verify has its own deploycmd/promote). DL_FACTS = extra frozen lines; DL_FLAGS = extra land args.
DL_FACTS="" DL_FLAGS=""
if [[ "$MODE" == "merge-to-main" ]]; then
  DL_FACTS="DELIVERY=$(shq "$DELIVERY")"$'\n'
  DL_FLAGS=' --delivery "$DELIVERY"'
  if [[ "$DELIVERY" == "local-script" ]]; then
    DL_FACTS="${DL_FACTS}DELIVERYCMD=$(shq "$DELIVERYCMD")"$'\n'
    DL_FLAGS="${DL_FLAGS}"' --delivery-cmd "$DELIVERYCMD"'
  fi
fi

# frozen facts pre-quoted (see shq) — the heredoc emits these single-quoted literals verbatim.
ROOT_Q=$(shq "$ROOT"); BASE_Q=$(shq "$BASE"); MODE_Q=$(shq "$MODE")
ANCHOR_Q=$(shq "$ANCHOR"); TESTCMD_Q=$(shq "$TESTCMD"); DEPCMD_Q=$(shq "$DEPCMD")

if [[ "$MODE" == "pr" ]]; then
  MODE_NOTE="lands by OPENING A PR — base is PR-gated; the brain NEVER pushes main in pr mode. The PR opens and the run stops; a human/CI merges it."
  EXIT0_NOTE="exit 0 PR opened/already-open"
elif [[ "$MODE" == "deploy-verify" ]]; then
  MODE_NOTE="deploys a PREVIEW, runs e2e against the LIVE preview url, and ONLY on green promotes via '$PROMOTE'. The brain never promotes an unverified build."
  EXIT0_NOTE="exit 0 deployed+verified+promoted ($PROMOTE)"
else
  MODE_NOTE="solo trunk-based: the brain merges the verified branch behind the gated fast-forward then pushes to base.${DEPLOY_WF:+ CI workflow '$DEPLOY_WF' deploys on push.}"
  EXIT0_NOTE="exit 0 landed+pushed+cleaned"
fi

cat > "$OUT" <<EOF
#!/usr/bin/env bash
# $(basename "$ROOT")/.claude/scripts/$OUTNAME — FROZEN per-project land DATA. audience: AI agents.
# GENERATED by ship-init.sh — do not hand-edit logic; re-run ship-init.sh --force to re-sniff.
#
# This file is DATA, not logic. Every risky/orchestration step lives ONCE in the shared brain
# (finish-branch.sh \`land\`/\`drift\`); here we only pin the frozen facts and delegate. A brain
# contract change updates ALL projects at once. Re-init (re-sniff) only when drift fails (exit 3).
#
# This project $MODE_NOTE
#
# Contract (from the brain): $EXIT0_NOTE · exit 20 agent-action-needed (read the JSON
# stage/next/detail, act, re-run — idempotent) · exit 3 drift/usage (re-sniff this wrapper).
set -uo pipefail

LIB="$BRAIN"

# ── FROZEN FACTS (sniffed once; the brain's drift-check defends them) ──────────────────────────
ROOT=${ROOT_Q}
BASE=${BASE_Q}
MODE=${MODE_Q}
ANCHOR=${ANCHOR_Q}
TESTCMD=${TESTCMD_Q}
DEPCMD=${DEPCMD_Q}
${DV_FACTS}${DL_FACTS}
case "\${1:-}" in
  land)  shift; exec bash "\$LIB" land --root "\$ROOT" --base "\$BASE" --mode "\$MODE" \\
                  --anchor "\$ANCHOR" --testcmd "\$TESTCMD" --depcmd "\$DEPCMD"${DV_FLAGS}${DL_FLAGS} -- "\$@";;
  submit) shift; exec bash "\$LIB" submit --root "\$ROOT" --base "\$BASE" --mode "\$MODE" \\
                  --anchor "\$ANCHOR" --testcmd "\$TESTCMD" --depcmd "\$DEPCMD" -- "\$@";;
  status) shift; exec bash "\$LIB" submit-status --root "\$ROOT" -- "\$@";;
  drift) exec bash "\$LIB" drift --root "\$ROOT" --base "\$BASE" --mode "\$MODE" --anchor "\$ANCHOR";;
  *) echo "usage: $OUTNAME {submit <branch> <worktree> [--assets-ok]|status <ticket>|land <branch> <worktree> [--assets-ok]|drift}" >&2; exit 2;;
esac
EOF
chmod +x "$OUT"

# ── register the local exclude when .claude is TRACKED (committed) — keep the machine-local wrapper
# (it points to a machine-local brain) out of the shared repo without editing the tracked .gitignore.
EXCLUDED="n/a (.claude already ignored)"
if [[ "$CLAUDE_TRACKED" == "yes" ]]; then
  EX="$ROOT/.git/info/exclude"
  grep -qxF '**/.claude/scripts/' "$EX" 2>/dev/null || printf '**/.claude/scripts/\n' >> "$EX"
  EXCLUDED="added '**/.claude/scripts/' to .git/info/exclude (local-only)"
fi

# ── emit a thin smoke test alongside (validates frozen facts vs the live repo + delegation).
TESTOUT="$ROOT/.claude/scripts/test-${OUTNAME%.sh}.sh"
cat > "$TESTOUT" <<EOF
#!/usr/bin/env bash
# GENERATED smoke test. Land ORCHESTRATION is tested in the brain (test-finish-branch.sh). Here:
# (1) frozen facts still valid vs the live repo (drift→ok), (2) delegation/usage wired.
set -uo pipefail
SHIP="$OUT"
pass=0; fail=0
ok(){ if [[ "\$2" == "\$3" ]]; then echo "PASS: \$1"; pass=\$((pass+1)); else echo "FAIL: \$1 — got '\$2' want '\$3'"; fail=\$((fail+1)); fi; }
bash "\$SHIP" drift >/dev/null 2>&1; ok "drift: live frozen facts valid" "\$?" "0"
out=\$(bash "\$SHIP" drift 2>/dev/null); ok "drift: status ok" "\$(printf '%s' "\$out" | grep -c '"status":"ok"')" "1"
bash "\$SHIP" >/dev/null 2>&1; ok "usage: no args → exit2" "\$?" "2"
bash "\$SHIP" land >/dev/null 2>&1; ok "land: missing args → exit3 (delegated)" "\$?" "3"
echo "── \$pass passed, \$fail failed ──"; [[ \$fail -eq 0 ]]
EOF

printf '{"phase":"emit","wrapper":"%s","mode":"%s","base":"%s","anchor":"%s","testcmd":"%s","depcmd":"%s","delivery":"%s","exclude":"%s","next":"run %s to verify"}\n' \
  "$(jstr "$OUT")" "$MODE" "$(jstr "$BASE")" "$(jstr "$ANCHOR")" "$(jstr "$TESTCMD")" "$(jstr "$DEPCMD")" "$(jstr "$DELIVERY")" "$(jstr "$EXCLUDED")" "$(jstr "$TESTOUT")"
exit 0
