specmod 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- specmod-0.2.0/.git-blame-ignore-revs +6 -0
- specmod-0.2.0/.github/workflows/build.yml +40 -0
- specmod-0.2.0/.github/workflows/docs.yml +51 -0
- specmod-0.2.0/.github/workflows/release.yml +76 -0
- specmod-0.2.0/.github/workflows/test.yml +130 -0
- specmod-0.2.0/.gitignore +48 -0
- specmod-0.2.0/.pre-commit-config.yaml +62 -0
- specmod-0.2.0/.readthedocs.yaml +47 -0
- specmod-0.2.0/.release-please-manifest.json +3 -0
- specmod-0.2.0/AGENTS.md +92 -0
- specmod-0.2.0/CHANGELOG.md +175 -0
- specmod-0.2.0/CITATION.cff +59 -0
- specmod-0.2.0/CLAUDE.md +5 -0
- specmod-0.2.0/CONTRIBUTING.md +90 -0
- specmod-0.2.0/LICENSE +21 -0
- specmod-0.2.0/PKG-INFO +294 -0
- specmod-0.2.0/README.md +238 -0
- specmod-0.2.0/datasets/magna_2020.toml +45 -0
- specmod-0.2.0/datasets/pnr_2019.toml +37 -0
- specmod-0.2.0/docs/REFACTOR_PLAN.md +3704 -0
- specmod-0.2.0/docs/api.md +121 -0
- specmod-0.2.0/docs/choosing-a-transform.md +608 -0
- specmod-0.2.0/docs/conf.py +110 -0
- specmod-0.2.0/docs/development.md +427 -0
- specmod-0.2.0/docs/documentation.md +233 -0
- specmod-0.2.0/docs/index.md +109 -0
- specmod-0.2.0/docs/notebooks/_build_notebook.py +683 -0
- specmod-0.2.0/docs/notebooks/choosing-a-transform.ipynb +887 -0
- specmod-0.2.0/docs/notes/api-audit.md +201 -0
- specmod-0.2.0/docs/notes/window-position.md +85 -0
- specmod-0.2.0/docs/pick-formats.md +215 -0
- specmod-0.2.0/docs/processing.md +403 -0
- specmod-0.2.0/docs/releasing-data.md +206 -0
- specmod-0.2.0/docs/releasing.md +167 -0
- specmod-0.2.0/docs/roadmap.md +109 -0
- specmod-0.2.0/pyproject.toml +260 -0
- specmod-0.2.0/release-please-config.json +26 -0
- specmod-0.2.0/requirements.txt +52 -0
- specmod-0.2.0/src/specmod/__init__.py +17 -0
- specmod-0.2.0/src/specmod/_vendor/__init__.py +21 -0
- specmod-0.2.0/src/specmod/_vendor/qiinv.py +243 -0
- specmod-0.2.0/src/specmod/acquire.py +358 -0
- specmod-0.2.0/src/specmod/api.py +480 -0
- specmod-0.2.0/src/specmod/cli.py +139 -0
- specmod-0.2.0/src/specmod/config/__init__.py +44 -0
- specmod-0.2.0/src/specmod/config/layers.py +168 -0
- specmod-0.2.0/src/specmod/config/provenance.py +77 -0
- specmod-0.2.0/src/specmod/config/sections.py +385 -0
- specmod-0.2.0/src/specmod/config/serialize.py +58 -0
- specmod-0.2.0/src/specmod/core/__init__.py +41 -0
- specmod-0.2.0/src/specmod/core/bandwidth.py +187 -0
- specmod-0.2.0/src/specmod/core/collection.py +549 -0
- specmod-0.2.0/src/specmod/core/noise.py +478 -0
- specmod-0.2.0/src/specmod/core/scalogram.py +234 -0
- specmod-0.2.0/src/specmod/core/spectrum.py +326 -0
- specmod-0.2.0/src/specmod/core/units.py +116 -0
- specmod-0.2.0/src/specmod/datasets.py +316 -0
- specmod-0.2.0/src/specmod/distance.py +190 -0
- specmod-0.2.0/src/specmod/exceptions.py +58 -0
- specmod-0.2.0/src/specmod/fitting/__init__.py +58 -0
- specmod-0.2.0/src/specmod/fitting/base.py +50 -0
- specmod-0.2.0/src/specmod/fitting/event.py +284 -0
- specmod-0.2.0/src/specmod/fitting/guess.py +170 -0
- specmod-0.2.0/src/specmod/fitting/spectrum.py +330 -0
- specmod-0.2.0/src/specmod/io.py +241 -0
- specmod-0.2.0/src/specmod/magnitude.py +312 -0
- specmod-0.2.0/src/specmod/picks/__init__.py +182 -0
- specmod-0.2.0/src/specmod/picks/base.py +250 -0
- specmod-0.2.0/src/specmod/picks/delimited.py +224 -0
- specmod-0.2.0/src/specmod/picks/events.py +157 -0
- specmod-0.2.0/src/specmod/picks/resolution.py +149 -0
- specmod-0.2.0/src/specmod/picks/snuffler.py +92 -0
- specmod-0.2.0/src/specmod/pipeline.py +280 -0
- specmod-0.2.0/src/specmod/plotting.py +203 -0
- specmod-0.2.0/src/specmod/preprocess.py +554 -0
- specmod-0.2.0/src/specmod/smoothing/__init__.py +50 -0
- specmod-0.2.0/src/specmod/smoothing/base.py +56 -0
- specmod-0.2.0/src/specmod/smoothing/konno_ohmachi.py +83 -0
- specmod-0.2.0/src/specmod/smoothing/log_bins.py +171 -0
- specmod-0.2.0/src/specmod/sources/__init__.py +65 -0
- specmod-0.2.0/src/specmod/sources/attenuation.py +110 -0
- specmod-0.2.0/src/specmod/sources/composite.py +135 -0
- specmod-0.2.0/src/specmod/sources/motion.py +40 -0
- specmod-0.2.0/src/specmod/sources/source.py +147 -0
- specmod-0.2.0/src/specmod/spreading.py +209 -0
- specmod-0.2.0/src/specmod/staged.py +523 -0
- specmod-0.2.0/src/specmod/tables.py +110 -0
- specmod-0.2.0/src/specmod/transforms/__init__.py +50 -0
- specmod-0.2.0/src/specmod/transforms/base.py +242 -0
- specmod-0.2.0/src/specmod/transforms/cwt.py +219 -0
- specmod-0.2.0/src/specmod/transforms/fft.py +157 -0
- specmod-0.2.0/src/specmod/transforms/multitaper.py +357 -0
- specmod-0.2.0/src/specmod/transforms/prieto.py +272 -0
- specmod-0.2.0/src/specmod/transforms/quadratic.py +221 -0
- specmod-0.2.0/src/specmod/utils.py +305 -0
- specmod-0.2.0/stubs/README.md +75 -0
- specmod-0.2.0/stubs/lmfit/__init__.pyi +2 -0
- specmod-0.2.0/stubs/lmfit/model.pyi +65 -0
- specmod-0.2.0/stubs/lmfit/parameter.pyi +48 -0
- specmod-0.2.0/stubs/obspy/__init__.pyi +36 -0
- specmod-0.2.0/stubs/obspy/clients/__init__.pyi +1 -0
- specmod-0.2.0/stubs/obspy/clients/fdsn/__init__.pyi +29 -0
- specmod-0.2.0/stubs/obspy/core/__init__.pyi +3 -0
- specmod-0.2.0/stubs/obspy/core/event.pyi +56 -0
- specmod-0.2.0/stubs/obspy/core/inventory.pyi +13 -0
- specmod-0.2.0/stubs/obspy/core/stream.pyi +64 -0
- specmod-0.2.0/stubs/obspy/core/trace.pyi +78 -0
- specmod-0.2.0/stubs/obspy/core/utcdatetime.pyi +43 -0
- specmod-0.2.0/stubs/obspy/core/util/__init__.pyi +0 -0
- specmod-0.2.0/stubs/obspy/core/util/base.pyi +12 -0
- specmod-0.2.0/stubs/obspy/geodetics/__init__.pyi +14 -0
- specmod-0.2.0/stubs/obspy/signal/__init__.pyi +0 -0
- specmod-0.2.0/stubs/obspy/signal/konnoohmachismoothing.pyi +14 -0
- specmod-0.2.0/studies/magna_2020_paper.toml +136 -0
- specmod-0.2.0/tests/__init__.py +0 -0
- specmod-0.2.0/tests/conftest.py +191 -0
- specmod-0.2.0/tests/golden/motion_reference.json +3763 -0
- specmod-0.2.0/tests/golden/pipeline_reference.json +29839 -0
- specmod-0.2.0/tests/golden/window_reference.json +963 -0
- specmod-0.2.0/tests/test_acquire.py +375 -0
- specmod-0.2.0/tests/test_ambient_state.py +221 -0
- specmod-0.2.0/tests/test_api_surface.py +318 -0
- specmod-0.2.0/tests/test_collection.py +644 -0
- specmod-0.2.0/tests/test_config.py +273 -0
- specmod-0.2.0/tests/test_cwt.py +250 -0
- specmod-0.2.0/tests/test_datasets.py +178 -0
- specmod-0.2.0/tests/test_docs_are_current.py +80 -0
- specmod-0.2.0/tests/test_end_to_end.py +333 -0
- specmod-0.2.0/tests/test_fitting_defaults.py +372 -0
- specmod-0.2.0/tests/test_golden_reference.py +461 -0
- specmod-0.2.0/tests/test_import.py +100 -0
- specmod-0.2.0/tests/test_io_and_plotting.py +586 -0
- specmod-0.2.0/tests/test_legacy_fixes.py +192 -0
- specmod-0.2.0/tests/test_magnitude.py +278 -0
- specmod-0.2.0/tests/test_make_golden.py +127 -0
- specmod-0.2.0/tests/test_pick_plugins.py +413 -0
- specmod-0.2.0/tests/test_pick_readers.py +266 -0
- specmod-0.2.0/tests/test_picks.py +338 -0
- specmod-0.2.0/tests/test_pipeline.py +452 -0
- specmod-0.2.0/tests/test_pipeline_smoke.py +241 -0
- specmod-0.2.0/tests/test_preprocess.py +638 -0
- specmod-0.2.0/tests/test_prieto.py +210 -0
- specmod-0.2.0/tests/test_quadratic.py +359 -0
- specmod-0.2.0/tests/test_release_config.py +172 -0
- specmod-0.2.0/tests/test_smoothing.py +226 -0
- specmod-0.2.0/tests/test_sources.py +355 -0
- specmod-0.2.0/tests/test_spectral_wiring.py +270 -0
- specmod-0.2.0/tests/test_staged.py +407 -0
- specmod-0.2.0/tests/test_stubs.py +303 -0
- specmod-0.2.0/tests/test_transforms.py +872 -0
- specmod-0.2.0/tests/test_tutorial.py +154 -0
- specmod-0.2.0/tests/test_typing_backlog.py +129 -0
- specmod-0.2.0/tests/test_utils.py +345 -0
- specmod-0.2.0/tests/test_versioning.py +61 -0
- specmod-0.2.0/tools/check_built_version.py +84 -0
- specmod-0.2.0/tools/check_floors.py +139 -0
- specmod-0.2.0/tools/make_golden.py +295 -0
- specmod-0.2.0/tools/measure_docs.py +719 -0
- specmod-0.2.0/tutorial/SpecModTutorial.ipynb +2000 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.picks +31 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.xml +217 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/2019-08-26T07:30:47.000000Z.h5 +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/flatfiles/2019-08-26T07:30:47.000000Z.csv +29 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/flatfiles/2019-08-26T07:30:47.000000Z.parquet +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/stations/inventory.xml +7379 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH1_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH2_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH3_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ02.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: build
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0 # hatch-vcs derives the version from tags
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
- run: uv python install 3.11
|
|
18
|
+
|
|
19
|
+
- name: Build sdist and wheel
|
|
20
|
+
run: uv build
|
|
21
|
+
|
|
22
|
+
- name: Check metadata
|
|
23
|
+
run: uvx twine check dist/*
|
|
24
|
+
|
|
25
|
+
- name: Install from the wheel in a clean env
|
|
26
|
+
# Catches missing package data and bad entry points, which an editable
|
|
27
|
+
# install will happily hide.
|
|
28
|
+
run: |
|
|
29
|
+
uv venv /tmp/smoke
|
|
30
|
+
uv pip install --python /tmp/smoke/bin/python dist/*.whl
|
|
31
|
+
/tmp/smoke/bin/python -c "
|
|
32
|
+
import specmod
|
|
33
|
+
from specmod import core, fitting, pipeline, preprocess, transforms, utils
|
|
34
|
+
print('specmod', specmod.__version__, '- wheel imports cleanly')
|
|
35
|
+
"
|
|
36
|
+
|
|
37
|
+
- uses: actions/upload-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: dist
|
|
40
|
+
path: dist/
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
# Read the Docs publishes the site; this job only checks that it builds. It is
|
|
10
|
+
# kept alongside Read the Docs' own pull request build because it is the fast,
|
|
11
|
+
# in-repository check that does not depend on a third-party service being up —
|
|
12
|
+
# and because it is an import check, which is worth having in CI regardless.
|
|
13
|
+
# See docs/documentation.md.
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: docs-${{ github.ref }}
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
# The job name is what branch protection matches, not the workflow name.
|
|
21
|
+
# This was `build`, which collided with build.yml's job of the same name and
|
|
22
|
+
# left two unrelated checks sharing one name — and no check called `docs` for
|
|
23
|
+
# a required-checks list to name.
|
|
24
|
+
docs:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
with:
|
|
29
|
+
fetch-depth: 0 # hatch-vcs derives the version from tags
|
|
30
|
+
- uses: astral-sh/setup-uv@v5
|
|
31
|
+
- run: uv python install 3.11
|
|
32
|
+
- run: uv venv # create a venv to install into
|
|
33
|
+
|
|
34
|
+
# The package itself, not just the docs extra: autodoc imports every
|
|
35
|
+
# module it documents, so a docs build is also an import check.
|
|
36
|
+
- run: uv pip install -e ".[docs,io]"
|
|
37
|
+
|
|
38
|
+
- name: Build
|
|
39
|
+
# No -W. Intersphinx resolves seven inventories over the network and
|
|
40
|
+
# warns when one is briefly unreachable, which would turn a third
|
|
41
|
+
# party's downtime into a red build. A genuinely broken build exits
|
|
42
|
+
# non-zero on its own. `.readthedocs.yaml` sets fail_on_warning: false
|
|
43
|
+
# for the same reason — keep the two in step.
|
|
44
|
+
run: uv run sphinx-build -b html docs docs/_build/html
|
|
45
|
+
|
|
46
|
+
# Not a Pages artefact: this is a plain zip anyone can download from the
|
|
47
|
+
# run and open locally, and it stays useful if Read the Docs is down.
|
|
48
|
+
- uses: actions/upload-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: docs-html
|
|
51
|
+
path: docs/_build/html
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
# release-please pushes the release branch, opens the PR, and on merge creates
|
|
9
|
+
# the tag and the GitHub Release. The publish job below needs neither: PyPI
|
|
10
|
+
# Trusted Publishing authenticates with an OIDC token, so nothing long-lived
|
|
11
|
+
# lives in secrets.
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
pull-requests: write
|
|
15
|
+
|
|
16
|
+
# Two pushes to main should not race to update the same release PR.
|
|
17
|
+
concurrency:
|
|
18
|
+
group: release
|
|
19
|
+
cancel-in-progress: false
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
release-please:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
outputs:
|
|
25
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
26
|
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
27
|
+
steps:
|
|
28
|
+
- uses: googleapis/release-please-action@v4
|
|
29
|
+
id: release
|
|
30
|
+
with:
|
|
31
|
+
config-file: release-please-config.json
|
|
32
|
+
manifest-file: .release-please-manifest.json
|
|
33
|
+
|
|
34
|
+
# Deliberately in this workflow rather than a separate publish.yml keyed on
|
|
35
|
+
# `release: published`, which is what §6.5 of the plan used to describe.
|
|
36
|
+
# release-please creates the release with the default GITHUB_TOKEN, and
|
|
37
|
+
# per GitHub's docs "events triggered by the GITHUB_TOKEN will not create a
|
|
38
|
+
# new workflow run" — so that publish workflow would never fire. The
|
|
39
|
+
# alternative is a personal access token in secrets, which is the one thing
|
|
40
|
+
# Trusted Publishing exists to avoid. Gating on the action's own output
|
|
41
|
+
# keeps the token count at zero.
|
|
42
|
+
#
|
|
43
|
+
# Zenodo is unaffected: it listens to the release *webhook*, which is not
|
|
44
|
+
# subject to that restriction, so the DOI is still minted from the release.
|
|
45
|
+
publish:
|
|
46
|
+
needs: release-please
|
|
47
|
+
if: needs.release-please.outputs.release_created == 'true'
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
environment:
|
|
50
|
+
name: pypi
|
|
51
|
+
url: https://pypi.org/p/specmod
|
|
52
|
+
permissions:
|
|
53
|
+
id-token: write # mandatory for Trusted Publishing
|
|
54
|
+
contents: read
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
with:
|
|
58
|
+
# The tag, not the branch. main has already moved on by one commit
|
|
59
|
+
# (the release PR merge), and hatch-vcs would derive a .postN.devN
|
|
60
|
+
# version from it.
|
|
61
|
+
ref: ${{ needs.release-please.outputs.tag_name }}
|
|
62
|
+
fetch-depth: 0 # hatch-vcs derives the version by describing tags
|
|
63
|
+
|
|
64
|
+
- uses: astral-sh/setup-uv@v5
|
|
65
|
+
- run: uv python install 3.11
|
|
66
|
+
- run: uv build
|
|
67
|
+
|
|
68
|
+
# The tag is the version, and pyproject's tag_regex decides whether
|
|
69
|
+
# hatch-vcs can read it. If those two ever disagree the wheel is built
|
|
70
|
+
# as 0.1.1.postN.devN and would be uploaded under that name — PyPI does
|
|
71
|
+
# not let it be taken back. tests/test_release_config.py checks the
|
|
72
|
+
# formats agree; this checks the artefact that is about to be published.
|
|
73
|
+
- name: The built version is the tag
|
|
74
|
+
run: python tools/check_built_version.py "${{ needs.release-please.outputs.tag_name }}" dist
|
|
75
|
+
|
|
76
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: test-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
enable-cache: true
|
|
21
|
+
- run: uv python install 3.11
|
|
22
|
+
- run: uv venv
|
|
23
|
+
- name: ruff check
|
|
24
|
+
run: uvx ruff@0.16.1 check --output-format=github src/ tests/ tools/
|
|
25
|
+
- name: ruff format
|
|
26
|
+
run: uvx ruff@0.16.1 format --check src/ tests/ tools/
|
|
27
|
+
|
|
28
|
+
typecheck:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
with:
|
|
33
|
+
fetch-depth: 0 # hatch-vcs needs history to derive the version
|
|
34
|
+
- uses: astral-sh/setup-uv@v5
|
|
35
|
+
with:
|
|
36
|
+
enable-cache: true
|
|
37
|
+
- run: uv python install 3.11
|
|
38
|
+
- run: uv venv
|
|
39
|
+
- run: uv pip install -e ".[dev]"
|
|
40
|
+
- run: uv pip install mypy
|
|
41
|
+
- run: uv run mypy
|
|
42
|
+
|
|
43
|
+
test:
|
|
44
|
+
runs-on: ${{ matrix.os }}
|
|
45
|
+
strategy:
|
|
46
|
+
fail-fast: false
|
|
47
|
+
matrix:
|
|
48
|
+
os: [ubuntu-latest, macos-latest]
|
|
49
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
with:
|
|
53
|
+
fetch-depth: 0
|
|
54
|
+
- uses: astral-sh/setup-uv@v5
|
|
55
|
+
with:
|
|
56
|
+
enable-cache: true
|
|
57
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
58
|
+
- run: uv venv
|
|
59
|
+
- run: uv pip install -e ".[dev]"
|
|
60
|
+
- run: uv pip install pytest pytest-cov
|
|
61
|
+
- name: pytest
|
|
62
|
+
# Dataset-marked tests need a network fetch; they are excluded here so
|
|
63
|
+
# the suite stays hermetic. See docs/REFACTOR_PLAN.md §5.2. Notebook
|
|
64
|
+
# execution is excluded because it costs ~40s and a Jupyter kernel, and
|
|
65
|
+
# is worth paying once rather than six times — see the notebook job.
|
|
66
|
+
run: uv run pytest -m "not dataset and not notebook" --cov=specmod --cov-report=xml --cov-report=term
|
|
67
|
+
- uses: codecov/codecov-action@v4
|
|
68
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
|
|
69
|
+
with:
|
|
70
|
+
files: coverage.xml
|
|
71
|
+
fail_ci_if_error: false
|
|
72
|
+
|
|
73
|
+
floors:
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
with:
|
|
78
|
+
fetch-depth: 0
|
|
79
|
+
- uses: astral-sh/setup-uv@v5
|
|
80
|
+
- run: uv python install 3.11
|
|
81
|
+
- run: uv venv
|
|
82
|
+
# Exercise the declared minimums. CI otherwise installs the newest of
|
|
83
|
+
# everything, so the floors are only ever tested by a user.
|
|
84
|
+
- run: uv pip install --resolution lowest-direct -e ".[dev]"
|
|
85
|
+
- run: uv run --no-sync python tools/check_floors.py
|
|
86
|
+
- run: uv run --no-sync pytest -m "not dataset and not notebook" -q
|
|
87
|
+
|
|
88
|
+
notebook:
|
|
89
|
+
# The tutorial is the documented entry point and has broken three times:
|
|
90
|
+
# twice on renamed modules, once on a renamed data directory that left
|
|
91
|
+
# every import resolving. Executing it is the only check that catches the
|
|
92
|
+
# third kind, so it runs per-PR rather than at release.
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/checkout@v4
|
|
96
|
+
with:
|
|
97
|
+
fetch-depth: 0
|
|
98
|
+
- uses: astral-sh/setup-uv@v5
|
|
99
|
+
with:
|
|
100
|
+
enable-cache: true
|
|
101
|
+
- run: uv python install 3.11
|
|
102
|
+
- run: uv venv
|
|
103
|
+
- run: uv pip install -e ".[dev,tutorial]"
|
|
104
|
+
- run: uv run pytest -m notebook -q
|
|
105
|
+
|
|
106
|
+
# One check to require in branch protection, instead of ten — and stable when
|
|
107
|
+
# the matrix changes, which the six `test (os, version)` names are not.
|
|
108
|
+
#
|
|
109
|
+
# `if: always()` is load-bearing. Without it a failing dependency *skips* this
|
|
110
|
+
# job rather than failing it, and GitHub treats a skipped required check as
|
|
111
|
+
# satisfied — so branch protection would go green on a red build. Running
|
|
112
|
+
# always, and failing explicitly on any non-success, is what closes that.
|
|
113
|
+
#
|
|
114
|
+
# `skipped` counts as a failure here on purpose: nothing above is
|
|
115
|
+
# conditionally skipped, so a skip means something upstream went wrong.
|
|
116
|
+
ci:
|
|
117
|
+
if: always()
|
|
118
|
+
needs: [lint, typecheck, test, floors, notebook]
|
|
119
|
+
runs-on: ubuntu-latest
|
|
120
|
+
steps:
|
|
121
|
+
- name: Report what every job did
|
|
122
|
+
run: echo '${{ toJSON(needs) }}'
|
|
123
|
+
- name: Fail unless all of them succeeded
|
|
124
|
+
if: >-
|
|
125
|
+
contains(needs.*.result, 'failure')
|
|
126
|
+
|| contains(needs.*.result, 'cancelled')
|
|
127
|
+
|| contains(needs.*.result, 'skipped')
|
|
128
|
+
run: |
|
|
129
|
+
echo "::error::a required job did not succeed; see the report above"
|
|
130
|
+
exit 1
|
specmod-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Local, uncommitted configuration overrides (see docs/REFACTOR_PLAN.md §4.7).
|
|
2
|
+
# Promote one deliberately with `specmod config freeze > studies/<name>.toml`.
|
|
3
|
+
specmod.local.toml
|
|
4
|
+
*.local.toml
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*.egg-info/
|
|
10
|
+
build/
|
|
11
|
+
dist/
|
|
12
|
+
.eggs/
|
|
13
|
+
|
|
14
|
+
# Environments
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
|
|
19
|
+
# Tooling caches
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
coverage.xml
|
|
25
|
+
htmlcov/
|
|
26
|
+
|
|
27
|
+
# Docs
|
|
28
|
+
docs/_build/
|
|
29
|
+
|
|
30
|
+
# Editors / OS
|
|
31
|
+
.ftpconfig
|
|
32
|
+
.DS_Store
|
|
33
|
+
.idea/
|
|
34
|
+
.vscode/
|
|
35
|
+
*.swp
|
|
36
|
+
|
|
37
|
+
# Notebook checkpoints
|
|
38
|
+
.ipynb_checkpoints/
|
|
39
|
+
|
|
40
|
+
# Tutorial output. Regenerated by running the notebook; committing it means a
|
|
41
|
+
# diff every time anyone executes it, and a stale copy the moment the pipeline
|
|
42
|
+
# changes. The inputs (Tutorial/Data, Tutorial/MetaData) *are* committed.
|
|
43
|
+
Tutorial/Spectra/*.h5
|
|
44
|
+
Tutorial/Spectra/FlatFiles/
|
|
45
|
+
|
|
46
|
+
# Created by `uv run` without --no-sync; this project resolves fresh on
|
|
47
|
+
# purpose so the floors job can test the declared minimums.
|
|
48
|
+
uv.lock
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# `pre-commit install` wires only the pre-commit stage by default, which left
|
|
2
|
+
# the commit-msg hook below inert in every clone that ran the documented setup
|
|
3
|
+
# — including the one that then committed three session links. Naming the
|
|
4
|
+
# stages here means one `pre-commit install` installs both.
|
|
5
|
+
default_install_hook_types: [pre-commit, commit-msg]
|
|
6
|
+
|
|
7
|
+
repos:
|
|
8
|
+
# CI runs `uvx ruff` unpinned, so it always gets the newest release. Keep
|
|
9
|
+
# this rev at that newest release or CI will fail on rules this does not have.
|
|
10
|
+
# It has bitten twice: 0.9.6 against latest, then 0.15.8 against 0.16.1, where
|
|
11
|
+
# PLR0917 graduated out of preview and started firing only in CI.
|
|
12
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
13
|
+
rev: v0.16.1
|
|
14
|
+
hooks:
|
|
15
|
+
# Scoped to match CI, which runs `ruff ... src/ tests/ tools/`. Without
|
|
16
|
+
# this the hook lints everything staged and CI lints three directories,
|
|
17
|
+
# so a commit touching `docs/notebooks/` or `stubs/` is blocked by rules
|
|
18
|
+
# nothing else enforces — and the notebook builder legitimately contains
|
|
19
|
+
# long markdown lines and mathematical unicode that `RUF001` reads as
|
|
20
|
+
# ambiguous. Widen both together or neither.
|
|
21
|
+
- id: ruff-check
|
|
22
|
+
args: [--fix]
|
|
23
|
+
files: ^(src|tests|tools)/
|
|
24
|
+
- id: ruff-format
|
|
25
|
+
files: ^(src|tests|tools)/
|
|
26
|
+
|
|
27
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
28
|
+
rev: v5.0.0
|
|
29
|
+
hooks:
|
|
30
|
+
- id: check-added-large-files
|
|
31
|
+
# The repo carried 25 MB of data before this refactor; keep it out.
|
|
32
|
+
args: [--maxkb=1024]
|
|
33
|
+
- id: check-toml
|
|
34
|
+
- id: check-yaml
|
|
35
|
+
# These three rewrite files as text. `.spec` artifacts are pickles, and
|
|
36
|
+
# running the hooks over one silently stripped six bytes from the middle
|
|
37
|
+
# of it — a corrupted pickle that still looks like a plausible diff.
|
|
38
|
+
# Anything binary has to be excluded by hand; the hooks do not detect it.
|
|
39
|
+
- id: end-of-file-fixer
|
|
40
|
+
exclude: '\.spec$'
|
|
41
|
+
- id: trailing-whitespace
|
|
42
|
+
exclude: '\.spec$'
|
|
43
|
+
- id: mixed-line-ending
|
|
44
|
+
exclude: '\.spec$'
|
|
45
|
+
|
|
46
|
+
- repo: https://github.com/kynan/nbstripout
|
|
47
|
+
rev: 0.8.1
|
|
48
|
+
hooks:
|
|
49
|
+
- id: nbstripout
|
|
50
|
+
# The tutorial notebook was 4.4 MB of embedded PNGs. Under myst-nb the
|
|
51
|
+
# outputs are regenerated at docs build time.
|
|
52
|
+
|
|
53
|
+
- repo: local
|
|
54
|
+
hooks:
|
|
55
|
+
- id: no-session-links
|
|
56
|
+
name: reject Claude session URLs in commit messages
|
|
57
|
+
# This repository is public; session links are private state.
|
|
58
|
+
entry: >-
|
|
59
|
+
bash -c 'if grep -qiE "^Claude-Session:|claude\.ai/code/session_" "$1";
|
|
60
|
+
then echo "commit-msg: message contains a Claude session link"; exit 1; fi'
|
|
61
|
+
language: system
|
|
62
|
+
stages: [commit-msg]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Read the Docs build configuration.
|
|
2
|
+
#
|
|
3
|
+
# Read the Docs publishes the documentation; GitHub Actions only checks that it
|
|
4
|
+
# builds. The reason for the split is versions: this package is alpha and users
|
|
5
|
+
# are told to pin an exact version, so the documentation for a release has to
|
|
6
|
+
# stay readable after the trunk has moved on. Read the Docs keeps one build per
|
|
7
|
+
# tag, a `stable` pointing at the newest, and a `latest` from `main`, with a
|
|
8
|
+
# version switcher between them — and it builds pull requests to their own
|
|
9
|
+
# throwaway URL.
|
|
10
|
+
#
|
|
11
|
+
# Schema: https://docs.readthedocs.com/platform/stable/config-file/v2.html
|
|
12
|
+
|
|
13
|
+
version: 2
|
|
14
|
+
|
|
15
|
+
build:
|
|
16
|
+
os: ubuntu-24.04
|
|
17
|
+
tools:
|
|
18
|
+
python: "3.11" # the version CI's lint, typecheck and docs jobs use
|
|
19
|
+
jobs:
|
|
20
|
+
post_checkout:
|
|
21
|
+
# Read the Docs clones shallow and without tags, to save time. hatch-vcs
|
|
22
|
+
# derives the version from `git describe`, and `docs/conf.py` reads it
|
|
23
|
+
# back through importlib.metadata — so without these the sidebar reads
|
|
24
|
+
# the fallback 0.0.0 on every build, including tagged ones.
|
|
25
|
+
# `|| true` because a repository that is already complete makes
|
|
26
|
+
# --unshallow exit non-zero, which would fail the build.
|
|
27
|
+
- git fetch --unshallow || true
|
|
28
|
+
- git fetch --tags || true
|
|
29
|
+
|
|
30
|
+
sphinx:
|
|
31
|
+
configuration: docs/conf.py
|
|
32
|
+
# Deliberately off, for the same reason the CI job has no `-W`: intersphinx
|
|
33
|
+
# resolves seven inventories over the network and warns whenever one of them
|
|
34
|
+
# is briefly unreachable. That would make a third party's downtime a failed
|
|
35
|
+
# documentation build. A genuinely broken build still exits non-zero.
|
|
36
|
+
fail_on_warning: false
|
|
37
|
+
|
|
38
|
+
python:
|
|
39
|
+
install:
|
|
40
|
+
- method: pip
|
|
41
|
+
path: .
|
|
42
|
+
extra_requirements:
|
|
43
|
+
# `io` alongside `docs` because autodoc imports every module it
|
|
44
|
+
# documents, and `specmod.io` imports h5py and pyarrow. Without it the
|
|
45
|
+
# API reference loses those pages to import errors.
|
|
46
|
+
- docs
|
|
47
|
+
- io
|
specmod-0.2.0/AGENTS.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Working on SpecMod with a coding agent
|
|
2
|
+
|
|
3
|
+
Rules for Claude Code, Codex, and any other agent committing to this
|
|
4
|
+
repository. `CLAUDE.md` points here so there is one copy.
|
|
5
|
+
|
|
6
|
+
The long version of everything below is
|
|
7
|
+
[`docs/development.md`](docs/development.md). This file is the part an agent
|
|
8
|
+
must not get wrong, and every entry is here because it has actually gone wrong.
|
|
9
|
+
|
|
10
|
+
## Before the first commit
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
uv venv && uv pip install -e ".[dev]"
|
|
14
|
+
pre-commit install # installs BOTH the pre-commit and commit-msg hooks
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`pre-commit install` is not optional. In a fresh container it is easy to skip,
|
|
18
|
+
and the `commit-msg` hook is the only thing enforcing the rule below.
|
|
19
|
+
|
|
20
|
+
## Never publish session links
|
|
21
|
+
|
|
22
|
+
**No `Claude-Session:` trailers, session URLs, or agent-console links** in
|
|
23
|
+
commit messages, PR titles, PR bodies, code comments, or anything else that
|
|
24
|
+
lands in the repository. It is public; those links are private state.
|
|
25
|
+
|
|
26
|
+
`Co-Authored-By:` is fine. If your harness appends a session trailer by
|
|
27
|
+
default, strip it — the repository's rule wins over the harness default. The
|
|
28
|
+
`commit-msg` hook rejects it, which is why installing the hooks comes first.
|
|
29
|
+
|
|
30
|
+
## Commit messages are load-bearing
|
|
31
|
+
|
|
32
|
+
[Conventional Commits](https://www.conventionalcommits.org). They are not a
|
|
33
|
+
style preference: `release-please` reads them to compute the version bump and
|
|
34
|
+
to generate `CHANGELOG.md`. See
|
|
35
|
+
[`docs/releasing.md`](docs/releasing.md).
|
|
36
|
+
|
|
37
|
+
- `feat:` minor, `fix:` patch, `refactor:` / `docs:` / `build:` appear in the
|
|
38
|
+
changelog, `test:` / `ci:` / `chore:` are hidden.
|
|
39
|
+
- `!` or a `BREAKING CHANGE:` footer bumps the minor while the project is
|
|
40
|
+
`0.x`, not the major.
|
|
41
|
+
- Say *why*, with the measurement if there was one. The history is the record
|
|
42
|
+
of what was checked; a message that only restates the diff wastes it.
|
|
43
|
+
|
|
44
|
+
## Workflow files need a permission you may not have
|
|
45
|
+
|
|
46
|
+
`.github/workflows/` is editable directly, but only when the session's GitHub
|
|
47
|
+
App token carries the `workflows` permission. Without it the push is rejected
|
|
48
|
+
outright:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
refusing to allow a GitHub App to create or update workflow
|
|
52
|
+
`.github/workflows/test.yml` without `workflows` permission
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That is a loud failure, not a silent one. If you meet it, say so and ask for
|
|
56
|
+
the permission — do not reintroduce a parallel copy of the workflows to work
|
|
57
|
+
around it. There used to be one, in `ci/`, and keeping two versions of every
|
|
58
|
+
workflow in step cost more than the problem it solved.
|
|
59
|
+
|
|
60
|
+
## Verify before reporting
|
|
61
|
+
|
|
62
|
+
Run these, and report what they actually printed:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
pytest -m "not dataset and not notebook" # the suite CI runs
|
|
66
|
+
pytest --without-optional-extras # what a default install sees
|
|
67
|
+
ruff check src/ tests/ tools/ && ruff format --check src/ tests/ tools/
|
|
68
|
+
mypy
|
|
69
|
+
sphinx-build -b html docs docs/_build/html # if docs/ changed
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--without-optional-extras` matters: a development environment with
|
|
73
|
+
`specmod[multitaper]` installed passes tests that CI fails.
|
|
74
|
+
|
|
75
|
+
## Things that look like noise and are not
|
|
76
|
+
|
|
77
|
+
- **Golden references.** `tests/golden/*.json` is a record of numbers this code
|
|
78
|
+
used to produce. Do not regenerate it to make a test pass. If a change moves
|
|
79
|
+
a number, that is the finding — say which number, by how much, and why, and
|
|
80
|
+
regenerate deliberately with `python tools/make_golden.py`.
|
|
81
|
+
- **Measured tables in the docs.** Numbers in `docs/*.md` are generated between
|
|
82
|
+
markers by `python tools/measure_docs.py`. Edit the tool, not the table.
|
|
83
|
+
- **Tolerances.** Several carry a comment explaining what was measured to
|
|
84
|
+
choose them. Widening one to get to green, without measuring, is the specific
|
|
85
|
+
failure `docs/REFACTOR_PLAN.md` §6.6 exists to catch.
|
|
86
|
+
|
|
87
|
+
## Say what you did not check
|
|
88
|
+
|
|
89
|
+
The plan's §6.6 is an audit of claims in this repository that turned out to
|
|
90
|
+
describe mechanisms nobody had built. Do not add to it. If something is
|
|
91
|
+
untested, unreproducible, or assumed, write that down next to the claim — a
|
|
92
|
+
bound with a number behind it beats a confident sentence.
|