Changelog backfill + CI gate, and console scripts replacing the main_*.py entry points#45
Conversation
…n CI The changelog stopped at 1.0.1 while six weeks of API changes shipped: opt-in persistence, the compression= -> basis= rename, the auto-basis default flip, seven input-injection kwargs, three retired config keys, and the do_cross default flip. None of it was recorded anywhere but the ADRs and the commit messages. - changelog.rst: an Unreleased section covering everything from the opt-in persistence work through the dead-config-surface retirement, written in terms of the public surface a user touches. Both silent behaviour changes (basis=None now resolves to auto; do_cross now defaults to False) are called out as breaking -- they change what existing scripts compute without changing a signature. - CI: a `changelog` job blocks a PR that touches importable package source without updating changelog.rst. Escape hatch is the `skip-changelog` label. Replayed against history, it blocks exactly the three PRs that caused this and skips the docs-only and release commits. - Pull-request template and a CLAUDE.md rule, so the gate is a backstop rather than a surprise.
The three main_*.py scripts were never installed -- no [project.scripts]
entry, so they were only reachable from a repo checkout -- and nothing
called them: no sbatch job, no test, no doc example that ran. Predictably,
main_picslike.py had rotted out of sync with PICSLike, calling three methods
that no longer exist (setup_parameter_grid(args), load_maps,
compute_likelihood_grid) and treating InputParams as a dict. main_fisher.py
reported `matrix.max() / matrix.min()` as a "condition number", which is the
ratio of the largest to smallest matrix *element* -- Fisher off-diagonals go
negative, so it could print a negative one.
Between them the three files were ~570 lines wrapping an API that is three
calls wide. The notebooks now cover the worked-example role, and unlike these
they execute in CI.
Replaced by real entry points, on PATH after a plain pip install:
qube-fisher-run Fisher matrix only -- the two-job cluster workflow, and
reusing one F across many map sets
qube-spectra-run QML spectra end to end (Spectra builds its own Fisher)
qube-run alias for qube-spectra-run
picslike-run pixel likelihood over the parameter grid
A console script is an ordinary executable, so MPI needs no python prefix:
`mpirun -n 8 qube-run config.yaml`.
Each command warns when it completes a run whose result is not being
persisted. Spectra never auto-writes -- only write_power_spectra does -- so
without --out a CLI run would otherwise compute for hours and exit silently
having saved nothing (ADR-0015).
tests/test_cli.py in both packages drives the entry points end to end against
the nside=4 fixtures, rather than only asserting that --help parses. A --help
test would not have caught the rot that killed main_picslike.py; running the
pipeline does.
The active-pixel-index work landed on master while this branch was open, and it had already opened its own Unreleased changelog section. Both sections are kept: this branch's backfills the arc from opt-in persistence through the console scripts, master's covers active_pixel_index and the field/component mask bugs. They are complementary, so the resolution folds master's entries into the matching Breaking/Added/Fixed groups rather than choosing a side. Both READMEs auto-merged. Verified after the merge: cosmocore 534 passed, qube 176 passed, picslike 100 passed; ruff clean; changelog.rst builds with no reST errors and no dangling references to the deleted main_*.py pages.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #45 +/- ##
==========================================
+ Coverage 92.56% 92.58% +0.01%
==========================================
Files 38 40 +2
Lines 5030 5084 +54
==========================================
+ Hits 4656 4707 +51
- Misses 374 377 +3
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR replaces uninstalled/rotted main_*.py entry-point scripts with properly packaged console scripts for QUBE and PICSLike, backfills the user-facing changelog, and adds a CI gate to enforce changelog updates for public-surface source changes.
Changes:
- Add installed console scripts (
qube-fisher-run,qube-spectra-run/qube-run,picslike-run) with opt-in persistence warnings, and remove the legacymain_*.pyscripts. - Add end-to-end CLI tests for both packages and update docs/READMEs to point at the new CLI modules.
- Add a GitHub Actions
changelogjob + PR template +CLAUDE.mdguidance to enforce changelog hygiene (withskip-changeloglabel escape hatch).
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/cosmoforge.qube/tests/test_cli.py | Adds end-to-end tests for the new QUBE console scripts. |
| src/cosmoforge.qube/README.md | Updates documentation links from removed “main scripts” to CLI docs. |
| src/cosmoforge.qube/qube/cli.py | Introduces QUBE CLI entry points (qube-fisher-run, qube-spectra-run/qube-run). |
| src/cosmoforge.qube/pyproject.toml | Registers QUBE console scripts under [project.scripts]. |
| src/cosmoforge.qube/main_qml.py | Removes legacy script entry point (superseded by console scripts). |
| src/cosmoforge.qube/main_fisher.py | Removes legacy script entry point (superseded by console scripts). |
| src/cosmoforge.picslike/tests/test_cli.py | Adds end-to-end tests for the new picslike-run console script. |
| src/cosmoforge.picslike/README.md | Updates example usage to use picslike-run instead of python main_picslike.py. |
| src/cosmoforge.picslike/pyproject.toml | Registers picslike-run under [project.scripts]. |
| src/cosmoforge.picslike/picslike/cli.py | Introduces PICSLike CLI entry point with opt-in persistence warnings. |
| src/cosmoforge.picslike/main_picslike.py | Removes legacy script entry point (superseded by console scripts). |
| docs/source/changelog.rst | Backfills changelog with recent breaking/added/fixed items and CLI migration notes. |
| docs/source/api/qube/spectra.rst | Updates “See also” references to point to CLI docs instead of removed scripts. |
| docs/source/api/qube/produce_mock_inputs.rst | Updates pipeline examples and “See also” to use console scripts + CLI docs. |
| docs/source/api/qube/main_qml.rst | Removes outdated autodoc page for deleted main_qml.py. |
| docs/source/api/qube/main_fisher.rst | Removes outdated autodoc page for deleted main_fisher.py. |
| docs/source/api/qube/fisher.rst | Updates “See also” references to point to CLI docs. |
| docs/source/api/qube/cli.rst | Adds new CLI documentation page for QUBE console scripts. |
| docs/source/api/qube.rst | Replaces “Main Execution Scripts” toctree section with “Command-Line Interface”. |
| docs/source/api/picslike/cli.rst | Adds new CLI documentation page for picslike-run. |
| docs/source/api/picslike.rst | Adds picslike/cli to the API docs toctree. |
| CLAUDE.md | Documents the changelog requirement and the skip-changelog escape hatch. |
| .github/workflows/test.yml | Adds a changelog CI job to enforce changelog updates on package-source changes. |
| .github/pull_request_template.md | Adds PR template checklist including changelog/ADR/doc-drift reminders. |
| import sys | ||
| from importlib.metadata import entry_points | ||
|
|
||
| import pytest | ||
|
|
||
| from qube import Spectra | ||
| from qube.cli import DEFAULT_CONFIG, fisher_main, spectra_main | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def config(config_resolver): | ||
| """A cheap single-field configuration.""" | ||
| return config_resolver("tests/data/nside4/T/config.yaml") | ||
|
|
| def test_run_writes_when_out_is_given(fast_config_path, monkeypatch, tmp_path): | ||
| """picslike-run evaluates the grid and persists when --out is passed.""" | ||
| out = tmp_path / "results.npz" | ||
| monkeypatch.setattr( | ||
| sys, "argv", ["picslike-run", fast_config_path, "--out", str(out)] | ||
| ) | ||
| main() | ||
|
|
||
| assert any(tmp_path.iterdir()), "--out was given but nothing was written" | ||
|
|
||
|
|
||
| def test_run_writes_nothing_without_out(fast_config_path, monkeypatch): | ||
| """Without --out the grid is evaluated and discarded, not written (ADR-0015). | ||
|
|
||
| Asserted on the writer rather than the filesystem: the config's paths are | ||
| relative to the repo root, so chdir-ing into a tmp_path to watch it would | ||
| break the fixture's own inputs. | ||
| """ | ||
| calls = [] | ||
| monkeypatch.setattr(PICSLike, "save_results", lambda self, path: calls.append(path)) | ||
| monkeypatch.setattr(sys, "argv", ["picslike-run", fast_config_path]) | ||
| main() | ||
|
|
||
| assert calls == [], "no --out, yet the writer was called" |
…ust do Addresses the Copilot review and a self-review pass for over-engineering. Copilot: the CLI tests wrote into the fixture tree. Correct — driving the entry points on the shipped nside=4 configs re-ran their out* writes into tests/data/. (Not, as the review said, dirtying tracked files: those artifacts are gitignored, and the whole qube suite has always rewritten them. But the underlying point stands.) Both packages gain a `sandboxed_config` fixture -- absolute inputs, every out* key stripped -- reusing the pattern already in test_no_implicit_io. The CLI tests now chdir into tmp_path and assert it stays empty, so "writes nothing without --out" is a claim about the filesystem instead of a mock of the writer. That is the ADR-0015 invariant, asserted directly. Verified by stamping the fixtures to 2000-01-01 and confirming the run leaves them untouched. Self-review: I deleted main_fisher.py partly for wrapping a three-call API in an ad-hoc statistics dump, then wrote a smaller one. Cut it. `Fisher matrix: 90 x 90` earns its line in an sbatch log; per-label sigma ranges and array shapes are decoration the API already exposes. Dropping the spectra summary also removes the only reason the CLI called get_power_spectra, so the convolved-mode 3-tuple unpack goes with it. Also cut: two tests that exercised argparse rather than our code (choices= validation, a required positional); the single-use MODES constant; and the prose in the module docstrings already stated in the .rst pages. The changelog gate no longer clones the repo. It diffed two SHAs, which needs fetch-depth: 0 -- a full-history clone, notebooks and executed outputs and all, on every PR, to compute a filename list the API will hand over directly. Now one `gh api .../pulls/N/files` call, no checkout step. qube 174 passed, picslike 99 passed, ruff clean.
Addressed — Copilot review + a self-review passCopilot's finding was real, with one correction. Driving the entry points on the shipped nside=4 configs did re-run their The correction: this does not mutate tracked fixture files. Those artifacts are gitignored ( The underlying point stands anyway, because it exposed a weakness in my test: Fix: a Self-reviewI deleted Also cut: two tests that exercised The changelog gate no longer clones the repo. It diffed two SHAs, which needs
|
What changed
Two related pieces of rot, found while re-orienting on the codebase.
The changelog stopped at 1.0.1 while six weeks of API changes shipped. Opt-in persistence, the
compression=→basis=rename, the auto-basis default flip, seven input-injection kwargs, three retired config keys, and thedo_crossdefault flip existed nowhere but the ADRs and the commit messages. Two of those silently change what existing scripts compute without changing a signature, and neither was written down anywhere a user would look.The
main_*.pyscripts were dead weight. They were never installed — no[project.scripts]entry, so they were only reachable from a repo checkout — and nothing called them: no sbatch job, no test, no doc example that ran. Predictably they rotted.main_picslike.pycalled three methods that no longer exist onPICSLike(setup_parameter_grid(args),load_maps,compute_likelihood_grid) and treatedInputParamsas a dict.main_fisher.pyreportedmatrix.max() / matrix.min()as a condition number — that is the ratio of the largest to smallest matrix element, and Fisher off-diagonals go negative, so it could print a negative one. Between them the three files were ~570 lines wrapping an API that is three calls wide.Public surface
Added — console scripts, on
PATHafter a plainpip install, no checkout needed:qube-runqube-spectra-run)qube-spectra-runqube-fisher-runpicslike-runA console script is an ordinary executable, so MPI needs no
pythonprefix:mpirun -n 8 qube-run config.yaml. The QUBE commands take an optional config path and fall back to the packagedTEB_defaults.yaml;picslike-runrequires one.Each command warns when it completes a run whose result is not being persisted.
Spectranever auto-writes — onlywrite_power_spectradoes — so without--outa CLI run would otherwise compute for hours and exit silently having saved nothing (ADR-0015).Removed:
main_fisher.py,main_qml.py,main_picslike.py, and their two hand-written autodoc pages. Migration is in the changelog.The changelog rule
A rule that lives only in a markdown file is exactly the rule that just rotted for six weeks, so the load-bearing piece is a CI gate; the docs are the reminder that stops you hitting it.
changelogCI job — fails a PR that touches importable package source (src/cosmoforge.<pkg>/<pkg>/**.py) without updatingdocs/source/changelog.rst. Escape hatch: theskip-changeloglabel, for changes with genuinely no user-visible surface.CLAUDE.mdrule.Replayed against history, the gate blocks exactly the three PRs that caused this and skips the docs-only and release commits. Tests, benchmarks and scripts are siblings of the inner package directory, so they never trip it.
The gate cannot check that an entry is good, only that one exists. Nothing can. The template carries that burden.
Testing
tests/test_cli.pyin both packages drives the entry points end to end against the nside=4 fixtures rather than only asserting that--helpparses — a--helptest would not have caught the rot that killedmain_picslike.py; running the pipeline does.Post-merge with master: cosmocore 534 passed, qube 176 passed, picslike 100 passed. Ruff clean. Docs build with no reST errors and no dangling references to the deleted pages. All four console scripts verified on
PATH.Note on the merge
The active-pixel-index work landed on master while this branch was open, and had already opened its own
Unreleasedsection. Both are kept — they are complementary, so master's entries are folded into the matching Breaking/Added/Fixed groups rather than one side winning.