Merge pull request #19 from ScienceLiveHub/fix/init-template-substitu… #36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Lint conventions | |
| # Mechanical enforcement of the repo conventions that were previously stated in | |
| # prose and enforced by asking a human (or a model) to remember them. Every check | |
| # here already existed as a copy-paste snippet in docs/cicd-conventions.md | |
| # § "Audit before push" or as a "never do X" rule in CLAUDE.md / DOMAIN.md — | |
| # see the per-check comments for where each one is written down. | |
| # | |
| # Design notes: | |
| # * Runs on the UNINITIALISED template too, so the template lints itself. Only | |
| # the placeholder-token check is gated on initialisation (the template | |
| # legitimately ships {{...}} tokens until /init-template runs). | |
| # * Collects every failure and reports them together. A linter that stops at | |
| # the first problem makes you re-run it N times. | |
| # * Pure grep — no pixi, no network, no deps. Seconds, not minutes. | |
| # | |
| # Deliberately NOT checked here: "every notebook import is in pixi.toml" | |
| # (docs/cicd-conventions.md:232). The snippet there only *lists* imports for a | |
| # human to eyeball; automating it needs stdlib filtering and an import-name -> | |
| # package-name map (`import yaml` -> `pyyaml`), which is exactly the kind of | |
| # check that cries wolf. Left to a human until it can be done without false | |
| # positives. `pixi.lock` freshness is already enforced: ci.yml's setup-pixi runs | |
| # with `locked: true`. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Check conventions | |
| shell: bash | |
| run: | | |
| set -uo pipefail | |
| fail=0 | |
| note() { echo " ok $1"; } | |
| bad() { echo "::error::$1"; fail=1; } | |
| echo "--- notebooks ---" | |
| # NOTE on the `^[^#]*` prefix used by the two checks below: the | |
| # notebooks legitimately DISCUSS these anti-patterns in comments — e.g. | |
| # notebooks/04_figures.py carries "# `matplotlib.use('Agg')` — it | |
| # blocks inline display." A naive grep flags that warning as a | |
| # violation, and a linter that fires on a comment telling you not to do | |
| # the thing is a linter people learn to ignore. `^[^#]*` requires the | |
| # match to appear with no `#` earlier on the line, i.e. in real code. | |
| # CLAUDE.md:107, docs/cicd-conventions.md:110-112, notebooks/README.md:34, | |
| # .claude/agents/replication-coder.md:41 all prohibit this. Four | |
| # statements, zero enforcement, until now. | |
| if ls notebooks/*.py >/dev/null 2>&1 && grep -nE "^[^#]*matplotlib\.use" notebooks/*.py; then | |
| bad "matplotlib.use(...) found in notebooks/ (see the grep hits above). It blocks inline plot display, which builds an empty Jupyter Book. Use plt.show() after fig.savefig() instead. Prohibited by CLAUDE.md § Phase 2." | |
| else | |
| note "no matplotlib.use() in notebooks/ code" | |
| fi | |
| # DOMAIN.md:31,38-40,66 — .npz is pickle-based and drops dims, units | |
| # and CRS, so it is not a self-describing artefact. | |
| if ls notebooks/*.py >/dev/null 2>&1 && grep -nE "^[^#]*(\.npz|np\.savez)" notebooks/*.py; then | |
| bad ".npz / np.savez found in notebooks/ (see the grep hits above). It drops dimension labels, units and CRS metadata. Use NetCDF (<=2 GB) or Zarr instead — DOMAIN.md § Data formats." | |
| else | |
| note "no .npz / np.savez in notebooks/ code" | |
| fi | |
| echo "--- jupyter book ---" | |
| # docs/cicd-conventions.md:69-78,239 — the TOC must point at the | |
| # EXECUTED .ipynb, not the jupytext .py source, or the book renders | |
| # without outputs. | |
| if grep -nE "\.py" myst.yml; then | |
| bad "myst.yml references .py (see the hits above). The TOC must list the executed notebooks/*.ipynb, not the jupytext .py sources, or the book builds with empty cells — docs/cicd-conventions.md § myst.yml." | |
| else | |
| note "myst.yml TOC references .ipynb, not .py" | |
| fi | |
| # docs/cicd-conventions.md:52-63,242 — MyST silently ignores base_url | |
| # in myst.yml, so it MUST come from the workflow env or every link on | |
| # the deployed site 404s. | |
| if grep -q "BASE_URL" .github/workflows/jupyter-book.yml; then | |
| note "jupyter-book.yml sets BASE_URL" | |
| else | |
| bad "jupyter-book.yml does not set BASE_URL. MyST ignores base_url in myst.yml, so without the env var every link on the deployed site breaks — docs/cicd-conventions.md § BASE_URL." | |
| fi | |
| # docs/cicd-conventions.md:106,245 — executing a hardcoded list means a | |
| # newly added notebook is silently never run. | |
| if grep -q "notebooks/\*.ipynb" .github/workflows/jupyter-book.yml; then | |
| note "jupyter-book.yml executes notebooks via glob" | |
| else | |
| bad "jupyter-book.yml does not execute notebooks/*.ipynb via a glob. A hardcoded list silently skips newly added notebooks — docs/cicd-conventions.md § Notebook execution." | |
| fi | |
| echo "--- placeholder tokens ---" | |
| # CLAUDE.md:62. Only meaningful once /init-template has run: before | |
| # that the tokens are the point. Scoped to the files that must be | |
| # substituted — NOT a repo-wide grep, because docs/ and .claude/ | |
| # legitimately document the token system, and grepping them is what | |
| # used to cause false-positive skips and silent-green CI | |
| # (see .github/actions/check-ready). | |
| if [ -f .template-uninitialised ]; then | |
| note "skipped — template not initialised yet (sentinel present)" | |
| else | |
| for f in CITATION.cff codemeta.json myst.yml pixi.toml Dockerfile ro-crate-metadata.json; do | |
| [ -f "$f" ] || continue | |
| if grep -nE "\{\{[A-Z_]+\}\}" "$f"; then | |
| bad "$f still contains unsubstituted {{...}} placeholder tokens (see the hits above). A release would ship these into Zenodo, Software Heritage and the RO-Crate permanently. Re-run the substitution or fix by hand — CLAUDE.md § Phase 0." | |
| else | |
| note "$f has no leftover tokens" | |
| fi | |
| done | |
| fi | |
| echo | |
| if [ "$fail" -ne 0 ]; then | |
| echo "Convention checks FAILED — see the ::error:: annotations above." | |
| exit 1 | |
| fi | |
| echo "All convention checks passed." |