# -*- coding: utf-8 -*-
"""hooks.spawn_confirmed_boot_hook — 봇 부팅 시 spawn-confirmed marker 생성.

task-2946 (P0 4096자 절벽 해소) — task-2942 가 프롬프트 최상단에 주입하던
"세션 시작 즉시 마커 생성" 지시(197자)를 **봇 부팅 경로**로 이전한다.

배경:
  dispatch 는 ``cokacdir --cron`` 으로 봇 세션을 예약하는데, 전달 채널이
  프롬프트 4096자 초과 시 silent drop 한다(task-2945 실측). team_prompts 의
  base 프롬프트가 이미 4,040자대였으므로 마커 지시 197자가 dev1~7 을 절벽
  밖으로 밀어냈다. 마커 지시는 **프롬프트에서 제거**하고, 봇 세션이 실제로
  뜰 때 UserPromptSubmit 훅이 마커를 대신 생성한다.

부수 효과 (의도된 개선):
  기존 방식은 "봇이 지시를 읽고 첫 명령으로 실행" 에 의존했다 — 봇이 순서를
  어기면 마커가 늦거나 없었다. 훅 방식은 세션이 프롬프트를 받는 시점에
  **무조건** 발화하므로 판정이 봇의 순종도와 무관해진다.

판정 계약 (scripts/harness/v36/terminal_state_classifier.py::_verify_bot_spawn):
  ``{events_dir}/{task_id}.spawn-confirmed-*.json`` glob 만 본다. bot_id 는
  대조에 쓰이지 않는 메타데이터이므로 best-effort 로 채운다.

false-positive 방지 (중요):
  이 훅은 **모든** Claude 세션(회장 채팅 포함)의 UserPromptSubmit 에서 돈다.
  단순히 "task-NNNN" 문자열만 보고 마커를 쓰면, 회장이 채팅에서 task id 를
  언급하기만 해도 dispatch 가 "봇이 떴다"고 오판한다. 따라서 dispatch 프롬프트
  고유 지문 2개가 **모두** 있을 때만 쓴다:
    1) ``memory/tasks/<task_id>.md``  (build_prompt 가 항상 넣는 작업파일 경로)
    2) ``collector_role=ANU``         (self-collector doctrine block, 전 팀 공통)

무손상 원칙:
  훅은 봇 세션을 절대 죽이면 안 된다. 어떤 예외에서도 exit 0 이며 stdout 에
  아무것도 쓰지 않는다(UserPromptSubmit stdout 은 프롬프트에 주입되므로
  출력하면 그 자체가 프롬프트를 늘린다 — 절벽 회귀 방지).
"""
from __future__ import annotations

import json
import os
import re
import sys
from pathlib import Path

_REPO_ROOT = Path(__file__).resolve().parent.parent

# dispatch 프롬프트 지문 — 이 2개가 모두 있어야 marker 를 쓴다.
_TASK_FILE_RE = re.compile(r"memory/tasks/(task-[A-Za-z0-9_.+-]+)\.md")
_DOCTRINE_FINGERPRINT = "collector_role=ANU"

# marker 생성 사실만 남기는 경량 로그 (stdout 오염 금지 → 파일로)
_LOG_PATH = _REPO_ROOT / "logs" / "spawn_confirmed_boot_hook.log"


def extract_task_id(prompt: str) -> str | None:
    """dispatch 프롬프트에서 task_id 를 추출한다.

    지문 2개가 모두 있을 때만 task_id 를 돌려준다. 그 외에는 None
    (= 일반 대화이므로 marker 를 쓰면 안 된다).
    """
    if not prompt or _DOCTRINE_FINGERPRINT not in prompt:
        return None
    m = _TASK_FILE_RE.search(prompt)
    return m.group(1) if m else None


def resolve_bot_id(payload: dict) -> str:
    """봇 식별자 (판정에 쓰이지 않는 메타데이터 — best-effort).

    cokacdir 봇 세션의 cwd 는 ``/home/jay/.cokacdir/workspace/<ID>`` 이다.
    """
    cwd = payload.get("cwd") or os.environ.get("PWD") or ""
    name = Path(cwd).name if cwd else ""
    return name or "unknown"


def _load_marker_writer():
    """``write_spawn_confirmed_marker`` 를 **파일 경로로** 로드한다.

    ``from dispatch.spawn_verification import ...`` 를 쓰면 dispatch 패키지
    __init__ 전체(수십 모듈 + .env.keys 로드 + 로거 설정)가 **모든 세션의 모든
    프롬프트마다** 실행된다. spawn_verification 은 stdlib 만 쓰도록 설계된
    모듈이므로 패키지를 거치지 않고 직접 로드해 부작용과 지연을 없앤다.
    """
    import importlib.util

    path = _REPO_ROOT / "dispatch" / "spawn_verification.py"
    try:
        spec = importlib.util.spec_from_file_location("_spawn_verification_boot", path)
        if spec is None or spec.loader is None:
            return None
        mod = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(mod)
        return mod.write_spawn_confirmed_marker
    except Exception as exc:  # pragma: no cover - 로드 실패는 비차단
        _log(f"[spawn-boot-hook] 모듈 로드 실패 (무시): {exc}")
        return None


def _log(line: str) -> None:
    try:
        _LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
        with _LOG_PATH.open("a", encoding="utf-8") as fh:
            fh.write(line + "\n")
    except Exception:
        pass


def main() -> int:
    """UserPromptSubmit 훅 진입점. 어떤 경우에도 0 을 반환한다."""
    try:
        raw = sys.stdin.read()
    except Exception:
        return 0

    try:
        payload = json.loads(raw) if raw.strip() else {}
    except Exception:
        # 훅 payload 형식이 바뀌어도 원문에서 직접 지문을 찾아본다.
        payload = {"prompt": raw}

    if not isinstance(payload, dict):
        return 0

    prompt = payload.get("prompt") or ""
    if not isinstance(prompt, str):
        return 0

    task_id = extract_task_id(prompt)
    if not task_id:
        return 0

    writer = _load_marker_writer()
    if writer is None:
        return 0

    try:
        path = writer(task_id, resolve_bot_id(payload))
        _log(f"[spawn-boot-hook] {task_id} marker={path}")
    except Exception as exc:  # pragma: no cover - 방어
        _log(f"[spawn-boot-hook] {task_id} 실패 (무시): {exc}")

    return 0


if __name__ == "__main__":  # pragma: no cover - CLI
    raise SystemExit(main())
