Skip to content

chore: ruff format + region-aware lint gate#206

Merged
cailmdaley merged 23 commits into
developfrom
chore/ruff
Jun 30, 2026
Merged

chore: ruff format + region-aware lint gate#206
cailmdaley merged 23 commits into
developfrom
chore/ruff

Conversation

@cailmdaley

@cailmdaley cailmdaley commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What this does, and why

Wires ruff (one fast tool that both formats and lints) into sp_validation under a warn-locally / account-on-develop discipline: lint never interrupts local flow, but the shared branch stays clean and every regression is pinned to a person.

After this lands: every commit auto-applies ruff's safe fixes and warns on the rest; the library is formatted and lint-clean; and develop is gated — a red check blocks the merge while you're told exactly what to fix.

Two postures

Locally (pre-commit) — auto-fix the safe stuff, warn on the rest. ruff format and ruff check --fix's safe fixes (formatting, import sorting, unused imports) auto-apply — a one-time re-stage, not a block. What ruff won't safely fix — undefined names (F821), unused variables (F841) — prints as a warning and never blocks the commit. (That split is ruff's own safe/unsafe-fix taxonomy, not a hand-curated list.) Bloat guards (nbstripout, large-files) still block.

On develop (CI) — the gate. On push to develop and PRs into it, CI runs the full region-aware policy. On failure it goes red (blocks the merge) and tells the author where they already are:

  • on a PR → an auto-updating comment on the PR with the full violation list, which turns green once ruff passes;
  • on a direct push to develop (no PR) → a per-committer auto-closing lint-debt issue, @-mentioning + assigning them.

Fork PRs are covered via pull_request_target, hardened with uvx --no-config so an untrusted PR can't redirect ruff's download to a trojaned index.

Why the policy is region-aware

A library and a one-off analysis script don't deserve the same standard. src/sp_validation is strict — zero ignores. The peripheral trees (workflow/, papers/*/scripts, the per-paper analysis scripts, scripts/, scratch/, cosmo_inference/) are formatted, safe-autofixed, and have their real bugs fixed, but waive a few structurally-intentional patterns: E402 (imports after sys.path/backend setup) and F403/F405 (intentional star-imports in interactive scripts). E501 is waived everywhere — the formatter owns line width (ruff's own guidance). Genuine bug-classes stay enforced everywhere.

The commits

  1. style(ruff) — repo-wide format pass (mechanical; listed in .git-blame-ignore-revs).
  2. chore(ruff) — region-aware lint policy in pyproject.toml.
  3. fix(ruff) — the behavior-preserving lint fixes, adversarially verified (the diff worth reading).
  4. chore(ruff) — scope tuning for the per-paper analysis trees.
  5. chore(ruff) — the pivot: pre-commit warn + auto-fix-safe; CI red + author feedback.
  6. chore(ruff) — baseline cleanup: repo-wide ruff down from 125 errors to clean.
  7. Merge develop + chore(ruff) — PR feedback as a PR comment, issue only for direct develop pushes.

Baseline

Repo-wide ruff is clean (ruff check . = 0 errors). The bulk of the original noise was one false positive — 111 of 116 undefined-names were snakemake, the object Snakemake injects into rule scripts at runtime, now silenced with builtins = ["snakemake"] (genuine undefined names in those scripts are still caught). The mixed-tabs SQL f-string (E101) and the cosmo_inference notebook E402s were fixed; the 5 genuine undefined names (@sachaguer's) he cleared himself.

Verification

pre-commit behavior verified by doing — safe fixes auto-apply, undefined/unused names warn without blocking. ruff check . / ruff format --check . clean. Workflow YAML + the github-script logic validated; two independent reviews folded in (the pull_request_target --no-config hardening, a clean-run-can't-false-red fix, and the PR-comment-vs-issue split). The merge of develop was verified ruff-clean, format-clean, all .py compiling, with develop's dep pins and #199's deletions carried in; CI build + test suite green.

— Claude on behalf of Cail

cailmdaley and others added 18 commits June 20, 2026 04:50
The module named "basic" was a grab-bag: the 546-line `metacal` response
class (the heart of shear calibration) plus galaxy-selection masks and a
handful of cosmology-independent statistics helpers — none of which "basic"
described. Its symbols now live where they belong, and basic.py is deleted.

- `metacal` class + `mask_gal_size`/`mask_gal_SNR` (galaxy selection) →
  calibration.py, joining the m/c routines that already consumed a
  `gal_metacal` instance. One subsystem, one module.
- `jackknif_weighted_average2`, `corr_from_cov`, `chi2_and_pte`,
  `cov_from_one_covariance` → new statistics.py (a clean leaf: numpy/scipy
  only; calibration imports the jackknife from it).
- Every importer repointed (papers, scripts, the two scratch/guerrini
  import lines — path-only, his logic untouched); dead `from sp_validation
  import basic` lines removed from calibration.py and cat.py; `__all__` and
  the architecture docs updated.
- Tests split: metacal + mask pins → test_calibration.py, jackknife pin →
  test_statistics.py; test_basic.py removed.

All moved code is byte-identical to the original (md5-verified); value-drift
pins (metacal R-matrix rtol 1e-12) and the full suite pass in-container,
except the pre-existing galaxy/cs_util.size old-sandbox gap. No circular
imports. Verified by an adversarial multi-agent pass (byte-identity,
no-stale-refs, value-pins, no-cycles).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scratch/guerrini/ and the namaster_utils→source / Gaussian-sims work he
reserved for his next PR in the #197 review. So future workers don't touch it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ation

Follow-up polish on the basic.py dissolution (#199).

Tests — characterization (value-drift) coverage for the three statistics.py
helpers that had none, with literals generated by running the real functions
in-container and teeth on each:
- corr_from_cov: unit diagonal + reconstruction from cov/outer(std,std)
- chi2_and_pte: diagonal reduces to sum((d/sigma)^2) with matching scipy PTE,
  plus a non-diagonal case exercising the full d^T C^-1 d path
- cov_from_one_covariance: gaussian(col 10) vs non-gaussian(col 9) selection
  and a row-major-layout check (a transpose would be caught)

Calibration — strictly behavior-preserving dead-code removal:
- 3 unused module imports (util, io, get_footprint — verified unreferenced)
- an unused local (col_noshear) in metacal._read_data
- the uncallable metacal._return method (defined without self, references
  self.* in its body — would NameError if ever invoked; referenced nowhere)

Value pins (metacal R-matrix, m/c bias) stay green; conservatively skipped any
change that would reorder float ops or restructure an estimator. Verified by an
adversarial behavior-preservation review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
info.py had zero importers; its only content was a redundant
__name__ = 'sp_validation'. cat.py imported __version__ and __name__
from the package root but used only __version__ (line 607); the
software name at line 606 is already hardcoded. Repoint to the
canonical home: from sp_validation.version import __version__.

Register the retired import path in the dangling-move guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The old __all__ listed modules nobody imports through the package
(io, plot_style, cosmo_val) and omitted the two genuinely public
diagnostic modules rho_tau and b_modes. Replace it with the real
public surface, alphabetised, and drop the stale commented-out
explicit-import block. Nothing does `from sp_validation import *`,
so this is purely a documentation fix.

(util and run_joint_cat are renamed in the Tier-2 commits.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Five methods each imported from .b_modes inside their bodies. b_modes
is import-time side-effect-light (it pulls only .cosmology) and has no
back-edge to cosmo_val, so the locals were defensive, not necessary.
Consolidate the union of imported names into one top-level block next
to the existing cosmology/rho_tau imports and drop the five inline
imports. test_cosmo_val: 11 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two module-level defs named confusion_matrix existed; the first
(mask, confidence_level=0.9) was a near-duplicate of correlation_matrix
and was unconditionally shadowed by the second
(prediction, observation) ~40 lines later. Every caller — in
scripts/calibration and scripts/examples — uses the
(prediction, observation) signature. Remove the dead first def.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Five functions had zero callers anywhere in the repo (src, scripts,
papers, scratch, notebooks), including across the star-imports of
plots:
  cosmology.py: get_clusters, stack_mm3, gamma_T_tc, xi_gal_gal_tc
  plots.py:     plot_map_stacked
Removing the cosmology block also orphaned the imports that existed
solely for it (treecorr, fits, canfar, radec2xy, cKDTree, tqdm,
get_footprint); drop those too. test_cosmology + test_plots: 29 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
util.py held only millify / print_millified — number formatting, not a
grab-bag. Rename it to format.py and sweep all importers:
  internal: cat.py, run_joint_cat.py (util.millify -> format.millify)
  scripts:  apply_alpha.py, examples/demo_calibrate_minimal_cat.py,
            calibration/extract_info.py (star-import x2)
  papers:   catalog/hist_mag.py
Register the rename in the dangling-move guard; update package __all__.

B1: scripts/plot_leakage.py imported transform_nan from the old util
module — a symbol removed from the library long ago and never used in
the script. Drop the dead import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SquareRootScale is a matplotlib ScaleBase subclass — plotting
infrastructure, not rho/tau logic. Move the class and its
register_scale call into plots.py (which now carries the
matplotlib.scale/ticker/transforms imports it needs). rho_tau.py keeps
a compat re-export so existing
`from sp_validation.rho_tau import SquareRootScale` callers — in
cosmo_inference, scratch/guerrini, papers/harmonic — still resolve.

workflow/scripts/plotting_utils.py holds a near-duplicate that
diverges (ScalarFormatter(useMathText=True); inverted transform method
named transform_non_affine not transform), so it is left in place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Six harmonic-space scripts imported SquareRootScale from
sp_validation.utils_cosmo_val, a module that no longer exists. Repoint
to sp_validation.rho_tau (the compat re-export), matching the sibling
2026_03_17 script. get_params_rho_tau was already correctly imported
from rho_tau. No other utils_cosmo_val imports remain (the two
scratch/guerrini mentions are prose noting the module's removal).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The module holds the catalogue-builder runner classes (JointCat,
ApplyHspMasks, CalibrateCat) and their run_* entry-point functions;
catalog_builders names that role. Sweep all importers (the
`as sp_joint` alias is preserved, only the module name changes):
  scripts/apply_hsp_masks.py
  scripts/examples/{demo_check_footprint, create_binned_mask_comprehensive,
    demo_comprehensive_to_minimal_cat, demo_create_footprint_mask,
    demo_calibrate_minimal_cat}.py
  scripts/calibration/{create_joint_comprehensive_cat (direct symbol),
    demo_apply_hsp_masks, calibrate_comprehensive_cat}.py
  scratch/kilbinger/demo_binned_mask.py
  papers/catalog/hist_mag.py
Also update the two prose references in docs and update __all__ and
the dangling-move guard.

The OPTIONAL masks.py extraction (Mask + mask-algebra fns) is deferred:
those symbols are reached externally through the sp_joint.* module
alias (Mask, get_masks_from_config, print_mask_stats across 4 scripts +
papers/catalog), so splitting them out would require either re-exports
or sweeping the public call surface — beyond a behavior-preserving move.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the healsparse-backed spatial-masking cluster out of
catalog_builders.py into a dedicated masks.py: the Mask class plus
get_masks_from_config, print_mask_stats, correlation_matrix, and
confusion_matrix. Bodies are byte-identical; the move carries the
numexpr/scipy.stats imports those helpers need (now removed from
catalog_builders, which no longer references them).

catalog_builders.py re-exports the five symbols from sp_validation.masks
so external code using `from sp_validation import catalog_builders as
sp_joint` keeps resolving sp_joint.Mask, sp_joint.get_masks_from_config,
etc. The *Cat runner classes (ApplyHspMasks, ReadCat, run_* entry points)
stay; ApplyHspMasks uses healsparse directly, not the Mask class.

masks added to __init__.__all__. No MOVE_MAP entry: this is an
extraction-in-place (catalog_builders survives), not a retired path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The catalogue module pair now reads by role: catalog.py is the data
layer (read/write/column-access/matching free functions), catalog_builders.py
is the construction pipeline (runner classes built on it). Module docstrings
state this hierarchy explicitly.

Behaviour-preserving. Every importer of the local sp_validation.cat module is
swept to sp_validation.catalog, each preserving its local binding (bare
`import cat` forms gain `as cat` so function bodies are unchanged). The
cs_util `cat` import is a different module and is left untouched throughout.

The dangling-reference guard registers the retired flat-import form
`sp_validation.cat import` rather than the bare `sp_validation.cat` token,
which would false-positive on the live `sp_validation.catalog` /
`sp_validation.catalog_builders` modules (same prefix trap as glass_mock).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aper analysis scripts

The gate revealed peripheral residual the per-file-ignore globs didn't reach
(analysis scripts under papers/<paper>/*.py, cosmo_inference notebooks) plus the
Snakemake-injected `snakemake` global (F821). Region-aware decision: enforce lint
on src/ only (matching the pre-commit hook), keep format repo-wide, run the
repo-wide lint advisory. src/ stays pristine (ruff check src/ => clean).
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@cailmdaley

Copy link
Copy Markdown
Collaborator Author

Sasha and i have decided on this project: we want the pre-commit hook to issue a warning — not a block — for all checks we run in ci. all lint checks should run at commit time too, but only produce warnings.

in ci, we want any lint failures to automatically open an issue tagging the person who made the commit. this way, work isn't blocked, but there's a record of what's wrong. the person who committed it also has to fix it.

Flip the lint discipline from "block" to "warn while you work, account
when it lands."

Pre-commit now auto-applies ruff's SAFE fixes (`ruff format` + `ruff
check --fix`: import sorting, unused imports) — a one-time re-stage, not
a block — and only WARNS on what ruff won't safely fix (undefined names
F821, unused variables F841), which never blocks a commit. That split is
ruff's own safe/unsafe-fix taxonomy, not a hand-curated list. Bloat
guards (nbstripout, large-files) still block.

CI lint now runs only on push to `develop` and PRs targeting `develop`.
On failure it goes red (blocks the merge) AND opens/updates a single
auto-closing lint-debt issue per committer, @-mentioning + assigning
them; a clean run closes it. Fork PRs are covered via
pull_request_target, hardened with `uvx --no-config` so an untrusted PR
can't redirect ruff's download to a trojaned index. Issue management
runs continue-on-error so it can't red a clean run, and a ruff/uvx
tooling error reds the gate without filing a committer issue.

Settled with Sacha, 2026-06-23.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkTwLDgicfeoK8Np2bgwp3
@cailmdaley

Copy link
Copy Markdown
Collaborator Author

Reworked this PR to the model we settled on (warn locally, account on develop):

  • pre-commit now auto-applies ruff's safe fixes (ruff format + import sorting / unused imports) and only warns — never blocks — on what it won't safely fix (undefined names, unused variables).
  • CI runs only on push to develop and PRs into it. On failure it goes red (blocks the merge) and opens/updates a single auto-closing lint-debt issue per committer, assigning + @-mentioning them; a clean run closes it.
  • Fork PRs are covered via pull_request_target, hardened with uvx --no-config so an untrusted PR can't redirect ruff's download.

Full description above. Next: cleaning the repo-wide baseline to green before the gate activates — almost all of it is the snakemake injected-global false positive (silenced via builtins), plus a few genuine items (@sachaguer, I'll tag you on the validation_cov_cell.py ones).

— Claude on behalf of Cail

@cailmdaley cailmdaley marked this pull request as ready for review June 30, 2026 12:16
Bring the repo-wide ruff baseline from 125 errors down to 5, so the
develop gate's first run is honest rather than a wall of false positives:

- builtins = ["snakemake"] silences ~110 false F821s — Snakemake injects
  a `snakemake` object into rule scripts at runtime, so ruff can't see
  where it's defined. Every other undefined name in those scripts is
  still caught.
- broaden the cosmo_inference E402 ignore from `*.py` to `**` so it also
  covers `.ipynb` cells (same intentional sys.path / mpl-backend-before-
  import pattern the .py scripts already get).
- normalize mixed tabs/spaces in download_gaia_catalogues.py's SQL
  f-string to spaces (E101; also unbreaks the surrounding dedent()).

Remaining are 5 genuine undefined names (cov_sim_gaussian in
papers/harmonic/...validation_cov_cell.py; index/n_contours in the
papers/consistency glass-mocks notebook), triaged with their authors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkTwLDgicfeoK8Np2bgwp3
@cailmdaley

Copy link
Copy Markdown
Collaborator Author

@sachaguer — heads up: the new ruff lint gate (this PR) flags two genuine undefined names in your analysis code. They're the only things left keeping the repo-wide baseline from being clean, and once the gate is live on develop each would open a lint-debt issue assigned to its author. Could you take a look when you get a chance?

1. cov_sim_gaussianpapers/harmonic/2025_09_11_validation_cov_cell.py:876 & :881

cov_gaussian_bb = cov_sim_gaussian[96:, 96:]
...
cov_gaussian_eb = cov_sim_gaussian[64:96, 64:96]

cov_sim_gaussian is never defined in the file — looks like it used to come from a cell that's no longer there (loaded from disk, or computed earlier?).

2. index & n_contourspapers/consistency/2026_02_11_test_sampling_glass_mocks.ipynb, cell 15

legend_labels = [
    label
    for i in range(index, index + n_contours)
    for label in (rf"$C_\ell$, GLASS mock {i} (smooth 0.5)", rf"$C_\ell$, GLASS mock {i} (smooth 0.1)")
]

Neither index nor n_contours is defined in the notebook. From the chain-building loop it looks like it might want index = 0 and n_contours = len(chains) // 2, but you'd know the intended labeling.

No rush — nothing's blocked locally (pre-commit only warns). Just flagging so they don't surprise you when the gate goes live.

— Claude on behalf of Cail

#199 (the old PR base, refactor/src-module-layout) was squash-merged into
develop (5b8ffdc), so the old base is dead — this brings the ruff work
onto current develop instead.

Conflicts resolved toward develop's content (the canonical post-#199
code), then `ruff format` + the region-aware policy re-applied on top, so
chore/ruff's net contribution over develop stays exactly: the repo-wide
format pass, the lint gate (pre-commit + CI), and the behavior-preserving
bug fixes. develop's #200 dependency pins are carried in, and the two
obsolete scripts #199 deleted (cosmo_val/match_LF_SP.py,
scripts/create_joint_shape_cat.py) stay deleted.

Verified: `ruff check .` clean except the 5 known undefined names
(@sachaguer's, flagged on the PR), `ruff format --check .` clean, every
tracked .py compiles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkTwLDgicfeoK8Np2bgwp3
@cailmdaley cailmdaley changed the base branch from refactor/src-module-layout to develop June 30, 2026 12:38
…nal scratch. Removed the lines with undefined variable in the harmonic space script.
@sachaguer

Copy link
Copy Markdown
Contributor

This is fixed. I removed the problematic lines and moved the consistency notebook to my scratch, as it contains no content related to our analysis.

Refine the lint gate's feedback surface to fit the event (Cail's call: a
separate issue for a PR is disconnected — comment where the author is
already looking; issues are for things going straight into develop):

- PR against develop → red check (still blocks the merge) + a single
  auto-updating comment ON the PR with the full violation list, turning
  green when ruff passes. ruff annotations (--output-format=github) are
  emitted too (they surface in the run's Checks view; pull_request_target
  anchors them to the base commit, so not inline on the diff). No issue.
- direct push to develop → unchanged: one per-committer auto-closing
  lint-debt issue (no PR exists to comment on).

Adds pull-requests: write for the comment path. Fresh-eyes reviewed
(SHIP); the over-promised "inline on Files changed" wording was corrected
since the comment's file:line list is the reliable surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkTwLDgicfeoK8Np2bgwp3
@cailmdaley cailmdaley linked an issue Jun 30, 2026 that may be closed by this pull request
@cailmdaley cailmdaley merged commit f344c0d into develop Jun 30, 2026
2 checks passed
@cailmdaley cailmdaley deleted the chore/ruff branch June 30, 2026 14:51
cailmdaley added a commit that referenced this pull request Jun 30, 2026
…licts)

#206 (ruff gate) and #199 (basic.py dissolve) squash-merged to develop while
#207 still carried their pre-squash commits, so this merge collapses the stack.
Resolved by category:

- develop's side where it carried fixes #207 lacked: Sacha's cov_sim_gaussian
  removal in papers/harmonic/2025_09_11_validation_cov_cell.py (21c7d96), the
  consistency-notebook deletion (moved to scratch), the canonical #206
  lint.yml / pre-commit enforcement model, the broader cosmo_inference/** E402
  per-file-ignore.
- #207's side for its genuine work: the cosmo_val/ package (flat cosmo_val.py
  deleted), the pseudo_cl primitives, survey.area_from_coords, chi2_and_pte
  solve+sf (pins re-blessed for it), the glass_mock dedup, the demo
  hsp_map_logical_or fix; dropped develop's duplicate SquareRootScale.

Verified on the fresh container: ruff format+check clean repo-wide;
CosmologyValidation imports (75 methods); value-drift + golden pins
32 passed / 1 skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmk58xivawGCVFgejJJeDp
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.

Project-wide ruff pass (lint + format the settled tree)

2 participants