From 0df7ccf24bf2057302c31d2b1db7229b00a8b742 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Wed, 28 Oct 2026 01:11:41 -0500 Subject: [PATCH] Shorten AGENTS.md to tips; the sound-change and seam-gate rules move to CONTRIBUTING --- AGENTS.md | 131 +++++------------------------------------------- CLAUDE.md | 1 + CONTRIBUTING.md | 27 ++++++++++ 3 files changed, 40 insertions(+), 119 deletions(-) create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 151b0e1b..98043dcf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,119 +1,12 @@ -# AGENTS.md — audiocomponents - -`audioinstruments` (55 instruments) and `audioeffects` (45 effect classes, -racks included): the pure-Python audio component tier that PyDevices owns, -built on [audiodsp](https://github.com/PyDevices/audiodsp)'s nodes. **This -repository publishes both** — `pydevices-audioinstruments` and -`pydevices-audioeffects` on TestPyPI, and the `audioinstruments` and -`audioeffects` entries in the MIP index. audiodsp publishes the core only. - -## Read this before you change anything - -This is the only home of both packages. audiodsp's pre-rewrite copy of -`lib/audioinstruments/` and `lib/audioeffects/` was deleted on 2026-09-03 -([#2](https://github.com/PyDevices/audiocomponents/issues/2)), so every bug -fix and every piece of accuracy work belongs here. - -## The floor is pinned - -`AUDIODSP_PIN` names the exact audiodsp release every gate here runs against. -It is the same discipline as audiodsp's own `CIRCUITPYTHON_ORACLE`, for the -same reason: with a floating core underneath, a component failure is -unattributable — you cannot tell a rewritten instrument from a moved node -beneath it. Moving the pin is its own change, with the gates re-run, never a -side effect of other work. - -## Layout - -- `lib//` — the packages, each with its own `pyproject.toml`. That - layout is load-bearing twice over: it is what makes each a standalone - distribution, and the MIP publisher expects `/lib/`. - `pyproject.toml`'s `project.urls` point here, and its `dependencies` carry - the audiodsp floor, `pydevices-audiodsp>=` — the newest audiodsp - *release*. That is not the pin: `AUDIODSP_PIN` may name a commit ahead of - the floor, and the gates use the pin. -- `docs/reference/component-api.md` — the runtime contract (construction, - methods, properties). `docs/reference/metadata.md` — the static metadata - manifest. `tools/validate_api.py` and `tools/validate_metadata.py` enforce - them, and the tests import the latter. -- `tests/parity/` — the instrument parity harness, its probes and goldens. - -## Gates - -What CI runs, and what you should run before committing: - -```bash -python -m unittest discover -s tests -p "test_*.py" -python tools/validate_api.py -python tests/parity/effects_library_smoke.py -python -m flake8 -``` - -`.flake8` selects defect checks only (`F,E9,W6`) — no layout rules. The -instrument modules are deliberately compact and generated modules put -`MACRO_LABELS` above their imports; gating layout here would be a fight with -the house style, not a check. - -The heavier gates are workspace-local: - -```bash -python3 tests/parity/run_instruments_parity.py --verify --batch all -MICROPYPATH=$PWD:$PWD/lib ../bin/micropython \ - tests/parity/scheduling_seam_live.py -``` - -The second is the scheduling seam against the **real** pump -(audiocomponents#92). `tests/test_scheduling_seam.py` proves which events -the seam writes, against a Python stand-in for `audiopump_events.c`, and -runs anywhere; this one asks what was *heard*, off `audiopump.Tap`, with -the loop advanced by `audiopump.service()` on the calling thread. It needs -a MicroPython build carrying audiodsp as a usermod, because `audiopump` -exists only where the pump does. Four cases, each with a planted fault that -must make it exit non-zero: `--fault early|flat|held|armed`. - -It renders each component under every interpreter it finds and holds it to a -hash captured from the original micropython-vst3 script — read out of that -repository's **git history** at `ac87f13`, not its working tree, because its -tree imports these packages now and would no longer be an independent oracle. -Needs `../bin/micropython` (or `--micropython`) and an `mpvst` checkout beside this one (or `--old-root`). -Comparison is always within one interpreter; cross-interpreter agreement is -recorded as an observation, never enforced. - -**`--capture-old` cannot absorb a deliberate change.** It re-reads -micropython-vst3 at the fixed revision `ac87f13`, so re-running it rewrites the -same hashes. An instrument whose sound was changed on purpose will never match -that oracle again. - -**Changing an instrument's sound on purpose.** Instruments are tuned by ear, by -whoever is playing them: change the voice, play it, keep it or don't. When a -change is kept, the same commit names the instrument in `REBUILT` in -`tests/parity/run_instruments_parity.py`, with the date, and `--verify` then -reports it as `rebuilt` instead of failing. No separate ruling, dossier or -re-capture is needed. Everything not named there is still held to the old -oracle exactly as before, because the gate's job is to catch sound that changes -by accident: a refactor, a moved node, an audiodsp bump. `--include-rebuilt` -compares the rebuilt ones anyway. - -What stays mechanical for a kept change is only breakage: the instrument still -plays, isn't silent, doesn't clip, and still runs in real time on a board. - -## The release chain - -- `prepare-release.yml` opens the release PR (`VERSION` + `CHANGELOG.md`); - `tag-release.yml` tags the merged `VERSION`; `publish-release-packages.yml` - runs the gates above against the pin, builds both packages from the tag - (`working-directory: lib/`), publishes them to TestPyPI, and - requests the two MIP index entries (`mip-profile: - audioinstruments,audioeffects`, on one call — mip serializes them). -- **Versions are the maintainer's to name.** An agent never edits `VERSION`, - never tags, never dispatches a workflow. `VERSION` holds a placeholder until - the maintainer names the release, and `tag-release.yml` refuses to tag anything that is - not a release version, so the placeholder cannot leak into a tag. -- The two packages version and release together. A local editable install - reports `0.0.0` because the build workflow writes `lib//VERSION` - at build time; that is cosmetic. - -## What is not here - -- No C. The native nodes these components call live in audiodsp; if a fix needs - to go below the Python, it goes there. +# AGENTS.md + +- Test the code you changed, not the suite: `python tools/scoped_tests.py class:DeEsser` (or `effects`, `contract`, `auto`; `--list` prints the choice and runs nothing). The full run is very long ([CONTRIBUTING.md](CONTRIBUTING.md#run-the-tests)). +- CI also runs `python tools/validate_api.py`, `python tests/parity/effects_library_smoke.py` and `python -m flake8` (defect checks only, no layout rules). +- Set up against the audiodsp commit `AUDIODSP_PIN` names, not a TestPyPI release; a gate result against a floating core can't be attributed ([setup](CONTRIBUTING.md#set-up)). Moving the pin is its own change with the gates re-run. +- In a git worktree, an editable install still imports the main clone's `lib`. Put `PYTHONPATH=$PWD/lib` first, or your edit won't run. +- Instrument sound is held to hashes of the original scripts. `--capture-old` can't absorb a deliberate change; name the instrument in `REBUILT` in `tests/parity/run_instruments_parity.py` instead ([how](CONTRIBUTING.md#the-instrument-parity-gate)). Don't re-capture a golden casually. +- The parity gate and the live scheduling-seam gate need a MicroPython build with audiodsp built in, so CI doesn't run them. +- Don't edit `VERSION`, tag or dispatch a release; the version is named in the release pull request ([Release](CONTRIBUTING.md#release)). An editable install reports `0.0.0`; that is cosmetic. +- No C here. If a fix has to go below the Python, it goes in [audiodsp](https://github.com/PyDevices/audiodsp). +- The pages under `docs/reference/` are generated on every build; don't edit or commit them. +- A frozen module wins over `/lib` on MicroPython and CircuitPython, so a copied or mip-installed `audioinstruments` never runs until you rebuild or put `/lib` first. On a source checkout set `PYTHONPATH` (CPython) or `MICROPYPATH` (MicroPython) to `lib`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 261fba94..faf1d62c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,6 +88,33 @@ Re-capturing a golden is a maintainer's decision, not a routine step. A failure here during a deliberate rebuild is expected; it stops being a gate the moment a golden is re-captured casually. +`--capture-old` can't absorb a deliberate change: it re-reads the original at the +fixed revision (`ac87f13`), so re-running it rewrites the same hashes, and an +instrument whose sound was changed on purpose never matches that oracle again. + +To change an instrument's sound on purpose, tune it by ear: change the voice, +play it, keep it or don't. When a change is kept, the same commit names the +instrument in `REBUILT` in `tests/parity/run_instruments_parity.py`, with the +date, and `--verify` reports it as `rebuilt` instead of failing. No separate +ruling or re-capture is needed. Everything not named there is still held to the +old oracle, because the gate's job is to catch sound that changes by accident: +a refactor, a moved node, an audiodsp bump. `--include-rebuilt` compares the +rebuilt ones anyway. What stays mechanical for a kept change is breakage: the +instrument still plays, isn't silent, doesn't clip, and still runs in real time +on a board. + +A second heavy gate asks what the real pump plays, not which events the +scheduling seam writes (`tests/test_scheduling_seam.py` does that against a +Python stand-in, and runs anywhere): + +```bash +MICROPYPATH=$PWD:$PWD/lib micropython tests/parity/scheduling_seam_live.py +``` + +It needs a MicroPython build with audiodsp as a usermod, because `audiopump` +exists only where the pump does. It has four cases, each with a planted fault +that must make it exit non-zero: `--fault early|flat|held|armed`. + ## Build the docs ```bash