"""task-2729+3 회귀: FINISH_TASK_MERGE_POLICY_ENFORCEMENT (Option A — env-first 재설계).

finish-task.sh 가 merge_policy=none / FINALIZE_ONLY=1(주 경로) / --finalize-only(보조호환) /
fail-CLOSED 일 때 merge block(worktree_manager finish · PR gate · owner_gemini trigger) + 후속
2.3 PR 머지 검증을 스킵하고, 나머지(QC/scope/callback/.done)는 유지하는지 회귀로 검증한다.

★ Option A 핵심(PR#176~178 계열 HIGH 종결): 위치인자 파싱을 bash array(_POS_ARGS) 가 아니라
  인덱스 카운터(_pos_idx)+case 로 수행한다. old bash(≤3.2)+`set -u` 에서 빈 배열 인덱스 참조가
  'unbound variable' 로 죽는 edge 를 구조적으로 제거한다(무인자 / --finalize-only 단독 안전).

회장 verbatim 필수 회귀 10:
  1. FINALIZE_ONLY=1 + PROJECT_PATH      → worktree_manager finish 미호출
  2. 동                                   → PR gate / owner_gemini trigger 미호출
  3. merge_policy=none + PROJECT_PATH     → side-effect 0
  4. --finalize-only $1/$2/$3 위치무관    → positional 오염 0
  5. --finalize-only 단독                 → set -u / old bash unbound 0
  6. 무인자                               → unbound 0
  7. resolver 실패                        → fail-open 금지(fail-CLOSED)
  8. QC/scope/callback/.done              → 유지
  9. 기존 task(tiered/auto/미지정)        → 동작 유지
 10. ACTIVE=false · ANU key raw 0 · forbidden 0
  + py_compile / bash -n PASS.

설계 원칙:
  - 실제 finish-task.sh 의 각 영역을 센티넬로 추출해 stub 환경에서 *실행*한다(복붙 검증 아님).
      · FINALIZE_ONLY_ARGPARSE_BEGIN/END      : env-first / 카운터 기반 위치무관 인자 파싱
      · FINALIZE_ONLY_RESOLVER_GATE_BEGIN/END : merge_policy fail-CLOSED 해석
      · FINALIZE_ONLY_MERGE_BLOCK_BEGIN/END   : 머지 블록(skip vs 진입)
      · FINALIZE_ONLY_PR_GATE_BEGIN/END       : 2.3 PR 머지 검증 게이트
  - merge_policy_resolver 는 순수 함수로 직접 import 하여 검증.
  - 실제 git push/merge/PR/Gemini 호출 0 (전부 stub/tmp).
"""
import importlib.util
import os
import subprocess
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]
FINISH_TASK_SH = REPO_ROOT / "scripts" / "finish-task.sh"
RESOLVER_PATH = REPO_ROOT / "scripts" / "harness" / "v36" / "merge_policy_resolver.py"
# ANU key 는 분할 저장: 리터럴이 이 파일에 단일 문자열로 존재하지 않게 함(회귀 ANU-0 자기참조 오탐 방지)
ANU_KEY_RAW = "c119085a" + "ddb0f8b7"

ARGPARSE_BEGIN = "FINALIZE_ONLY_ARGPARSE_BEGIN"
ARGPARSE_END = "FINALIZE_ONLY_ARGPARSE_END"
RESOLVER_BEGIN = "FINALIZE_ONLY_RESOLVER_GATE_BEGIN"
RESOLVER_END = "FINALIZE_ONLY_RESOLVER_GATE_END"
MERGE_BEGIN = "FINALIZE_ONLY_MERGE_BLOCK_BEGIN"
MERGE_END = "FINALIZE_ONLY_MERGE_BLOCK_END"
PR_GATE_BEGIN = "FINALIZE_ONLY_PR_GATE_BEGIN"
PR_GATE_END = "FINALIZE_ONLY_PR_GATE_END"


# ───────────────────────── 공통 헬퍼 ─────────────────────────
def _load_resolver():
    spec = importlib.util.spec_from_file_location("merge_policy_resolver", RESOLVER_PATH)
    assert spec is not None and spec.loader is not None
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


def _finish_task_text():
    return FINISH_TASK_SH.read_text(encoding="utf-8")


def _extract(begin_mark, end_mark):
    lines = _finish_task_text().splitlines()
    begin = end = None
    for i, ln in enumerate(lines):
        if begin_mark in ln:
            begin = i
        elif end_mark in ln:
            end = i
    assert begin is not None, f"{begin_mark} 센티넬 미발견"
    assert end is not None and end > begin, f"{end_mark} 센티넬 미발견/순서 오류"
    return "\n".join(lines[begin + 1:end])


def _make_worktree_stub(fake_ws):
    """worktree_manager.py stub — 호출되면 카운터 파일에 기록(merge/PR gate/Gemini trigger 시도 증거)."""
    (fake_ws / "scripts").mkdir(parents=True, exist_ok=True)
    counter = fake_ws / "wm_calls.log"
    (fake_ws / "scripts" / "worktree_manager.py").write_text(
        "import sys, pathlib\n"
        f"pathlib.Path(r'{counter}').open('a').write('CALLED ' + ' '.join(sys.argv[1:]) + chr(10))\n",
        encoding="utf-8",
    )
    return counter


def _install_resolver(fake_ws, mode):
    """mode: 'real'(실제 resolver 복사) | 'fail'(exit 1 stub) | 'missing'(미설치)."""
    dest = fake_ws / "scripts" / "harness" / "v36" / "merge_policy_resolver.py"
    if mode == "missing":
        return
    dest.parent.mkdir(parents=True, exist_ok=True)
    if mode == "real":
        dest.write_text(RESOLVER_PATH.read_text(encoding="utf-8"), encoding="utf-8")
    elif mode == "fail":
        dest.write_text("import sys\nsys.stderr.write('boom\\n')\nsys.exit(1)\n", encoding="utf-8")
    else:
        raise ValueError(mode)


def _run_gate_and_merge(tmp_path, resolver_mode, task_md_policy, project_path,
                        initial_finalize=0):
    """resolver gate + merge block 을 연결해 실행. _FINALIZE_ONLY 초기값/해석/스킵 흐름을 end-to-end 검증."""
    task_id = "test-mp-task"
    fake_ws = tmp_path / "ws"
    events = tmp_path / "events"
    events.mkdir(parents=True, exist_ok=True)
    (fake_ws / "scripts").mkdir(parents=True, exist_ok=True)
    counter = _make_worktree_stub(fake_ws)
    _install_resolver(fake_ws, resolver_mode)
    if task_md_policy is not None:
        tasks_dir = fake_ws / "memory" / "tasks"
        tasks_dir.mkdir(parents=True, exist_ok=True)
        (tasks_dir / f"{task_id}.md").write_text(
            "## allowed_resources\n\n```yaml\nallowed_resources:\n"
            f"  merge_policy: {task_md_policy}\n  ttl_hours: 48\n```\n",
            encoding="utf-8",
        )
    block = _extract(RESOLVER_BEGIN, RESOLVER_END) + "\n" + _extract(MERGE_BEGIN, MERGE_END)
    script = (
        "set -euo pipefail\n"
        f'WORKSPACE="{fake_ws}"\n'
        f'EVENTS_DIR="{events}"\n'
        f'TASK_ID="{task_id}"\n'
        'TEAM_SHORT="dev1"\n'
        f'PROJECT_PATH="{project_path}"\n'
        f"_FINALIZE_ONLY={initial_finalize}\n"
        "_timer_ended=0\n"
        + block + "\n"
    )
    proc = subprocess.run(["bash", "-c", script], capture_output=True, text=True)
    return {
        "rc": proc.returncode,
        "stdout": proc.stdout,
        "stderr": proc.stderr,
        "wm_called": counter.exists() and counter.read_text().strip() != "",
        "finalize_marker": (events / f"{task_id}.finalize-only").exists(),
        "merge_done": (events / f"{task_id}.merge-done").exists(),
    }


def _run_argparse(args, extra_env=None):
    """ARGPARSE 센티넬 영역만 추출 실행. set -euo pipefail 강제.
    반환: dict(TASK_ID/TEAM_SHORT/PROJECT_PATH/FINALIZE/rc/stderr). assert 는 호출측에서."""
    block = _extract(ARGPARSE_BEGIN, ARGPARSE_END)
    script = ("set -euo pipefail\n" + block
              + '\nprintf "RESULT|%s|%s|%s|%s\\n" "$TASK_ID" "$TEAM_SHORT" "$PROJECT_PATH" "$_FINALIZE_ONLY"\n')
    env = dict(os.environ)
    if extra_env:
        env.update(extra_env)
    proc = subprocess.run(["bash", "-c", script, "finish-task.sh", *args],
                          capture_output=True, text=True, env=env)
    out = {"rc": proc.returncode, "stderr": proc.stderr, "stdout": proc.stdout}
    res = [ln for ln in proc.stdout.splitlines() if ln.startswith("RESULT|")]
    if res:
        _, task_id, team, proj, fin = res[-1].split("|")
        out.update({"TASK_ID": task_id, "TEAM_SHORT": team, "PROJECT_PATH": proj, "FINALIZE": fin})
    return out


def _run_pr_gate(tmp_path, finalize_only):
    """PR_GATE 센티넬 영역 실행. open PR 1건(gh stub)을 가정 → finalize-only 면 스킵, 아니면 BLOCKED(exit1)."""
    bindir = tmp_path / "bin"
    bindir.mkdir(exist_ok=True)
    gh_stub = bindir / "gh"
    gh_stub.write_text("#!/usr/bin/env bash\necho 1\n", encoding="utf-8")
    gh_stub.chmod(0o755)
    proj = tmp_path / "proj"
    (proj / ".git").mkdir(parents=True, exist_ok=True)
    block = _extract(PR_GATE_BEGIN, PR_GATE_END)
    script = (
        "set -euo pipefail\n"
        f'export PATH="{bindir}:$PATH"\n'
        'TASK_ID="test-mp-task"\n'
        'TEAM_SHORT="dev1"\n'
        f'PROJECT_PATH="{proj}"\n'
        f"_FINALIZE_ONLY={finalize_only}\n"
        + block + "\n"
    )
    return subprocess.run(["bash", "-c", script], capture_output=True, text=True)


# ════════════════════ 회귀 1: FINALIZE_ONLY=1 + PROJECT_PATH → worktree_manager finish 미호출 ════════════════════
def test_reg01_finalize_only_skips_worktree_manager(tmp_path):
    # initial_finalize=1 = ARGPARSE 가 env FINALIZE_ONLY=1 또는 --finalize-only 로 산출한 상태.
    r = _run_gate_and_merge(tmp_path, resolver_mode="real", task_md_policy='"tiered"',
                            project_path="/home/jay/projects/Dummy", initial_finalize=1)
    assert r["rc"] == 0, r["stderr"]
    assert r["wm_called"] is False, "FINALIZE_ONLY=1 인데 worktree_manager finish 가 호출됨(side-effect!)"
    assert r["finalize_marker"] is True, ".finalize-only 마커 미생성"
    assert r["merge_done"] is False, ".merge-done 가 생성됨(merge 미수행이므로 미생성이어야)"


def test_reg01b_finalize_env_drives_skip_end_to_end(tmp_path):
    # ARGPARSE(env FINALIZE_ONLY=1) → RESOLVER GATE → MERGE BLOCK 을 한 셸에서 연결 실행(통합).
    task_id = "test-mp-task"
    fake_ws = tmp_path / "ws"
    events = tmp_path / "events"
    events.mkdir(parents=True, exist_ok=True)
    counter = _make_worktree_stub(fake_ws)
    _install_resolver(fake_ws, "real")
    block = (_extract(ARGPARSE_BEGIN, ARGPARSE_END) + "\n"
             + _extract(RESOLVER_BEGIN, RESOLVER_END) + "\n"
             + _extract(MERGE_BEGIN, MERGE_END))
    script = (
        "set -euo pipefail\n"
        f'WORKSPACE="{fake_ws}"\n'
        f'EVENTS_DIR="{events}"\n'
        + block + "\n"
    )
    env = dict(os.environ, FINALIZE_ONLY="1")
    proc = subprocess.run(["bash", "-c", script, "finish-task.sh", task_id, "dev1",
                           "/home/jay/projects/Dummy"], capture_output=True, text=True, env=env)
    assert proc.returncode == 0, proc.stderr
    assert (counter.exists() and counter.read_text().strip()) is False or not counter.exists(), \
        "env FINALIZE_ONLY=1 인데 worktree_manager 호출됨"
    assert (events / f"{task_id}.finalize-only").exists()


# ════════════════════ 회귀 2: 동일 → PR gate / owner_gemini trigger 미호출 ════════════════════
def test_reg02_finalize_only_no_pr_gate_no_gemini(tmp_path):
    # PR gate · owner_gemini trigger 는 worktree_manager finish 내부에서만 발생 → wm 미호출 = 둘 다 미발생.
    r = _run_gate_and_merge(tmp_path, resolver_mode="real", task_md_policy='"tiered"',
                            project_path="/home/jay/projects/Dummy", initial_finalize=1)
    assert r["wm_called"] is False
    assert "worktree_manager finish/PR gate/Gemini trigger 미실행" in r["stdout"]
    # 2.3 PR 머지 검증 게이트도 finalize-only 면 스킵(open PR 이 정상).
    g = _run_pr_gate(tmp_path, finalize_only=1)
    assert g.returncode == 0, f"finalize-only 인데 PR-GATE BLOCKED: {g.stdout}{g.stderr}"
    assert "2.3 PR 머지 검증 스킵" in g.stdout
    assert "[PR-GATE] BLOCKED" not in g.stdout


# ════════════════════ 회귀 3: merge_policy=none + PROJECT_PATH → side-effect 0 ════════════════════
def test_reg03_merge_policy_none_side_effects_zero(tmp_path):
    r = _run_gate_and_merge(tmp_path, resolver_mode="real", task_md_policy='"none"',
                            project_path="/home/jay/projects/Dummy")
    assert r["rc"] == 0, r["stderr"]
    assert r["wm_called"] is False, "merge_policy=none 인데 worktree_manager finish 가 호출됨"
    assert r["finalize_marker"] is True, ".finalize-only 마커 미생성"
    assert r["merge_done"] is False, ".merge-done 가 생성됨(기대: 미생성)"
    assert "merge_policy=none honored" in r["stdout"]


# ════════════════════ 회귀 4: --finalize-only $1/$2/$3 위치무관 → positional 오염 0 ════════════════════
def test_reg04_flag_position_independent_no_contamination():
    expected = ("mytask", "dev1", "/home/jay/projects/Dummy")
    # $1/$2/$3/$4(후행) 어디에 와도 동일 결과 + FINALIZE=1, 오염 0.
    for args in (
        ["--finalize-only", "mytask", "dev1", "/home/jay/projects/Dummy"],   # $1
        ["mytask", "--finalize-only", "dev1", "/home/jay/projects/Dummy"],   # $2
        ["mytask", "dev1", "--finalize-only", "/home/jay/projects/Dummy"],   # $3
        ["mytask", "dev1", "/home/jay/projects/Dummy", "--finalize-only"],   # $4(후행)
    ):
        r = _run_argparse(args)
        assert r["rc"] == 0, r.get("stderr")
        assert (r["TASK_ID"], r["TEAM_SHORT"], r["PROJECT_PATH"]) == expected, f"오염: {args} → {r}"
        assert "--finalize-only" not in (r["TASK_ID"], r["TEAM_SHORT"], r["PROJECT_PATH"])
        assert r["FINALIZE"] == "1"
    # baseline: 플래그 없음 → FINALIZE=0, 오염 0 / env FINALIZE_ONLY=1 → 플래그 없이 승격, positional 보존.
    b = _run_argparse(["mytask", "dev1", "/proj"])
    assert (b["TASK_ID"], b["TEAM_SHORT"], b["PROJECT_PATH"], b["FINALIZE"]) == ("mytask", "dev1", "/proj", "0")
    e = _run_argparse(["mytask", "dev1", "/proj"], extra_env={"FINALIZE_ONLY": "1"})
    assert (e["TASK_ID"], e["TEAM_SHORT"], e["PROJECT_PATH"], e["FINALIZE"]) == ("mytask", "dev1", "/proj", "1")


# ════════════════════ 회귀 5: --finalize-only 단독 → set -u / old bash unbound 0 ════════════════════
def test_reg05_flag_alone_no_unbound():
    r = _run_argparse(["--finalize-only"])
    assert r["rc"] == 0, f"--finalize-only 단독에서 비정상 종료(unbound 의심): rc={r['rc']} stderr={r.get('stderr')}"
    assert "unbound variable" not in r["stderr"], f"unbound 발생: {r['stderr']}"
    assert r["FINALIZE"] == "1"
    assert (r["TASK_ID"], r["TEAM_SHORT"], r["PROJECT_PATH"]) == ("", "", "")


# ════════════════════ 회귀 6: 무인자 → unbound 0 ════════════════════
def test_reg06_no_args_no_unbound():
    r = _run_argparse([])
    assert r["rc"] == 0, f"무인자에서 비정상 종료(unbound 의심): rc={r['rc']} stderr={r.get('stderr')}"
    assert "unbound variable" not in r["stderr"], f"unbound 발생: {r['stderr']}"
    assert r["FINALIZE"] == "0"
    assert (r["TASK_ID"], r["TEAM_SHORT"], r["PROJECT_PATH"]) == ("", "", "")


def test_reg56_argparse_uses_no_bash_array():
    """구조적 보장: ARGPARSE 코드(주석 제외)에 bash 배열 구문이 없어야 한다(old bash unbound edge 원천 차단)."""
    code_lines = [ln for ln in _extract(ARGPARSE_BEGIN, ARGPARSE_END).splitlines()
                  if not ln.lstrip().startswith("#")]
    code = "\n".join(code_lines)
    for token in ("=()", "+=(", "[@]", "[*]", "_POS_ARGS"):
        assert token not in code, f"ARGPARSE 코드에 배열 구문/식별자 발견: {token!r}"
    # 카운터 기반임을 명시적으로 확인.
    assert "_pos_idx" in code and "case" in code


# ════════════════════ 회귀 7: resolver 실패 → fail-open 금지(fail-CLOSED) ════════════════════
def test_reg07_resolver_failure_is_fail_closed(tmp_path):
    r = _run_gate_and_merge(tmp_path, resolver_mode="fail", task_md_policy="tiered",
                            project_path="/home/jay/projects/Dummy")
    assert r["rc"] == 0, r["stderr"]
    assert r["wm_called"] is False, "resolver 실패인데 worktree_manager 가 호출됨(fail-OPEN!)"
    assert r["finalize_marker"] is True, "fail-CLOSED 인데 .finalize-only 마커 미생성"
    assert r["merge_done"] is False
    assert "fail-closed" in (r["stdout"] + r["stderr"]).lower()


def test_reg07b_resolver_missing_is_fail_closed(tmp_path):
    r = _run_gate_and_merge(tmp_path, resolver_mode="missing", task_md_policy=None,
                            project_path="/home/jay/projects/Dummy")
    assert r["wm_called"] is False
    assert r["finalize_marker"] is True


def test_reg07c_resolver_empty_policy_is_fail_closed(tmp_path):
    # task md 에 merge_policy 자체가 없음 → resolver 빈문자열 → fail-CLOSED(merge 금지).
    fake_ws = tmp_path / "ws"
    events = tmp_path / "events"
    events.mkdir(parents=True, exist_ok=True)
    counter = _make_worktree_stub(fake_ws)
    _install_resolver(fake_ws, "real")
    tasks_dir = fake_ws / "memory" / "tasks"
    tasks_dir.mkdir(parents=True, exist_ok=True)
    (tasks_dir / "test-mp-task.md").write_text("## allowed_resources\n\n```yaml\nallowed_resources:\n  ttl_hours: 48\n```\n",
                                               encoding="utf-8")
    block = _extract(RESOLVER_BEGIN, RESOLVER_END) + "\n" + _extract(MERGE_BEGIN, MERGE_END)
    script = ("set -euo pipefail\n"
              f'WORKSPACE="{fake_ws}"\nEVENTS_DIR="{events}"\nTASK_ID="test-mp-task"\n'
              'TEAM_SHORT="dev1"\nPROJECT_PATH="/home/jay/projects/Dummy"\n_FINALIZE_ONLY=0\n' + block + "\n")
    proc = subprocess.run(["bash", "-c", script], capture_output=True, text=True)
    assert proc.returncode == 0, proc.stderr
    assert not (counter.exists() and counter.read_text().strip()), "빈 merge_policy 에서 fail-OPEN(merge 실행)"
    assert (events / "test-mp-task.finalize-only").exists()
    assert "fail-closed" in (proc.stdout + proc.stderr).lower()


# ════════════════════ 회귀 8: QC/scope/callback/.done 경로는 merge block 밖에 유지 ════════════════════
def test_reg08_other_paths_outside_merge_block():
    lines = _finish_task_text().splitlines()
    bi = next(i for i, ln in enumerate(lines) if MERGE_BEGIN in ln)
    ei = next(i for i, ln in enumerate(lines) if MERGE_END in ln)
    inside = "\n".join(lines[bi:ei + 1])
    outside = "\n".join(lines[:bi] + lines[ei + 1:])
    full = _finish_task_text()
    for token in ["scope-guard", "callback", "DONE_FILE", "qc"]:
        assert token.lower() in full.lower(), f"{token} 토큰이 finish-task.sh 에 없음(회귀 손상)"
    # .done(merge 산출물 .merge-done 과 구분) 정의는 merge block 밖에 있어야 함.
    done_def = 'DONE_FILE="$EVENTS_DIR/${TASK_ID}.done"'
    assert done_def in outside, ".done 정의가 merge block 밖에 있어야 함"
    assert done_def not in inside, ".done 정의가 merge block 안으로 들어옴(스킵 시 누락 위험)"
    assert "worktree_manager.py" in inside, "merge block 센티넬 범위 검증 실패"


# ════════════════════ 회귀 9: 기존 task(tiered/auto/미지정) 동작 유지 ════════════════════
def test_reg09_tiered_preserves_existing_behavior(tmp_path):
    mod = _load_resolver()
    for policy in ['"tiered"', '"auto"', '"tiered"  # comment']:
        md = tmp_path / f"t_{abs(hash(policy))}.md"
        md.write_text(f"x\n```yaml\nallowed_resources:\n  merge_policy: {policy}\n```\n", encoding="utf-8")
        assert mod.resolve_merge_policy(str(md)) != "none"
    # tiered: resolver gate 가 _FINALIZE_ONLY 를 승격하지 않음 → merge block 정상 경로(PROJECT_PATH="" → 시스템 스킵).
    r = _run_gate_and_merge(tmp_path, resolver_mode="real", task_md_policy='"tiered"', project_path="")
    assert r["rc"] == 0, r["stderr"]
    assert r["finalize_marker"] is False, "tiered 인데 finalize 로 전환됨(기존 동작 손상)"
    assert "merge block 정상 진입(기존 동작 보존)" in r["stdout"]
    assert "[FINALIZE-ONLY] merge block 스킵" not in r["stdout"]


# ════════════════════ 회귀 10: ACTIVE=false · ANU key raw 0 · forbidden 0 ════════════════════
def test_reg10a_no_active_mutation_and_no_raw_anu_key():
    added = (_extract(ARGPARSE_BEGIN, ARGPARSE_END) + _extract(RESOLVER_BEGIN, RESOLVER_END)
             + _extract(MERGE_BEGIN, MERGE_END) + _extract(PR_GATE_BEGIN, PR_GATE_END))
    resolver_src = RESOLVER_PATH.read_text(encoding="utf-8")
    for src in (added, resolver_src):
        for tok in ("ACTIVE=true", "ACTIVE=True", "ACTIVE = True", "capability_active=true"):
            assert tok not in src, f"ACTIVE 변경 토큰 발견: {tok}"
    assert ANU_KEY_RAW not in resolver_src
    assert ANU_KEY_RAW not in added
    assert ANU_KEY_RAW not in Path(__file__).read_text(encoding="utf-8")


def test_reg10b_no_forbidden_actions_in_finalize_skip_branch():
    # finalize skip-branch(merge block 첫 if 분기)에 force/rebase/admin/실머지 토큰이 없어야 함.
    merge = _extract(MERGE_BEGIN, MERGE_END).splitlines()
    skip_branch, seen_if = [], False
    for ln in merge:
        if ln.strip().startswith("if [") and not seen_if:
            seen_if = True
            continue
        if seen_if and ln.strip().startswith("elif"):
            break
        if seen_if:
            skip_branch.append(ln)
    skip_text = "\n".join(skip_branch)
    assert skip_text.strip(), "skip-branch 추출 실패"
    for forbidden in ["push --force", "--force", "rebase", "--admin", "gh pr merge"]:
        assert forbidden not in skip_text, f"finalize skip-branch 에 금지 토큰: {forbidden}"


def test_reg10c_static_wiring_flag_env_marker():
    txt = _finish_task_text()
    assert "--finalize-only" in txt and "FINALIZE_ONLY:-0" in txt and "_FINALIZE_ONLY=1" in txt
    assert ".finalize-only" in txt  # 마커


# ════════════════════ resolver 단위 + py_compile / bash -n ════════════════════
def test_resolver_pure_function_parsing(tmp_path):
    mod = _load_resolver()
    md = tmp_path / "a.md"
    md.write_text("산문 merge_policy: none 은 무시\n```yaml\nallowed_resources:\n  merge_policy: \"none\"\n```\n",
                  encoding="utf-8")
    assert mod.resolve_merge_policy(str(md)) == "none"
    # 파일 부재 → 빈문자열(fail-CLOSED 신호)
    assert mod.resolve_merge_policy(str(tmp_path / "nope.md")) == ""
    # allowed_resources 블록 밖 산문은 무시(빈문자열)
    md2 = tmp_path / "b.md"
    md2.write_text("merge_policy: none\n본문만 있음\n", encoding="utf-8")
    assert mod.resolve_merge_policy(str(md2)) == ""


def test_py_compile_resolver():
    proc = subprocess.run(["python3", "-m", "py_compile", str(RESOLVER_PATH)],
                          capture_output=True, text=True)
    assert proc.returncode == 0, f"py_compile 실패: {proc.stderr}"


def test_bash_n_finish_task():
    proc = subprocess.run(["bash", "-n", str(FINISH_TASK_SH)], capture_output=True, text=True)
    assert proc.returncode == 0, f"bash -n 실패: {proc.stderr}"
