mirror of
https://github.com/Imbad0202/academic-research-skills.git
synced 2026-09-14 13:51:17 +08:00
9524237024
* #215 (1/4): field-norm severity gold set — first-party regression fixture W1 (field-norm boundary, n=54) + F.3.4 (significance boundary, 56 errors) severity-miscalibration cases extracted verbatim from Kim et al. 2026 (arXiv:2605.20668v1), 10 cases (5 + 5). Cross-model (codex xhigh) review fixes baked in: - Not a detector/calibration set — no deterministic predictor exists for field-norm severity miscalibration, so the lint validates DATA INTEGRITY + first-party PROVENANCE, not FNR/FPR (no fluent-wrongness calibration ritual). - Subtypes, not mutually-exclusive labels (a case can be both field-norm-like and significance-like, e.g. Coulombic efficiency >100%). - Provenance pins paper section/example IDs + the paper's own citation tokens (P9 · GPT-5.2 · item 5) + verbatim anchor snippets — NOT session-scoped pdftotext line numbers (those rot). - SAR 11.7T case retained as exception=true (experts concurred with the AI in that specific instance) so a severity check can't blanket-flag the "core result" formula. - metadata.task_type=regression-fixture; no distributional-calibration claim from n=10. Lint scripts/check_field_norm_severity.py + 13 tests (mutation-style: each invariant RED-s when broken). Wired into the CI pytest manifest. run_evals discovers the manifest and marks it pending (no native measurer, by design — not gated by the threshold gate). Author list + section anchors verified first-party against the PDF (the KB summary's author list had drifted; PDF is authoritative). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * #215 (2/4): three reviewer surfaces enforce field-norm severity discipline Builds on the gold set (1/4). All three layers the #215 spec names, with the cross-model (codex xhigh) review fixes folded in: - domain_reviewer_agent.md — new "### Step 5: Field-Norm Severity Discipline" hard rule: a critique whose severity rests on a field norm MUST ground that norm in an external checkable source (NOT model knowledge), else down-rate to advisory + [FIELD-NORM UNVERIFIED]. codex fix: evidence is NOT limited to a literature citation — venue/data policy, community standard, reporting guideline, or documented expert practice all count. Runs at severity-assign time, applies to every field-norm-dependent finding, not only CRITICAL. - devils_advocate_reviewer_agent.md — 9th challenge dimension "Field-Norm Severity Calibration" (the DA turns the lens on its own findings, since adversarial intensity amplifies an ungrounded norm into a CRITICAL) + two required CRITICAL/MAJOR fields field_norm_boundary + evidence_crossing_rationale. - calibration_mode_protocol.md — new "### Phase 3.5: Severity-miscalibration measurement" + a low/med/high histogram in the Calibration Report, a signal the binary FNR/FPR matrix cannot show. codex fix (anti-circularity): the classifier rates whether the reviewer SUPPLIED external grounding, NOT whether the norm is factually correct — guessing norm-correctness would repeat the W1 failure under audit; anchored to evals/gold/field_norm_severity/. Lint scripts/check_215_field_norm.py asserts all three surfaces carry their blocks with block-scoped keyword checks (bare keyword elsewhere can't mask a missing rule). 8 mutation tests + CI step + pytest manifest entry. /simplify applied (test helper reuses the lint's _block(); minor dedup). Existing phase-boundary lint unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * #215 (3/4): close codex ship-gate review — lint false-pass paths Cross-model (codex xhigh) review of the branch found four lint false-pass paths (no design/boundary issues). All fixed + regression-tested: - [P1] check_field_norm_severity.py: an exception case (SAR) that lost BOTH its `exception` flag and `exception_reason` passed the paired check silently (both branches false) and reverted to a clean positive. Added an id-suffix guard: an id ending `-exception` MUST carry exception=true. - [P1] check_215_field_norm.py: the DA field checks were file-wide, so deleting the CRITICAL/MAJOR output-format columns still passed (the snake_case names survive in the gating prose). Now scoped: the Title-Case column HEADERS are checked inside the `## Output Format` block; the snake_case definitions are checked inside the `### What Constitutes a CRITICAL Finding` block. - [P2] domain Step 5: a bare `MUST` check passed on `MUST NOT` alone — deleting the load-bearing "ground the norm in an external ... source" clause was not caught. Now asserts that specific clause. - [P2] calibration Phase 3.5: a bare `low`/`med`/`high` substring check passed on the intro line even after the definition bullets were deleted. Now requires the **`level`** definition markers. Also fixed a real _block() bug surfaced while scoping the DA Output Format check: _block() treated `##` lines INSIDE a ```fenced sample report as headers and truncated the block early. Made it fence-aware so the scoped column check reaches the CRITICAL table below the fence. +5 mutation tests (26 total) covering each fixed false-pass + the fence case. personal-boundary 0 violations; sensitive-term scan clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * #215 (4/4): close codex re-review — two residual lint false-passes The ship-gate fixes (3/4) introduced two finer false-passes, caught on codex re-verify. Both fixed + regression-tested: - [P1] check_215_field_norm.py: the DA column check scoped to the whole ## Output Format block, but that block holds BOTH the CRITICAL and MAJOR tables. If only one table lost its columns, the other table's column names still satisfied the substring check. Now each #### severity subsection is sliced and checked separately (#### CRITICAL / #### MAJOR). - [P1] check_215_field_norm.py: Step 5 checked the grounding phrase and the MUST NOT prohibition independently, so weakening "**MUST** ground the norm" to "**SHOULD** ground" would still pass. The clause now binds the modal: "MUST** ground the norm in an external". +2 mutation tests (28 total): single-table column deletion, and MUST→SHOULD modal weakening. personal-boundary 0 violations; sensitive-term scan clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * #215: add Unreleased CHANGELOG entry for field-norm severity calibration Records the three-surface field-norm severity work + first-party regression fixture under [Unreleased] so it is not missed at the next release tag (per the release-doc-alignment discipline — the v3.12.0 Unreleased gap was the lesson). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
174 lines
8.6 KiB
Python
174 lines
8.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Static lint for #215 field-norm severity coverage across the three reviewer surfaces.
|
|
|
|
Issue: #215 (field-norm severity calibration, Kim et al. 2026 arXiv:2605.20668v1 W1/§F.3.4).
|
|
|
|
Enforces that the three #215 changes are present AND scoped to their own block, so a
|
|
bare keyword appearing elsewhere in a file cannot make the lint pass while the actual
|
|
load-bearing instruction is missing (falsifiability discipline, per
|
|
feedback_lint_passes_but_prompt_silent.md — same pattern as check_v3_9_2_phase_boundary).
|
|
|
|
Three surfaces:
|
|
|
|
1. domain_reviewer_agent.md — a `### Step 5: Field-Norm Severity Discipline (#215)` block
|
|
that contains the hard rule (MUST ground the norm in an external source, MUST NOT
|
|
assert from model knowledge), the broadened evidence definition (not just a literature
|
|
citation), and the `[FIELD-NORM UNVERIFIED]` down-rate label.
|
|
|
|
2. devils_advocate_reviewer_agent.md — a 9th challenge dimension
|
|
`### 9. Field-Norm Severity Calibration (#215)` AND the two required CRITICAL/MAJOR
|
|
fields `field_norm_boundary` + `evidence_crossing_rationale`.
|
|
|
|
3. calibration_mode_protocol.md — a `### Phase 3.5: Severity-miscalibration measurement (#215)`
|
|
block carrying the low/med/high risk classification and the anti-circularity grounding
|
|
discipline (classify grounding, NOT norm-correctness — do not repeat the W1 failure).
|
|
|
|
Exit 0 = clean, 1 = any failure.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
def _read(rel: str) -> str:
|
|
return (REPO_ROOT / rel).read_text(encoding="utf-8")
|
|
|
|
|
|
def _block(text: str, header_re: str) -> str | None:
|
|
"""Return the markdown block from a header matching header_re up to the next header of
|
|
the same-or-higher level (## or ###), or end of file. None if the header is absent.
|
|
|
|
Scopes keyword checks to the block so a keyword elsewhere in the file does not count.
|
|
A ``##``/``###`` line INSIDE a ``` fenced code block is NOT a real header (e.g. an
|
|
Output Format section embeds a sample report whose code fence contains ``## ...`` lines);
|
|
treating it as one would truncate the block early and drop content below it.
|
|
"""
|
|
m = re.search(header_re, text, re.M)
|
|
if not m:
|
|
return None
|
|
start = m.start()
|
|
rest = text[m.end():]
|
|
in_fence = False
|
|
offset = 0
|
|
for line in rest.splitlines(keepends=True):
|
|
if line.lstrip().startswith("```"):
|
|
in_fence = not in_fence
|
|
elif not in_fence and re.match(r"\#{2,3} ", line):
|
|
# A real header line (column 0, outside any fence) ends the block.
|
|
return text[start : m.end() + offset]
|
|
offset += len(line)
|
|
return text[start:]
|
|
|
|
|
|
def check() -> list[str]:
|
|
errors: list[str] = []
|
|
|
|
# --- Surface 1: domain_reviewer_agent.md Step 5 ---
|
|
dr = _read("academic-paper-reviewer/agents/domain_reviewer_agent.md")
|
|
step5 = _block(dr, r"^### Step 5: Field-Norm Severity Discipline \(#215\)")
|
|
if step5 is None:
|
|
errors.append("domain_reviewer_agent.md: missing '### Step 5: Field-Norm Severity Discipline (#215)' block")
|
|
else:
|
|
# The down-rate prohibition AND the load-bearing positive MUST-ground clause must
|
|
# both survive. The positive clause binds the modal to the grounding requirement
|
|
# ("MUST** ground the norm in an external"), so weakening MUST→SHOULD is caught — a
|
|
# bare "ground the norm" substring would still pass under SHOULD (codex re-review P1).
|
|
for clause in ("MUST NOT", "[FIELD-NORM UNVERIFIED]", "MUST** ground the norm in an external"):
|
|
if clause not in step5:
|
|
errors.append(f"domain_reviewer_agent.md Step 5: missing required clause {clause!r}")
|
|
# codex P1: evidence is NOT limited to a literature citation.
|
|
if "not limited to a literature citation" not in step5:
|
|
errors.append(
|
|
"domain_reviewer_agent.md Step 5: missing the broadened-evidence rule "
|
|
"('not limited to a literature citation')"
|
|
)
|
|
|
|
# --- Surface 2: devils_advocate_reviewer_agent.md dimension 9 + CRITICAL fields ---
|
|
da = _read("academic-paper-reviewer/agents/devils_advocate_reviewer_agent.md")
|
|
dim9 = _block(da, r"^### 9\. Field-Norm Severity Calibration \(#215\)")
|
|
if dim9 is None:
|
|
errors.append("devils_advocate_reviewer_agent.md: missing '### 9. Field-Norm Severity Calibration (#215)' dimension")
|
|
# The two required fields must land in the OUTPUT FORMAT block — that is what makes the
|
|
# rule reach the actual review output. A file-wide check would pass on the prose mention
|
|
# in the gating section even if the output-format columns were deleted (codex P1).
|
|
output_fmt = _block(da, r"^## Output Format")
|
|
if output_fmt is None:
|
|
errors.append("devils_advocate_reviewer_agent.md: missing '## Output Format' block")
|
|
else:
|
|
# The CRITICAL/MAJOR tables carry the fields as human-readable column HEADERS
|
|
# (Title Case), which is what actually reaches the review output. The snake_case
|
|
# names live only in the gating prose. Scope to EACH severity subsection separately:
|
|
# if only one table loses its columns, a whole-block check still finds the names in
|
|
# the other table and false-passes (codex re-review P1). These #### headers sit inside
|
|
# the ```markdown sample, so slice between #### markers rather than using _block.
|
|
for severity in ("CRITICAL", "MAJOR"):
|
|
sub = re.search(rf"^#### {severity}\n(.*?)(?=^#### |\Z)", output_fmt, re.M | re.S)
|
|
if sub is None:
|
|
errors.append(f"devils_advocate_reviewer_agent.md Output Format: missing '#### {severity}' table")
|
|
continue
|
|
for column in ("Field-Norm Boundary", "Evidence-Crossing Rationale"):
|
|
if column not in sub.group(1):
|
|
errors.append(
|
|
f"devils_advocate_reviewer_agent.md Output Format {severity} table: "
|
|
f"missing required column {column!r}"
|
|
)
|
|
# The snake_case field NAMES + their grounding definition live in the CRITICAL-finding
|
|
# gating block; check them there (scoped) so a definition deleted from that block is
|
|
# caught independently of the output-format columns.
|
|
crit_block = _block(da, r"^### What Constitutes a CRITICAL Finding")
|
|
if crit_block is None:
|
|
errors.append("devils_advocate_reviewer_agent.md: missing '### What Constitutes a CRITICAL Finding' block")
|
|
else:
|
|
for field in ("field_norm_boundary", "evidence_crossing_rationale"):
|
|
if field not in crit_block:
|
|
errors.append(
|
|
f"devils_advocate_reviewer_agent.md CRITICAL-finding block: missing field "
|
|
f"definition {field!r}"
|
|
)
|
|
if "[FIELD-NORM UNVERIFIED]" not in crit_block:
|
|
errors.append(
|
|
"devils_advocate_reviewer_agent.md CRITICAL-finding block: missing "
|
|
"'[FIELD-NORM UNVERIFIED]' down-rate label"
|
|
)
|
|
|
|
# --- Surface 3: calibration_mode_protocol.md Phase 3.5 ---
|
|
cal = _read("academic-paper-reviewer/references/calibration_mode_protocol.md")
|
|
phase35 = _block(cal, r"^### Phase 3\.5: Severity-miscalibration measurement \(#215\)")
|
|
if phase35 is None:
|
|
errors.append("calibration_mode_protocol.md: missing '### Phase 3.5: Severity-miscalibration measurement (#215)' block")
|
|
else:
|
|
# Require the actual risk-level DEFINITIONS, not bare words. The intro line already
|
|
# contains "low / med / high", so a substring check passes even if all three
|
|
# definition bullets are deleted (codex P2). Each level is defined as **`level`** — …
|
|
for level in ("low", "med", "high"):
|
|
if f"**`{level}`**" not in phase35:
|
|
errors.append(
|
|
f"calibration_mode_protocol.md Phase 3.5: missing the {level!r} risk-level definition"
|
|
)
|
|
# codex P1: classify GROUNDING, not norm-correctness — do not repeat the failure.
|
|
if "MUST NOT" not in phase35 or "evals/gold/field_norm_severity" not in phase35:
|
|
errors.append(
|
|
"calibration_mode_protocol.md Phase 3.5: missing the anti-circularity grounding "
|
|
"discipline (MUST NOT guess norm-correctness; anchor to evals/gold/field_norm_severity)"
|
|
)
|
|
|
|
return errors
|
|
|
|
|
|
def main() -> int:
|
|
errors = check()
|
|
if errors:
|
|
for e in errors:
|
|
print(f"ERROR: {e}", file=sys.stderr)
|
|
return 1
|
|
print("215_field_norm: all three reviewer surfaces carry their scoped #215 blocks")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|