Skip to content

Changelog backfill + CI gate, and console scripts replacing the main_*.py entry points#45

Merged
ggalloni merged 4 commits into
masterfrom
docs/changelog-backfill-and-gate
Jul 14, 2026
Merged

Changelog backfill + CI gate, and console scripts replacing the main_*.py entry points#45
ggalloni merged 4 commits into
masterfrom
docs/changelog-backfill-and-gate

Conversation

@ggalloni

Copy link
Copy Markdown
Owner

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 the do_cross default 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_*.py scripts 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.py called three methods that no longer exist on PICSLike (setup_parameter_grid(args), load_maps, compute_likelihood_grid) and treated InputParams as a dict. main_fisher.py reported matrix.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 PATH after a plain pip install, no checkout needed:

Command Does
qube-run QML spectra end to end (alias for qube-spectra-run)
qube-spectra-run same, explicit
qube-fisher-run Fisher matrix only — the two-job cluster workflow, and reusing one F across many map sets
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. The QUBE commands take an optional config path and fall back to the packaged TEB_defaults.yaml; picslike-run requires one.

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).

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.

  • changelog CI job — fails a PR that touches importable package source (src/cosmoforge.<pkg>/<pkg>/**.py) without updating docs/source/changelog.rst. Escape hatch: the skip-changelog label, for changes with genuinely no user-visible surface.
  • Pull-request template and a CLAUDE.md rule.

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.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.

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 Unreleased section. Both are kept — they are complementary, so master's entries are folded into the matching Breaking/Added/Fixed groups rather than one side winning.

ggalloni added 3 commits July 14, 2026 10:50
…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.
Copilot AI review requested due to automatic review settings July 14, 2026 10:17
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.44444% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.58%. Comparing base (01d1eb2) to head (a7d1bb9).

Files with missing lines Patch % Lines
src/cosmoforge.qube/qube/cli.py 94.44% 2 Missing ⚠️
src/cosmoforge.picslike/picslike/cli.py 94.44% 1 Missing ⚠️
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     
Flag Coverage Δ
cosmocore 92.22% <ø> (ø)
mpi 92.50% <94.44%> (+0.02%) ⬆️
nompi 91.14% <94.44%> (+0.03%) ⬆️
picslike 96.83% <94.44%> (-0.11%) ⬇️
qube 92.02% <94.44%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/cosmoforge.picslike/picslike/cli.py 94.44% <94.44%> (ø)
src/cosmoforge.qube/qube/cli.py 94.44% <94.44%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 legacy main_*.py scripts.
  • Add end-to-end CLI tests for both packages and update docs/READMEs to point at the new CLI modules.
  • Add a GitHub Actions changelog job + PR template + CLAUDE.md guidance to enforce changelog hygiene (with skip-changelog label 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.

Comment on lines +9 to +22
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")

Comment on lines +24 to +47
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.
@ggalloni

Copy link
Copy Markdown
Owner Author

Addressed — Copilot review + a self-review pass

Copilot's finding was real, with one correction. Driving the entry points on the shipped nside=4 configs did re-run their out* writes into tests/data/. Verified by stamping fisher.dat and geometry.dat to 2000-01-01 and watching the run move them.

The correction: this does not mutate tracked fixture files. Those artifacts are gitignored (.gitignore:30) and git ls-files returns nothing for them — which is why the working tree stayed clean. And it isn't specific to the CLI tests: stamping the same files and running the pre-existing test_fisher.py rewrites them too. The whole qube suite has always done this.

The underlying point stands anyway, because it exposed a weakness in my test: test_spectra_run_writes_nothing_without_out proved its claim by mocking write_power_spectra, while Fisher was busily writing fisher.dat from the config. The name overpromised.

Fix: a sandboxed_config fixture in both packages — absolute inputs, every out* key stripped — reusing the pattern already in test_no_implicit_io.py. The CLI tests now chdir into tmp_path and assert it stays empty. "Writes nothing without --out" is now an assertion about the filesystem rather than a mock, which is what ADR-0015 actually promises.

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. 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. Removing the spectra summary also removed the only reason the CLI called get_power_spectra, so the convolved-mode 3-tuple unpack went with it.

Also cut: two tests that exercised argparse rather than this code (choices= validation, a required positional), the single-use MODES constant, and module-docstring prose 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 included, on every PR, purely to compute a filename list. Now one gh api .../pulls/N/files call and no checkout step.

qube 174 passed, picslike 99 passed, ruff clean.

@ggalloni
ggalloni merged commit 453a273 into master Jul 14, 2026
20 checks passed
@ggalloni
ggalloni deleted the docs/changelog-backfill-and-gate branch July 14, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants