Skip to content

📦 WP-130 — refine(scripts): consolidate scripts/-testing tests, wire remaining lint hooks

Engineering log · Phase 11 — Rolling, toward v0.5.0

🔗 Depends on WP-129 · Needed by WP-130b

⬅️ WP-129 · Roadmap · WP-130b ➡️

WP-130 — a test of a script belongs beside the script

WP-129 split the doctest-audit check itself out of tests/meta/ but left its test where the whole family had always lived, tests/meta/test_audit_test_doctests.py -- and left the other eight tests/meta/ files, plus tests/scripts/, tests/train/test_shapes_regression.py and tests/integration/test_overfit_micro.py, exactly where they were: every one of them a test whose subject is a scripts/ module, sitting in tests/ anyway because that is where tests conventionally go. The convention was wrong for this shape: src/lucid_yolo/ and tests/unit/ mirror each other by design, and scripts/ had grown its own testable surface without growing its own mirror. Fixed by moving all fifteen files into a new scripts/_tests/, each renamed test_<script_name>.py after the module it exercises -- test_license_audit.py becomes test_audit_licenses.py, test_golden_harness.py becomes test_check_goldens.py -- so the pairing reads directly off the two filenames rather than needing the docstring to say which script a test covers.

The other five tests/meta/ checkers still inline (docs presence, docs site, figure captions, license headers, version single source) convert the same way WP-129 converted the doctest audit: a scripts/lint/<name>.py script plus a .pre-commit-config.yaml hook, scripts/_tests/test_<name>.py left as the functional-core test. release_guard.py and check_goldens.py were already scripts with no hook -- both gain one, stages: [manual] like nothing else in the file, since a release tag and a golden recompute are not per-commit checks. release_guard.py needed one real change to be hook-shaped at all: --tag was required=True, unrunnable from a hook that fires with no arguments on every commit. It now defaults to git describe --tags --exact-match HEAD and no-ops when HEAD isn't exactly a tag -- true for nearly every commit, which is the point.

Every hook's name: field gained a distinct leading emoji chosen for the action it performs (🧠 mypy, ⚖️ license audit, 📋 docs presence, 🗺️ docs site, 🖼️ figure captions, 📄 license headers, 🔢 version single source, 📝 doctest audit, 🏷️ commit trailers, 🥇 golden check, 🛡️ release guard) -- eleven hooks read a lot faster on pre-commit run --all-files's output when each line starts with a different glyph than when all eleven start with the same bullet.

The move itself broke two things that had never been exercised before, both structural rather than content bugs. mypy: [tool.mypy] scans files = ["src", "scripts"] with no mypy_path entry for the repo root, so a file inside scripts/ got two identities at once -- absolutize_readme from the direct directory walk, scripts.absolutize_readme from scripts/_tests/test_absolutize_readme.py's own from scripts.absolutize_readme import ..., now itself swept into the scripts scan for the first time. explicit_package_bases = true plus mypy_path = "src:." gives both paths the same resolution; scripts/_tests/ itself is excluded from strict checking afterward, matching tests/'s own long-standing exemption for exactly the same fixture-heavy, monkeypatch-typed, dynamically-imported character. pytest: scripts/_tests/conftest.py and tests/conftest.py are both non-package conftest.py files with the bare module name conftest; under the default prepend import mode, loading them from two different top-level collection roots (scripts and tests in the same pytest ... src scripts tests invocation) rather than nested under a shared one raised import file mismatch. --import-mode=importlib fixes it structurally, keying each module's identity on its resolved path rather than its stem, and is now the addopts default for every invocation, not just this one.

scripts/README.md is new: a table per subtree (core, lint/, _tests/) naming every script, what it does, what invokes it, and which test covers it -- the crossroad a developer new to scripts/ reads first, and the thing that makes "no dedicated test, doctests only" for golden_producers.py, plot_training.py, and dump_debug_grid.py a documented decision instead of a silent gap.