#!/usr/bin/env python3 """Judge-prompt-version drift guard (#361). The judge cache key includes a `prompt_version` component so a judge-prompt revision invalidates stale cache entries automatically (issue #361). That gate keys on `JUDGE_PROMPT_SHA256` — the prompt's own fingerprint — so invalidation only works if that hash actually tracks the prompt text. If the hash could drift from the prompt, an edited prompt would still hash-match an old cache key and a stale verdict would be served, silently re-opening the exact bug #361 closes. This lint is the backstop: it recomputes the SHA-256 of the canonical judge-prompt section (the text between the JUDGE-PROMPT-CANONICAL-START / -END markers in `academic-pipeline/agents/claim_ref_alignment_audit_agent.md`) and compares it to `JUDGE_PROMPT_SHA256` in `scripts/_claim_audit_constants.py`. On drift it fails, instructing the author to re-pin the hash AND bump the `JUDGE_PROMPT_VERSION` human-readable label (a log/diff aid, not the cache key) in the same change. It does NOT (and cannot) verify that the version literal itself changed — that is a human judgment about whether the edit is behavior-affecting. What it guarantees is that a prompt edit cannot land WITHOUT the author touching the version machinery at all: the hash line and the version line live two lines apart, so updating the hash makes the missing version bump glaringly visible in the diff. Usage: python scripts/check_judge_prompt_version.py python scripts/check_judge_prompt_version.py --root PATH (for test fixtures) Exit codes: 0 — prompt section hash matches the pinned constant 1 — hash drift (prompt changed without re-pinning) — bump version + hash 2 — invocation error (markers missing, file missing, constant unreadable) """ from __future__ import annotations import argparse import hashlib import re import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] _AGENT_REL = "academic-pipeline/agents/claim_ref_alignment_audit_agent.md" _CONSTANTS_REL = "scripts/_claim_audit_constants.py" _SECTION_RE = re.compile( r"(.*?)