#!/usr/bin/env bash
# test_prior_art_prompt_lanes.sh — T2 훅의 known-pair 앵커.
# 🟥 이 훅의 사활은 «막나» 가 아니라 **«소음이 아닌가»** 다. 냉독자가 지목한 자기무력화 경로:
# 트리거가 부정확하면 하루 수십 번 뜨고, 3회 거절 억제가 오전 중에 켜져서 기능이 조용히 죽는다.
# 그래서 음성 레인이 양성보다 많다.
set -uo pipefail
FH="$(cd "$(dirname "$0")/.." && pwd)"
H="$FH/scripts/prior_art_prompt.sh"
PASS=0; FAIL=0
T="$(mktemp -d -t pap.XXXXXX)" || exit 3
trap 'rm -rf "$T"' EXIT INT TERM
chk(){ if [ "$2" = "$3" ]; then echo "  ✅ $1"; PASS=$((PASS+1)); else echo "  ❌ $1 (got=$2 want=$3)"; FAIL=$((FAIL+1)); fi; }
# 각 호출은 세션-스탬프를 격리해야 «한 번만» 규칙에 서로 안 걸린다
fires(){ local d; d="$(mktemp -d -t papd.XXXXXX)"; mkdir -p "$d/.claude"
  local o; o=$(printf '{"tool_input":{"file_path":"%s"}}' "$1" | CLAUDE_PROJECT_DIR="$d" bash "$H" 2>/dev/null)
  rm -rf "$d"; [ -n "$o" ] && echo yes || echo no; }

echo "── 양성: 새 «메커니즘» 을 쓰기 직전 ──"
chk "P1 새 .sh"            "$(fires "$T/new_gate.sh")" yes
chk "P2 새 SKILL.md"       "$(fires "$T/plugins/x/skills/y/SKILL.md")" yes
chk "P3 새 agents/*.md"    "$(fires "$T/plugins/x/agents/z.md")" yes
chk "P4 새 .py"            "$(fires "$T/tool.py")" yes

echo "── 음성: 소음이 되면 이 능력이 죽는다 ──"
printf 'x\n' > "$T/exists.sh"
chk "N1 🟥 «이미 있는 파일 편집» 은 새로 짓기가 아니다" "$(fires "$T/exists.sh")" no
chk "N2 기록/산출물(.md 일반)"      "$(fires "$T/notes.md")" no
chk "N3 tracks/ 아래 기록"          "$(fires "$T/tracks/_meta/x.sh")" no
chk "N4 로그"                       "$(fires "$T/logs/a.sh")" no
chk "N5 json/lock"                  "$(fires "$T/a.json")" no

echo "── 🟥 스코프가 «세션» 이지 «날짜» 가 아니다 (초판 결함의 앵커) ──"
# 초판은 스탬프를 날짜로 잡아, 하루에 세션을 여럿 도는 환경에서 **첫 세션만 뜨고 나머지는 전부
# 침묵**했다. 주석은 «세션당» 이었고 구현은 «하루당» 이었다 — 그 둘이 갈리는 것이 이 레인이다.
S="$(mktemp -d -t papsid.XXXXXX)"; mkdir -p "$S/.claude"
sid_fire(){ local o; o=$(printf '{"session_id":"%s","tool_input":{"file_path":"%s/%s.sh"}}' "$1" "$T" "$2" \
  | CLAUDE_PROJECT_DIR="$S" bash "$H" 2>/dev/null); [ -n "$o" ] && echo yes || echo no; }
chk "S1 세션 A 1회차 → 뜬다"        "$(sid_fire sess-A f1)" yes
chk "S2 세션 A 2회차 → 무음"        "$(sid_fire sess-A f2)" no
chk "S3 🟥 세션 B 1회차 → 뜬다 (같은 날 다른 세션)" "$(sid_fire sess-B f3)" yes
chk "S4 CONTROL — session_id 없으면 날짜로 degrade(무음 아님)" \
    "$(printf '{"tool_input":{"file_path":"%s/f4.sh"}}' "$T" | CLAUDE_PROJECT_DIR="$(mktemp -d -t papnos.XXXXXX)" bash "$H" 2>/dev/null | grep -c additionalContext)" 1
rm -rf "$S"

echo "── 세션당 한 번 (3회 거절 억제로 조용히 죽는 경로 차단) ──"
D="$(mktemp -d -t paponce.XXXXXX)"; mkdir -p "$D/.claude"
o1=$(printf '{"tool_input":{"file_path":"%s/a.sh"}}' "$T" | CLAUDE_PROJECT_DIR="$D" bash "$H" 2>/dev/null)
o2=$(printf '{"tool_input":{"file_path":"%s/b.sh"}}' "$T" | CLAUDE_PROJECT_DIR="$D" bash "$H" 2>/dev/null)
chk "O1 첫 번째는 뜬다"   "$([ -n "$o1" ] && echo yes || echo no)" yes
chk "O2 두 번째는 안 뜬다" "$([ -n "$o2" ] && echo yes || echo no)" no
rm -rf "$D"

echo "── 🟥 절대 안 막는다 (가역 표면) ──"
D2="$(mktemp -d -t papnb.XXXXXX)"; mkdir -p "$D2/.claude"
out=$(printf '{"tool_input":{"file_path":"%s/c.sh"}}' "$T" | CLAUDE_PROJECT_DIR="$D2" bash "$H" 2>/dev/null); rc=$?
chk "B1 exit 0" "$rc" 0
chk "B2 permissionDecision 을 안 낸다 (자동승인 역전 금지)" \
    "$(printf '%s' "$out" | grep -c permissionDecision)" 0
chk "B3 모델 채널(additionalContext)로 간다" \
    "$(printf '%s' "$out" | grep -c additionalContext)" 1
rm -rf "$D2"

echo "── D: Bash 경로 — 🟥 첫 실사용이 «비발화» 로 났던 자리 (2026-08-21) ──"
# matcher 가 `Write` 뿐이었는데 auto-mode 는 파일을 **Bash 리다이렉트/heredoc** 으로 만든다.
# 즉 이 훅이 겨냥한 순간이 구조적으로 Write 를 안 탔다. 아래가 그 재현이다.
D3=$(mktemp -d); export CLAUDE_PROJECT_DIR="$D3"; mkdir -p "$D3/.claude"
chk "D1 Bash heredoc 로 새 .sh → 발화" \
    "$(printf '%s' '{"session_id":"d1","tool_input":{"command":"cat > scripts/new_thing.sh <<EOF\necho hi\nEOF"}}' | bash "$H" 2>/dev/null | grep -c additionalContext)" 1
rm -f "$D3/.claude/.prior_art_prompted_"*
chk "D2 printf 리다이렉트로 새 SKILL.md → 발화" \
    "$(printf '%s' '{"session_id":"d2","tool_input":{"command":"printf x > plugins/z/skills/q/SKILL.md"}}' | bash "$H" 2>/dev/null | grep -c additionalContext)" 1
rm -f "$D3/.claude/.prior_art_prompted_"*
# ★컨트롤 — D1/D2 가 «Bash 면 무조건 발화» 가 아님을 보인다. 소음이 이 훅을 죽인다.
chk "D3 ★컨트롤 로그 리다이렉트 + 2>/dev/null → 침묵" \
    "$(printf '%s' '{"session_id":"d3","tool_input":{"command":"echo x > /tmp/a.log 2>/dev/null"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
chk "D4 ★컨트롤 tracks/ 산출물 리다이렉트 → 침묵" \
    "$(printf '%s' '{"session_id":"d4","tool_input":{"command":"cat > tracks/_meta/note.sh <<EOF\nx\nEOF"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
chk "D5 ★컨트롤 리다이렉트 없는 명령 → 침묵" \
    "$(printf '%s' '{"session_id":"d5","tool_input":{"command":"git status --short"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
chk "D6 ★컨트롤 같은 세션 두 번째 Bash → 침묵 (세션당 1회가 Bash 에서도 산다)" \
    "$( { printf '%s' '{"session_id":"d6","tool_input":{"command":"cat > scripts/a.sh <<EOF\nx\nEOF"}}' | bash "$H" >/dev/null 2>&1
         printf '%s' '{"session_id":"d6","tool_input":{"command":"cat > scripts/b.sh <<EOF\nx\nEOF"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' '; } )" 0
# 🟥 E: cross-family 4 HIGH — **전부 오발화**다 (2026-08-21). 이 훅에서 오발화는 미발화보다
#    훨씬 비싸다: 소음이 되면 운영자가 무시하고 능력이 죽는다(원 저자가 «자기무력화 경로» 라 명명).
rm -f "$D3/.claude/.prior_art_prompted_"*
chk "E1 ★컨트롤 tee_probe.py 언급 (tee 에 뒤 경계 없던 자리) → 침묵" \
    "$(printf '%s' '{"session_id":"e1","tool_input":{"command":"rg tee_probe.py scripts"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
chk "E2 ★컨트롤 stderr 리다이렉트 2> …/errors.sh → 침묵 (>>? 가 2> 안의 > 를 잡던 자리)" \
    "$(printf '%s' '{"session_id":"e2","tool_input":{"command":"pytest 2> /tmp/pytest_errors.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
chk "E2b ★컨트롤 stdout 버리고 stderr 만 .sh 로 → 침묵" \
    "$(printf '%s' '{"session_id":"e2b","tool_input":{"command":"make test > /dev/null 2> /tmp/failures.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
chk "E3 ★컨트롤 프로젝트 밖 리포트 git diff > /tmp/review_delta.sh → 침묵" \
    "$(printf '%s' '{"session_id":"e3","tool_input":{"command":"git diff > /tmp/review_delta.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
chk "E4 ★컨트롤 미확장 변수 \$LOG_DIR/x.sh → 침묵 (리터럴로 존재검사하면 거짓이다)" \
    "$(printf '%s' '{"session_id":"e4","tool_input":{"command":"echo more >> \"$LOG_DIR/x.sh\""}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
# 🟥 E1b/E2c — **프로젝트 «안»** 경로로 같은 두 결함을 친다. E1/E2 는 /tmp 라서
#    프로젝트-범위 규칙(ⓒ)만으로도 침묵했다 — 되돌림 실측에서 정규식 수리를 되돌려도
#    적색이 0 이었고(=장식), 그게 이 두 레인을 만든 이유다. 아래는 ⓒ 가 못 잡는 자리다.
rm -f "$D3/.claude/.prior_art_prompted_"*
chk "E1b ★컨트롤 프로젝트 안 tee_probe.sh 언급 → 침묵 (tee 뒤 경계를 격리해서 친다)" \
    "$(printf '%s' '{"session_id":"e1b","tool_input":{"command":"rg tee_probe.sh scripts"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
chk "E2c ★컨트롤 프로젝트 안 stderr 2> scripts/errors.sh → 침묵 (리다이렉트 앞 경계를 격리해서 친다)" \
    "$(printf '%s' '{"session_id":"e2c","tool_input":{"command":"pytest 2> scripts/errors.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0
# E5/E6 — session_id 는 신뢰할 수 없는 입력이다
_ESC=$(dirname "$D3")/pap_lane_escape_$$
rm -f "$_ESC"
printf '%s' "{\"session_id\":\"x/../../../$(basename "$_ESC")\",\"tool_input\":{\"file_path\":\"scripts/zz_new.sh\"}}" | bash "$H" >/dev/null 2>&1
chk "E5 session_id 경로 탈출 → 프로젝트 밖에 파일이 안 생긴다" \
    "$([ -e "$_ESC" ] && echo escaped || echo none)" none
rm -f "$_ESC"
rm -f "$D3/.claude/.prior_art_prompted_"*
chk "E6 session_id 끝에 «/» → 두 번째 호출은 침묵 (스탬프가 실제로 써진다)" \
    "$( { printf '%s' '{"session_id":"x/","tool_input":{"file_path":"scripts/zz_a.sh"}}' | bash "$H" >/dev/null 2>&1
         printf '%s' '{"session_id":"x/","tool_input":{"file_path":"scripts/zz_b.sh"}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' '; } )" 0
# E7 — 🟥 «additionalContext 가 떴다» 는 파서가 **옳은 토큰**을 뽑았다는 증거가 아니다(cross-family 레인 지적).
rm -f "$D3/.claude/.prior_art_prompted_"*
chk "E7 추출된 대상이 실제 리다이렉트 대상이다 (파서가 엉뚱한 토큰을 뽑아도 통과하던 자리)" \
    "$(printf '%s' '{"session_id":"e7","tool_input":{"command":"cat > scripts/uniq_target_name.sh <<EOF\nx\nEOF"}}' | bash "$H" 2>/dev/null | grep -c 'uniq_target_name.sh')" 1
unset CLAUDE_PROJECT_DIR; rm -rf "$D3"

echo "── CONTROL: 계기가 죽으면 조용히 통과 (advisory 는 세션을 못 죽인다) ──"
chk "C1 입력이 JSON 이 아니면 무음 exit 0" \
    "$(printf 'not json' | bash "$H" >/dev/null 2>&1; echo $?)" 0
chk "C2 file_path 없으면 무음" "$(printf '{"tool_input":{}}' | bash "$H" 2>/dev/null | wc -c | tr -d ' ')" 0

echo "── T2 재무장 (2026-08-22): 세션당 1회가 T2 를 구조적으로 못 잡던 자리 ──"
# 🟥 실측 기반 픽스처다. 한 세션에서 새 메커니즘 3건이 08:38 · 13:06 · 13:07 에 났고, 세션당 1회는
# 첫 건만 띄웠다. 뒤의 둘이 정확히 이 훅이 존재 이유로 적어둔 T2 다. 시각은 주입한다 —
# 4.5시간을 실시간으로 기다리는 앵커는 아무도 안 돌린다.
_T1=1787387902   # 08:38:22Z
_T2=1787404013   # 13:06:53Z  (+16111s = 4h28m)
_T3=1787404067   # 13:07:47Z  (+54s — T2 와 **같은 메커니즘**: 스크립트와 그 레인 스위트)
DT=$(mktemp -d); export CLAUDE_PROJECT_DIR="$DT"
_fire() { # $1=NOW $2=name → 발화면 1
  printf '{"session_id":"t2rearm","tool_input":{"file_path":"%s/scripts/%s"}}' "$DT" "$2" \
    | PRIOR_ART_NOW="$1" bash "$H" 2>/dev/null | grep -c additionalContext | tr -d ' '
}
chk "T2-1 첫 메커니즘 → 발화"                       "$(_fire $_T1 m1.sh)" 1
chk "T2-2 4h28m 뒤 새 메커니즘 → **재발화** (이 수리의 요점)" "$(_fire $_T2 m2.sh)" 1
chk "T2-3 54초 뒤 같은 메커니즘의 레인 → 억제 (과발화 방지)"  "$(_fire $_T3 m3.sh)" 0
# 과차단 컨트롤 — 메커니즘 하나짜리 평범한 세션에서 발화가 1회를 넘지 않는다.
rm -f "$DT/.claude/.prior_art_prompted_"* "$DT/.claude/.prior_art_events.tsv"
_n=$(( $(_fire $_T1 solo.sh) + $(_fire $((_T1+60)) solo_b.sh) + $(_fire $((_T1+120)) solo_c.sh) ))
chk "T2-4 과차단 컨트롤: 한 버스트(2분 내 3파일) → 발화 총 1회" "$_n" 1
# 로그가 실제로 남는가 — 이 상수(N)를 다음에 재정할 유일한 근거다.
rm -f "$DT/.claude/.prior_art_prompted_"* "$DT/.claude/.prior_art_events.tsv"
_fire $_T2 g1.sh >/dev/null; _fire $_T3 g2.sh >/dev/null   # T2→T3 = 54s, 억제 구간
chk "T2-5 로그에 FIRE 와 SUPPRESS 가 둘 다 남는다" \
    "$(awk -F'\t' '{print $2}' "$DT/.claude/.prior_art_events.tsv" 2>/dev/null | sort -u | tr '\n' ',' )" "FIRE,SUPPRESS,"
chk "T2-6 억제 로그가 경과초와 파일명을 같이 싣는다 (옳은 억제였는지 가르려면 필요)" \
    "$(awk -F'\t' '$2=="SUPPRESS" && $4 ~ /^[0-9]+$/ && $5 ~ /g2\.sh$/ {n++} END{print n+0}' "$DT/.claude/.prior_art_events.tsv" 2>/dev/null)" 1
unset CLAUDE_PROJECT_DIR; rm -rf "$DT"

echo "── 특성화: 인용부호 안 텍스트를 빌드로 읽는다 (오발화 5번째 얼굴) ──"
# 🟥 2026-08-22 실측: 훅을 시험하려고 `echo` 인자 **안에** 픽스처 문자열을 넣은 호출에서 발화했다.
# 이 파일 머리가 센 cross-family 오발화 4종(tee 경계 · `2>` · 리포트가 .sh · 미확장 변수)의 **5번째**다.
# 🟥 이 레인은 «현재 동작»을 박는 특성화 레인이고, **수리됐다는 주장이 아니다.** 지금은 의도적으로
#    미수리다 — 인용 문맥을 정규식으로 가르는 것은 비싸고 표본이 1이다.
#    ⚠️ 다음 사람에게: 정규식을 **넓히면** 이 클래스가 더 나빠진다. 이 줄이 그때 걸린다.
#       그리고 이 오발화를 실제로 **고쳤다면** 기대값을 1 → 0 으로 뒤집고 이 주석을 지워라.
# 픽스처는 실물에서 왔다 — 초판은 이스케이프가 뭉개져 «침묵» 이 나왔고, 그건 결함이 사라진 게
# 아니라 **재현을 못 한 것**이었다. 그래서 heredoc 으로 원문 그대로 넣는다.
# 🟥 자기 CLAUDE_PROJECT_DIR 를 반드시 준다. 초판은 안 줘서 스탬프가 **레포 안**에 쌓였고,
#    2회차부터 «침묵» 이 나왔다 — 탐지기가 아니라 스탬프가 억제한 것이라 결함이 사라진 것처럼
#    보였다. 계기가 자기 부작용에 오염되는 형태이고, 레인이 idempotent 하지 않다는 뜻이다.
_QD=$(mktemp -d)
_QJ=$(mktemp); cat > "$_QJ" <<'QEOF'
{"session_id":"q1","tool_input":{"command":"echo \"cat > scripts/not_real.sh <<EOF\""}}
QEOF
chk "Q1 [특성화·미수리] 인용문 안 텍스트에 발화한다" \
    "$(CLAUDE_PROJECT_DIR="$_QD" bash "$H" < "$_QJ" 2>/dev/null | grep -c additionalContext | tr -d ' ')" 1
rm -f "$_QJ"; rm -rf "$_QD"

echo
if [ "$FAIL" -eq 0 ]; then echo "════ prior-art-prompt lanes: $PASS passed · 0 failed ════"; exit 0
else echo "🟥 prior-art-prompt lanes: $FAIL 실패 / $((PASS+FAIL))"; exit 1; fi
