mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
Follow-up to PR #10. Addresses comments left by Devin Review. Primary fix (the BUG comment, .pre-commit-config.yaml:86-87): The receipt-required-for-math-content hook used files: '<math-track regex>' with pass_filenames: true. Pre-commit applies that regex to the staged file list BEFORE invoking the hook, so evidence files (receipts under shared-data/artifacts/deepseek_review/, claims.yaml) were stripped from argv. require_math_evidence.py then saw only the math-track files, found no evidence, and exited 1 -- even when proper evidence was committed alongside. The only case that worked was Lean-only commits, because Lean files are dual-classified as both math-track and evidence. Fix: drive the hook from the index instead of argv. * require_math_evidence.py grows a --staged mode that runs 'git diff --cached --name-only' itself, plus a mutex check so --staged, --from-git-diff, and explicit FILES cannot be combined. * .pre-commit-config.yaml hook switches to always_run: true, pass_filenames: false, and 'entry: ... --staged'. The script exits 0 early when no math-track files are staged, so the cost of always_run is negligible. Polish: * claims-registry.schema.json: add required: ["status"] inside each 'if' subschema. Without it, an entry missing 'status' would also spuriously trip the 'then' clauses (review_receipts, lean) before the top-level required catch. Pure error-message cleanup. * validate_claims_registry.py: replace the catch-all re.compile(r'^[A-Za-z]+:') with a closed list of well-known URI schemes (http, https, arxiv, doi, isbn, mailto, urn). Module-name-shaped strings like 'Module:Theorem' will no longer silently bypass the on-disk path check. * validate_claims_registry.py: thread a FormatChecker through the Draft202012Validator so format-keyword behaviour matches validate_deepseek_receipts.py. No-op for today's schema but cheap insurance for the next contributor who adds 'format'. Regression tests: * New scripts/math-first/test_require_math_evidence.py covers ten classification cases plus the actual --staged regression: it spins up a temp git repo, stages a math-track file + a receipt, invokes the script with --staged, and asserts exit 0. Without the fix this case fails, demonstrating the bug end-to-end. * math-check.yml runs the new self-tests in CI. Docs: * docs/math-first-tooling.md: document the --staged contract, why always_run + pass_filenames: false is necessary, and how to run the new self-tests. Co-Authored-By: Allaun Silverfox <bigdataiscoming+9i37y6j2@protonmail.com>
177 lines
6.2 KiB
Python
Executable file
177 lines
6.2 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Require math evidence alongside math-track edits.
|
|
|
|
When a commit (or PR) touches files under one of the math-track surfaces,
|
|
this script asserts that at least one file under a math-evidence surface is
|
|
also part of the same change set. The two surfaces are configurable -- the
|
|
defaults below match ``docs/math-first-tooling.md`` and the pre-commit hook
|
|
declared in ``.pre-commit-config.yaml``.
|
|
|
|
Math-track surfaces (need evidence):
|
|
- 0-Core-Formalism/lean/Semantics/...
|
|
- 6-Documentation/docs/distilled/...
|
|
- shared-data/data/stack_solidification/...
|
|
|
|
Math-evidence surfaces (accepted as evidence):
|
|
- shared-data/artifacts/deepseek_review/*.receipt.json
|
|
- 0-Core-Formalism/lean/Semantics/... (a Lean change in the same commit
|
|
counts because Lean is the source
|
|
of truth per AGENTS.md)
|
|
- claims.yaml (registry update)
|
|
|
|
Usage:
|
|
scripts/math-first/require_math_evidence.py [FILES ...]
|
|
scripts/math-first/require_math_evidence.py --staged
|
|
scripts/math-first/require_math_evidence.py --from-git-diff BASE_REF
|
|
|
|
If no input mode is supplied the script exits 0 with a noop. Pre-commit
|
|
invokes it with ``--staged`` so the script sees the entire staged set
|
|
regardless of pre-commit's own ``files`` filter; CI invokes it with
|
|
``--from-git-diff origin/<base>``.
|
|
|
|
Exit code:
|
|
0 evidence present, nothing to do, or no math-track files changed.
|
|
1 math-track files changed without accompanying evidence.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
MATH_TRACK_PREFIXES: tuple[str, ...] = (
|
|
"0-Core-Formalism/lean/Semantics/",
|
|
"6-Documentation/docs/distilled/",
|
|
"shared-data/data/stack_solidification/",
|
|
)
|
|
|
|
EVIDENCE_PREFIXES: tuple[str, ...] = (
|
|
"shared-data/artifacts/deepseek_review/",
|
|
"0-Core-Formalism/lean/Semantics/",
|
|
)
|
|
|
|
EVIDENCE_FILES: tuple[str, ...] = (
|
|
"claims.yaml",
|
|
)
|
|
|
|
|
|
def _normalise(path: str) -> str:
|
|
return path.replace("\\", "/")
|
|
|
|
|
|
def _is_math_track(path: str) -> bool:
|
|
norm = _normalise(path)
|
|
return any(norm.startswith(prefix) for prefix in MATH_TRACK_PREFIXES)
|
|
|
|
|
|
def _is_evidence(path: str) -> bool:
|
|
norm = _normalise(path)
|
|
if norm in EVIDENCE_FILES:
|
|
return True
|
|
if any(norm.startswith(prefix) for prefix in EVIDENCE_PREFIXES):
|
|
# A *new or updated* receipt counts. A bare Lean kernel edit also
|
|
# counts because Lean is treated as the source of truth -- the change
|
|
# itself is the evidence.
|
|
if norm.startswith("shared-data/artifacts/deepseek_review/"):
|
|
return norm.endswith(".receipt.json") or norm.endswith(".md")
|
|
return True
|
|
return False
|
|
|
|
|
|
def _files_from_git_diff(base_ref: str) -> list[str]:
|
|
cmd = ["git", "diff", "--name-only", f"{base_ref}...HEAD"]
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True, cwd=REPO_ROOT)
|
|
except subprocess.CalledProcessError as exc:
|
|
print(f"error: `{' '.join(cmd)}` failed: {exc.stderr.strip()}", file=sys.stderr)
|
|
raise SystemExit(2)
|
|
return [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
|
|
|
|
|
def _files_from_staged() -> list[str]:
|
|
"""Return the staged file list via ``git diff --cached --name-only``.
|
|
|
|
Used by the pre-commit hook so the script sees every staged file --
|
|
math-track *and* evidence -- regardless of pre-commit's per-hook
|
|
``files`` filter. This is important because pre-commit otherwise strips
|
|
receipts and ``claims.yaml`` from the argv before the script ever sees
|
|
them, which would cause the evidence check to falsely fail.
|
|
"""
|
|
cmd = ["git", "diff", "--cached", "--name-only"]
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True, cwd=REPO_ROOT)
|
|
except subprocess.CalledProcessError as exc:
|
|
print(f"error: `{' '.join(cmd)}` failed: {exc.stderr.strip()}", file=sys.stderr)
|
|
raise SystemExit(2)
|
|
return [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"files",
|
|
nargs="*",
|
|
help="Explicit list of files to check (typically supplied by pre-commit).",
|
|
)
|
|
parser.add_argument(
|
|
"--from-git-diff",
|
|
metavar="BASE_REF",
|
|
help="Compute the file list from `git diff --name-only BASE_REF...HEAD`.",
|
|
)
|
|
parser.add_argument(
|
|
"--staged",
|
|
action="store_true",
|
|
help="Compute the file list from `git diff --cached --name-only` "
|
|
"(use this from pre-commit so the entire staged set is visible).",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
if sum(bool(x) for x in (args.from_git_diff, args.staged, args.files)) > 1:
|
|
parser.error("--from-git-diff, --staged, and explicit FILES are mutually exclusive")
|
|
|
|
if args.from_git_diff:
|
|
files = _files_from_git_diff(args.from_git_diff)
|
|
elif args.staged:
|
|
files = _files_from_staged()
|
|
else:
|
|
files = list(args.files)
|
|
|
|
if not files:
|
|
return 0
|
|
|
|
math_track = sorted({f for f in files if _is_math_track(f)})
|
|
evidence = sorted({f for f in files if _is_evidence(f)})
|
|
|
|
if not math_track:
|
|
return 0
|
|
|
|
if evidence:
|
|
print("math-evidence check: OK")
|
|
print(" math-track files:")
|
|
for path in math_track:
|
|
print(f" - {path}")
|
|
print(" evidence files:")
|
|
for path in evidence:
|
|
print(f" - {path}")
|
|
return 0
|
|
|
|
print("math-evidence check: FAIL", file=sys.stderr)
|
|
print(" math-track files changed without accompanying evidence:", file=sys.stderr)
|
|
for path in math_track:
|
|
print(f" - {path}", file=sys.stderr)
|
|
print(
|
|
"\n Add at least one of:\n"
|
|
" - a DeepSeek review receipt under shared-data/artifacts/deepseek_review/\n"
|
|
" - a Lean change under 0-Core-Formalism/lean/Semantics/\n"
|
|
" - a claims.yaml update\n"
|
|
" See docs/math-first-tooling.md.",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|